Confirm delete workflow (#1772)

* Add confirm delete workflow prompt

* Add confirm delete workflow setting

* Add delete workflow tests

* Change dialog to modal, set default cancel

* Fix setting version key

* Rename for clarity

* Fix tests

- Move into correct section
- Add confirm control

* Export type: ShowDialogOptions

* Replace workflow overwrite with new dialog

* Add delete workflow confirmation dialog

* Update i18n

* Add item list support to confirmation dialog

* Prevent multiple dialogs for same action

* Add confirm close file when dirty

* Add i18n for overwrite dialog

* Fix regression: confirm dialog setting ignored

* Fix delete last workflow does not open replacement

* Update tests
This commit is contained in:
filtered
2024-12-05 06:11:49 +11:00
committed by GitHub
parent d04cc4e272
commit 735153886f
15 changed files with 333 additions and 37 deletions

View File

@@ -75,6 +75,28 @@ type FolderStructure = {
[key: string]: FolderStructure | string
}
type KeysOfType<T, Match> = {
[K in keyof T]: T[K] extends Match ? K : never
}[keyof T]
class ConfirmDialog {
public readonly delete: Locator
public readonly overwrite: Locator
public readonly reject: Locator
constructor(public readonly page: Page) {
this.delete = page.locator('button.p-button[aria-label="Delete"]')
this.overwrite = page.locator('button.p-button[aria-label="Overwrite"]')
this.reject = page.locator('button.p-button[aria-label="Cancel"]')
}
async click(locator: KeysOfType<ConfirmDialog, Locator>) {
const loc = this[locator]
await expect(loc).toBeVisible()
await loc.click()
}
}
export class ComfyPage {
public readonly url: string
// All canvas position operations are based on default view of canvas.
@@ -94,6 +116,7 @@ export class ComfyPage {
public readonly actionbar: ComfyActionbar
public readonly templates: ComfyTemplates
public readonly settingDialog: SettingDialog
public readonly confirmDialog: ConfirmDialog
/** Worker index to test user ID */
public readonly userIds: string[] = []
@@ -118,6 +141,7 @@ export class ComfyPage {
this.actionbar = new ComfyActionbar(page)
this.templates = new ComfyTemplates(page)
this.settingDialog = new SettingDialog(page)
this.confirmDialog = new ConfirmDialog(page)
}
convertLeafToContent(structure: FolderStructure): FolderStructure {
@@ -740,14 +764,14 @@ export class ComfyPage {
)
}
async confirmDialog(prompt: string, text: string = 'Yes') {
async clickDialogButton(prompt: string, buttonText: string = 'Yes') {
const modal = this.page.locator(
`.comfy-modal-content:has-text("${prompt}")`
)
await expect(modal).toBeVisible()
await modal
.locator('.comfyui-button', {
hasText: text
hasText: buttonText
})
.click()
await expect(modal).toBeHidden()

View File

@@ -442,7 +442,7 @@ test.describe('Menu', () => {
).toEqual(['workflow5.json'])
await comfyPage.menu.topbar.saveWorkflowAs('workflow5.json')
await comfyPage.confirmDialog('Overwrite existing file?', 'Yes')
await comfyPage.confirmDialog.click('overwrite')
expect(
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
).toEqual(['workflow5.json'])
@@ -477,7 +477,7 @@ test.describe('Menu', () => {
)
await topbar.saveWorkflowAs('workflow1.json')
await comfyPage.confirmDialog('Overwrite existing file?', 'Yes')
await comfyPage.confirmDialog.click('overwrite')
// The old workflow1.json should be deleted and the new one should be saved.
expect(
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
@@ -519,6 +519,43 @@ test.describe('Menu', () => {
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
).toEqual(['*Unsaved Workflow.json'])
})
test('Can delete workflows (confirm disabled)', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.Workflow.ConfirmDelete', false)
const { topbar, workflowsTab } = comfyPage.menu
const filename = 'workflow18.json'
await topbar.saveWorkflow(filename)
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([filename])
await workflowsTab.getOpenedItem(filename).click({ button: 'right' })
await comfyPage.nextFrame()
await comfyPage.clickContextMenuItem('Delete')
await expect(workflowsTab.getOpenedItem(filename)).not.toBeVisible()
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json'
])
})
test('Can delete workflows', async ({ comfyPage }) => {
const { topbar, workflowsTab } = comfyPage.menu
const filename = 'workflow18.json'
await topbar.saveWorkflow(filename)
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([filename])
await workflowsTab.getOpenedItem(filename).click({ button: 'right' })
await comfyPage.clickContextMenuItem('Delete')
await comfyPage.confirmDialog.click('delete')
await expect(workflowsTab.getOpenedItem(filename)).not.toBeVisible()
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json'
])
})
})
test.describe('Workflows topbar tabs', () => {

View File

@@ -0,0 +1,64 @@
<template>
<section class="prompt-dialog-content flex flex-col gap-6 m-2 mt-4">
<span>{{ message }}</span>
<ul v-if="itemList?.length" class="pl-4 m-0 flex flex-col gap-2">
<li v-for="item of itemList" :key="item">{{ item }}</li>
</ul>
<div class="flex gap-4 justify-end">
<Button
:label="$t('cancel')"
icon="pi pi-undo"
severity="secondary"
@click="onCancel"
autofocus
/>
<Button
v-if="type === 'delete'"
:label="$t('delete')"
severity="danger"
@click="onConfirm"
icon="pi pi-trash"
/>
<Button
v-else-if="type === 'overwrite'"
:label="$t('overwrite')"
severity="warn"
@click="onConfirm"
icon="pi pi-save"
/>
<template v-else>
<Button
:label="$t('no')"
severity="secondary"
@click="onDeny"
icon="pi pi-times"
/>
<Button :label="$t('save')" @click="onConfirm" icon="pi pi-save" />
</template>
</div>
</section>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { useDialogStore } from '@/stores/dialogStore'
const props = defineProps<{
message: string
type: 'overwrite' | 'delete' | 'dirtyClose'
onConfirm: (value?: boolean) => void
itemList?: string[]
}>()
const onCancel = () => useDialogStore().closeDialog()
const onDeny = () => {
props.onConfirm(false)
useDialogStore().closeDialog()
}
const onConfirm = () => {
props.onConfirm(true)
useDialogStore().closeDialog()
}
</script>

View File

@@ -123,6 +123,7 @@
</div>
</template>
</SidebarTabTemplate>
<ConfirmDialog />
</template>
<script setup lang="ts">
@@ -150,6 +151,9 @@ import { workflowService } 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()
const workflowTabsPosition = computed(() =>
@@ -218,6 +222,9 @@ const openWorkflowsTree = computed(() =>
buildTree(workflowStore.openWorkflows, (workflow) => [workflow.key])
)
const confirm = useConfirm()
const toast = useToast()
const renderTreeNode = (
node: TreeNode,
type: WorkflowTreeType
@@ -236,6 +243,7 @@ const renderTreeNode = (
toggleNodeOnEvent(e, node)
}
}
const actions = node.leaf
? {
handleClick,
@@ -252,8 +260,8 @@ const renderTreeNode = (
},
handleDelete: workflow.isTemporary
? undefined
: () => {
workflowService.deleteWorkflow(workflow)
: async () => {
await workflowService.deleteWorkflow(workflow)
},
contextMenuItems: (node: TreeExplorerNode<ComfyWorkflow>) => {
return [

View File

@@ -658,5 +658,12 @@ export const CORE_SETTINGS: SettingParams[] = [
type: 'boolean',
defaultValue: true,
versionModified: '1.5.4'
},
{
id: 'Comfy.Workflow.ConfirmDelete',
name: 'Show confirmation when deleting workflows',
type: 'boolean',
defaultValue: true,
versionAdded: '1.5.6'
}
]

View File

@@ -16,3 +16,6 @@ export const i18n = createI18n({
ja
}
})
/** Convenience shorthand: i18n.global should be . */
export const { t } = i18n.global

View File

@@ -128,6 +128,10 @@
"reconnected": "Reconnected",
"delete": "Delete",
"rename": "Rename",
"save": "Save",
"no": "No",
"cancel": "Cancel",
"overwrite": "Overwrite",
"customize": "Customize",
"experimental": "BETA",
"deprecated": "DEPR",
@@ -454,6 +458,17 @@
"hideCached": "Hide Cached",
"hideCanceled": "Hide Canceled"
}
},
"workflowTab": {
"confirmDeleteTitle": "Delete workflow?",
"confirmDelete": "Are you sure you want to delete this workflow?",
"deleted": "Workflow deleted",
"deleteFailedTitle": "Delete failed",
"deleteFailed": "Attempt to delete the workflow failed.",
"dirtyCloseTitle": "Save Changes?",
"dirtyClose": "The files below have been changed. Would you like to save them before closing?",
"confirmOverwriteTitle": "Overwrite existing file?",
"confirmOverwrite": "The file below already exists. Would you like to overwrite it?"
}
},
"menu": {
@@ -573,5 +588,9 @@
"Feedback": "Feedback",
"Reinstall": "Reinstall",
"Restart": "Restart"
},
"desktopMenu": {
"reinstall": "Reinstall",
"confirmReinstall": "This will clear your extra_models_config.yaml file, and begin installation again. Are you sure?"
}
}
}

