From 0f4057c8b2eabab3c37363866f8196c55d2c0dd2 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Fri, 15 Aug 2025 19:25:59 -0700 Subject: [PATCH] [backport] Refactor app menu items and update side toolbar (#4665, #4946) (#5030) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor app menu items (#4665) * Restructures the application menu - rename Workflow to File - move new & template items to top level - add View menu and related sub items Commands - add "active" state getter shown as checkmark in the menu Node side panel - add refresh node defs - change reset view icon Help center - change to use store for visibility Fixes - Fix bug with mouse down where if you drag mouse out, mouse up wasn't caught - Fix issue with canvas info setting not triggering a redraw on change * Fix missing translation warnings * Add separator under new * tidy * Update locales [skip ci] * fix some tests * fix * Hide icon if there is an active state within the menu item group * Update locales [skip ci] * Fix tests * Implement feedback - Remove queue, node lib, model lib, workflows, manager, help center - Add minimap, link visibility * Update locales [skip ci] * Add plus icon on "New" menu item * Update locales [skip ci] * Fix test * Fix translations * Update locales [skip ci] * Update locales [skip ci] --------- Co-authored-by: github-actions * Update side toolbar menu (#4946) Side toolbar menu UI updates - Currently the template modal is very hidden. Many users do not find it - The current icons are quite aleatory **What**: - Add templates shortcut button - Add item label in normal size - Use custom icon Critical design decisions or edge cases that need attention: - Sidebar tabs registered using custom icons will have their associated command registed with an undefined icon (currently only string icons are accepted, not components). I couldn't see anywhere directly using this icon, but we should consider autogenerating an icon font so we can use classes for our custom icons (or locating and updating locations to support both icon types) Normal mode: image Small mode: image ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-4946-Update-side-toolbar-menu-24d6d73d365081c5b2bdc0ee8b61dc50) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions --------- Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Co-authored-by: github-actions --- browser_tests/fixtures/components/Topbar.ts | 13 +- browser_tests/tests/groupNode.spec.ts | 9 +- browser_tests/tests/interaction.spec.ts | 2 +- browser_tests/tests/menu.spec.ts | 2 +- browser_tests/tests/rerouteNode.spec.ts | 2 +- .../tests/workflowTabThumbnail.spec.ts | 6 +- src/assets/icons/custom/ai-model.svg | 6 + src/assets/icons/custom/node.svg | 3 + src/assets/icons/custom/template.svg | 5 + src/components/sidebar/SideToolbar.vue | 6 +- .../sidebar/SidebarHelpCenterIcon.vue | 11 +- src/components/sidebar/SidebarIcon.vue | 53 +- .../sidebar/SidebarTemplatesButton.vue | 35 + .../sidebar/tabs/NodeLibrarySidebarTab.vue | 11 +- src/components/topbar/CommandMenubar.vue | 68 +- .../sidebarTabs/useModelLibrarySidebarTab.ts | 9 +- .../sidebarTabs/useNodeLibrarySidebarTab.ts | 9 +- .../sidebarTabs/useQueueSidebarTab.ts | 1 + .../sidebarTabs/useWorkflowsSidebarTab.ts | 9 +- src/composables/useCoreCommands.ts | 47 +- src/composables/useLitegraphSettings.ts | 1 + src/constants/coreMenuCommands.ts | 9 +- src/locales/ar/commands.json | 9 + src/locales/ar/main.json | 815 +++++++++++++++++- src/locales/en/commands.json | 9 + src/locales/en/main.json | 37 +- src/locales/es/commands.json | 9 + src/locales/es/main.json | 37 +- src/locales/fr/commands.json | 9 + src/locales/fr/main.json | 37 +- src/locales/ja/commands.json | 9 + src/locales/ja/main.json | 37 +- src/locales/ko/commands.json | 9 + src/locales/ko/main.json | 37 +- src/locales/ru/commands.json | 9 + src/locales/ru/main.json | 37 +- src/locales/zh-TW/commands.json | 9 + src/locales/zh-TW/main.json | 37 +- src/locales/zh/commands.json | 9 + src/locales/zh/main.json | 37 +- src/stores/commandStore.ts | 3 + src/stores/helpCenterStore.ts | 25 + src/stores/menuItemStore.ts | 15 +- src/stores/workspace/sidebarTabStore.ts | 40 +- src/types/extensionTypes.ts | 3 +- src/utils/mouseDownUtil.ts | 13 +- src/vite-env.d.ts | 12 + vitest.config.ts | 12 +- 48 files changed, 1477 insertions(+), 155 deletions(-) create mode 100644 src/assets/icons/custom/ai-model.svg create mode 100644 src/assets/icons/custom/node.svg create mode 100644 src/assets/icons/custom/template.svg create mode 100644 src/components/sidebar/SidebarTemplatesButton.vue create mode 100644 src/stores/helpCenterStore.ts diff --git a/browser_tests/fixtures/components/Topbar.ts b/browser_tests/fixtures/components/Topbar.ts index 81fcf6764..b138ff7b7 100644 --- a/browser_tests/fixtures/components/Topbar.ts +++ b/browser_tests/fixtures/components/Topbar.ts @@ -50,7 +50,7 @@ export class Topbar { workflowName: string, command: 'Save' | 'Save As' | 'Export' ) { - await this.triggerTopbarCommand(['Workflow', command]) + await this.triggerTopbarCommand(['File', command]) await this.getSaveDialog().fill(workflowName) await this.page.keyboard.press('Enter') @@ -72,8 +72,8 @@ export class Topbar { } async triggerTopbarCommand(path: string[]) { - if (path.length < 2) { - throw new Error('Path is too short') + if (path.length < 1) { + throw new Error('Path cannot be empty') } const menu = await this.openTopbarMenu() @@ -85,6 +85,13 @@ export class Topbar { .locator('.p-tieredmenu-item') .filter({ has: topLevelMenuItem }) await topLevelMenu.waitFor({ state: 'visible' }) + + // Handle top-level commands (like "New") + if (path.length === 1) { + await topLevelMenuItem.click() + return + } + await topLevelMenu.hover() let currentMenu = topLevelMenu diff --git a/browser_tests/tests/groupNode.spec.ts b/browser_tests/tests/groupNode.spec.ts index a6cb1e67f..fb282e6a7 100644 --- a/browser_tests/tests/groupNode.spec.ts +++ b/browser_tests/tests/groupNode.spec.ts @@ -268,10 +268,7 @@ test.describe('Group Node', () => { await comfyPage.setSetting('Comfy.ConfirmClear', false) // Clear workflow - await comfyPage.menu.topbar.triggerTopbarCommand([ - 'Edit', - 'Clear Workflow' - ]) + await comfyPage.executeCommand('Comfy.ClearWorkflow') await comfyPage.ctrlV() await verifyNodeLoaded(comfyPage, 1) @@ -280,7 +277,7 @@ test.describe('Group Node', () => { test('Copies and pastes group node into a newly created blank workflow', async ({ comfyPage }) => { - await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New']) + await comfyPage.menu.topbar.triggerTopbarCommand(['New']) await comfyPage.ctrlV() await verifyNodeLoaded(comfyPage, 1) }) @@ -296,7 +293,7 @@ test.describe('Group Node', () => { test('Serializes group node after copy and paste across workflows', async ({ comfyPage }) => { - await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New']) + await comfyPage.menu.topbar.triggerTopbarCommand(['New']) await comfyPage.ctrlV() const currentGraphState = await comfyPage.page.evaluate(() => window['app'].graph.serialize() diff --git a/browser_tests/tests/interaction.spec.ts b/browser_tests/tests/interaction.spec.ts index a19248c2e..934107792 100644 --- a/browser_tests/tests/interaction.spec.ts +++ b/browser_tests/tests/interaction.spec.ts @@ -684,7 +684,7 @@ test.describe('Load workflow', () => { workflowA = generateUniqueFilename() await comfyPage.menu.topbar.saveWorkflow(workflowA) workflowB = generateUniqueFilename() - await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New']) + await comfyPage.menu.topbar.triggerTopbarCommand(['New']) await comfyPage.menu.topbar.saveWorkflow(workflowB) // Wait for localStorage to persist the workflow paths before reloading diff --git a/browser_tests/tests/menu.spec.ts b/browser_tests/tests/menu.spec.ts index a771257f2..1eabe222c 100644 --- a/browser_tests/tests/menu.spec.ts +++ b/browser_tests/tests/menu.spec.ts @@ -75,7 +75,7 @@ test.describe('Menu', () => { test('Displays keybinding next to item', async ({ comfyPage }) => { await comfyPage.menu.topbar.openTopbarMenu() - const workflowMenuItem = comfyPage.menu.topbar.getMenuItem('Workflow') + const workflowMenuItem = comfyPage.menu.topbar.getMenuItem('File') await workflowMenuItem.hover() const exportTag = comfyPage.page.locator('.keybinding-tag', { hasText: 'Ctrl + s' diff --git a/browser_tests/tests/rerouteNode.spec.ts b/browser_tests/tests/rerouteNode.spec.ts index 4c7065e3f..b61014f6d 100644 --- a/browser_tests/tests/rerouteNode.spec.ts +++ b/browser_tests/tests/rerouteNode.spec.ts @@ -18,7 +18,7 @@ test.describe('Reroute Node', () => { [workflowName]: workflowName }) await comfyPage.setup() - await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New']) + await comfyPage.menu.topbar.triggerTopbarCommand(['New']) // Insert the workflow const workflowsTab = comfyPage.menu.workflowsTab diff --git a/browser_tests/tests/workflowTabThumbnail.spec.ts b/browser_tests/tests/workflowTabThumbnail.spec.ts index a1869430b..31b15067f 100644 --- a/browser_tests/tests/workflowTabThumbnail.spec.ts +++ b/browser_tests/tests/workflowTabThumbnail.spec.ts @@ -63,7 +63,7 @@ test.describe('Workflow Tab Thumbnails', () => { test('Should show thumbnail when hovering over a non-active tab', async ({ comfyPage }) => { - await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New']) + await comfyPage.menu.topbar.triggerTopbarCommand(['New']) const thumbnailImg = await getTabThumbnailImage( comfyPage, 0, @@ -73,7 +73,7 @@ test.describe('Workflow Tab Thumbnails', () => { }) test('Should not show thumbnail for active tab', async ({ comfyPage }) => { - await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New']) + await comfyPage.menu.topbar.triggerTopbarCommand(['New']) const thumbnailImg = await getTabThumbnailImage( comfyPage, 1, @@ -105,7 +105,7 @@ test.describe('Workflow Tab Thumbnails', () => { await comfyPage.nextFrame() // Create a new workflow (tab 1) which will be empty - await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New']) + await comfyPage.menu.topbar.triggerTopbarCommand(['New']) await comfyPage.nextFrame() // Now we have two tabs: tab 0 (default workflow with nodes) and tab 1 (empty) diff --git a/src/assets/icons/custom/ai-model.svg b/src/assets/icons/custom/ai-model.svg new file mode 100644 index 000000000..ede8e5c7e --- /dev/null +++ b/src/assets/icons/custom/ai-model.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/assets/icons/custom/node.svg b/src/assets/icons/custom/node.svg new file mode 100644 index 000000000..3239b59bd --- /dev/null +++ b/src/assets/icons/custom/node.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/custom/template.svg b/src/assets/icons/custom/template.svg new file mode 100644 index 000000000..2a2a75f8d --- /dev/null +++ b/src/assets/icons/custom/template.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/components/sidebar/SideToolbar.vue b/src/components/sidebar/SideToolbar.vue index 0ef8598d1..72a451d81 100644 --- a/src/components/sidebar/SideToolbar.vue +++ b/src/components/sidebar/SideToolbar.vue @@ -8,10 +8,13 @@ :icon-badge="tab.iconBadge" :tooltip="tab.tooltip" :tooltip-suffix="getTabTooltipSuffix(tab)" + :label="tab.label || tab.title" + :is-small="isSmall" :selected="tab.id === selectedTab?.id" :class="tab.id + '-tab-button'" @click="onTabClick(tab)" /> +
@@ -43,6 +46,7 @@ import type { SidebarTabExtension } from '@/types/extensionTypes' import SidebarHelpCenterIcon from './SidebarHelpCenterIcon.vue' import SidebarIcon from './SidebarIcon.vue' import SidebarLogoutIcon from './SidebarLogoutIcon.vue' +import SidebarTemplatesButton from './SidebarTemplatesButton.vue' const workspaceStore = useWorkspaceStore() const settingStore = useSettingStore() @@ -86,7 +90,7 @@ const getTabTooltipSuffix = (tab: SidebarTabExtension) => { box-shadow: var(--bar-shadow); --sidebar-width: 4rem; - --sidebar-icon-size: 1.5rem; + --sidebar-icon-size: 1rem; } .side-tool-bar-container.small-sidebar { diff --git a/src/components/sidebar/SidebarHelpCenterIcon.vue b/src/components/sidebar/SidebarHelpCenterIcon.vue index c37cd973e..1a15b8487 100644 --- a/src/components/sidebar/SidebarHelpCenterIcon.vue +++ b/src/components/sidebar/SidebarHelpCenterIcon.vue @@ -58,11 +58,12 @@ diff --git a/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue b/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue index c80e1cc2f..533934eaa 100644 --- a/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue +++ b/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue @@ -30,11 +30,18 @@ />