diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index 90f271b701..c0a2800178 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -208,7 +208,23 @@ import { combineDataTypeForDatabase, dataTypeLengthInputValue, getDataTypeOption import { useToast } from "@/composables/useToast"; import type { DatabaseType, SqlShortcutAction, SqlSnippet } from "@/types/database"; import { uuid } from "@/lib/common/utils"; -import { findSqlShortcutConflicts, hasSqlShortcutConflicts as sqlShortcutsHaveConflicts, SQL_SHORTCUT_TABLE_TOKEN } from "@/lib/sql/sqlShortcutActions"; +import { DEFAULT_SQL_SHORTCUT_SELECT_LIMIT } from "@/lib/sql/sqlDialectSelectLimit"; +import { + BUILTIN_SQL_SHORTCUT_COUNT_ID, + BUILTIN_SQL_SHORTCUT_SELECT_LIMIT_ID, + canonicalSqlShortcutSql, + DEFAULT_SQL_SHORTCUTS, + findSqlShortcutConflicts, + hasSqlShortcutConflicts as sqlShortcutsHaveConflicts, + isBuiltinSqlShortcut, + mergeDefaultSqlShortcuts, + normalizeSqlShortcutLimit, + resolveSqlShortcutBody, + SQL_SHORTCUT_TABLE_TOKEN, + sqlShortcutDisplaySql, + deriveSqlShortcutDatabaseTypes, +} from "@/lib/sql/sqlShortcutActions"; +import { applySqlShortcutBodyToAllSelected, buildSqlShortcutBodiesForSave, clearSqlShortcutFormDatabaseTypes as clearSqlShortcutFormBodies, switchSqlShortcutEditingDatabaseType, toggleSqlShortcutFormDatabaseType, type SqlShortcutFormBodies } from "@/lib/sql/sqlShortcutFormBodies"; import { DEFAULT_SQL_SNIPPETS } from "@/lib/sql/sqlCompletion"; import AiProviderLogo from "@/components/icons/AiProviderLogo.vue"; import AppLogo from "@/components/icons/AppLogo.vue"; @@ -738,10 +754,79 @@ function editableSnippet(snippet: SqlSnippet): SqlSnippet { const editSnippets = ref(settingsStore.editorSettings.snippets.map(editableSnippet)); function editableSqlShortcut(action: SqlShortcutAction): SqlShortcutAction { - return { ...action, enabled: action.enabled !== false }; + const next: SqlShortcutAction = { ...action, enabled: action.enabled !== false }; + if (action.kind === "select-limit" || action.id === BUILTIN_SQL_SHORTCUT_SELECT_LIMIT_ID) { + next.kind = "select-limit"; + next.limit = normalizeSqlShortcutLimit(action.limit); + next.sql = canonicalSqlShortcutSql(next); + delete next.databaseTypes; + delete next.sqlByDatabaseType; + } else { + delete next.kind; + delete next.limit; + if (action.sqlByDatabaseType && Object.keys(action.sqlByDatabaseType).length > 0) { + next.sqlByDatabaseType = { ...action.sqlByDatabaseType }; + } else { + delete next.sqlByDatabaseType; + } + const databaseTypes = deriveSqlShortcutDatabaseTypes(action.databaseTypes?.length ? [...action.databaseTypes] : undefined, next.sqlByDatabaseType); + if (databaseTypes) next.databaseTypes = databaseTypes; + else delete next.databaseTypes; + } + return next; } -const editSqlShortcuts = ref(settingsStore.editorSettings.sqlShortcuts.map(editableSqlShortcut)); +const editSqlShortcuts = ref(mergeDefaultSqlShortcuts(settingsStore.editorSettings.sqlShortcuts.map(editableSqlShortcut))); + +type SqlShortcutFormState = { + label: string; + shortcut: string; + /** Default fallback template (payload.sql). */ + sql: string; + /** Textarea content for the current editing context. */ + body: string; + kind: "template" | "select-limit"; + limit: number; + databaseTypes: DatabaseType[]; + sqlByDatabaseType: Partial>; + editingDatabaseType: DatabaseType | ""; +}; + +function emptySqlShortcutForm(): SqlShortcutFormState { + const sql = `SELECT * FROM ${SQL_SHORTCUT_TABLE_TOKEN}`; + return { + label: "", + shortcut: "", + sql, + body: sql, + kind: "template", + limit: DEFAULT_SQL_SHORTCUT_SELECT_LIMIT, + databaseTypes: [], + sqlByDatabaseType: {}, + editingDatabaseType: "", + }; +} + +function sqlShortcutFormBodies(form: SqlShortcutFormState): SqlShortcutFormBodies { + return { + sql: form.sql, + body: form.body, + databaseTypes: form.databaseTypes, + sqlByDatabaseType: form.sqlByDatabaseType, + editingDatabaseType: form.editingDatabaseType, + }; +} + +function patchSqlShortcutFormBodies(next: SqlShortcutFormBodies) { + sqlShortcutForm.value = { + ...sqlShortcutForm.value, + sql: next.sql, + body: next.body, + databaseTypes: next.databaseTypes, + sqlByDatabaseType: next.sqlByDatabaseType, + editingDatabaseType: next.editingDatabaseType, + }; +} function currentEditorSettingsDraft(): EditorSettingsDraft { return { @@ -963,8 +1048,10 @@ const snippetFormPrefixError = ref(""); const sqlShortcutDialogOpen = ref(false); const sqlShortcutEditingId = ref(null); -const sqlShortcutForm = ref({ label: "", shortcut: "", sql: "" }); +const sqlShortcutForm = ref(emptySqlShortcutForm()); const sqlShortcutFormLabelError = ref(""); +const sqlShortcutPreviewDatabaseType = ref("mysql"); +const sqlShortcutDatabaseTypesOpen = ref(false); const editingSqlShortcutInputId = ref(null); const iconThemeBlackDescriptionText = computed(() => (isMacOS() ? t("settings.iconThemeBlackDescriptionMac") : t("settings.iconThemeBlackDescription"))); const layoutDescTruncated = { @@ -1095,56 +1182,163 @@ function confirmDeleteSnippet(snippet: SqlSnippet) { function openAddSqlShortcutDialog() { sqlShortcutEditingId.value = null; - sqlShortcutForm.value = { label: "", shortcut: "", sql: `SELECT * FROM ${SQL_SHORTCUT_TABLE_TOKEN}` }; + sqlShortcutForm.value = emptySqlShortcutForm(); sqlShortcutFormLabelError.value = ""; + sqlShortcutPreviewDatabaseType.value = "mysql"; + sqlShortcutDatabaseTypesOpen.value = false; editingSqlShortcutInputId.value = null; sqlShortcutDialogOpen.value = true; } function openEditSqlShortcutDialog(action: SqlShortcutAction) { sqlShortcutEditingId.value = action.id; + const databaseTypes = action.databaseTypes ? [...action.databaseTypes] : []; + const sqlByDatabaseType = action.sqlByDatabaseType ? { ...action.sqlByDatabaseType } : {}; + const editingDatabaseType = databaseTypes[0] ?? ""; + const fallbackSql = action.sql; sqlShortcutForm.value = { label: action.label, shortcut: action.shortcut, - sql: action.sql, + sql: fallbackSql, + body: editingDatabaseType ? resolveSqlShortcutBody(action, editingDatabaseType) : fallbackSql, + kind: action.kind === "select-limit" || action.id === BUILTIN_SQL_SHORTCUT_SELECT_LIMIT_ID ? "select-limit" : "template", + limit: normalizeSqlShortcutLimit(action.limit), + databaseTypes, + sqlByDatabaseType, + editingDatabaseType, }; sqlShortcutFormLabelError.value = ""; + sqlShortcutPreviewDatabaseType.value = databaseTypes[0] ?? "mysql"; + sqlShortcutDatabaseTypesOpen.value = false; editingSqlShortcutInputId.value = null; sqlShortcutDialogOpen.value = true; } +const sqlShortcutFormIsBuiltin = computed(() => !!sqlShortcutEditingId.value && isBuiltinSqlShortcut(sqlShortcutEditingId.value)); + +const sqlShortcutFormBuiltinLabel = computed(() => { + if (!sqlShortcutEditingId.value) return ""; + return sqlShortcutDisplayLabel({ + id: sqlShortcutEditingId.value, + label: sqlShortcutForm.value.label, + shortcut: "", + sql: "", + }); +}); + +function sqlShortcutDisplayLabel(action: SqlShortcutAction): string { + if (action.id === BUILTIN_SQL_SHORTCUT_SELECT_LIMIT_ID) return t("settings.sqlShortcutBuiltinSelectLimit"); + if (action.id === BUILTIN_SQL_SHORTCUT_COUNT_ID) return t("settings.sqlShortcutBuiltinCount"); + return action.label; +} + +function onSqlShortcutEditingDatabaseTypeChange(next: DatabaseType | "") { + if (!next) return; + patchSqlShortcutFormBodies(switchSqlShortcutEditingDatabaseType(sqlShortcutFormBodies(sqlShortcutForm.value), next)); +} + +function toggleSqlShortcutDatabaseType(dbType: DatabaseType) { + patchSqlShortcutFormBodies(toggleSqlShortcutFormDatabaseType(sqlShortcutFormBodies(sqlShortcutForm.value), dbType)); +} + +function clearSqlShortcutDatabaseTypes() { + patchSqlShortcutFormBodies(clearSqlShortcutFormBodies(sqlShortcutFormBodies(sqlShortcutForm.value))); +} + +function applySqlShortcutSqlToAllSelected() { + patchSqlShortcutFormBodies(applySqlShortcutBodyToAllSelected(sqlShortcutFormBodies(sqlShortcutForm.value))); + toast(t("settings.sqlShortcutsApplySqlToAllSelectedDone"), 2500); +} + +const sqlShortcutFormPreviewSql = computed(() => + sqlShortcutDisplaySql( + { + id: sqlShortcutEditingId.value ?? "preview", + label: "preview", + shortcut: "", + sql: sqlShortcutForm.value.sql, + kind: sqlShortcutForm.value.kind, + limit: sqlShortcutForm.value.limit, + sqlByDatabaseType: sqlShortcutForm.value.sqlByDatabaseType, + }, + sqlShortcutForm.value.kind === "select-limit" ? sqlShortcutPreviewDatabaseType.value : sqlShortcutForm.value.editingDatabaseType || undefined, + ), +); + +function sqlShortcutListSql(action: SqlShortcutAction): string { + return sqlShortcutDisplaySql(action); +} + +function sqlShortcutDatabaseTypesLabel(action: SqlShortcutAction): string { + if (isBuiltinSqlShortcut(action.id) || !action.databaseTypes?.length) return t("settings.sqlShortcutsDatabaseTypesAll"); + return action.databaseTypes.map((dbType) => dbTypeLabel(dbType)).join(", "); +} + function saveSqlShortcut() { + const editingId = sqlShortcutEditingId.value; + const existing = editingId ? editSqlShortcuts.value.find((item) => item.id === editingId) : undefined; + const isBuiltin = !!editingId && isBuiltinSqlShortcut(editingId); + + if (isBuiltin && existing) { + const limit = normalizeSqlShortcutLimit(sqlShortcutForm.value.limit); + const payload: SqlShortcutAction = { + ...existing, + shortcut: sqlShortcutForm.value.shortcut.trim(), + enabled: existing.enabled !== false, + }; + if (existing.id === BUILTIN_SQL_SHORTCUT_SELECT_LIMIT_ID || existing.kind === "select-limit") { + payload.kind = "select-limit"; + payload.limit = limit; + payload.sql = canonicalSqlShortcutSql({ kind: "select-limit", limit, sql: "" }); + delete payload.databaseTypes; + delete payload.sqlByDatabaseType; + } + const nextShortcuts = editSqlShortcuts.value.map((item) => (item.id === editingId ? payload : item)); + if (sqlShortcutsHaveConflicts(nextShortcuts, editShortcuts.value)) { + toast(t("settings.shortcutConflict"), 3000); + return; + } + editSqlShortcuts.value = nextShortcuts.map(editableSqlShortcut); + sqlShortcutDialogOpen.value = false; + void commitSqlShortcuts(); + return; + } + const label = sqlShortcutForm.value.label.trim(); if (!label) { sqlShortcutFormLabelError.value = t("settings.sqlShortcutsLabelRequired"); return; } + const bodies = buildSqlShortcutBodiesForSave(sqlShortcutFormBodies(sqlShortcutForm.value)); const payload: SqlShortcutAction = { - id: sqlShortcutEditingId.value ?? uuid(), + id: editingId ?? uuid(), label, shortcut: sqlShortcutForm.value.shortcut.trim(), - sql: sqlShortcutForm.value.sql, - enabled: true, + sql: bodies.sql, + enabled: existing?.enabled !== false, }; - const nextShortcuts = sqlShortcutEditingId.value - ? editSqlShortcuts.value.map((item) => - item.id === sqlShortcutEditingId.value - ? { - ...payload, - enabled: item.enabled !== false, - } - : item, - ) - : [...editSqlShortcuts.value, payload]; + if (bodies.databaseTypes) payload.databaseTypes = bodies.databaseTypes; + if (bodies.sqlByDatabaseType) payload.sqlByDatabaseType = bodies.sqlByDatabaseType; + const nextShortcuts = editingId ? editSqlShortcuts.value.map((item) => (item.id === editingId ? payload : item)) : [...editSqlShortcuts.value, payload]; if (sqlShortcutsHaveConflicts(nextShortcuts, editShortcuts.value)) { toast(t("settings.shortcutConflict"), 3000); return; } - editSqlShortcuts.value = nextShortcuts; + editSqlShortcuts.value = nextShortcuts.map(editableSqlShortcut); sqlShortcutDialogOpen.value = false; void commitSqlShortcuts(); } +function confirmDeleteSqlShortcut(action: SqlShortcutAction) { + if (isBuiltinSqlShortcut(action.id)) { + toast(t("settings.sqlShortcutsBuiltinDeleteBlocked"), 3000); + return; + } + if (window.confirm(t("settings.sqlShortcutsDeleteConfirm", { name: sqlShortcutDisplayLabel(action) }))) { + deleteSqlShortcut(action.id); + } +} + function setSqlShortcutEnabled(id: string, enabled: boolean) { const idx = editSqlShortcuts.value.findIndex((item) => item.id === id); if (idx === -1) return; @@ -1160,16 +1354,11 @@ function setSqlShortcutEnabled(id: string, enabled: boolean) { } function deleteSqlShortcut(id: string) { + if (isBuiltinSqlShortcut(id)) return; editSqlShortcuts.value = editSqlShortcuts.value.filter((item) => item.id !== id); void commitSqlShortcuts(); } -function confirmDeleteSqlShortcut(action: SqlShortcutAction) { - if (window.confirm(t("settings.sqlShortcutsDeleteConfirm", { name: action.label }))) { - deleteSqlShortcut(action.id); - } -} - function onSqlShortcutBindingKeydown(id: string, event: KeyboardEvent) { event.preventDefault(); event.stopPropagation(); @@ -1376,7 +1565,7 @@ function syncEditorSettingsDraftFromStore() { editUpdateDownloadSource.value = settingsStore.editorSettings.updateDownloadSource; editToolbarItems.value = { ...settingsStore.editorSettings.toolbarItems }; editSnippets.value = settingsStore.editorSettings.snippets.map(editableSnippet); - editSqlShortcuts.value = settingsStore.editorSettings.sqlShortcuts.map(editableSqlShortcut); + editSqlShortcuts.value = mergeDefaultSqlShortcuts(settingsStore.editorSettings.sqlShortcuts.map(editableSqlShortcut)); editSqlVariableSubstitutionEnabled.value = settingsStore.editorSettings.sqlVariableSubstitutionEnabled; editSqlVariableSyntaxOverrides.value = normalizeSqlVariableSyntaxOverrides(settingsStore.editorSettings.sqlVariableSyntaxOverrides); editClickTableNavigationTarget.value = settingsStore.editorSettings.clickTableNavigationTarget; @@ -1514,7 +1703,7 @@ function applyEditorSettingsKeysToRefs(draft: EditorSettingsDraft, keys: readonl redisKeyTemplates: (value) => normalizeRedisKeyTemplates(value as string[]).join("\n"), toolbarItems: (value) => ({ ...(value as EditorSettings["toolbarItems"]) }), snippets: (value) => (value as SqlSnippet[]).map(editableSnippet), - sqlShortcuts: (value) => (value as SqlShortcutAction[]).map(editableSqlShortcut), + sqlShortcuts: (value) => mergeDefaultSqlShortcuts((value as SqlShortcutAction[]).map(editableSqlShortcut)), sqlVariableSyntaxOverrides: (value) => normalizeSqlVariableSyntaxOverrides(value as EditorSettings["sqlVariableSyntaxOverrides"]), backgroundImage: (value) => cloneBackgroundImageDraft(value as BackgroundImageSettings), }); @@ -2071,7 +2260,7 @@ function resetAllDefaults() { editUpdateDownloadSource.value = DEFAULT_EDITOR_SETTINGS.updateDownloadSource; editToolbarItems.value = { ...DEFAULT_EDITOR_SETTINGS.toolbarItems }; editSnippets.value = DEFAULT_SQL_SNIPPETS.map((s) => ({ ...s })); - editSqlShortcuts.value = []; + editSqlShortcuts.value = DEFAULT_SQL_SHORTCUTS.map(editableSqlShortcut); } function addTableColumnTemplateRow() { @@ -7857,11 +8046,13 @@ onUnmounted(() => { {{ t("settings.sqlShortcutsEmpty") }}
- +
+ + @@ -7869,12 +8060,20 @@ onUnmounted(() => { - + + + - +
{{ t("settings.sqlShortcutsLabel") }}{{ t("settings.sqlShortcutsSource") }} {{ t("settings.shortcutPressShortcut") }}{{ t("settings.sqlShortcutsDatabaseTypes") }} {{ t("settings.snippetsStatus") }} {{ t("settings.sqlShortcutsSql") }}
{{ action.label }}{{ sqlShortcutDisplayLabel(action) }} + + {{ isBuiltinSqlShortcut(action.id) ? t("settings.sqlShortcutsSourceBuiltin") : t("settings.sqlShortcutsSourceCustom") }} + + {{ action.shortcut ? formatShortcutPill(action.shortcut) : t("settings.sqlShortcutsUnbound") }} + {{ sqlShortcutDatabaseTypesLabel(action) }} +
@@ -7883,13 +8082,13 @@ onUnmounted(() => {
{{ action.sql }}{{ sqlShortcutListSql(action) }}
-
@@ -9970,7 +10169,7 @@ LIMIT 100; - + {{ sqlShortcutEditingId ? t("settings.sqlShortcutsEditTitle") : t("settings.sqlShortcutsAddTitle") }} @@ -9979,7 +10178,8 @@ LIMIT 100;
- + +

{{ sqlShortcutFormLabelError }}

@@ -10009,19 +10209,104 @@ LIMIT 100;
-
- -