View File

@@ -4,6 +4,7 @@
"bookmark": "ブックマーク",
"box": "ボックス",
"briefcase": "ブリーフケース",
"cancel": "キャンセル",
"color": "色",
"comingSoon": "近日公開",
"confirm": "確認",
@@ -13,6 +14,10 @@
"customizeFolder": "フォルダーをカスタマイズ",
"delete": "削除",
"deprecated": "非推奨",
"desktopMenu": {
"confirmReinstall": "これにより、extra_models_config.yamlファイルがクリアされ、再インストールが開始されます。本当によろしいですか",
"reinstall": "再インストール"
},
"devices": "デバイス",
"download": "ダウンロード",
"downloadGit": {
@@ -184,6 +189,7 @@
"Zoom Out": "ズームアウト"
},
"newFolder": "新しいフォルダー",
"no": "いいえ",
"noResultsFound": "結果が見つかりませんでした",
"noTasksFound": "タスクが見つかりませんでした",
"noTasksFoundMessage": "キューにタスクがありません。",
@@ -199,6 +205,7 @@
"title": "お使いのデバイスはサポートされていません"
},
"openNewIssue": "新しいIssueを開く",
"overwrite": "上書き",
"reconnected": "再接続しました",
"reconnecting": "再接続中",
"refresh": "更新",
@@ -209,6 +216,7 @@
"reportSent": "レポートを送信しました",
"reset": "リセット",
"resetKeybindingsTooltip": "キーバインドをデフォルトに戻す",
"save": "保存",
"searchExtensions": "拡張機能を検索",
"searchFailedMessage": "検索条件に一致する設定が見つかりませんでした。条件を変更して再試行してください。",
"searchKeybindings": "キーバインドを検索",
@@ -541,6 +549,17 @@
"showFlatList": "フラットリストを表示"
},
"themeToggle": "テーマの切り替え",
"workflowTab": {
"confirmDelete": "このワークフローを削除してもよろしいですか?",
"confirmDeleteTitle": "ワークフローを削除しますか?",
"confirmOverwrite": "以下のファイルはすでに存在します。上書きしますか?",
"confirmOverwriteTitle": "既存のファイルを上書きしますか?",
"deleteFailed": "ワークフローの削除を試みましたが、失敗しました。",
"deleteFailedTitle": "削除に失敗しました",
"deleted": "ワークフローが削除されました",
"dirtyClose": "以下のファイルが変更されました。閉じる前に保存しますか?",
"dirtyCloseTitle": "変更を保存しますか?"
},
"workflows": "ワークフロー"
},
"star": "スター",

