fix: localize errors, persist fallback, and validate preset names

- Localize loadPreset and deletePreset error messages via i18n
- Persist 'default' setting when preset load fails in initPresets
- Reject names ending with .json extension in presetFilePath
- Add overwrite confirmation in switchPreset save-as flow
This commit is contained in:
Johnpaul
2026-03-10 01:21:46 +01:00
parent b0171dd6c5
commit 35dbc244e9
4 changed files with 30 additions and 5 deletions

View File

@@ -209,6 +209,7 @@ async function initPresets() {
} else {
keybindingStore.currentPresetName = 'default'
keybindingStore.savedPresetData = null
await settingStore.set('Comfy.Keybinding.CurrentPreset', 'default')
}
}
}

View File

@@ -278,6 +278,8 @@
"presetImported": "Keybinding preset imported",
"invalidPresetFile": "Preset file must be valid JSON exported from ComfyUI",
"invalidPresetName": "Preset name must not be empty or contain path separators",
"loadPresetFailed": "Failed to load preset \"{name}\"",
"deletePresetFailed": "Failed to delete preset \"{name}\"",
"overwritePresetTitle": "Overwrite Preset",
"overwritePresetMessage": "A preset named \"{name}\" already exists. Overwrite it?",
"presetNamePrompt": "Enter a name for the preset",

View File

@@ -195,7 +195,7 @@ describe('useKeybindingPresetService', () => {
const service = await getPresetService()
await expect(service.deletePreset('vim')).rejects.toThrow(
'Failed to delete preset "vim"'
'g.keybindingPresets.deletePresetFailed'
)
})
})
@@ -266,6 +266,12 @@ describe('useKeybindingPresetService', () => {
await expect(service.savePreset('default')).rejects.toThrow()
})
it('rejects names ending with .json extension', async () => {
const service = await getPresetService()
await expect(service.savePreset('vim.json')).rejects.toThrow()
await expect(service.savePreset('preset.JSON')).rejects.toThrow()
})
it('rejects empty names', async () => {
const service = await getPresetService()
await expect(service.savePreset('')).rejects.toThrow()

View File

@@ -23,6 +23,7 @@ function presetFilePath(name: string): string {
if (
!trimmed ||
trimmed === 'default' ||
trimmed.toLowerCase().endsWith('.json') ||
trimmed.includes('/') ||
trimmed.includes('\\') ||
trimmed.includes('..') ||
@@ -62,13 +63,15 @@ export function useKeybindingPresetService() {
async function loadPreset(name: string): Promise<KeybindingPreset> {
const resp = await api.getUserData(presetFilePath(name))
if (!resp.ok) {
throw new Error(`Failed to load preset "${name}"`)
throw new Error(t('g.keybindingPresets.loadPresetFailed', { name }))
}
const data = await resp.json()
const result = zKeybindingPreset.safeParse(data)
if (!result.success) {
throw new Error(
`Invalid preset file: ${fromZodError(result.error).message}`
t('g.keybindingPresets.invalidPresetFile') +
': ' +
fromZodError(result.error).message
)
}
return { ...result.data, name }
@@ -112,7 +115,7 @@ export function useKeybindingPresetService() {
const resp = await api.deleteUserData(presetFilePath(name))
if (!resp.ok) {
throw new Error(`Failed to delete preset "${name}"`)
throw new Error(t('g.keybindingPresets.deletePresetFailed', { name }))
}
if (keybindingStore.currentPresetName === name) {
@@ -190,7 +193,20 @@ export function useKeybindingPresetService() {
defaultValue: ''
})
if (!name) return
await savePreset(name.trim())
const trimmedName = name.trim()
if (!trimmedName) return
const existingPresets = await listPresets()
if (existingPresets.includes(trimmedName)) {
const overwrite = await dialogService.confirm({
title: t('g.keybindingPresets.overwritePresetTitle'),
message: t('g.keybindingPresets.overwritePresetMessage', {
name: trimmedName
}),
type: 'overwrite'
})
if (!overwrite) return
}
await savePreset(trimmedName)
}
}
}