From f69e88bf41b21f67df195cf05c682d8228cb2e34 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Thu, 26 Dec 2024 00:26:01 -0500 Subject: [PATCH] [Refactor] Convert dialogService to composable (#2058) --- src/extensions/core/electronAdapter.ts | 4 +- src/extensions/core/groupNode.ts | 4 +- src/extensions/core/nodeTemplates.ts | 4 +- src/hooks/coreCommandHooks.ts | 14 +- src/scripts/app.ts | 12 +- src/scripts/ui.ts | 6 +- src/services/dialogService.ts | 290 +++++++++++++------------ src/services/workflowService.ts | 13 +- 8 files changed, 176 insertions(+), 171 deletions(-) diff --git a/src/extensions/core/electronAdapter.ts b/src/extensions/core/electronAdapter.ts index 4112cbf13..cf95756d3 100644 --- a/src/extensions/core/electronAdapter.ts +++ b/src/extensions/core/electronAdapter.ts @@ -1,6 +1,6 @@ import { t } from '@/i18n' import { app } from '@/scripts/app' -import { showConfirmationDialog } from '@/services/dialogService' +import { useDialogService } from '@/services/dialogService' import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil' ;(async () => { if (!isElectron()) return @@ -114,7 +114,7 @@ import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil' label: 'Reinstall', icon: 'pi pi-refresh', async function() { - const proceed = await showConfirmationDialog({ + const proceed = await useDialogService().showConfirmationDialog({ message: t('desktopMenu.confirmReinstall'), title: t('desktopMenu.reinstall'), type: 'reinstall' diff --git a/src/extensions/core/groupNode.ts b/src/extensions/core/groupNode.ts index b47130e8b..c30f98ee2 100644 --- a/src/extensions/core/groupNode.ts +++ b/src/extensions/core/groupNode.ts @@ -14,7 +14,7 @@ import { serialise } from '@/extensions/core/vintageClipboard' import type { ComfyNodeDef } from '@/types/apiTypes' -import { showPromptDialog } from '@/services/dialogService' +import { useDialogService } from '@/services/dialogService' import { t } from '@/i18n' type GroupNodeWorkflowData = { @@ -80,7 +80,7 @@ class GroupNodeBuilder { } async getName() { - const name = await showPromptDialog({ + const name = await useDialogService().showPromptDialog({ title: t('groupNode.create'), message: t('groupNode.enterName'), defaultValue: '' diff --git a/src/extensions/core/nodeTemplates.ts b/src/extensions/core/nodeTemplates.ts index adebc7996..2a7f61f40 100644 --- a/src/extensions/core/nodeTemplates.ts +++ b/src/extensions/core/nodeTemplates.ts @@ -6,7 +6,7 @@ import { GroupNodeConfig, GroupNodeHandler } from './groupNode' import { LGraphCanvas } from '@comfyorg/litegraph' import { useToastStore } from '@/stores/toastStore' import { deserialiseAndCreate } from '@/extensions/core/vintageClipboard' -import { showPromptDialog } from '@/services/dialogService' +import { useDialogService } from '@/services/dialogService' import { t } from '@/i18n' // Adds the ability to save and add multiple nodes as a template @@ -351,7 +351,7 @@ app.registerExtension({ content: `Save Selected as Template`, disabled: !Object.keys(app.canvas.selected_nodes || {}).length, callback: async () => { - const name = await showPromptDialog({ + const name = await useDialogService().showPromptDialog({ title: t('nodeTemplates.saveAsTemplate'), message: t('nodeTemplates.enterName'), defaultValue: '' diff --git a/src/hooks/coreCommandHooks.ts b/src/hooks/coreCommandHooks.ts index 80f15627d..dad40e412 100644 --- a/src/hooks/coreCommandHooks.ts +++ b/src/hooks/coreCommandHooks.ts @@ -1,9 +1,6 @@ import { app } from '@/scripts/app' import { api } from '@/scripts/api' -import { - showSettingsDialog, - showTemplateWorkflowsDialog -} from '@/services/dialogService' +import { useDialogService } from '@/services/dialogService' import { useWorkflowService } from '@/services/workflowService' import type { ComfyCommand } from '@/stores/commandStore' import { useTitleEditorStore } from '@/stores/graphStore' @@ -23,6 +20,7 @@ import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore' export function useCoreCommands(): ComfyCommand[] { const workflowService = useWorkflowService() + const dialogService = useDialogService() const getTracker = () => useWorkflowStore()?.activeWorkflow?.changeTracker const getSelectedNodes = (): LGraphNode[] => { @@ -200,7 +198,9 @@ export function useCoreCommands(): ComfyCommand[] { id: 'Comfy.BrowseTemplates', icon: 'pi pi-folder-open', label: 'Browse Templates', - function: showTemplateWorkflowsDialog + function: () => { + dialogService.showTemplateWorkflowsDialog() + } }, { id: 'Comfy.Canvas.ZoomIn', @@ -302,7 +302,7 @@ export function useCoreCommands(): ComfyCommand[] { label: 'Show Settings Dialog', versionAdded: '1.3.7', function: () => { - showSettingsDialog() + dialogService.showSettingsDialog() } }, { @@ -509,7 +509,7 @@ export function useCoreCommands(): ComfyCommand[] { menubarLabel: 'About ComfyUI', versionAdded: '1.6.4', function: () => { - showSettingsDialog('about') + dialogService.showSettingsDialog('about') } } ] diff --git a/src/scripts/app.ts b/src/scripts/app.ts index b79be4c15..a7b0c4d1b 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -41,11 +41,7 @@ import { } from '@/stores/nodeDefStore' import { INodeInputSlot, Vector2 } from '@comfyorg/litegraph' import _ from 'lodash' -import { - showExecutionErrorDialog, - showLoadWorkflowWarning, - showMissingModelsWarning -} from '@/services/dialogService' +import { useDialogService } from '@/services/dialogService' import { useSettingStore } from '@/stores/settingStore' import { useToastStore } from '@/stores/toastStore' import { useModelStore } from '@/stores/modelStore' @@ -1528,7 +1524,7 @@ export class ComfyApp { api.addEventListener('execution_error', ({ detail }) => { this.lastExecutionError = detail - showExecutionErrorDialog(detail) + useDialogService().showExecutionErrorDialog(detail) this.canvas.draw(true, true) }) @@ -1992,13 +1988,13 @@ export class ComfyApp { #showMissingNodesError(missingNodeTypes: MissingNodeType[]) { if (useSettingStore().get('Comfy.Workflow.ShowMissingNodesWarning')) { - showLoadWorkflowWarning({ missingNodeTypes }) + useDialogService().showLoadWorkflowWarning({ missingNodeTypes }) } } #showMissingModelsError(missingModels, paths) { if (useSettingStore().get('Comfy.Workflow.ShowMissingModelsWarning')) { - showMissingModelsWarning({ + useDialogService().showMissingModelsWarning({ missingModels, paths }) diff --git a/src/scripts/ui.ts b/src/scripts/ui.ts index b5f686b6e..196b43de9 100644 --- a/src/scripts/ui.ts +++ b/src/scripts/ui.ts @@ -5,7 +5,7 @@ import { toggleSwitch } from './ui/toggleSwitch' import { ComfySettingsDialog } from './ui/settings' import { ComfyApp, app } from './app' import { TaskItem, type StatusWsMessageStatus } from '@/types/apiTypes' -import { showSettingsDialog } from '@/services/dialogService' +import { useDialogService } from '@/services/dialogService' import { useSettingStore } from '@/stores/settingStore' import { useCommandStore } from '@/stores/commandStore' import { useWorkspaceStore } from '@/stores/workspaceStore' @@ -438,7 +438,9 @@ export class ComfyUI { $el('div.comfy-menu-actions', [ $el('button.comfy-settings-btn', { textContent: '⚙️', - onclick: showSettingsDialog + onclick: () => { + useDialogService().showSettingsDialog() + } }), $el('button.comfy-close-menu-btn', { textContent: '\u00d7', diff --git a/src/services/dialogService.ts b/src/services/dialogService.ts index 3c7f69231..be30c6baa 100644 --- a/src/services/dialogService.ts +++ b/src/services/dialogService.ts @@ -1,117 +1,15 @@ -// This module is mocked in tests-ui/ -// Import vue components here to avoid tests-ui/ reporting errors -// about importing primevue components. import { useDialogStore, type ShowDialogOptions } from '@/stores/dialogStore' +import type { ExecutionErrorWsMessage } from '@/types/apiTypes' +import type { MissingNodeType } from '@/types/comfy' import LoadWorkflowWarning from '@/components/dialog/content/LoadWorkflowWarning.vue' import MissingModelsWarning from '@/components/dialog/content/MissingModelsWarning.vue' import SettingDialogContent from '@/components/dialog/content/SettingDialogContent.vue' import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue' -import type { ExecutionErrorWsMessage } from '@/types/apiTypes' import ExecutionErrorDialogContent from '@/components/dialog/content/ExecutionErrorDialogContent.vue' import TemplateWorkflowsContent from '@/components/templates/TemplateWorkflowsContent.vue' import PromptDialogContent from '@/components/dialog/content/PromptDialogContent.vue' import ConfirmationDialogContent from '@/components/dialog/content/ConfirmationDialogContent.vue' import { t } from '@/i18n' -import type { MissingNodeType } from '@/types/comfy' - -export function showLoadWorkflowWarning(props: { - missingNodeTypes: MissingNodeType[] - [key: string]: any -}) { - const dialogStore = useDialogStore() - dialogStore.showDialog({ - key: 'global-load-workflow-warning', - component: LoadWorkflowWarning, - props - }) -} - -export function showMissingModelsWarning(props: { - missingModels: any[] - paths: Record - [key: string]: any -}) { - const dialogStore = useDialogStore() - dialogStore.showDialog({ - key: 'global-missing-models-warning', - component: MissingModelsWarning, - props - }) -} - -export function showSettingsDialog( - panel?: 'about' | 'keybinding' | 'extension' | 'server-config' -) { - const props = panel ? { props: { defaultPanel: panel } } : undefined - - useDialogStore().showDialog({ - key: 'global-settings', - headerComponent: SettingDialogHeader, - component: SettingDialogContent, - ...props - }) -} - -export function showAboutDialog() { - useDialogStore().showDialog({ - key: 'global-settings', - headerComponent: SettingDialogHeader, - component: SettingDialogContent, - props: { - defaultPanel: 'about' - } - }) -} - -export function showExecutionErrorDialog(error: ExecutionErrorWsMessage) { - useDialogStore().showDialog({ - key: 'global-execution-error', - component: ExecutionErrorDialogContent, - props: { - error - } - }) -} - -export function showTemplateWorkflowsDialog() { - useDialogStore().showDialog({ - key: 'global-template-workflows', - title: t('templateWorkflows.title'), - component: TemplateWorkflowsContent - }) -} - -export async function showPromptDialog({ - title, - message, - defaultValue = '' -}: { - title: string - message: string - defaultValue?: string -}): Promise { - const dialogStore = useDialogStore() - - return new Promise((resolve) => { - dialogStore.showDialog({ - key: 'global-prompt', - title, - component: PromptDialogContent, - props: { - message, - defaultValue, - onConfirm: (value: string) => { - resolve(value) - } - }, - dialogComponentProps: { - onClose: () => { - resolve(null) - } - } - }) - }) -} export type ConfirmationDialogType = | 'overwrite' @@ -119,43 +17,151 @@ export type ConfirmationDialogType = | 'dirtyClose' | 'reinstall' -/** - * - * @returns `true` if the user confirms the dialog, - * `false` if denied (e.g. no in yes/no/cancel), or - * `null` if the dialog is cancelled or closed - */ -export async function showConfirmationDialog({ - title, - type, - message, - itemList = [] -}: { - /** Dialog heading */ - title: string - /** Pre-configured dialog type */ - type: ConfirmationDialogType - /** The main message body */ - message: string - /** Displayed as an unorderd list immediately below the message body */ - itemList?: string[] -}): Promise { - return new Promise((resolve) => { - const options: ShowDialogOptions = { - key: 'global-prompt', - title, - component: ConfirmationDialogContent, - props: { - message, - type, - itemList, - onConfirm: resolve - }, - dialogComponentProps: { - onClose: () => resolve(null) - } - } +export const useDialogService = () => { + const dialogStore = useDialogStore() + function showLoadWorkflowWarning(props: { + missingNodeTypes: MissingNodeType[] + [key: string]: any + }) { + dialogStore.showDialog({ + key: 'global-load-workflow-warning', + component: LoadWorkflowWarning, + props + }) + } - useDialogStore().showDialog(options) - }) + function showMissingModelsWarning(props: { + missingModels: any[] + paths: Record + [key: string]: any + }) { + dialogStore.showDialog({ + key: 'global-missing-models-warning', + component: MissingModelsWarning, + props + }) + } + + function showSettingsDialog( + panel?: 'about' | 'keybinding' | 'extension' | 'server-config' + ) { + const props = panel ? { props: { defaultPanel: panel } } : undefined + + dialogStore.showDialog({ + key: 'global-settings', + headerComponent: SettingDialogHeader, + component: SettingDialogContent, + ...props + }) + } + + function showAboutDialog() { + dialogStore.showDialog({ + key: 'global-settings', + headerComponent: SettingDialogHeader, + component: SettingDialogContent, + props: { + defaultPanel: 'about' + } + }) + } + + function showExecutionErrorDialog(error: ExecutionErrorWsMessage) { + dialogStore.showDialog({ + key: 'global-execution-error', + component: ExecutionErrorDialogContent, + props: { + error + } + }) + } + + function showTemplateWorkflowsDialog() { + dialogStore.showDialog({ + key: 'global-template-workflows', + title: t('templateWorkflows.title'), + component: TemplateWorkflowsContent + }) + } + + async function showPromptDialog({ + title, + message, + defaultValue = '' + }: { + title: string + message: string + defaultValue?: string + }): Promise { + return new Promise((resolve) => { + dialogStore.showDialog({ + key: 'global-prompt', + title, + component: PromptDialogContent, + props: { + message, + defaultValue, + onConfirm: (value: string) => { + resolve(value) + } + }, + dialogComponentProps: { + onClose: () => { + resolve(null) + } + } + }) + }) + } + + /** + * @returns `true` if the user confirms the dialog, + * `false` if denied (e.g. no in yes/no/cancel), or + * `null` if the dialog is cancelled or closed + */ + async function showConfirmationDialog({ + title, + type, + message, + itemList = [] + }: { + /** Dialog heading */ + title: string + /** Pre-configured dialog type */ + type: ConfirmationDialogType + /** The main message body */ + message: string + /** Displayed as an unorderd list immediately below the message body */ + itemList?: string[] + }): Promise { + return new Promise((resolve) => { + const options: ShowDialogOptions = { + key: 'global-prompt', + title, + component: ConfirmationDialogContent, + props: { + message, + type, + itemList, + onConfirm: resolve + }, + dialogComponentProps: { + onClose: () => resolve(null) + } + } + + dialogStore.showDialog(options) + }) + } + + return { + showLoadWorkflowWarning, + showMissingModelsWarning, + showSettingsDialog, + showAboutDialog, + showExecutionErrorDialog, + showTemplateWorkflowsDialog, + showPromptDialog, + showConfirmationDialog + } } diff --git a/src/services/workflowService.ts b/src/services/workflowService.ts index 74cfd046a..206b896ed 100644 --- a/src/services/workflowService.ts +++ b/src/services/workflowService.ts @@ -1,7 +1,7 @@ import { downloadBlob } from '@/scripts/utils' import { useSettingStore } from '@/stores/settingStore' import { useWorkflowStore, ComfyWorkflow } from '@/stores/workflowStore' -import { showConfirmationDialog, showPromptDialog } from './dialogService' +import { useDialogService } from './dialogService' import { app } from '@/scripts/app' import { useWorkspaceStore } from '@/stores/workspaceStore' import { LGraphCanvas } from '@comfyorg/litegraph' @@ -16,10 +16,11 @@ export const useWorkflowService = () => { const settingStore = useSettingStore() const workflowStore = useWorkflowStore() const toastStore = useToastStore() + const dialogService = useDialogService() async function getFilename(defaultName: string): Promise { if (settingStore.get('Comfy.PromptFilename')) { - let filename = await showPromptDialog({ + let filename = await dialogService.showPromptDialog({ title: t('workflowService.exportWorkflow'), message: t('workflowService.enterFilename') + ':', defaultValue: defaultName @@ -57,7 +58,7 @@ export const useWorkflowService = () => { * @param workflow The workflow to save */ const saveWorkflowAs = async (workflow: ComfyWorkflow) => { - const newFilename = await showPromptDialog({ + const newFilename = await dialogService.showPromptDialog({ title: t('workflowService.saveWorkflow'), message: t('workflowService.enterFilename') + ':', defaultValue: workflow.filename @@ -69,7 +70,7 @@ export const useWorkflowService = () => { const existingWorkflow = workflowStore.getWorkflowByPath(newPath) if (existingWorkflow && !existingWorkflow.isTemporary) { - const res = await showConfirmationDialog({ + const res = await dialogService.showConfirmationDialog({ title: t('sideToolbar.workflowTab.confirmOverwriteTitle'), type: 'overwrite', message: t('sideToolbar.workflowTab.confirmOverwrite'), @@ -178,7 +179,7 @@ export const useWorkflowService = () => { } if (workflow.isModified && options.warnIfUnsaved) { - const confirmed = await showConfirmationDialog({ + const confirmed = await dialogService.showConfirmationDialog({ title: t('sideToolbar.workflowTab.dirtyCloseTitle'), type: 'dirtyClose', message: t('sideToolbar.workflowTab.dirtyClose'), @@ -222,7 +223,7 @@ export const useWorkflowService = () => { let confirmed: boolean | null = bypassConfirm || silent if (!confirmed) { - confirmed = await showConfirmationDialog({ + confirmed = await dialogService.showConfirmationDialog({ title: t('sideToolbar.workflowTab.confirmDeleteTitle'), type: 'delete', message: t('sideToolbar.workflowTab.confirmDelete'),