View File

@@ -4,6 +4,7 @@
"bookmark": "Закладка",
"box": "Ящик",
"briefcase": "Чемодан",
"cancel": "Отмена",
"color": "Цвет",
"comingSoon": "Скоро",
"confirm": "Подтвердить",
@@ -13,6 +14,10 @@
"customizeFolder": "Настроить папку",
"delete": "Удалить",
"deprecated": "УСТАР",
"desktopMenu": {
"confirmReinstall": "Это очистит ваш файл extra_models_config.yaml и начнет установку заново. Вы уверены?",
"reinstall": "Переустановить"
},
"devices": "Устройства",
"download": "Скачать",
"downloadGit": {
@@ -184,6 +189,7 @@
"Zoom Out": "Уменьшить"
},
"newFolder": "Новая папка",
"no": "Нет",
"noResultsFound": "Ничего не найдено",
"noTasksFound": "Задачи не найдены",
"noTasksFoundMessage": "В очереди нет задач.",
@@ -199,6 +205,7 @@
"title": "Ваше устройство не поддерживается"
},
"openNewIssue": "Открыть новый Issue",
"overwrite": "Перезаписать",
"reconnected": "Переподключено",
"reconnecting": "Переподключение",
"refresh": "Обновить",
@@ -209,6 +216,7 @@
"reportSent": "Отчет отправлен",
"reset": "Сбросить",
"resetKeybindingsTooltip": "Сбросить сочетания клавиш по умолчанию",
"save": "Сохранить",
"searchExtensions": "Поиск расширений",
"searchFailedMessage": "Не удалось найти ни одной настройки, соответствующей вашему запросу. Попробуйте скорректировать поисковый запрос.",
"searchKeybindings": "Поиск сочетаний клавиш",
@@ -541,6 +549,17 @@
"showFlatList": "Показать плоский список"
},
"themeToggle": "Переключить тему",
"workflowTab": {
"confirmDelete": "Вы уверены, что хотите удалить этот рабочий процесс?",
"confirmDeleteTitle": "Удалить рабочий процесс?",
"confirmOverwrite": "Файл ниже уже существует. Вы хотите его перезаписать?",
"confirmOverwriteTitle": "Перезаписать существующий файл?",
"deleteFailed": "Попытка удалить рабочий процесс не удалась.",
"deleteFailedTitle": "Не удалось удалить",
"deleted": "Рабочий процесс удален",
"dirtyClose": "Файлы ниже были изменены. Вы хотите сохранить их перед закрытием?",
"dirtyCloseTitle": "Сохранить изменения?"
},
"workflows": "Рабочие процессы"
},
"star": "Звёздочка",

