From 4e554503c051ef6b042def82195eca5c861b85b8 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Thu, 26 Dec 2024 00:11:27 -0500 Subject: [PATCH] [Refactor] Convert workflowService to composable (#2057) --- src/components/graph/GraphCanvas.vue | 4 +- .../sidebar/tabs/WorkflowsSidebarTab.vue | 8 +- src/components/topbar/WorkflowTab.vue | 4 +- src/components/topbar/WorkflowTabs.vue | 3 +- src/hooks/coreCommandHooks.ts | 3 +- src/scripts/app.ts | 19 +- src/scripts/pnginfo.ts | 1 - src/services/workflowService.ts | 178 ++++++++++-------- 8 files changed, 119 insertions(+), 101 deletions(-) diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index f11042813..60e03e748 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -64,7 +64,7 @@ import { setStorageValue } from '@/scripts/utils' import { ChangeTracker } from '@/scripts/changeTracker' import { api } from '@/scripts/api' import { useCommandStore } from '@/stores/commandStore' -import { workflowService } from '@/services/workflowService' +import { useWorkflowService } from '@/services/workflowService' import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { useColorPaletteService } from '@/services/colorPaletteService' @@ -335,7 +335,7 @@ onMounted(async () => { () => settingStore.get('Comfy.Locale'), async () => { await useCommandStore().execute('Comfy.RefreshNodeDefinitions') - workflowService.reloadCurrentWorkflow() + useWorkflowService().reloadCurrentWorkflow() } ) diff --git a/src/components/sidebar/tabs/WorkflowsSidebarTab.vue b/src/components/sidebar/tabs/WorkflowsSidebarTab.vue index 42fcd6a62..1346e84ca 100644 --- a/src/components/sidebar/tabs/WorkflowsSidebarTab.vue +++ b/src/components/sidebar/tabs/WorkflowsSidebarTab.vue @@ -159,12 +159,10 @@ import { ComfyWorkflow } from '@/stores/workflowStore' import { useI18n } from 'vue-i18n' import { useTreeExpansion } from '@/hooks/treeHooks' import { useSettingStore } from '@/stores/settingStore' -import { workflowService } from '@/services/workflowService' +import { useWorkflowService } from '@/services/workflowService' import { useWorkspaceStore } from '@/stores/workspaceStore' import { appendJsonExt } from '@/utils/formatUtil' import { buildTree, sortedTree } from '@/utils/treeUtil' -import { useConfirm } from 'primevue/useconfirm' -import { useToast } from 'primevue/usetoast' import ConfirmDialog from 'primevue/confirmdialog' const settingStore = useSettingStore() @@ -195,6 +193,7 @@ const handleSearch = (query: string) => { const commandStore = useCommandStore() const workflowStore = useWorkflowStore() +const workflowService = useWorkflowService() const workspaceStore = useWorkspaceStore() const { t } = useI18n() const expandedKeys = ref>({}) @@ -234,9 +233,6 @@ const openWorkflowsTree = computed(() => buildTree(workflowStore.openWorkflows, (workflow) => [workflow.key]) ) -const confirm = useConfirm() -const toast = useToast() - const renderTreeNode = ( node: TreeNode, type: WorkflowTreeType diff --git a/src/components/topbar/WorkflowTab.vue b/src/components/topbar/WorkflowTab.vue index 2296d0f49..644974851 100644 --- a/src/components/topbar/WorkflowTab.vue +++ b/src/components/topbar/WorkflowTab.vue @@ -33,7 +33,7 @@ import { ComfyWorkflow } from '@/stores/workflowStore' import { useWorkflowStore } from '@/stores/workflowStore' import Button from 'primevue/button' import { ref } from 'vue' -import { workflowService } from '@/services/workflowService' +import { useWorkflowService } from '@/services/workflowService' import { useWorkspaceStore } from '@/stores/workspaceStore' import { usePragmaticDraggable, usePragmaticDroppable } from '@/hooks/dndHooks' @@ -54,7 +54,7 @@ const workflowTabRef = ref(null) const closeWorkflows = async (options: WorkflowOption[]) => { for (const opt of options) { if ( - !(await workflowService.closeWorkflow(opt.workflow, { + !(await useWorkflowService().closeWorkflow(opt.workflow, { warnIfUnsaved: !workspaceStore.shiftDown })) ) { diff --git a/src/components/topbar/WorkflowTabs.vue b/src/components/topbar/WorkflowTabs.vue index be565906a..a0c1ddc64 100644 --- a/src/components/topbar/WorkflowTabs.vue +++ b/src/components/topbar/WorkflowTabs.vue @@ -34,7 +34,7 @@ import { useCommandStore } from '@/stores/commandStore' import SelectButton from 'primevue/selectbutton' import Button from 'primevue/button' import { computed, ref } from 'vue' -import { workflowService } from '@/services/workflowService' +import { useWorkflowService } from '@/services/workflowService' import { useWorkspaceStore } from '@/stores/workspaceStore' import ContextMenu from 'primevue/contextmenu' import { useI18n } from 'vue-i18n' @@ -51,6 +51,7 @@ const props = defineProps<{ const { t } = useI18n() const workspaceStore = useWorkspaceStore() const workflowStore = useWorkflowStore() +const workflowService = useWorkflowService() const rightClickedTab = ref(null) const menu = ref() diff --git a/src/hooks/coreCommandHooks.ts b/src/hooks/coreCommandHooks.ts index f56e459ee..80f15627d 100644 --- a/src/hooks/coreCommandHooks.ts +++ b/src/hooks/coreCommandHooks.ts @@ -4,7 +4,7 @@ import { showSettingsDialog, showTemplateWorkflowsDialog } from '@/services/dialogService' -import { workflowService } from '@/services/workflowService' +import { useWorkflowService } from '@/services/workflowService' import type { ComfyCommand } from '@/stores/commandStore' import { useTitleEditorStore } from '@/stores/graphStore' import { useQueueSettingsStore, useQueueStore } from '@/stores/queueStore' @@ -22,6 +22,7 @@ import { import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore' export function useCoreCommands(): ComfyCommand[] { + const workflowService = useWorkflowService() const getTracker = () => useWorkflowStore()?.activeWorkflow?.changeTracker const getSelectedNodes = (): LGraphNode[] => { diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 40db4ef4e..b79be4c15 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -58,7 +58,7 @@ import { KeyComboImpl, useKeybindingStore } from '@/stores/keybindingStore' import { useCommandStore } from '@/stores/commandStore' import { shallowReactive } from 'vue' import { type IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets' -import { workflowService } from '@/services/workflowService' +import { useWorkflowService } from '@/services/workflowService' import { useWidgetStore } from '@/stores/widgetStore' import { deserialiseAndCreate } from '@/extensions/core/vintageClipboard' import { st } from '@/i18n' @@ -2041,7 +2041,7 @@ export class ComfyApp { graphData = validatedGraphData ?? graphData } - workflowService.beforeLoadNewGraph() + useWorkflowService().beforeLoadNewGraph() const missingNodeTypes: MissingNodeType[] = [] const missingModels = [] @@ -2207,8 +2207,11 @@ export class ComfyApp { 'afterConfigureGraph', missingNodeTypes ) - // @ts-expect-error zod types issue. Will be fixed after we enable ts-strict - await workflowService.afterLoadNewGraph(workflow, this.graph.serialize()) + await useWorkflowService().afterLoadNewGraph( + workflow, + // @ts-expect-error zod types issue. Will be fixed after we enable ts-strict + this.graph.serialize() + ) requestAnimationFrame(() => { this.graph.setDirtyCanvas(true, true) }) @@ -2522,10 +2525,10 @@ export class ComfyApp { } else if (pngInfo?.parameters) { // Note: Not putting this in `importA1111` as it is mostly not used // by external callers, and `importA1111` has no access to `app`. - workflowService.beforeLoadNewGraph() + useWorkflowService().beforeLoadNewGraph() importA1111(this.graph, pngInfo.parameters) // @ts-expect-error zod type issue on ComfyWorkflowJSON. Should be resolved after enabling ts-strict globally. - workflowService.afterLoadNewGraph(fileName, this.serializeGraph()) + useWorkflowService().afterLoadNewGraph(fileName, this.serializeGraph()) } else { this.showErrorOnFileLoad(file) } @@ -2609,7 +2612,7 @@ export class ComfyApp { } loadApiJson(apiData, fileName: string) { - workflowService.beforeLoadNewGraph() + useWorkflowService().beforeLoadNewGraph() const missingNodeTypes = Object.values(apiData).filter( // @ts-expect-error @@ -2701,7 +2704,7 @@ export class ComfyApp { app.graph.arrange() // @ts-expect-error zod type issue on ComfyWorkflowJSON. Should be resolved after enabling ts-strict globally. - workflowService.afterLoadNewGraph(fileName, this.serializeGraph()) + useWorkflowService().afterLoadNewGraph(fileName, this.serializeGraph()) } /** diff --git a/src/scripts/pnginfo.ts b/src/scripts/pnginfo.ts index 3509a39fb..3473cce2b 100644 --- a/src/scripts/pnginfo.ts +++ b/src/scripts/pnginfo.ts @@ -3,7 +3,6 @@ import { LiteGraph } from '@comfyorg/litegraph' import { api } from './api' import { getFromPngFile } from './metadata/png' import { getFromFlacFile } from './metadata/flac' -import { workflowService } from '@/services/workflowService' // Original functions left in for backwards compatibility export function getPngMetadata(file: File): Promise> { diff --git a/src/services/workflowService.ts b/src/services/workflowService.ts index c67400732..74cfd046a 100644 --- a/src/services/workflowService.ts +++ b/src/services/workflowService.ts @@ -12,34 +12,36 @@ import { appendJsonExt } from '@/utils/formatUtil' import { t } from '@/i18n' import { useToastStore } from '@/stores/toastStore' -async function getFilename(defaultName: string): Promise { - if (useSettingStore().get('Comfy.PromptFilename')) { - let filename = await showPromptDialog({ - title: t('workflowService.exportWorkflow'), - message: t('workflowService.enterFilename') + ':', - defaultValue: defaultName - }) - if (!filename) return null - if (!filename.toLowerCase().endsWith('.json')) { - filename += '.json' - } - return filename - } - return defaultName -} +export const useWorkflowService = () => { + const settingStore = useSettingStore() + const workflowStore = useWorkflowStore() + const toastStore = useToastStore() -// TODO(huchenlei): Auto Error Handling for all methods. -export const workflowService = { + async function getFilename(defaultName: string): Promise { + if (settingStore.get('Comfy.PromptFilename')) { + let filename = await showPromptDialog({ + title: t('workflowService.exportWorkflow'), + message: t('workflowService.enterFilename') + ':', + defaultValue: defaultName + }) + if (!filename) return null + if (!filename.toLowerCase().endsWith('.json')) { + filename += '.json' + } + return filename + } + return defaultName + } /** * Export the current workflow as a JSON file * @param filename The filename to save the workflow as * @param promptProperty The property of the prompt to export */ - async exportWorkflow( + const exportWorkflow = async ( filename: string, promptProperty: 'workflow' | 'output' - ): Promise { - const workflow = useWorkflowStore().activeWorkflow + ): Promise => { + const workflow = workflowStore.activeWorkflow if (workflow?.path) { filename = workflow.filename } @@ -49,12 +51,12 @@ export const workflowService = { const file = await getFilename(filename) if (!file) return downloadBlob(file, blob) - }, + } /** * Save a workflow as a new file * @param workflow The workflow to save */ - async saveWorkflowAs(workflow: ComfyWorkflow) { + const saveWorkflowAs = async (workflow: ComfyWorkflow) => { const newFilename = await showPromptDialog({ title: t('workflowService.saveWorkflow'), message: t('workflowService.enterFilename') + ':', @@ -64,7 +66,6 @@ export const workflowService = { const newPath = workflow.directory + '/' + appendJsonExt(newFilename) const newKey = newPath.substring(ComfyWorkflow.basePath.length) - const workflowStore = useWorkflowStore() const existingWorkflow = workflowStore.getWorkflowByPath(newPath) if (existingWorkflow && !existingWorkflow.isTemporary) { @@ -78,73 +79,73 @@ export const workflowService = { if (res !== true) return if (existingWorkflow.path === workflow.path) { - await this.saveWorkflow(workflow) + await saveWorkflow(workflow) return } - const deleted = await this.deleteWorkflow(existingWorkflow, true) + const deleted = await deleteWorkflow(existingWorkflow, true) if (!deleted) return } if (workflow.isTemporary) { - await this.renameWorkflow(workflow, newPath) + await renameWorkflow(workflow, newPath) await workflowStore.saveWorkflow(workflow) } else { const tempWorkflow = workflowStore.createTemporary( newKey, workflow.activeState as ComfyWorkflowJSON ) - await this.openWorkflow(tempWorkflow) + await openWorkflow(tempWorkflow) await workflowStore.saveWorkflow(tempWorkflow) } - }, + } /** * Save a workflow * @param workflow The workflow to save */ - async saveWorkflow(workflow: ComfyWorkflow) { + const saveWorkflow = async (workflow: ComfyWorkflow) => { if (workflow.isTemporary) { - await this.saveWorkflowAs(workflow) + await saveWorkflowAs(workflow) } else { - await useWorkflowStore().saveWorkflow(workflow) + await workflowStore.saveWorkflow(workflow) } - }, + } /** * Load the default workflow */ - async loadDefaultWorkflow() { + const loadDefaultWorkflow = async () => { await app.loadGraphData(defaultGraph) - }, + } /** * Load a blank workflow */ - async loadBlankWorkflow() { + const loadBlankWorkflow = async () => { await app.loadGraphData(blankGraph) - }, + } /** * Reload the current workflow * This is used to refresh the node definitions update, e.g. when the locale changes. */ - async reloadCurrentWorkflow() { - const workflow = useWorkflowStore().activeWorkflow + const reloadCurrentWorkflow = async () => { + const workflow = workflowStore.activeWorkflow if (workflow) { - await this.openWorkflow(workflow, { force: true }) + await openWorkflow(workflow, { force: true }) } - }, + } /** * Open a workflow in the current workspace * @param workflow The workflow to open * @param options The options for opening the workflow */ - async openWorkflow( + const openWorkflow = async ( workflow: ComfyWorkflow, options: { force: boolean } = { force: false } - ) { - if (useWorkflowStore().isActive(workflow) && !options.force) return + ) => { + if (workflowStore.isActive(workflow) && !options.force) return const loadFromRemote = !workflow.isLoaded if (loadFromRemote) { @@ -161,17 +162,17 @@ export const workflowService = { showMissingNodesDialog: loadFromRemote } ) - }, + } /** * Close a workflow with confirmation if there are unsaved changes * @param workflow The workflow to close * @returns true if the workflow was closed, false if the user cancelled */ - async closeWorkflow( + const closeWorkflow = async ( workflow: ComfyWorkflow, options: { warnIfUnsaved: boolean } = { warnIfUnsaved: true } - ): Promise { + ): Promise => { if (!workflow.isLoaded) { return true } @@ -187,38 +188,37 @@ export const workflowService = { if (confirmed === null) return false if (confirmed === true) { - await this.saveWorkflow(workflow) + await saveWorkflow(workflow) } } - const workflowStore = useWorkflowStore() // If this is the last workflow, create a new default temporary workflow if (workflowStore.openWorkflows.length === 1) { - await this.loadDefaultWorkflow() + await loadDefaultWorkflow() } // If this is the active workflow, load the next workflow if (workflowStore.isActive(workflow)) { - await this.loadNextOpenedWorkflow() + await loadNextOpenedWorkflow() } await workflowStore.closeWorkflow(workflow) return true - }, + } - async renameWorkflow(workflow: ComfyWorkflow, newPath: string) { - await useWorkflowStore().renameWorkflow(workflow, newPath) - }, + const renameWorkflow = async (workflow: ComfyWorkflow, newPath: string) => { + await workflowStore.renameWorkflow(workflow, newPath) + } /** * Delete a workflow * @param workflow The workflow to delete * @returns `true` if the workflow was deleted, `false` if the user cancelled */ - async deleteWorkflow( + const deleteWorkflow = async ( workflow: ComfyWorkflow, silent = false - ): Promise { - const bypassConfirm = !useSettingStore().get('Comfy.Workflow.ConfirmDelete') + ): Promise => { + const bypassConfirm = !settingStore.get('Comfy.Workflow.ConfirmDelete') let confirmed: boolean | null = bypassConfirm || silent if (!confirmed) { @@ -231,23 +231,22 @@ export const workflowService = { if (!confirmed) return false } - const workflowStore = useWorkflowStore() if (workflowStore.isOpen(workflow)) { - const closed = await this.closeWorkflow(workflow, { + const closed = await closeWorkflow(workflow, { warnIfUnsaved: !confirmed }) if (!closed) return false } await workflowStore.deleteWorkflow(workflow) if (!silent) { - useToastStore().add({ + toastStore.add({ severity: 'info', summary: t('sideToolbar.workflowTab.deleted'), life: 1000 }) } return true - }, + } /** * This method is called before loading a new graph. @@ -259,30 +258,30 @@ export const workflowService = { * This function is used to save the current workflow states before loading * a new graph. */ - beforeLoadNewGraph() { + const beforeLoadNewGraph = () => { // Use workspaceStore here as it is patched in jest tests. const workflowStore = useWorkspaceStore().workflow const activeWorkflow = workflowStore.activeWorkflow if (activeWorkflow) { activeWorkflow.changeTracker.store() } - }, + } /** * Set the active workflow after the new graph is loaded. * * The call relationship is - * workflowService.openWorkflow -> app.loadGraphData -> workflowService.afterLoadNewGraph - * app.loadApiJson -> workflowService.afterLoadNewGraph - * app.importA1111 -> workflowService.afterLoadNewGraph + * useWorkflowService().openWorkflow -> app.loadGraphData -> useWorkflowService().afterLoadNewGraph + * app.loadApiJson -> useWorkflowService().afterLoadNewGraph + * app.importA1111 -> useWorkflowService().afterLoadNewGraph * * @param value The value to set as the active workflow. * @param workflowData The initial workflow data loaded to the graph editor. */ - async afterLoadNewGraph( + const afterLoadNewGraph = async ( value: string | ComfyWorkflow | null, workflowData: ComfyWorkflowJSON - ) { + ) => { // Use workspaceStore here as it is patched in jest tests. const workflowStore = useWorkspaceStore().workflow if (typeof value === 'string') { @@ -311,12 +310,12 @@ export const workflowService = { const loadedWorkflow = await workflowStore.openWorkflow(value) loadedWorkflow.changeTracker.reset(workflowData) loadedWorkflow.changeTracker.restore() - }, + } /** * Insert the given workflow into the current graph editor. */ - async insertWorkflow(workflow: ComfyWorkflow) { + const insertWorkflow = async (workflow: ComfyWorkflow) => { const loadedWorkflow = await workflow.load() const data = loadedWorkflow.initialState const old = localStorage.getItem('litegrapheditor_clipboard') @@ -334,27 +333,46 @@ export const workflowService = { if (old !== null) { localStorage.setItem('litegrapheditor_clipboard', old) } - }, + } - async loadNextOpenedWorkflow() { - const nextWorkflow = useWorkflowStore().openedWorkflowIndexShift(1) + const loadNextOpenedWorkflow = async () => { + const nextWorkflow = workflowStore.openedWorkflowIndexShift(1) if (nextWorkflow) { - await this.openWorkflow(nextWorkflow) + await openWorkflow(nextWorkflow) } - }, + } - async loadPreviousOpenedWorkflow() { - const previousWorkflow = useWorkflowStore().openedWorkflowIndexShift(-1) + const loadPreviousOpenedWorkflow = async () => { + const previousWorkflow = workflowStore.openedWorkflowIndexShift(-1) if (previousWorkflow) { - await this.openWorkflow(previousWorkflow) + await openWorkflow(previousWorkflow) } - }, + } /** * Takes an existing workflow and duplicates it with a new name */ - async duplicateWorkflow(workflow: ComfyWorkflow) { + const duplicateWorkflow = async (workflow: ComfyWorkflow) => { const state = JSON.parse(JSON.stringify(workflow.activeState)) await app.loadGraphData(state, true, true, workflow.filename) } + + return { + exportWorkflow, + saveWorkflowAs, + saveWorkflow, + loadDefaultWorkflow, + loadBlankWorkflow, + reloadCurrentWorkflow, + openWorkflow, + closeWorkflow, + renameWorkflow, + deleteWorkflow, + insertWorkflow, + loadNextOpenedWorkflow, + loadPreviousOpenedWorkflow, + duplicateWorkflow, + afterLoadNewGraph, + beforeLoadNewGraph + } }