Right-click menu settings: placement as two drawn cards, a preview line
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2836cceb7c
commit
2f7cda709c
+1
-1
@@ -453,7 +453,7 @@ covered by the `styles.css` manifest.
|
||||
(`/anomalous/find_model`, `openModelDetail`), save its values or the picked nodes as a combo, each
|
||||
only where it applies; for the empty canvas, AMB's pages. `canvas_menu_settings.js` keeps which
|
||||
entries are on, their order and whether they sit in one "AMB" submenu or right in the menu
|
||||
(localStorage, no DOM); `ui_canvas_menu_settings.js` is its Settings -> Canvas group.
|
||||
(localStorage, no DOM); `ui_canvas_menu_settings.js` is its Settings -> Canvas group (two placement cards drawing a small menu each, a preview line per list).
|
||||
`ui_quick_search.js` is the canvas's quick search (Alt+B, the command
|
||||
`Anomalous.ModelBrowser.QuickSearch`, or the empty canvas's menu): a bar that finds combos,
|
||||
models and saved prompts as you type and puts the chosen one where the pointer was (a combo
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
### 🖱️ AMB in the canvas's right-click menu (画布右键菜单)
|
||||
- **Right-click a node → AMB** offers what Current node does for it, without opening the browser first: **Open in AMB** (a prompt node opens Prompts on it, any other node Current node), **Change model by picture**, **Insert a LoRA before / after**, **Apply saved values**, **Pictures made with this model**, **Model details**, **Save this node's values** and **Save as combo**. Each entry shows only where it applies: a checkpoint loader has no "before", a prompt node no model entries.
|
||||
- **Right-click the empty canvas → AMB** opens any of AMB's pages.
|
||||
- **Make it yours: Settings → Canvas → Right-click menu**. Switch each entry on or off, move it up or down, and choose whether AMB's entries sit in one **AMB** submenu (the default, ComfyUI's menu stays tidy) or right in the menu. *Adjust this menu…* at the end of the submenu leads there. **Save as combo** moved into the AMB submenu with the rest.
|
||||
- **Make it yours: Settings → Canvas → Right-click menu**. Switch each entry on or off and move it up or down; a line above each list shows what the menu will offer. Two cards, each with a small drawing of the menu, choose whether AMB's entries sit **together under "AMB"** (the default, ComfyUI's menu gets one line more) or are **listed in the menu**. *Adjust this menu…* at the end of the submenu leads there. **Save as combo** moved into the AMB submenu with the rest.
|
||||
- In the model picker, Esc now closes the picker wherever the focus is, and only the picker: it used to close the browser behind it when the search box had lost the focus.
|
||||
- Fixed: when a model folder is a link (a junction to another drive, say), a combo kept from a picture and other lookups of saved model paths got a wrong place for the model, or failed.
|
||||
|
||||
|
||||
+18
-8
@@ -655,10 +655,15 @@ export const i18n = {
|
||||
canvasMenuAdjust: "调整这个菜单…",
|
||||
canvasMenuModelUnknown: "AMB 没找到这个模型,可能还没扫描,或者文件不在模型文件夹里。",
|
||||
canvasMenuGroup: "右键菜单",
|
||||
canvasMenuPlacement: "放在哪里",
|
||||
canvasMenuPlacementHelp: "收进一个“AMB”子菜单,ComfyUI 的菜单保持清爽;或者直接放在菜单里,少点一下。",
|
||||
canvasMenuPlacementSubmenu: "收进 AMB 子菜单",
|
||||
canvasMenuPlacementTop: "直接放外层",
|
||||
canvasMenuPlacement: "AMB 的选项出现在哪里",
|
||||
canvasMenuPlace_submenu: "归到一个“AMB”里",
|
||||
canvasMenuPlaceHelp_submenu: "ComfyUI 的菜单只多一行;要用时把鼠标移到 AMB 上",
|
||||
canvasMenuPlace_top: "直接列在菜单里",
|
||||
canvasMenuPlaceHelp_top: "少移一下鼠标,但 ComfyUI 的菜单会变长",
|
||||
canvasMenuSketchComfy: "属性|克隆|删除",
|
||||
canvasMenuPreview: "会看到:",
|
||||
canvasMenuPreviewNone: "什么也没有:AMB 不会出现在这个菜单里",
|
||||
canvasMenuPreviewNote: "(只列这个节点用得上的)",
|
||||
canvasMenuList_node: "右键一个节点",
|
||||
canvasMenuList_canvas: "右键画布空白处",
|
||||
canvasMenuUp: "上移",
|
||||
@@ -3462,10 +3467,15 @@ export const i18n = {
|
||||
canvasMenuAdjust: "Adjust this menu…",
|
||||
canvasMenuModelUnknown: "AMB does not know this model: maybe it is not scanned yet, or the file is outside the model folders.",
|
||||
canvasMenuGroup: "Right-click menu",
|
||||
canvasMenuPlacement: "Where",
|
||||
canvasMenuPlacementHelp: "In one “AMB” submenu, keeping ComfyUI's menu tidy, or right in the menu, one click less.",
|
||||
canvasMenuPlacementSubmenu: "In an AMB submenu",
|
||||
canvasMenuPlacementTop: "Right in the menu",
|
||||
canvasMenuPlacement: "Where AMB's entries show",
|
||||
canvasMenuPlace_submenu: "Together under “AMB”",
|
||||
canvasMenuPlaceHelp_submenu: "ComfyUI's menu gets one line more; point at AMB to see them",
|
||||
canvasMenuPlace_top: "Listed in the menu",
|
||||
canvasMenuPlaceHelp_top: "One move less, but ComfyUI's menu gets longer",
|
||||
canvasMenuSketchComfy: "Properties|Clone|Remove",
|
||||
canvasMenuPreview: "You will see:",
|
||||
canvasMenuPreviewNone: "Nothing: AMB stays out of this menu",
|
||||
canvasMenuPreviewNote: "(only those that fit the node)",
|
||||
canvasMenuList_node: "Right-click on a node",
|
||||
canvasMenuList_canvas: "Right-click on the empty canvas",
|
||||
canvasMenuUp: "Move up",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Settings -> Canvas -> Right-click menu: where AMB's entries sit (one "AMB" submenu or right
|
||||
* in ComfyUI's menu) and, for a node's menu and the empty canvas's, each entry on or off and
|
||||
* moved up or down, with Reset. Saved at once (canvas_menu_settings.js); the menu reads it each
|
||||
* time it opens.
|
||||
* Settings -> Canvas -> Right-click menu: where AMB's entries sit, as two cards each drawing a
|
||||
* small menu (one "AMB" line opening them, or them listed in ComfyUI's menu), and, for a node's
|
||||
* menu and the empty canvas's, each entry on or off and moved up or down, with a line showing
|
||||
* what the menu will offer, and Reset. Saved at once (canvas_menu_settings.js); the menu reads
|
||||
* it each time it opens.
|
||||
*/
|
||||
|
||||
import { translate as t } from './locales.js';
|
||||
@@ -22,8 +23,70 @@ function button(className, label, onClick) {
|
||||
return node;
|
||||
}
|
||||
|
||||
/** The rows of one menu's entries (`which`: 'node' or 'canvas'). */
|
||||
function entryList(settings, which, redraw) {
|
||||
const entryName = entry => t(`canvasMenuEntry_${entry.id}`);
|
||||
|
||||
/** What the menu will offer, in order: the entries switched on, as chips. */
|
||||
function previewLine(settings, which) {
|
||||
const line = el('div', 'anomalous-menu-preview');
|
||||
const draw = () => {
|
||||
const on = settings[which].filter(entry => entry.on);
|
||||
line.replaceChildren(el('span', 'anomalous-menu-preview-label', t('canvasMenuPreview')));
|
||||
if (!on.length) {
|
||||
line.append(el('span', 'anomalous-menu-preview-none', t('canvasMenuPreviewNone')));
|
||||
return;
|
||||
}
|
||||
line.append(...on.map(entry => el('span', 'anomalous-menu-preview-chip', entryName(entry))));
|
||||
if (which === 'node') line.append(el('span', 'anomalous-menu-preview-note', t('canvasMenuPreviewNote')));
|
||||
};
|
||||
line.redraw = draw;
|
||||
draw();
|
||||
return line;
|
||||
}
|
||||
|
||||
/** A small menu drawn in a card: ComfyUI's own lines, then AMB's (`amb`: 'submenu' or 'top'). */
|
||||
function sketch(settings, amb) {
|
||||
const box = el('span', `anomalous-menu-sketch is-${amb}`);
|
||||
const menu = el('span', 'anomalous-menu-sketch-menu');
|
||||
for (const word of t('canvasMenuSketchComfy').split('|')) menu.append(el('span', 'anomalous-menu-sketch-line is-comfy', word));
|
||||
const ours = settings.node.filter(entry => entry.on).slice(0, 3).map(entryName);
|
||||
if (!ours.length) ours.push(t('canvasMenuEntry_open'));
|
||||
if (amb === 'top') {
|
||||
menu.append(el('span', 'anomalous-menu-sketch-rule'), ...ours.map(name => el('span', 'anomalous-menu-sketch-line is-ours', name)));
|
||||
box.append(menu);
|
||||
return box;
|
||||
}
|
||||
menu.append(el('span', 'anomalous-menu-sketch-line is-amb', `${t('canvasMenuTitle')} ▸`));
|
||||
const sub = el('span', 'anomalous-menu-sketch-menu is-sub');
|
||||
sub.append(...ours.map(name => el('span', 'anomalous-menu-sketch-line', name)));
|
||||
box.append(menu, sub);
|
||||
return box;
|
||||
}
|
||||
|
||||
/** The two placement cards; a click picks one. */
|
||||
function placementCards(settings, redraw) {
|
||||
const cards = el('div', 'anomalous-menu-places');
|
||||
cards.setAttribute('role', 'radiogroup');
|
||||
cards.setAttribute('aria-label', t('canvasMenuPlacement'));
|
||||
for (const value of ['submenu', 'top']) {
|
||||
const picked = settings.placement === value;
|
||||
const card = button(`anomalous-menu-place${picked ? ' is-picked' : ''}`, undefined, () => {
|
||||
if (picked) return;
|
||||
settings.placement = value;
|
||||
saveMenuSettings(settings);
|
||||
redraw();
|
||||
});
|
||||
card.setAttribute('role', 'radio');
|
||||
card.setAttribute('aria-checked', String(picked));
|
||||
const head = el('span', 'anomalous-menu-place-head');
|
||||
head.append(el('span', 'anomalous-menu-place-mark', picked ? '✓' : ''), el('span', 'anomalous-menu-place-title', t(`canvasMenuPlace_${value}`)));
|
||||
card.append(head, sketch(settings, value), el('span', 'anomalous-menu-place-help', t(`canvasMenuPlaceHelp_${value}`)));
|
||||
cards.append(card);
|
||||
}
|
||||
return cards;
|
||||
}
|
||||
|
||||
/** The rows of one menu's entries (`which`: 'node' or 'canvas'); `changed()` after a switch. */
|
||||
function entryList(settings, which, redraw, changed) {
|
||||
const list = el('div', 'anomalous-menu-entries');
|
||||
list.setAttribute('aria-label', t(`canvasMenuList_${which}`));
|
||||
const entries = settings[which];
|
||||
@@ -53,6 +116,7 @@ function entryList(settings, which, redraw) {
|
||||
entry.on = toggle.checked;
|
||||
line.classList.toggle('is-off', !entry.on);
|
||||
saveMenuSettings(settings);
|
||||
changed();
|
||||
};
|
||||
line.append(copy, up, down, toggle);
|
||||
list.append(line);
|
||||
@@ -64,24 +128,31 @@ function entryList(settings, which, redraw) {
|
||||
* The group's rows: `row(titleKey, helpKey, control)` and `segment(choices, current, onPick)`
|
||||
* are the settings page's own helpers; `redraw()` draws the page again.
|
||||
*/
|
||||
export function canvasMenuRows({ row, segment, redraw }) {
|
||||
export function canvasMenuRows({ row, redraw }) {
|
||||
const settings = menuSettings();
|
||||
const placement = row('canvasMenuPlacement', 'canvasMenuPlacementHelp', segment(
|
||||
[['submenu', 'canvasMenuPlacementSubmenu'], ['top', 'canvasMenuPlacementTop']], settings.placement, (value) => {
|
||||
settings.placement = value;
|
||||
saveMenuSettings(settings);
|
||||
redraw();
|
||||
}));
|
||||
let places = placementCards(settings, redraw);
|
||||
const nodePreview = previewLine(settings, 'node');
|
||||
const canvasPreview = previewLine(settings, 'canvas');
|
||||
// A switch changes what the cards' small menus and the preview lines show.
|
||||
const nodeChanged = () => {
|
||||
nodePreview.redraw();
|
||||
const next = placementCards(settings, redraw);
|
||||
places.replaceWith(next);
|
||||
places = next;
|
||||
};
|
||||
const reset = row('canvasMenuReset', 'canvasMenuResetHelp', button('anomalous-scan-secondary anomalous-scan-small-btn', t('settingsReset'), () => {
|
||||
resetMenuSettings();
|
||||
redraw();
|
||||
}));
|
||||
return [
|
||||
placement,
|
||||
el('h4', 'anomalous-menu-entries-title is-first', t('canvasMenuPlacement')),
|
||||
places,
|
||||
el('h4', 'anomalous-menu-entries-title', t('canvasMenuList_node')),
|
||||
entryList(settings, 'node', redraw),
|
||||
nodePreview,
|
||||
entryList(settings, 'node', redraw, nodeChanged),
|
||||
el('h4', 'anomalous-menu-entries-title', t('canvasMenuList_canvas')),
|
||||
entryList(settings, 'canvas', redraw),
|
||||
canvasPreview,
|
||||
entryList(settings, 'canvas', redraw, () => canvasPreview.redraw()),
|
||||
reset,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -494,7 +494,7 @@ const TABS = [
|
||||
['look', 'settingsTabLook', (owner, redraw) => [appearanceGroup(owner, redraw), cardsGroup(owner, redraw), galleryGroup(owner), openingGroup(owner, redraw)]],
|
||||
['models', 'settingsTabModels', (owner, redraw) => [foldersGroup(owner), networkGroup(), downloadGroup(owner, redraw), sidecarGroup()]],
|
||||
['workflows', 'settingsTabWorkflows', (owner) => [workflowGroup(owner)]],
|
||||
['canvas', 'settingsTabCanvas', (owner, redraw) => [group('canvasMenuGroup', ...canvasMenuRows({ row, segment, redraw }))]],
|
||||
['canvas', 'settingsTabCanvas', (owner, redraw) => [group('canvasMenuGroup', ...canvasMenuRows({ row, redraw }))]],
|
||||
['packs', 'settingsTabPacks', (owner, redraw) => [renderPackUsage(owner, redraw)]],
|
||||
['prompts', 'settingsTabPrompts', () => [translationGroup()]],
|
||||
['backup', 'settingsTabBackup', (owner) => [backupGroup(owner)]],
|
||||
|
||||
+1
-1
@@ -30,5 +30,5 @@
|
||||
@import url("./styles/29-workflow-detail.css?v=20261011-wfd-3");
|
||||
@import url("./styles/30-scan-picker.css?v=20261016-lists");
|
||||
@import url("./styles/31-model-info.css?v=20261014-desc-2");
|
||||
@import url("./styles/32-canvas-tools.css?v=20261019-search");
|
||||
@import url("./styles/32-canvas-tools.css?v=20261019-places");
|
||||
@import url("./styles/33-kept-combos.css?v=20261019-hero");
|
||||
|
||||
@@ -149,6 +149,114 @@
|
||||
}
|
||||
|
||||
/* Settings -> Canvas -> Right-click menu (ui_canvas_menu_settings.js). */
|
||||
.anomalous-menu-entries-title.is-first { margin-top: 2px; }
|
||||
|
||||
.anomalous-menu-places {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.anomalous-menu-place {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--amb-border);
|
||||
border-radius: 12px;
|
||||
background: rgba(var(--amb-ink-rgb), 0.02);
|
||||
color: var(--amb-text-main);
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.anomalous-menu-place:hover { border-color: var(--amb-border-hover); }
|
||||
|
||||
.anomalous-menu-place.is-picked {
|
||||
border: 2px solid var(--amb-link);
|
||||
padding: 11px;
|
||||
background: color-mix(in srgb, var(--amb-link) 8%, transparent);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.anomalous-menu-place-head { display: flex; align-items: center; gap: 8px; }
|
||||
|
||||
.anomalous-menu-place-mark {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 1.5px solid var(--amb-border-strong);
|
||||
border-radius: 50%;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.is-picked .anomalous-menu-place-mark { border-color: var(--amb-link); background: var(--amb-link); color: var(--amb-bg-panel); }
|
||||
|
||||
.anomalous-menu-place-title { font-size: 13px; font-weight: 700; }
|
||||
|
||||
.anomalous-menu-place-help { color: var(--amb-text-muted); font-size: 11px; line-height: 1.45; }
|
||||
|
||||
/* The small menus drawn in the cards. */
|
||||
.anomalous-menu-sketch { display: flex; align-items: flex-start; gap: 4px; }
|
||||
|
||||
.anomalous-menu-sketch-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 118px;
|
||||
padding: 3px 0;
|
||||
border: 1px solid var(--amb-border);
|
||||
border-radius: 6px;
|
||||
background: var(--amb-bg-panel);
|
||||
}
|
||||
|
||||
/* The submenu opens beside the AMB line (the fourth line). */
|
||||
.anomalous-menu-sketch-menu.is-sub { margin-top: 63px; border-color: color-mix(in srgb, var(--amb-link) 50%, transparent); }
|
||||
|
||||
.anomalous-menu-sketch-line {
|
||||
padding: 2px 8px;
|
||||
overflow: hidden;
|
||||
color: var(--amb-text-main);
|
||||
font-size: 10.5px;
|
||||
line-height: 17px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.anomalous-menu-sketch-line.is-comfy { color: var(--amb-text-muted); }
|
||||
|
||||
.anomalous-menu-sketch-line.is-amb { background: color-mix(in srgb, var(--amb-link) 18%, transparent); color: var(--amb-link); font-weight: 700; }
|
||||
|
||||
.anomalous-menu-sketch-line.is-ours { color: var(--amb-link); }
|
||||
|
||||
.anomalous-menu-sketch-rule { margin: 2px 0; border-top: 1px solid var(--amb-border); }
|
||||
|
||||
/* What the menu will offer. */
|
||||
.anomalous-menu-preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.anomalous-menu-preview-label { color: var(--amb-text-muted); }
|
||||
|
||||
.anomalous-menu-preview-chip {
|
||||
padding: 2px 9px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--amb-link) 14%, transparent);
|
||||
color: var(--amb-link);
|
||||
}
|
||||
|
||||
.anomalous-menu-preview-none,
|
||||
.anomalous-menu-preview-note { color: var(--amb-text-muted); }
|
||||
|
||||
.anomalous-menu-entries-title {
|
||||
margin: 14px 0 4px;
|
||||
color: var(--amb-text-muted);
|
||||
|
||||
Reference in New Issue
Block a user