Files
ComfyAssets-ComfyUI_PromptM…/web/js/list-state.js
T
Vito Sansevero 7b6b7ecf3d fix: stale responses, double submits and leaked listeners
web/js/api-client.js wraps fetch with one failure path (ApiError for non-2xx,
success:false and non-JSON bodies) and latestOnly(), which aborts the previous
in-flight request and resolves superseded calls to STALE. The admin search and
recent-page loads share one guard, the gallery page load has its own, so fast
typing or clicking can no longer paint an older result over a newer one.

In-place prompt editing used a blur listener with {once:true} plus a keydown
listener added on every edit, which sent the PUT twice and stacked handlers.
The list now has one keydown/focusout pair bound at construction and
finishEdit() clears the edit state before touching the element, so a save is
sent exactly once. Mutations re-fetch the page currently shown (refreshList)
instead of calling search(), and delete, bulk delete, edit, restore and scan no
longer reload the whole page.

Pagination state is committed only after a successful fetch via the pure
ListState.nextPageState(current, response) in both admin and gallery, so a
failed or aborted page request leaves the current page intact. The gallery
lightbox removed its keydown handlers on a non-existent 'remove' event; a
MutationObserver now drops them when the modal leaves the document.

web/js/sse-stream.js reads server-sent events over fetch so POST-only progress
endpoints can stream.
2026-10-08 18:01:34 -07:00

51 lines
2.2 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;
}
const api = { nextPageState, shouldSaveEdit, hasMorePages };
if (typeof module !== "undefined" && module.exports) module.exports = api;
else root.ListState = api;
})(typeof window !== "undefined" ? window : globalThis);