View File

@@ -4,6 +4,7 @@
"bookmark": "书签",
"box": "盒子",
"briefcase": "公文包",
"cancel": "取消",
"color": "颜色",
"comingSoon": "敬请期待",
"confirm": "确认",
@@ -13,6 +14,10 @@
"customizeFolder": "定制文件夹",
"delete": "删除",
"deprecated": "弃用",
"desktopMenu": {
"confirmReinstall": "这将清除您的 extra_models_config.yaml 文件,并重新开始安装。您确定吗?",
"reinstall": "重新安装"
},
"devices": "设备",
"download": "下载",
"downloadGit": {
@@ -184,6 +189,7 @@
"Zoom Out": "缩小"
},
"newFolder": "新建文件夹",
"no": "不",
"noResultsFound": "未找到结果",
"noTasksFound": "未找到任务",
"noTasksFoundMessage": "队列中没有任务。",
@@ -199,6 +205,7 @@
"title": "您的设备不受支持"
},
"openNewIssue": "开启新 Issue",
"overwrite": "覆盖",
"reconnected": "已重新连接",
"reconnecting": "重新连接中",
"refresh": "刷新",
@@ -209,6 +216,7 @@
"reportSent": "报告已提交",
"reset": "重置",
"resetKeybindingsTooltip": "重置键位",
"save": "保存",
"searchExtensions": "搜索插件",
"searchFailedMessage": "我们找不到与您的搜索匹配的任何设置。请尝试调整搜索条件。",
"searchKeybindings": "搜索键位",
@@ -541,6 +549,17 @@
"showFlatList": "平铺结果"
},
"themeToggle": "主题切换",
"workflowTab": {
"confirmDelete": "您确定要删除此工作流吗?",
"confirmDeleteTitle": "删除工作流?",
"confirmOverwrite": "下面的文件已经存在。您想要覆盖它吗?",
"confirmOverwriteTitle": "覆盖现有文件?",
"deleteFailed": "尝试删除工作流失败。",
"deleteFailedTitle": "删除失败",
"deleted": "工作流已删除",
"dirtyClose": "以下文件已被更改。您想在关闭之前保存它们吗?",
"dirtyCloseTitle": "保存更改?"
},
"workflows": "工作流"
},
"star": "星星",

