Skip to content

Commit 4ca6220

Browse files
pythongosssssgithub-actions
andauthored
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 <[email protected]>
1 parent 1e41c6d commit 4ca6220

35 files changed

+451
-139
lines changed

browser_tests/fixtures/components/Topbar.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class Topbar {
5050
workflowName: string,
5151
command: 'Save' | 'Save As' | 'Export'
5252
) {
53-
await this.triggerTopbarCommand(['Workflow', command])
53+
await this.triggerTopbarCommand(['File', command])
5454
await this.getSaveDialog().fill(workflowName)
5555
await this.page.keyboard.press('Enter')
5656

@@ -72,8 +72,8 @@ export class Topbar {
7272
}
7373

7474
async triggerTopbarCommand(path: string[]) {
75-
if (path.length < 2) {
76-
throw new Error('Path is too short')
75+
if (path.length < 1) {
76+
throw new Error('Path cannot be empty')
7777
}
7878

7979
const menu = await this.openTopbarMenu()
@@ -85,6 +85,13 @@ export class Topbar {
8585
.locator('.p-tieredmenu-item')
8686
.filter({ has: topLevelMenuItem })
8787
await topLevelMenu.waitFor({ state: 'visible' })
88+
89+
// Handle top-level commands (like "New")
90+
if (path.length === 1) {
91+
await topLevelMenuItem.click()
92+
return
93+
}
94+
8895
await topLevelMenu.hover()
8996

9097
let currentMenu = topLevelMenu

browser_tests/tests/groupNode.spec.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -268,10 +268,7 @@ test.describe('Group Node', () => {
268268
await comfyPage.setSetting('Comfy.ConfirmClear', false)
269269

270270
// Clear workflow
271-
await comfyPage.menu.topbar.triggerTopbarCommand([
272-
'Edit',
273-
'Clear Workflow'
274-
])
271+
await comfyPage.executeCommand('Comfy.ClearWorkflow')
275272

276273
await comfyPage.ctrlV()
277274
await verifyNodeLoaded(comfyPage, 1)
@@ -280,7 +277,7 @@ test.describe('Group Node', () => {
280277
test('Copies and pastes group node into a newly created blank workflow', async ({
281278
comfyPage
282279
}) => {
283-
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
280+
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
284281
await comfyPage.ctrlV()
285282
await verifyNodeLoaded(comfyPage, 1)
286283
})
@@ -296,7 +293,7 @@ test.describe('Group Node', () => {
296293
test('Serializes group node after copy and paste across workflows', async ({
297294
comfyPage
298295
}) => {
299-
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
296+
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
300297
await comfyPage.ctrlV()
301298
const currentGraphState = await comfyPage.page.evaluate(() =>
302299
window['app'].graph.serialize()

browser_tests/tests/interaction.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ test.describe('Load workflow', () => {
684684
workflowA = generateUniqueFilename()
685685
await comfyPage.menu.topbar.saveWorkflow(workflowA)
686686
workflowB = generateUniqueFilename()
687-
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
687+
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
688688
await comfyPage.menu.topbar.saveWorkflow(workflowB)
689689

690690
// Wait for localStorage to persist the workflow paths before reloading

browser_tests/tests/menu.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ test.describe('Menu', () => {
7575

7676
test('Displays keybinding next to item', async ({ comfyPage }) => {
7777
await comfyPage.menu.topbar.openTopbarMenu()
78-
const workflowMenuItem = comfyPage.menu.topbar.getMenuItem('Workflow')
78+
const workflowMenuItem = comfyPage.menu.topbar.getMenuItem('File')
7979
await workflowMenuItem.hover()
8080
const exportTag = comfyPage.page.locator('.keybinding-tag', {
8181
hasText: 'Ctrl + s'

browser_tests/tests/rerouteNode.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ test.describe('Reroute Node', () => {
1818
[workflowName]: workflowName
1919
})
2020
await comfyPage.setup()
21-
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
21+
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
2222

2323
// Insert the workflow
2424
const workflowsTab = comfyPage.menu.workflowsTab

browser_tests/tests/workflowTabThumbnail.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ test.describe('Workflow Tab Thumbnails', () => {
6363
test('Should show thumbnail when hovering over a non-active tab', async ({
6464
comfyPage
6565
}) => {
66-
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
66+
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
6767
const thumbnailImg = await getTabThumbnailImage(
6868
comfyPage,
6969
0,
@@ -73,7 +73,7 @@ test.describe('Workflow Tab Thumbnails', () => {
7373
})
7474

7575
test('Should not show thumbnail for active tab', async ({ comfyPage }) => {
76-
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
76+
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
7777
const thumbnailImg = await getTabThumbnailImage(
7878
comfyPage,
7979
1,
@@ -105,7 +105,7 @@ test.describe('Workflow Tab Thumbnails', () => {
105105
await comfyPage.nextFrame()
106106

107107
// Create a new workflow (tab 1) which will be empty
108-
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
108+
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
109109
await comfyPage.nextFrame()
110110

111111
// Now we have two tabs: tab 0 (default workflow with nodes) and tab 1 (empty)

src/components/sidebar/SidebarHelpCenterIcon.vue

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,22 @@
5858

5959
<script setup lang="ts">
6060
import { storeToRefs } from 'pinia'
61-
import { computed, onMounted, ref } from 'vue'
61+
import { computed, onMounted } from 'vue'
6262
6363
import HelpCenterMenuContent from '@/components/helpcenter/HelpCenterMenuContent.vue'
6464
import ReleaseNotificationToast from '@/components/helpcenter/ReleaseNotificationToast.vue'
6565
import WhatsNewPopup from '@/components/helpcenter/WhatsNewPopup.vue'
66+
import { useHelpCenterStore } from '@/stores/helpCenterStore'
6667
import { useReleaseStore } from '@/stores/releaseStore'
6768
import { useSettingStore } from '@/stores/settingStore'
6869
6970
import SidebarIcon from './SidebarIcon.vue'
7071
7172
const settingStore = useSettingStore()
7273
const releaseStore = useReleaseStore()
74+
const helpCenterStore = useHelpCenterStore()
7375
const { shouldShowRedDot } = storeToRefs(releaseStore)
74-
const isHelpCenterVisible = ref(false)
76+
const { isVisible: isHelpCenterVisible } = storeToRefs(helpCenterStore)
7577
7678
const sidebarLocation = computed(() =>
7779
settingStore.get('Comfy.Sidebar.Location')
@@ -80,11 +82,11 @@ const sidebarLocation = computed(() =>
8082
const sidebarSize = computed(() => settingStore.get('Comfy.Sidebar.Size'))
8183
8284
const toggleHelpCenter = () => {
83-
isHelpCenterVisible.value = !isHelpCenterVisible.value
85+
helpCenterStore.toggle()
8486
}
8587
8688
const closeHelpCenter = () => {
87-
isHelpCenterVisible.value = false
89+
helpCenterStore.hide()
8890
}
8991
9092
// Initialize release store on mount
@@ -130,6 +132,7 @@ onMounted(async () => {
130132
opacity: 0;
131133
transform: translateY(20px);
132134
}
135+
133136
to {
134137
opacity: 1;
135138
transform: translateY(0);

src/components/sidebar/tabs/NodeLibrarySidebarTab.vue

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,18 @@
3030
/>
3131
<Button
3232
v-tooltip.bottom="$t('sideToolbar.nodeLibraryTab.resetView')"
33-
icon="pi pi-refresh"
33+
icon="pi pi-filter-slash"
3434
text
3535
severity="secondary"
3636
@click="resetOrganization"
3737
/>
38+
<Button
39+
v-tooltip.bottom="$t('menu.refresh')"
40+
icon="pi pi-refresh"
41+
text
42+
severity="secondary"
43+
@click="() => commandStore.execute('Comfy.RefreshNodeDefinitions')"
44+
/>
3845
<Popover ref="groupingPopover">
3946
<div class="flex flex-col gap-1 p-2">
4047
<Button
@@ -139,6 +146,7 @@ import {
139146
DEFAULT_SORTING_ID,
140147
nodeOrganizationService
141148
} from '@/services/nodeOrganizationService'
149+
import { useCommandStore } from '@/stores/commandStore'
142150
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
143151
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'
144152
import { useNodeHelpStore } from '@/stores/workspace/nodeHelpStore'
@@ -155,6 +163,7 @@ import NodeBookmarkTreeExplorer from './nodeLibrary/NodeBookmarkTreeExplorer.vue
155163
const nodeDefStore = useNodeDefStore()
156164
const nodeBookmarkStore = useNodeBookmarkStore()
157165
const nodeHelpStore = useNodeHelpStore()
166+
const commandStore = useCommandStore()
158167
const expandedKeys = ref<Record<string, boolean>>({})
159168
const { expandNode, toggleNodeOnEvent } = useTreeExpansion(expandedKeys)
160169

src/components/topbar/CommandMenubar.vue

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,30 @@
5555
v-bind="props.action"
5656
:href="item.url"
5757
target="_blank"
58+
:class="typeof item.class === 'function' ? item.class() : item.class"
59+
@mousedown="
60+
isZoomCommand(item) ? handleZoomMouseDown(item, $event) : undefined
61+
"
62+
@click="isZoomCommand(item) ? handleZoomClick($event) : undefined"
5863
>
59-
<span v-if="item.icon" class="p-menubar-item-icon" :class="item.icon" />
64+
<i
65+
v-if="hasActiveStateSiblings(item)"
66+
class="p-menubar-item-icon pi pi-check text-sm"
67+
:class="{ invisible: !item.comfyCommand?.active?.() }"
68+
/>
69+
<span
70+
v-else-if="
71+
item.icon && item.comfyCommand?.id !== 'Comfy.NewBlankWorkflow'
72+
"
73+
class="p-menubar-item-icon"
74+
:class="item.icon"
75+
/>
6076
<span class="p-menubar-item-label text-nowrap">{{ item.label }}</span>
77+
<i
78+
v-if="item.comfyCommand?.id === 'Comfy.NewBlankWorkflow'"
79+
class="ml-auto"
80+
:class="item.icon"
81+
/>
6182
<span
6283
v-if="item?.comfyCommand?.keybinding"
6384
class="ml-auto border border-surface rounded text-muted text-xs text-nowrap p-1 keybinding-tag"
@@ -94,6 +115,7 @@ import { useSettingStore } from '@/stores/settingStore'
94115
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
95116
import { showNativeSystemMenu } from '@/utils/envUtil'
96117
import { normalizeI18nKey } from '@/utils/formatUtil'
118+
import { whileMouseDown } from '@/utils/mouseDownUtil'
97119
98120
const colorPaletteStore = useColorPaletteStore()
99121
const menuItemsStore = useMenuItemStore()
@@ -163,16 +185,22 @@ const extraMenuItems: MenuItem[] = [
163185
},
164186
{ separator: true },
165187
{
166-
key: 'manage-extensions',
167-
label: t('menu.manageExtensions'),
168-
icon: 'mdi mdi-puzzle-outline',
169-
command: showManageExtensions
188+
key: 'browse-templates',
189+
label: t('menuLabels.Browse Templates'),
190+
icon: 'pi pi-folder-open',
191+
command: () => commandStore.execute('Comfy.BrowseTemplates')
170192
},
171193
{
172194
key: 'settings',
173195
label: t('g.settings'),
174196
icon: 'mdi mdi-cog-outline',
175197
command: () => showSettings()
198+
},
199+
{
200+
key: 'manage-extensions',
201+
label: t('menu.manageExtensions'),
202+
icon: 'mdi mdi-puzzle-outline',
203+
command: showManageExtensions
176204
}
177205
]
178206
@@ -237,6 +265,36 @@ const onMenuShow = () => {
237265
}
238266
})
239267
}
268+
269+
const isZoomCommand = (item: MenuItem) => {
270+
return (
271+
item.comfyCommand?.id === 'Comfy.Canvas.ZoomIn' ||
272+
item.comfyCommand?.id === 'Comfy.Canvas.ZoomOut'
273+
)
274+
}
275+
276+
const handleZoomMouseDown = (item: MenuItem, event: MouseEvent) => {
277+
if (item.comfyCommand) {
278+
whileMouseDown(
279+
event,
280+
async () => {
281+
await commandStore.execute(item.comfyCommand!.id)
282+
},
283+
50
284+
)
285+
}
286+
}
287+
288+
const handleZoomClick = (event: MouseEvent) => {
289+
event.preventDefault()
290+
event.stopPropagation()
291+
// Prevent the menu from closing for zoom commands
292+
return false
293+
}
294+
295+
const hasActiveStateSiblings = (item: MenuItem): boolean => {
296+
return menuItemsStore.menuItemHasActiveStateChildren[item.parentPath]
297+
}
240298
</script>
241299

242300
<style scoped>

0 commit comments

Comments
 (0)