Files
ComfyAssets-ComfyUI_PromptM…/web/js/list-state.js
T
Vito Sansevero 6e3fa65914 feat(gallery): duplicate scan runs as a background job with progress; infinite scrolling
The duplicate scan was one blocking request with a spinner: no progress,
a modal that could not be put away, and a result lost if the tab closed.

- POST /prompt_manager/scan_duplicates/stream starts the scan as a
  background job (same ScanJob fan-out as the output scan) or attaches to
  the running one; GET /prompt_manager/scan_duplicates/status reports it.
  The job holds the duplicates lock, so deletes are refused while it runs.
- hashing runs on the configured worker threads and reports progress
  about every percent
- the gallery modal shows a progress bar, closing it minimizes to a pill
  that reopens it, results wait in the pill if the modal was closed, and a
  reload re-attaches to a running scan

Infinite scrolling is a new Display Setting (off by default): the next page
is appended when the viewport nears the bottom, pagination controls hide,
and the load-more rule lives in list-state.js where it is unit tested.
Thumb-size buttons and the column setting no longer override each other.
2026-10-08 18:01:35 -07:00

65 lines
2.9 KiB
JavaScript

/**
* Pure state helpers for the paginated prompt and image lists.
*
* Loadable in the browser (window.ListState) and in Node for tests.
*/
(function (root) {
"use strict";
function nonNegativeInt(value) {
return Number.isInteger(value) && value >= 0 ? value : null;
}
/**
* Pagination state after a page fetch succeeded. Callers keep their current
* state untouched while the request is in flight and only adopt the result.
*
* @param {{page: number, limit: number, total: number}} current
* @param {{page?: number, limit?: number, total?: number}} response - the page
* that was requested and the total the server reported
* @returns {{page: number, limit: number, total: number, totalPages: number}}
*/
function nextPageState(current, response) {
const limit = Number.isInteger(response.limit) && response.limit > 0 ? response.limit : current.limit;
const total = nonNegativeInt(response.total) ?? current.total;
const totalPages = Math.max(1, Math.ceil(total / limit));
const requested = Number.isInteger(response.page) ? response.page : current.page;
const page = Math.min(Math.max(1, requested), totalPages);
return { page, limit, total, totalPages };
}
/** Whether an in-place prompt edit changed the text to something worth saving. */
function shouldSaveEdit(originalText, newText) {
if (typeof newText !== "string") return false;
const trimmed = newText.trim();
return trimmed !== "" && trimmed !== String(originalText ?? "").trim();
}
/**
* Whether another page should be fetched from a capped list endpoint such as
* /prompt_manager/images/all: the server said has_more and the last page was not empty.
*/
function hasMorePages(response, receivedCount) {
const pagination = response && response.pagination;
return Boolean(pagination && pagination.has_more) && receivedCount > 0;
}
/**
* Infinite scrolling: fetch the next page when the user is within `threshold`
* pixels of the bottom (or the content does not fill the viewport yet), no
* load is in flight and pages remain.
*/
function shouldLoadMore({ infiniteScroll, loading, page, limit, total, scrollTop, clientHeight, scrollHeight, threshold = 600 }) {
if (!infiniteScroll || loading) return false;
const pageSize = nonNegativeInt(limit) || 1;
const totalPages = Math.ceil(nonNegativeInt(total) / pageSize);
if (nonNegativeInt(page) >= totalPages) return false;
const remaining = scrollHeight - clientHeight - scrollTop;
return remaining < threshold;
}
const api = { nextPageState, shouldSaveEdit, hasMorePages, shouldLoadMore };
if (typeof module !== "undefined" && module.exports) module.exports = api;
else root.ListState = api;
})(typeof window !== "undefined" ? window : globalThis);