Files
Cuteminded--JellyXR/scripts/ui/library.gd
T
Lizzy 1a82550249 Fix library pagination grid appends
Increase Jellyfin library page requests to 25 items and keep subsequent results in the same poster grid so infinite scrolling fills rows correctly instead of creating a new container per page. The tests were updated to cover the new page size, retry behavior, and replacement of stale grid contents after refreshes.
2026-09-06 21:54:19 +02:00

130 lines
4.3 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
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 " + ("movies and shows" if library_id.is_empty() else 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 not items.is_empty():
var hero := LibraryHero.new()
hero.item = items[0]
hero.artwork = artwork
hero.chosen.connect(func(item: Dictionary): selected.emit(item))
content.add_child(hero)
content.move_child(hero, 0)
Widgets.label(("Your library" 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 _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)