View File

@@ -1,7 +1,7 @@
// This module is mocked in tests-ui/
// Import vue components here to avoid tests-ui/ reporting errors
// about importing primevue components.
import { useDialogStore } from '@/stores/dialogStore'
import { useDialogStore, type ShowDialogOptions } from '@/stores/dialogStore'
import LoadWorkflowWarning from '@/components/dialog/content/LoadWorkflowWarning.vue'
import MissingModelsWarning from '@/components/dialog/content/MissingModelsWarning.vue'
import SettingDialogContent from '@/components/dialog/content/SettingDialogContent.vue'
@@ -10,6 +10,7 @@ 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 { i18n } from '@/i18n'
import type { MissingNodeType } from '@/types/comfy'
@@ -95,3 +96,44 @@ export async function showPromptDialog({
})
})
}
/**
*
* @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: 'overwrite' | 'delete' | 'dirtyClose'
/** The main message body */
message: string
/** Displayed as an unorderd list immediately below the message body */
itemList?: string[]
}): Promise<boolean | null> {
return new Promise((resolve) => {
const options: ShowDialogOptions = {
key: 'global-prompt',
title,
component: ConfirmationDialogContent,
props: {
message,
type,
itemList,
onConfirm: resolve
},
dialogComponentProps: {
onClose: () => resolve(null)
}
}
useDialogStore().showDialog(options)
})
}

View File

