annie.pages.paging ================== .. py:module:: annie.pages.paging .. autoapi-nested-parse:: Incremental row paging shared by the Browse and Annotator tabs (UI helper). Both tabs render long lists of expensive rows — every row schedules a lazy frame decode — so they reveal only :attr:`annie.app_state.UiSettings.page_size` rows at a time. This module owns that behaviour once: * :class:`Pager` is the pure, side-effect-free cursor: how many rows are shown, how many remain, and which slice the next page covers. It is unit-tested on its own. * :func:`paged` is the thin NiceGUI shell around it, providing three affordances: 1. a **Jump to row** section, so a reviewer resuming a long session can type ``3400`` and land there. Rows before the target are never built — that is the whole point, since building them would schedule thousands of frame decodes. 2. a **Show more** button (the manual fallback, always present); 3. a **scroll sentinel** — a zero-noise ``q-intersection`` element parked below the rows. Quasar fires its ``visibility`` event as the sentinel nears the viewport, which reveals the next page without a click. Because the listener is bound to the element, it dies with it on ``refresh()`` rather than accumulating. Auto-scrolling is opt-out via the Settings tab; the sentinel is consulted on every event, so toggling the setting takes effect without rebuilding the list. **A row number identifies the sample, not its slot on screen.** It is :attr:`annie.core.models.VideoEntry.row_id` — the video's 1-based position in the whole scanned dataset, assigned once at scan time over the sorted manifest. So a filtered Browse tab shows a sparse ``#3, #17, #204`` rather than renumbering from 1, the Annotator's queued subset keeps each video's dataset number, and "I stopped at 3400" survives a restart, a filter change, and a re-queue. Classes ------- .. autoapisummary:: annie.pages.paging.Pager Functions --------- .. autoapisummary:: annie.pages.paging.index_of_row_id annie.pages.paging.paged Module Contents --------------- .. py:class:: Pager A cursor over ``total`` rows, revealed one page at a time. .. attribute:: total The number of rows in the backing list. .. attribute:: start Index of the first row actually rendered (non-zero after a seek). .. attribute:: shown Index one past the last rendered row; also the count of rendered rows when ``start`` is 0. .. py:method:: advance(page_size: int) -> slice Reserve the next page and return the slice of rows to render. :param page_size: Rows to reveal. Values below 1 are clamped to 1, so a bad setting can never yield an empty page and stall the caller's loop. :returns: The slice of the backing list covering the newly revealed rows. .. py:method:: seek(index: int) -> None Restart paging at ``index``, clamped into range. The next :meth:`advance` renders the page beginning at ``index``; rows before it are skipped entirely rather than rendered and scrolled past. :param index: Zero-based row index to resume from. .. py:property:: remaining :type: int How many rows are still unrendered. .. py:property:: exhausted :type: bool Whether every row from :attr:`start` onward has been rendered. .. py:function:: index_of_row_id(row_ids: collections.abc.Sequence[int], target: int) -> int Return where ``target`` sits among the shown rows' ``row_ids``. Row ids increase along the list (they are positions in the same sorted manifest), so a filtered list is still ordered and can be searched. A row id that was filtered out resolves to the next one still on screen, which is what a reviewer typing "3400" into a filtered Browse tab means: *take me to where 3400 would be*. :param row_ids: The dataset row ids of the shown rows, ascending. :param target: The dataset row id to land on. :returns: A 0-based index into ``row_ids``, clamped to the last row. .. py:function:: paged[T](entries: collections.abc.Sequence[T], render_row: collections.abc.Callable[[T], None], *, row_id: collections.abc.Callable[[T], int], total_rows: int, actions: collections.abc.Callable[[], None] | None = None, container: collections.abc.Callable[[], nicegui.ui.element] | None = None, page_size: int | None = None, jump_slot: nicegui.ui.element | None = None) -> None Render ``entries`` one page at a time, with jump, auto-scroll, and a button. :param entries: The rows to page through — already filtered, still ordered. :param render_row: Builds one row. Called inside the rows container. :param row_id: The dataset row id of an entry, used to drive the jump box. This is the sample's number in the whole dataset, not its index in ``entries``. :param total_rows: How many rows the *unfiltered* dataset has, so the jump box accepts any dataset row id even when the view is filtered. :param actions: Builds extra controls at the end of the jump row, for list-wide operations that belong next to it (the Annotator's "Clear all"). :param container: Factory for the element newly revealed rows are built into. Defaults to a vertical column; the Browse grid view passes a wrapping row so its boxes flow into a grid. :param page_size: Rows revealed per page. Defaults to :attr:`annie.app_state.UiSettings.page_size`; the grid view reveals more. :param jump_slot: Where to build the "Jump to row" card. When given, it is rendered into that (pre-existing, persistent) element — e.g. the Browse View panel — instead of inline above the rows. The slot is cleared first, so a re-paged list never stacks duplicate jump cards.