Files
DemonGatanjieu-Anomalous_Mo…/web/modules/gallery_card_menu.js
T
DemonGatanjieuandClaude Opus 5.5 983e9b1166 Model details, layout B: name and actions beside the cover
From a Bilibili comment (buttons small and in five places, the file name twice, a title bar
of little use), layout B chosen by the user from three mockups:
- The title bar goes. The head (new model_detail_head.js) tops the right column: a crumb
  back (Models / LoRA, or where history leads; still .anomalous-detail-back, so Esc presses
  it), the name, the source line with the source links, and one action row: Add node (the
  one accent button), Edit, Folder, Outputs, and ... with Copy path, Copy file name, Delete.
- Delete asks in AMB's dialog instead of window.confirm, and is no longer beside Add node.
- model_location.js: one path line with Copy; Show in folder is exported for Folder.
- action_menu.js: the small menu the gallery card's right-click had, shared with the ...
- Narrow (no cover column), a 64 px cover sits beside the name.
- Gone: the jump-to-bottom button, the full-width Outputs button, the location card's file
  name row, and their locale keys (back, backToPrev, detailJumpToBottom,
  detailPhysicalFilename, detailFullPath).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:45:44 +08:00

69 lines
3.2 KiB
JavaScript

/**
* A gallery card's right-click menu: put the picture on the canvas as a Load Image node, or
* open its parameters. The picture is uploaded into the input folder the way a dropped file is
* (ComfyUI keeps one copy of the same picture): a "name.png [output]" value would be flagged
* as a missing file by ComfyUI's checks for any picture not made since ComfyUI started.
*/
import { app } from '../../../scripts/app.js';
import { translate as t } from './locales.js';
import { recordCanvasStep } from './canvas_history.js';
import { showWorkbenchToast } from './ui_prompt_toast.js';
import { canvasPoint, followPointer } from './notebook_canvas.js';
import { openActionMenu } from './action_menu.js';
/** The output picture uploaded into the input folder; its name there for a Load Image node. */
async function copyToInput(image) {
const query = new URLSearchParams({ filename: image.filename, subfolder: image.subfolder || '', type: 'output' });
const picture = await fetch(`/view?${query}`);
if (!picture.ok) throw new Error(`view ${picture.status}`);
const blob = await picture.blob();
const form = new FormData();
form.append('image', new File([blob], image.filename, { type: blob.type }));
form.append('type', 'input');
const response = await fetch('/upload/image', { method: 'POST', body: form });
if (!response.ok) throw new Error(`upload ${response.status}`);
const data = await response.json();
return data.subfolder ? `${data.subfolder}/${data.name}` : data.name;
}
/**
* A Load Image node showing `image`: the browser closes and the node follows the pointer until a
* click puts it down (Esc takes it off again), like a combo's Put on canvas. `at` is the click
* that chose it, where the node starts.
*/
export async function insertLoadImageNode(owner, image, at) {
const node = globalThis.LiteGraph?.createNode('LoadImage');
const widget = node?.widgets?.find(item => item.name === 'image');
let file = null;
try {
if (node && widget && app.graph) file = await copyToInput(image);
} catch (error) {
console.error('[AMB] Could not copy the picture into the input folder:', error);
}
if (!file) {
showWorkbenchToast(t('galleryInsertNodeFailed'));
return;
}
const [x, y] = canvasPoint(at);
node.pos = [x - node.size[0] / 2, y - 20];
app.graph.add(node);
const choices = widget.options?.values;
if (Array.isArray(choices) && !choices.includes(file)) choices.push(file);
widget.value = file;
widget.callback?.(file); // shows the picture in the node
app.graph.setDirtyCanvas(true, true);
owner.close();
followPointer(owner, [{ node, relX: 0, relY: 0 }], () => recordCanvasStep(app));
showWorkbenchToast(t('galleryInsertNodeFollow'));
}
/** Opens the menu at the pointer; `openDetails()` opens the picture's parameters. */
export function openGalleryCardMenu(event, image, { owner, openDetails }) {
event.preventDefault();
openActionMenu(event, [
{ icon: '🖼️', label: t('galleryInsertAsNode'), run: (click) => insertLoadImageNode(owner, image, click) },
{ icon: '🔍', label: t('materialViewParameters'), run: openDetails },
]);
}