Files
DemonGatanjieu-Anomalous_Mo…/web/modules/ui_popup_menu.js
T
DemonGatanjieuandClaude Opus 5.5 617a7a3bc4 Workflows page: one bar, quieter cards
The page's three rows (kind pills, search, tag select, count and close; view, share and
save; the drag tip strip) become one bar: search, a Filter menu (kind and tag), the count,
grid or list, share and import, save the canvas. Cards keep the picture, the name, a line
of steps, sampler, LoRAs and size, and the model; the models' state is a dot (words only
when some are missing), 功能模块 marks a partial workflow, and Open and ⋯ (edit, export,
delete) show on hover. ui_popup_menu.js is the small menu both use.

The page's styles move to styles/28-workflows.css; the card and bar rules spread over four
stylesheets and the ~240 lines of CSS injected from ui_recipe_cards.js are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:50:53 +08:00

79 lines
3.0 KiB
JavaScript

/**
* A small menu that opens under a button (the workflow card's ⋯, the Workflows page's
* filter): rows that run an action, rows ticked as chosen, and headings. One is open at a
* time; a press outside it, Esc, scrolling or a chosen row closes it.
*/
let openMenu = null;
function el(tag, className, text) {
const node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
}
/** Closes the open menu, if any. */
export function closePopupMenu() {
openMenu?.();
}
/**
* Opens `items` under `anchor`: { heading } or { label, run, checked?, danger? }. A row with
* `checked` (true or false) shows a tick when true. Pressing the anchor again closes it.
*/
export function openPopupMenu(anchor, items) {
const wasOpenHere = openMenu?.anchor === anchor;
closePopupMenu();
if (wasOpenHere) return;
const menu = el('div', 'anomalous-popup-menu');
menu.setAttribute('role', 'menu');
for (const item of items) {
if (item.heading) {
menu.append(el('div', 'anomalous-popup-menu-heading', item.heading));
continue;
}
const row = el('button', `anomalous-popup-menu-item${item.danger ? ' is-danger' : ''}`);
row.type = 'button';
row.setAttribute('role', item.checked === undefined ? 'menuitem' : 'menuitemradio');
if (item.checked !== undefined) {
row.setAttribute('aria-checked', String(Boolean(item.checked)));
row.append(el('span', 'anomalous-popup-menu-tick', item.checked ? '✓' : ''));
}
row.append(el('span', 'anomalous-popup-menu-label', item.label));
row.onclick = (event) => {
event.stopPropagation();
close();
item.run?.();
};
menu.append(row);
}
document.body.append(menu);
const box = anchor.getBoundingClientRect();
const { width, height } = menu.getBoundingClientRect();
const left = Math.min(Math.max(8, box.right - width), window.innerWidth - width - 8);
const below = box.bottom + 4;
menu.style.left = `${left}px`;
menu.style.top = `${below + height > window.innerHeight - 8 ? Math.max(8, box.top - height - 4) : below}px`;
const onPointer = (event) => {
if (!menu.contains(event.target) && !anchor.contains(event.target)) close();
};
const onKey = (event) => {
if (event.key !== 'Escape') return;
event.stopPropagation();
close();
};
const close = () => {
menu.remove();
window.removeEventListener('pointerdown', onPointer, true);
window.removeEventListener('keydown', onKey, true);
window.removeEventListener('scroll', close, true);
if (openMenu === close) openMenu = null;
};
close.anchor = anchor;
window.addEventListener('pointerdown', onPointer, true);
window.addEventListener('keydown', onKey, true);
window.addEventListener('scroll', close, true);
openMenu = close;
}