@@ -1,8 +1,7 @@
import { downloadBlob } from '@/scripts/utils'
import { useSettingStore } from '@/stores/settingStore'
import { ComfyAsyncDialog } from '@/scripts/ui/components/asyncDialog'
import { useWorkflowStore, ComfyWorkflow } from '@/stores/workflowStore'
import { showPromptDialog } from './dialogService'
import { showConfirmationDialog, showPromptDialog } from './dialogService'
import { app } from '@/scripts/app'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { LGraphCanvas } from '@comfyorg/litegraph'
@@ -10,6 +9,8 @@ import { toRaw } from 'vue'
import { ComfyWorkflowJSON } from '@/types/comfyWorkflow'
import { blankGraph, defaultGraph } from '@/scripts/defaultGraph'
import { appendJsonExt } from '@/utils/formatUtil'
import { t } from '@/i18n'
import { useToastStore } from '@/stores/toastStore'
async function getFilename(defaultName: string): Promise<string | null> {
if (useSettingStore().get('Comfy.PromptFilename')) {
@@ -67,19 +68,20 @@ export const workflowService = {
const existingWorkflow = workflowStore.getWorkflowByPath(newPath)
if (existingWorkflow && !existingWorkflow.isTemporary) {
const res = (await ComfyAsyncDialog.prompt({
title: 'Overwrite existing file?',
message: `"${newPath}" already exists. Do you want to overwrite it?`,
actions: ['Yes', 'No']
})) as 'Yes' | 'No'
const res = await showConfirmationDialog({
title: t('sideToolbar.workflowTab.confirmOverwriteTitle'),
type: 'overwrite',
message: t('sideToolbar.workflowTab.confirmOverwrite'),
itemList: [newPath]
})
if (res === 'No') return
if (res !== true) return
if (existingWorkflow.path === workflow.path) {
await this.saveWorkflow(workflow)
return
}
const deleted = await this.deleteWorkflow(existingWorkflow)
const deleted = await this.deleteWorkflow(existingWorkflow, true)
if (!deleted) return
}
@@ -156,16 +158,17 @@ export const workflowService = {
}
if (workflow.isModified && options.warnIfUnsaved) {
const res = (await ComfyAsyncDialog.prompt({
title: 'Save Changes?',
message: `Do you want to save changes to "${workflow.path}" before closing?`,
actions: ['Yes', 'No', 'Cancel']
})) as 'Yes' | 'No' | 'Cancel'
const confirmed = await showConfirmationDialog({
title: t('sideToolbar.workflowTab.dirtyCloseTitle'),
type: 'dirtyClose',
message: t('sideToolbar.workflowTab.dirtyClose'),
itemList: [workflow.path]
})
// Cancel
if (confirmed === null) return false
if (res === 'Yes') {
if (confirmed === true) {
await this.saveWorkflow(workflow)
} else if (res === 'Cancel') {
return false
}
}
@@ -190,15 +193,40 @@ export const workflowService = {
/**
* Delete a workflow
* @param workflow The workflow to delete
* @returns true if the workflow was deleted, false if the user cancelled
* @returns `true` if the workflow was deleted, `false` if the user cancelled
*/
async deleteWorkflow(workflow: ComfyWorkflow): Promise<boolean> {
async deleteWorkflow(
workflow: ComfyWorkflow,
silent = false
): Promise<boolean> {
const bypassConfirm = !useSettingStore().get('Comfy.Workflow.ConfirmDelete')
let confirmed: boolean | null = bypassConfirm || silent
if (!confirmed) {
confirmed = await showConfirmationDialog({
title: t('sideToolbar.workflowTab.confirmDeleteTitle'),
type: 'delete',
message: t('sideToolbar.workflowTab.confirmDelete'),
itemList: [workflow.path]
})
if (!confirmed) return false
}
const workflowStore = useWorkflowStore()
if (workflowStore.isOpen(workflow)) {
const closed = await this.closeWorkflow(workflow)
const closed = await this.closeWorkflow(workflow, {
warnIfUnsaved: !confirmed
})
if (!closed) return false
}
await workflowStore.deleteWorkflow(workflow)
if (!silent) {
useToastStore().add({
severity: 'info',
summary: t('sideToolbar.workflowTab.deleted'),
life: 1000
})
}
return true
},

View File

@@ -14,6 +14,8 @@ export interface ComfyCommand {
/** Menubar item label, if different from command label */
menubarLabel?: string | (() => string)
versionAdded?: string
/** If non-nullish, this command will prompt for confirmation. */
confirmation?: string
}
export class ComfyCommandImpl implements ComfyCommand {
@@ -24,6 +26,7 @@ export class ComfyCommandImpl implements ComfyCommand {
_tooltip?: string | (() => string)
_menubarLabel?: string | (() => string)
versionAdded?: string
confirmation?: string
constructor(command: ComfyCommand) {
this.id = command.id
@@ -33,6 +36,7 @@ export class ComfyCommandImpl implements ComfyCommand {
this._tooltip = command.tooltip
this._menubarLabel = command.menubarLabel ?? command.label
this.versionAdded = command.versionAdded
this.confirmation = command.confirmation
}
get label() {

View File

@@ -19,6 +19,15 @@ interface DialogInstance {
dialogComponentProps: Record<string, any>
}
export interface ShowDialogOptions {
key?: string
title?: string
headerComponent?: Component
component: Component
props?: Record<string, any>
dialogComponentProps?: DialogComponentProps
}
export const useDialogStore = defineStore('dialog', () => {
const dialogStack = ref<DialogInstance[]>([])
@@ -102,14 +111,7 @@ export const useDialogStore = defineStore('dialog', () => {
return dialog
}
function showDialog(options: {
key?: string
title?: string
headerComponent?: Component
component: Component
props?: Record<string, any>
dialogComponentProps?: DialogComponentProps
}) {
function showDialog(options: ShowDialogOptions) {
const dialogKey = options.key || genDialogKey()
let dialog = dialogStack.value.find((d) => d.key === dialogKey)

View File

@@ -525,7 +525,8 @@ const zSettings = z.record(z.any()).and(
'Comfy.Node.SnapHighlightsNode': z.boolean(),
'Comfy.Server.ServerConfigValues': z.record(z.string(), z.any()),
'Comfy.Server.LaunchArgs': z.record(z.string(), z.string()),
'LiteGraph.Canvas.MaximumFps': z.number()
'LiteGraph.Canvas.MaximumFps': z.number(),
'Comfy.Workflow.ConfirmDelete': z.boolean()
})
.optional()
)