mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
feat: replace confirm dialogs with custom small layout dialogs
Use showSmallLayoutDialog pattern (like missing models dialog) for unsaved changes and delete preset dialogs with explicit action buttons.
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex w-full max-w-[420px] flex-col border-t border-border-default"
|
||||
>
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<p class="m-0 text-sm text-muted-foreground">
|
||||
{{ $t('g.keybindingPresets.deletePresetWarning') }}
|
||||
</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="textonly"
|
||||
class="text-muted-foreground"
|
||||
@click="onResult(false)"
|
||||
>
|
||||
{{ $t('g.close') }}
|
||||
</Button>
|
||||
<Button variant="secondary" @click="onResult(true)">
|
||||
{{ $t('g.delete') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
|
||||
const { onResult } = defineProps<{
|
||||
onResult: (result: boolean) => void
|
||||
}>()
|
||||
</script>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div class="flex w-full items-center p-4">
|
||||
<p class="m-0 text-sm font-medium">
|
||||
{{ $t('g.keybindingPresets.deletePresetTitle') }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex w-full max-w-[420px] flex-col border-t border-border-default"
|
||||
>
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<p class="m-0 text-sm text-muted-foreground">
|
||||
{{ $t('g.keybindingPresets.unsavedChangesMessage') }}
|
||||
</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="textonly"
|
||||
class="text-muted-foreground"
|
||||
@click="onResult(null)"
|
||||
>
|
||||
{{ $t('g.cancel') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
class="bg-secondary-background"
|
||||
@click="onResult(false)"
|
||||
>
|
||||
{{ $t('g.keybindingPresets.discardAndSwitch') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
class="bg-base-foreground text-base-background"
|
||||
@click="onResult(true)"
|
||||
>
|
||||
{{ $t('g.keybindingPresets.saveAndSwitch') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
|
||||
const { onResult } = defineProps<{
|
||||
onResult: (result: boolean | null) => void
|
||||
}>()
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div class="flex w-full items-center p-4">
|
||||
<p class="m-0 text-sm font-medium">
|
||||
{{ $t('g.keybindingPresets.unsavedChangesTo', { name: presetName }) }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { presetName } = defineProps<{
|
||||
presetName: string
|
||||
}>()
|
||||
</script>
|
||||
@@ -269,10 +269,12 @@
|
||||
"saveAsNewPreset": "Save as new preset",
|
||||
"resetToDefault": "Reset to default",
|
||||
"deletePreset": "Delete preset",
|
||||
"unsavedChangesTitle": "Unsaved Changes",
|
||||
"unsavedChangesMessage": "You have unsaved changes to the current keybinding preset. Would you like to save them before switching?",
|
||||
"deletePresetTitle": "Delete Preset",
|
||||
"deletePresetMessage": "Are you sure you want to delete preset \"{name}\"?",
|
||||
"unsavedChangesTo": "Unsaved changes to {name}",
|
||||
"unsavedChangesMessage": "You have unsaved changes that will be lost if you switch without saving.",
|
||||
"discardAndSwitch": "Discard and Switch",
|
||||
"saveAndSwitch": "Save and Switch",
|
||||
"deletePresetTitle": "Delete the current preset?",
|
||||
"deletePresetWarning": "This preset will be deleted. This cannot be undone.",
|
||||
"presetSaved": "Preset \"{name}\" saved",
|
||||
"presetDeleted": "Preset \"{name}\" deleted",
|
||||
"presetImported": "Keybinding preset imported",
|
||||
@@ -283,7 +285,7 @@
|
||||
"overwritePresetTitle": "Overwrite Preset",
|
||||
"overwritePresetMessage": "A preset named \"{name}\" already exists. Overwrite it?",
|
||||
"presetNamePrompt": "Enter a name for the preset",
|
||||
"default": "Default"
|
||||
"default": "Default Preset"
|
||||
},
|
||||
"commandProhibited": "Command {command} is prohibited. Contact an administrator for more information.",
|
||||
"startRecording": "Start Recording",
|
||||
|
||||
@@ -16,6 +16,13 @@ const mockApi = vi.hoisted(() => ({
|
||||
const mockDownloadBlob = vi.hoisted(() => vi.fn())
|
||||
const mockUploadFile = vi.hoisted(() => vi.fn())
|
||||
const mockConfirm = vi.hoisted(() => vi.fn().mockResolvedValue(true))
|
||||
const mockShowSmallLayoutDialog = vi.hoisted(() =>
|
||||
vi.fn().mockImplementation((options: Record<string, unknown>) => {
|
||||
const props = options.props as Record<string, unknown> | undefined
|
||||
const onResult = props?.onResult as ((v: boolean) => void) | undefined
|
||||
onResult?.(true)
|
||||
})
|
||||
)
|
||||
const mockSettingSet = vi.hoisted(() => vi.fn())
|
||||
const mockToastAdd = vi.hoisted(() => vi.fn())
|
||||
const mockPersistUserKeybindings = vi.hoisted(() =>
|
||||
@@ -37,7 +44,8 @@ vi.mock('@/scripts/utils', () => ({
|
||||
vi.mock('@/services/dialogService', () => ({
|
||||
useDialogService: () => ({
|
||||
confirm: mockConfirm,
|
||||
prompt: vi.fn().mockResolvedValue('test-preset')
|
||||
prompt: vi.fn().mockResolvedValue('test-preset'),
|
||||
showSmallLayoutDialog: mockShowSmallLayoutDialog
|
||||
})
|
||||
}))
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ import { toRaw } from 'vue'
|
||||
import { fromZodError } from 'zod-validation-error'
|
||||
|
||||
import { downloadBlob } from '@/base/common/downloadUtil'
|
||||
import DeletePresetContent from '@/components/dialog/content/setting/keybinding/DeletePresetContent.vue'
|
||||
import DeletePresetHeader from '@/components/dialog/content/setting/keybinding/DeletePresetHeader.vue'
|
||||
import UnsavedChangesContent from '@/components/dialog/content/setting/keybinding/UnsavedChangesContent.vue'
|
||||
import UnsavedChangesHeader from '@/components/dialog/content/setting/keybinding/UnsavedChangesHeader.vue'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import { t } from '@/i18n'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
@@ -9,6 +13,7 @@ import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { api } from '@/scripts/api'
|
||||
import { uploadFile } from '@/scripts/utils'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
import { KeybindingImpl } from './keybinding'
|
||||
import { useKeybindingService } from './keybindingService'
|
||||
@@ -50,9 +55,54 @@ export function useKeybindingPresetService() {
|
||||
const keybindingService = useKeybindingService()
|
||||
const settingStore = useSettingStore()
|
||||
const dialogService = useDialogService()
|
||||
const dialogStore = useDialogStore()
|
||||
const toast = useToastStore()
|
||||
const { wrapWithErrorHandlingAsync } = useErrorHandling()
|
||||
|
||||
const DELETE_DIALOG_KEY = 'delete-keybinding-preset'
|
||||
const UNSAVED_DIALOG_KEY = 'unsaved-keybinding-changes'
|
||||
|
||||
function showDeletePresetDialog(): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
dialogService.showSmallLayoutDialog({
|
||||
key: DELETE_DIALOG_KEY,
|
||||
headerComponent: DeletePresetHeader,
|
||||
component: DeletePresetContent,
|
||||
props: {
|
||||
onResult: (result: boolean) => {
|
||||
resolve(result)
|
||||
dialogStore.closeDialog({ key: DELETE_DIALOG_KEY })
|
||||
}
|
||||
},
|
||||
dialogComponentProps: {
|
||||
onClose: () => resolve(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function showUnsavedChangesDialog(
|
||||
presetName: string
|
||||
): Promise<boolean | null> {
|
||||
return new Promise((resolve) => {
|
||||
dialogService.showSmallLayoutDialog({
|
||||
key: UNSAVED_DIALOG_KEY,
|
||||
headerComponent: UnsavedChangesHeader,
|
||||
headerProps: { presetName },
|
||||
component: UnsavedChangesContent,
|
||||
props: {
|
||||
onResult: (result: boolean | null) => {
|
||||
resolve(result)
|
||||
dialogStore.closeDialog({ key: UNSAVED_DIALOG_KEY })
|
||||
}
|
||||
},
|
||||
dialogComponentProps: {
|
||||
onClose: () => resolve(null)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function listPresets(): Promise<string[]> {
|
||||
const files = await api.listUserDataFullInfo(PRESETS_DIR)
|
||||
return files
|
||||
@@ -106,11 +156,7 @@ export function useKeybindingPresetService() {
|
||||
}
|
||||
|
||||
async function deletePreset(name: string) {
|
||||
const confirmed = await dialogService.confirm({
|
||||
title: t('g.keybindingPresets.deletePresetTitle'),
|
||||
message: t('g.keybindingPresets.deletePresetMessage', { name }),
|
||||
type: 'delete'
|
||||
})
|
||||
const confirmed = await showDeletePresetDialog()
|
||||
if (!confirmed) return
|
||||
|
||||
const resp = await api.deleteUserData(presetFilePath(name))
|
||||
@@ -176,11 +222,11 @@ export function useKeybindingPresetService() {
|
||||
|
||||
async function switchPreset(targetName: string) {
|
||||
if (keybindingStore.isCurrentPresetModified) {
|
||||
const result = await dialogService.confirm({
|
||||
title: t('g.keybindingPresets.unsavedChangesTitle'),
|
||||
message: t('g.keybindingPresets.unsavedChangesMessage'),
|
||||
type: 'dirtyClose'
|
||||
})
|
||||
const displayName =
|
||||
keybindingStore.currentPresetName === 'default'
|
||||
? t('g.keybindingPresets.default')
|
||||
: keybindingStore.currentPresetName
|
||||
const result = await showUnsavedChangesDialog(displayName)
|
||||
|
||||
if (result === null) return
|
||||
if (result) {
|
||||
|
||||
Reference in New Issue
Block a user