diff --git a/browser_tests/colorPalette.spec.ts b/browser_tests/colorPalette.spec.ts index 8caca6600..2d80544eb 100644 --- a/browser_tests/colorPalette.spec.ts +++ b/browser_tests/colorPalette.spec.ts @@ -138,6 +138,7 @@ test.describe('Color Palette', () => { test.afterEach(async ({ comfyPage }) => { await comfyPage.setSetting('Comfy.CustomColorPalettes', {}) await comfyPage.setSetting('Comfy.ColorPalette', 'dark') + await comfyPage.setSetting('Comfy.Node.Opacity', 1.0) }) test('Can show custom color palette', async ({ comfyPage }) => { diff --git a/browser_tests/menu.spec.ts b/browser_tests/menu.spec.ts index 9eec3e97b..e1f48422d 100644 --- a/browser_tests/menu.spec.ts +++ b/browser_tests/menu.spec.ts @@ -85,7 +85,21 @@ test.describe('Menu', () => { const count = await comfyPage.getGraphNodesCount() // Drag the node onto the canvas const canvasSelector = '#graph-canvas' - await comfyPage.page.dragAndDrop(nodeSelector, canvasSelector) + + // Get the bounding box of the canvas element + const canvasBoundingBox = (await comfyPage.page + .locator(canvasSelector) + .boundingBox())! + + // Calculate the center position of the canvas + const targetPosition = { + x: canvasBoundingBox.x + canvasBoundingBox.width / 2, + y: canvasBoundingBox.y + canvasBoundingBox.height / 2 + } + + await comfyPage.page.dragAndDrop(nodeSelector, canvasSelector, { + targetPosition + }) // Verify the node is added to the canvas expect(await comfyPage.getGraphNodesCount()).toBe(count + 1) diff --git a/index.html b/index.html index f60011061..07f29df3e 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,7 @@ - +
@@ -115,12 +109,12 @@ import { useQueueSettingsStore } from '@/stores/queueStore' import { app } from '@/scripts/app' -import { api } from '@/scripts/api' import { storeToRefs } from 'pinia' import { useSettingStore } from '@/stores/settingStore' -import { useToastStore } from '@/stores/toastStore' +import { useCommandStore } from '@/stores/commandStore' const settingsStore = useSettingStore() +const commandStore = useCommandStore() const queueCountStore = storeToRefs(useQueuePendingTaskCountStore()) const { batchCount, mode: queueMode } = storeToRefs(useQueueSettingsStore()) @@ -147,31 +141,6 @@ const executingPrompt = computed(() => !!queueCountStore.count.value) const queuePrompt = (e: MouseEvent) => { app.queuePrompt(e.shiftKey ? -1 : 0, batchCount.value) } - -const actions = { - interrupt: async () => { - await api.interrupt() - useToastStore().add({ - severity: 'info', - summary: 'Interrupted', - detail: 'Execution has been interrupted', - life: 1000 - }) - }, - clearWorkflow: () => { - if ( - !(settingsStore.get('Comfy.ComfirmClear') ?? true) || - confirm('Clear workflow?') - ) { - app.clean() - app.graph.clear() - api.dispatchEvent(new CustomEvent('graphCleared')) - } - }, - resetView: () => app.resetView(), - openClipspace: () => app['openClipspace'](), - refresh: () => app.refreshComboInNodes() -} + + diff --git a/src/components/topbar/WorkflowTabs.vue b/src/components/topbar/WorkflowTabs.vue new file mode 100644 index 000000000..ef4b86902 --- /dev/null +++ b/src/components/topbar/WorkflowTabs.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/scripts/changeTracker.ts b/src/scripts/changeTracker.ts index cfc4d1b27..c6cc0c45b 100644 --- a/src/scripts/changeTracker.ts +++ b/src/scripts/changeTracker.ts @@ -7,8 +7,8 @@ import { ComfyWorkflow } from './workflows' export class ChangeTracker { static MAX_HISTORY = 50 #app: ComfyApp - undo = [] - redo = [] + undoQueue = [] + redoQueue = [] activeState = null isOurLoad = false workflow: ComfyWorkflow | null @@ -54,12 +54,12 @@ export class ChangeTracker { return } if (!ChangeTracker.graphEqual(this.activeState, currentState)) { - this.undo.push(this.activeState) - if (this.undo.length > ChangeTracker.MAX_HISTORY) { - this.undo.shift() + this.undoQueue.push(this.activeState) + if (this.undoQueue.length > ChangeTracker.MAX_HISTORY) { + this.undoQueue.shift() } this.activeState = clone(currentState) - this.redo.length = 0 + this.redoQueue.length = 0 this.workflow.unsaved = true api.dispatchEvent( new CustomEvent('graphChanged', { detail: this.activeState }) @@ -80,13 +80,21 @@ export class ChangeTracker { } } + async undo() { + await this.updateState(this.undoQueue, this.redoQueue) + } + + async redo() { + await this.updateState(this.redoQueue, this.undoQueue) + } + async undoRedo(e) { if (e.ctrlKey || e.metaKey) { if (e.key === 'y') { - this.updateState(this.redo, this.undo) + await this.redo() return true } else if (e.key === 'z') { - this.updateState(this.undo, this.redo) + await this.undo() return true } } @@ -276,4 +284,4 @@ export class ChangeTracker { } } -const globalTracker = new ChangeTracker({} as ComfyWorkflow) +export const globalTracker = new ChangeTracker({} as ComfyWorkflow) diff --git a/src/scripts/ui/menu/menu.css b/src/scripts/ui/menu/menu.css index c01dab45e..4a203d8b5 100644 --- a/src/scripts/ui/menu/menu.css +++ b/src/scripts/ui/menu/menu.css @@ -122,24 +122,6 @@ } /* Menu */ -.comfyui-menu { - width: 100vw; - background: var(--comfy-menu-bg); - color: var(--fg-color); - font-family: Arial, Helvetica, sans-serif; - font-size: 0.8em; - display: flex; - padding: 4px 8px; - align-items: center; - gap: 8px; - box-sizing: border-box; - z-index: 1000; - order: 0; - grid-column: 1/-1; - overflow: auto; - max-height: 90vh; -} - .comfyui-menu>* { flex-shrink: 0; } @@ -183,13 +165,6 @@ flex: auto; } -.comfyui-logo { - font-size: 1.2em; - margin: 0; - user-select: none; - cursor: default; -} - /** Send to workflow widget selection dialog */ .comfy-widget-selection-dialog { border: none; diff --git a/src/stores/commandStore.ts b/src/stores/commandStore.ts new file mode 100644 index 000000000..c299c406b --- /dev/null +++ b/src/stores/commandStore.ts @@ -0,0 +1,87 @@ +import { app } from '@/scripts/app' +import { api } from '@/scripts/api' +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { globalTracker } from '@/scripts/changeTracker' +import { useSettingStore } from '@/stores/settingStore' +import { useToastStore } from '@/stores/toastStore' +import { showTemplateWorkflowsDialog } from '@/services/dialogService' + +type Command = () => void | Promise + +const getTracker = () => + app.workflowManager.activeWorkflow?.changeTracker ?? globalTracker + +export const useCommandStore = defineStore('command', () => { + const settingStore = useSettingStore() + const commands = ref>({ + 'Comfy.NewBlankWorkflow': () => { + app.workflowManager.setWorkflow(null) + app.clean() + app.graph.clear() + app.workflowManager.activeWorkflow.track() + }, + 'Comfy.OpenWorkflow': () => { + app.ui.loadFile() + }, + 'Comfy.LoadDefaultWorkflow': async () => { + await app.loadGraphData() + }, + 'Comfy.SaveWorkflow': () => { + app.workflowManager.activeWorkflow.save() + }, + 'Comfy.SaveWorkflowAs': () => { + app.workflowManager.activeWorkflow.save(true) + }, + 'Comfy.ExportWorkflow': () => { + app.menu.exportWorkflow('workflow', 'workflow') + }, + 'Comfy.ExportWorkflowAPI': () => { + app.menu.exportWorkflow('workflow_api', 'output') + }, + 'Comfy.Undo': async () => { + await getTracker().undo() + }, + 'Comfy.Redo': async () => { + await getTracker().redo() + }, + 'Comfy.ClearWorkflow': () => { + if ( + !settingStore.get('Comfy.ComfirmClear') || + confirm('Clear workflow?') + ) { + app.clean() + app.graph.clear() + api.dispatchEvent(new CustomEvent('graphCleared')) + } + }, + 'Comfy.ResetView': () => { + app.resetView() + }, + 'Comfy.OpenClipspace': () => { + app['openClipspace']?.() + }, + 'Comfy.RefreshNodeDefinitions': async () => { + await app.refreshComboInNodes() + }, + 'Comfy.Interrupt': async () => { + await api.interrupt() + useToastStore().add({ + severity: 'info', + summary: 'Interrupted', + detail: 'Execution has been interrupted', + life: 1000 + }) + }, + 'Comfy.BrowseTemplates': showTemplateWorkflowsDialog + }) + + const getCommand = (command: string) => { + return commands.value[command] ?? (() => {}) + } + + return { + commands, + getCommand + } +}) diff --git a/src/stores/coreMenuItemStore.ts b/src/stores/coreMenuItemStore.ts new file mode 100644 index 000000000..e63bf9612 --- /dev/null +++ b/src/stores/coreMenuItemStore.ts @@ -0,0 +1,85 @@ +import { defineStore } from 'pinia' +import type { MenuItem } from 'primevue/menuitem' +import { computed } from 'vue' +import { useCommandStore } from './commandStore' + +export const useCoreMenuItemStore = defineStore('coreMenuItem', () => { + const commandStore = useCommandStore() + const menuItems = computed(() => { + return [ + { + label: 'Workflow', + items: [ + { + label: 'New', + icon: 'pi pi-plus', + command: commandStore.commands['Comfy.NewBlankWorkflow'] + }, + { + separator: true + }, + { + label: 'Open', + icon: 'pi pi-folder-open', + command: commandStore.commands['Comfy.OpenWorkflow'] + }, + { + label: 'Browse Templates', + icon: 'pi pi-th-large', + command: commandStore.commands['Comfy.BrowseTemplates'] + }, + { + separator: true + }, + { + label: 'Save', + icon: 'pi pi-save', + command: commandStore.commands['Comfy.SaveWorkflow'] + }, + { + label: 'Save As', + icon: 'pi pi-save', + command: commandStore.commands['Comfy.SaveWorkflowAs'] + }, + { + label: 'Export', + icon: 'pi pi-download', + command: commandStore.commands['Comfy.ExportWorkflow'] + }, + { + label: 'Export (API Format)', + icon: 'pi pi-download', + command: commandStore.commands['Comfy.ExportWorkflowAPI'] + } + ] + }, + { + label: 'Edit', + items: [ + { + label: 'Undo', + icon: 'pi pi-undo', + command: commandStore.commands['Comfy.Undo'] + }, + { + label: 'Redo', + icon: 'pi pi-refresh', + command: commandStore.commands['Comfy.Redo'] + }, + { + separator: true + }, + { + label: 'Clear Workflow', + icon: 'pi pi-trash', + command: commandStore.commands['Comfy.ClearWorkflow'] + } + ] + } + ] + }) + + return { + menuItems + } +}) diff --git a/src/views/GraphView.vue b/src/views/GraphView.vue index 1a492e7d1..e6a22b0de 100644 --- a/src/views/GraphView.vue +++ b/src/views/GraphView.vue @@ -4,6 +4,7 @@ +