Compare commits

..
4 changed files with 46 additions and 36 deletions
+8
View File
@@ -166,6 +166,14 @@ export function noteIsEmpty(note) {
);
}
// A note shows its tile face when it is in tile layout and has anything to
// show. The node is then just a button: the note itself opens in a dialog,
// so there is no card inside the node whose height needs fitting.
export function noteShowsTileFace(note) {
if (!note || typeof note !== "object") return false;
return note.layout === "tile" && !noteIsEmpty(note);
}
// "ComfyUI/models/loras" - the folder hint under each group of rows.
export function folderPath(dir) {
if (!dir) return "ComfyUI/models";
+5 -4
View File
@@ -34,6 +34,7 @@ import {
matchInstalled,
noteFit,
noteIsEmpty,
noteShowsTileFace,
normalizeNote,
packsFromGraph,
parseMarkdown,
@@ -708,13 +709,13 @@ function naturalHeight(state, width) {
// as the node's floor. It never shrinks the node: a taller note keeps its
// empty space until someone drags it shorter.
function fitNote(state) {
// A tile opens its content in a dialog. Keep its chosen canvas size,
// rather than growing it to a full note's measured height.
if (state.note.layout === "tile") {
if (!classicCanvas()) return;
// A tile face is a button, not a card: fitting it would measure the note
// behind the button and grow a new tile straight back to full height.
if (noteShowsTileFace(state.note)) {
state.fitHeight = undefined;
return;
}
if (!classicCanvas()) return;
const { node, root, domWidget } = state;
const width = node.size[0] - WIDGET_FRAME;
if (!(width > 0)) return;
+15
View File
@@ -114,6 +114,21 @@ for (const type of types) {
}));
return { size: [Math.round(node.size[0]), Math.round(node.size[1])], drawn: host ? box(host) : null, widgets: (node.widgets ?? []).length, panels };
})()`).catch((error) => ({ error: String(error.message ?? error).slice(0, 400) }));
if (!vue) {
const imgInfo = await evalJs(`(() => {
const node = window.app.graph._nodes.find((n) => n.type === ${JSON.stringify(type)});
if (!node) return null;
const img = Array.isArray(node.imgs) ? node.imgs[0] : null;
return {
imgs: Array.isArray(node.imgs) ? node.imgs.length : null,
src: img ? String(img.src || "").slice(0, 160) : null,
nat: img ? [img.naturalWidth, img.naturalHeight] : null,
previewWidget: Boolean(node.widgets?.find((w) => w.name === "$$canvas-image-preview")),
appPreviewKeys: Object.keys(window.app.nodePreviewImages ?? {}).slice(0, 10),
};
})()`).catch(() => null);
if (imgInfo && (imgInfo.imgs || imgInfo.previewWidget)) console.log("IMGS " + type + " " + JSON.stringify(imgInfo));
}
await sleep(150);
const problems = [];
if (seen.error) problems.push(seen.error);
+18 -32
View File
@@ -15,6 +15,7 @@ import {
matchInstalled,
noteFit,
noteIsEmpty,
noteShowsTileFace,
normalizeNote,
packsFromGraph,
parseInline,
@@ -198,6 +199,23 @@ test("noteFit ignores a card it could not measure", () => {
assert.deepEqual(noteFit({ natural: 794.125, nodeHeight: undefined, chrome: 22 }), { fit: null, grow: false });
});
test("a tile face shows only for a tile layout note that has content", () => {
assert.equal(noteShowsTileFace(normalizeNote('{"title":"Workflow info","layout":"tile"}')), true);
assert.equal(noteShowsTileFace(normalizeNote('{"title":"Workflow info","layout":"card"}')), false);
assert.equal(noteShowsTileFace(normalizeNote('{"layout":"tile"}')), false);
assert.equal(noteShowsTileFace(normalizeNote("")), false);
assert.equal(noteShowsTileFace(null), false);
});
test("the classic fit leaves a tile face compact", () => {
const source = readFileSync(new URL("../js/workflow_note/index.js", import.meta.url), "utf-8");
assert.match(
source,
/function fitNote\(state\) \{\n if \(!classicCanvas\(\)\) return;\n \/\/ A tile face is a button, not a card: fitting it would measure the note\n \/\/ behind the button and grow a new tile straight back to full height\.\n if \(noteShowsTileFace\(state\.note\)\) \{\n state\.fitHeight = undefined;\n return;\n \}/,
"fitNote skips a note that shows its tile face, so a new tile keeps its compact start",
);
});
test("parseInline handles bold, italic, code and safe links", () => {
assert.deepEqual(parseInline("a **b** *c* `d` [e](https://f.g) [h](javascript:x) i"), [
{ type: "text", text: "a " },
@@ -308,35 +326,3 @@ test("note formatting leaves old prose, unsafe markup and code literal", () => {
assert.equal(parseInline("`[amber]warning[/amber]`")[0].type, "code");
assert.equal(parseMarkdown("```\n|a|\n|---|\n```")[0].type, "code");
});
// Exercise the actual browser fitter with measured layout inputs. Keeping the
// DOM read behind a stub also proves a tile never measures its dialog content.
const noteFrontend = readFileSync(new URL("../js/workflow_note/index.js", import.meta.url), "utf-8");
const fitNoteSource = noteFrontend.slice(noteFrontend.indexOf("function fitNote(state) {"), noteFrontend.indexOf("// Every list widget", noteFrontend.indexOf("function fitNote(state) {")));
const runFitNote = new Function("classicCanvas", "naturalHeight", "noteFit", "WIDGET_FRAME", "PANEL_TOP", `${fitNoteSource}; return fitNote;`);
test("tile fitting preserves default and saved heights despite a stale widget position", () => {
const fit = runFitNote(() => true, () => 50, noteFit, 8, 2);
for (const height of [80, 110, 160]) {
const state = { note: { layout: "tile" }, fitHeight: 284,
node: { size: [380, height], setSize: () => assert.fail("tile fitting must preserve its size") },
root: { isConnected: true }, domWidget: { computedHeight: 50, y: 226 } };
fit(state);
assert.equal(state.fitHeight, undefined, "a tile must not retain a full note's resize floor");
assert.equal(state.node.size[1], height);
}
});
test("card and banner fitting still grows to measured content and keeps taller saved sizes", () => {
const fit = runFitNote(() => true, () => 300, noteFit, 8, 2);
for (const layout of ["card", "banner"]) {
for (const height of [80, 500]) {
const state = { note: { layout }, node: { size: [380, height],
setSize(size) { this.size = size; }, setDirtyCanvas() {} },
root: { isConnected: true }, domWidget: { computedHeight: 50, y: 2 } };
fit(state);
assert.equal(state.fitHeight, 310);
assert.equal(state.node.size[1], Math.max(height, 310));
}
}
});