Picking Node packs, Updates, the log or Temp files used to close the
Settings window and open that feature's own separate modal, with no way
back except reopening Settings from the menu bar -- not what v4 does,
and not what "unified" is supposed to mean. Each of those five files
(plus pipeline_window.js) now exports mount({ setFooter, close }), the
same content its standalone open() already built, taking the modal
chrome's footer and close from whoever hosts it instead of owning a
modal directly. open() is unchanged for anyone opening one alone --
pipeline_window.test.js and every Playwright spec for these five prove
it, unedited, still passing.
Also fixes two findings from the adversarial pass on the earlier
deep-link version: filterList's search only matched a section's visible
label/hint, so a section findable only by a keyword (e.g. "loaders" for
"Models and pipeline") silently vanished from the filtered list; and
createSettingsWindow's "one at a time" state lived at module scope, so
a second independent instance would have redrawn the first one's modal
instead of opening its own. Both now have regression tests.
111 lines
4.1 KiB
JavaScript
111 lines
4.1 KiB
JavaScript
// Where a file went when it did not land in the bin.
|
|
//
|
|
// ComfyUI writes previews and other transient outputs to a temp directory and
|
|
// wipes it on restart. Everything in here is therefore findable for exactly as
|
|
// long as the server has been up, which is the window in which somebody asks
|
|
// "where did that go?".
|
|
//
|
|
// Read-only on purpose: this is a place to FIND something, not to manage it.
|
|
// Deleting temp files is ComfyUI's job and it does it on restart.
|
|
|
|
import { composer } from "../composer/composer.js";
|
|
import { viewUrl } from "./run.js";
|
|
|
|
const KIND_ICON = { image: "▦", video: "▶", audio: "♪" };
|
|
|
|
/** 1.4 MB, not 1468006. A size is read to compare two files, not to audit one. */
|
|
function size(bytes) {
|
|
const n = Number(bytes) || 0;
|
|
if (n < 1024) return `${n} B`;
|
|
if (n < 1024 * 1024) return `${Math.round(n / 1024)} KB`;
|
|
return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
}
|
|
|
|
const when = (mtime) => new Date((Number(mtime) || 0) * 1000).toLocaleTimeString();
|
|
|
|
/**
|
|
* mount({ onOpen, setFooter, close }) -> a composer handle, no modal chrome.
|
|
*
|
|
* `setFooter`/`close` are the two things a modal gives this window that a
|
|
* container does not have for free: somewhere for Refresh/Close to live
|
|
* outside the scrolling body, and a way to leave when a file is picked.
|
|
* `open()` below is this plus a modal wired to itself; the unified Settings
|
|
* window mounts this directly, wired to ITS OWN footer and close instead --
|
|
* so picking a file from Settings closes Settings, and standalone Temp
|
|
* files behaves exactly as it always did.
|
|
*/
|
|
export function mount({ onOpen, setFooter = () => {}, close = () => {} } = {}) {
|
|
const body = composer.region.stack({ gap: "sm", fill: true, label: "Temp files" });
|
|
|
|
const cells = (files) => files.map((f) => ({
|
|
// The full address, so two files with the same name in different subfolders
|
|
// are two entries rather than one that wins.
|
|
id: `${f.subfolder}/${f.filename}`,
|
|
label: f.filename,
|
|
hint: `${f.kind} · ${size(f.size)} · ${when(f.mtime)}`,
|
|
thumb: f.kind === "image" ? viewUrl({ ...f, type: "temp" }) : null,
|
|
icon: KIND_ICON[f.kind] || "▦",
|
|
badge: f.kind === "image" ? null : f.kind,
|
|
}));
|
|
|
|
async function load() {
|
|
body.set([composer.hint.default({ text: "Looking…" })]);
|
|
let payload;
|
|
try {
|
|
const res = await fetch("/funpack/api/temp", { cache: "no-store" });
|
|
payload = await res.json();
|
|
} catch (err) {
|
|
body.set([composer.banner.danger({ text: `Could not read the temp directory: ${err.message}` })]);
|
|
return;
|
|
}
|
|
|
|
const files = payload.files || [];
|
|
body.set([files.length
|
|
// Rows, not tiles: this window is for finding a file, and a row shows the
|
|
// whole name, its size and when it was written. The grid draws none of
|
|
// those -- it is for looking at pictures, which is the bin's job.
|
|
? composer.gallery.list({
|
|
items: cells(files),
|
|
onActivate: (cell) => {
|
|
const file = files.find((f) => `${f.subfolder}/${f.filename}` === cell.id);
|
|
if (!file || !onOpen) return;
|
|
onOpen({ url: viewUrl({ ...file, type: "temp" }), kind: file.kind, file });
|
|
close();
|
|
},
|
|
})
|
|
// The reason there is nothing, which is not the same as "nothing was made".
|
|
: composer.emptyState.default({
|
|
icon: "▤", title: "Nothing in the temp directory",
|
|
hint: payload.detail || "",
|
|
})]);
|
|
|
|
setFooter({
|
|
note: payload.path || "",
|
|
actions: [
|
|
composer.button.md({ label: "Refresh", onClick: () => load() }),
|
|
composer.button.md({ label: "Close", tone: "primary", onClick: () => close() }),
|
|
],
|
|
});
|
|
}
|
|
|
|
load();
|
|
return body;
|
|
}
|
|
|
|
export function open({ onOpen } = {}) {
|
|
let window_ = null;
|
|
const body = mount({
|
|
onOpen,
|
|
setFooter: (f) => window_ && window_.setFooter(f),
|
|
close: () => window_ && window_.close("done"),
|
|
});
|
|
window_ = composer.modal.generic({
|
|
title: "Temp files",
|
|
subtitle: "Transient ComfyUI outputs. Wiped when the server restarts.",
|
|
size: "xl",
|
|
body,
|
|
onClose: () => { window_ = null; },
|
|
});
|
|
return window_;
|
|
}
|