mirror of
https://github.com/Cuteminded/jellyframe.git
synced 2026-10-28 00:00:27 +01:00
138 lines
4.8 KiB
GDScript
138 lines
4.8 KiB
GDScript
class_name LibraryView
|
|
extends VBoxContainer
|
|
|
|
signal selected(item: Dictionary)
|
|
signal status(message: String)
|
|
var api: JellyfinClient
|
|
var library_id := ""
|
|
var library_name := "Home"
|
|
var field: LineEdit
|
|
var content: VBoxContainer
|
|
var artwork: ArtworkLoader
|
|
var offset := 0
|
|
var search := ""
|
|
var generation := 0
|
|
var busy := false
|
|
var has_more := false
|
|
var scroll: ScrollContainer
|
|
var grid: HFlowContainer
|
|
static var previous_hero_ids: Dictionary = {}
|
|
|
|
const LOAD_AHEAD_PIXELS := 400.0
|
|
|
|
func _ready() -> void:
|
|
add_theme_constant_override("separation", 18)
|
|
artwork = ArtworkLoader.new()
|
|
artwork.api = api
|
|
add_child(artwork)
|
|
var tools := HBoxContainer.new()
|
|
add_child(tools)
|
|
field = Widgets.input("Search " + library_name, tools)
|
|
field.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
field.text_submitted.connect(refresh)
|
|
Widgets.button("Search", func(): refresh(field.text), tools)
|
|
scroll = ScrollContainer.new()
|
|
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
|
add_child(scroll)
|
|
content = VBoxContainer.new()
|
|
content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
content.add_theme_constant_override("separation", 24)
|
|
scroll.add_child(content)
|
|
var scrollbar := scroll.get_v_scroll_bar()
|
|
scrollbar.value_changed.connect(func(_value: float): _check_more.call_deferred())
|
|
scrollbar.changed.connect(func(): _check_more.call_deferred())
|
|
refresh()
|
|
|
|
func refresh(term: String = "") -> void:
|
|
generation += 1
|
|
var current := generation
|
|
busy = true
|
|
has_more = false
|
|
scroll.scroll_vertical = 0
|
|
search = term
|
|
offset = 0
|
|
grid = null
|
|
for child in content.get_children():
|
|
content.remove_child(child)
|
|
child.queue_free()
|
|
status.emit("Loading " + library_name.to_lower() + "...")
|
|
if term.is_empty() and library_id.is_empty():
|
|
var resume := await api.resume_items()
|
|
if current != generation: return
|
|
if resume.ok and not resume.data.get("Items", []).is_empty():
|
|
Widgets.label("Continue watching", content, 28)
|
|
_cards(resume.data.Items, true)
|
|
busy = false
|
|
await _more(current)
|
|
|
|
func _more(current: int) -> void:
|
|
if busy or current != generation: return
|
|
busy = true
|
|
has_more = false
|
|
var result := await api.library(search, offset, library_id)
|
|
if current != generation: return
|
|
if not result.ok:
|
|
busy = false
|
|
status.emit(result.error)
|
|
var retry := Widgets.button("Retry", func(): pass, content)
|
|
retry.pressed.connect(func(): retry.queue_free(); _more(current))
|
|
return
|
|
var items: Array = result.data.get("Items", [])
|
|
if offset == 0:
|
|
if search.is_empty() and items.any(func(item: Dictionary): return item.get("Type", "") != "Photo"):
|
|
var hero := LibraryHero.new()
|
|
hero.item = _choose_hero(items.filter(func(item: Dictionary): return item.get("Type", "") != "Photo"))
|
|
hero.artwork = artwork
|
|
hero.chosen.connect(func(item: Dictionary): selected.emit(item))
|
|
content.add_child(hero)
|
|
content.move_child(hero, 0)
|
|
Widgets.label(("Recently Added" if library_id.is_empty() else library_name) if search.is_empty() else "Search results", content, 28)
|
|
_cards(items)
|
|
offset += items.size()
|
|
status.emit("%d titles loaded" % offset if offset > 0 else "No matching titles.")
|
|
has_more = not items.is_empty() and offset < int(result.data.get("TotalRecordCount", offset))
|
|
await get_tree().process_frame
|
|
await get_tree().process_frame
|
|
if current != generation: return
|
|
busy = false
|
|
_check_more.call_deferred()
|
|
|
|
func _choose_hero(items: Array) -> Dictionary:
|
|
var previous_id := str(previous_hero_ids.get(library_id, ""))
|
|
var candidates := items.filter(func(item: Dictionary): return str(item.get("Id", "")) != previous_id)
|
|
var chosen: Dictionary = (items if candidates.is_empty() else candidates).pick_random()
|
|
previous_hero_ids[library_id] = str(chosen.get("Id", ""))
|
|
return chosen
|
|
|
|
func _check_more() -> void:
|
|
if busy or not has_more or not is_inside_tree(): return
|
|
var scrollbar := scroll.get_v_scroll_bar()
|
|
var remaining := scrollbar.max_value - scrollbar.page - scrollbar.value
|
|
if remaining <= LOAD_AHEAD_PIXELS:
|
|
_more(generation)
|
|
|
|
func _cards(items: Array, horizontal: bool = false) -> void:
|
|
var holder: Container
|
|
if horizontal:
|
|
var row_scroll := ScrollContainer.new()
|
|
row_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
|
row_scroll.custom_minimum_size.y = 352
|
|
content.add_child(row_scroll)
|
|
holder = HBoxContainer.new()
|
|
holder.add_theme_constant_override("separation", 18)
|
|
row_scroll.add_child(holder)
|
|
else:
|
|
if grid == null:
|
|
grid = HFlowContainer.new()
|
|
grid.add_theme_constant_override("h_separation", 18)
|
|
grid.add_theme_constant_override("v_separation", 18)
|
|
content.add_child(grid)
|
|
holder = grid
|
|
for item in items:
|
|
var card := MediaCard.new()
|
|
card.item = item
|
|
card.artwork = artwork
|
|
card.chosen.connect(func(chosen: Dictionary): selected.emit(chosen))
|
|
holder.add_child(card)
|