From 438164dd649133221af2e99ae3a4b9e38f394a39 Mon Sep 17 00:00:00 2001 From: yangqinlong Date: Thu, 23 Jul 2026 21:26:27 +0800 Subject: [PATCH] feat(quick-open): add SQL Library and SQL Files --- apps/desktop/src/App.vue | 25 ++- .../src/components/layout/SqlFilePanel.vue | 17 +- .../components/quick-open/QuickOpenDialog.vue | 41 +++-- .../__tests__/useQuickOpen.spec.ts | 143 +++++++++++++++- apps/desktop/src/composables/useQuickOpen.ts | 158 +++++++++++++++++- apps/desktop/src/i18n/locales/en.ts | 4 +- apps/desktop/src/i18n/locales/es.ts | 4 +- apps/desktop/src/i18n/locales/it.ts | 4 +- apps/desktop/src/i18n/locales/ja.ts | 4 +- apps/desktop/src/i18n/locales/pt-BR.ts | 4 +- apps/desktop/src/i18n/locales/zh-CN.ts | 4 +- apps/desktop/src/i18n/locales/zh-TW.ts | 4 +- .../desktop/src/lib/sqlFile/sqlFileFolders.ts | 34 ++++ 13 files changed, 409 insertions(+), 37 deletions(-) create mode 100644 apps/desktop/src/lib/sqlFile/sqlFileFolders.ts diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index 3ce53aa596..2faea6de12 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -1634,7 +1634,30 @@ async function handleQuickOpenSelect(item: any) { const connectionStore = useConnectionStore(); const queryStore = useQueryStore(); - // For all types, set the active connection + // Handle SQL file types first — they don't require a database connection + if (item.type === "sql_file" && item.filePath) { + try { + const content = await api.readExternalSqlFile(item.filePath); + const connectionId = connectionStore.activeConnectionId || connectionStore.connections[0]?.id || ""; + const connection = connectionId ? connectionStore.getConfig(connectionId) : undefined; + const database = connection ? resolveDefaultDatabase(connection, []) : ""; + queryStore.openExternalSqlFile(connectionId, database, item.filePath, content); + } catch (e: any) { + toast(e?.message || String(e), 5000); + } + return; + } + + if (item.type === "sql_library_file" && item.sqlFileId) { + const file = await savedSqlStore.ensureFileContent(item.sqlFileId); + if (!file) return; + queryStore.openSavedSql(file); + connectionStore.activeConnectionId = file.connectionId; + void savedSqlStore.recordFileUsage(file.id); + return; + } + + // For all other types, set the active connection connectionStore.activeConnectionId = item.connectionId; // Ensure connection is connected diff --git a/apps/desktop/src/components/layout/SqlFilePanel.vue b/apps/desktop/src/components/layout/SqlFilePanel.vue index fcc161988b..d93457b44f 100644 --- a/apps/desktop/src/components/layout/SqlFilePanel.vue +++ b/apps/desktop/src/components/layout/SqlFilePanel.vue @@ -9,13 +9,11 @@ import { useQueryStore } from "@/stores/queryStore"; import { useConnectionStore } from "@/stores/connectionStore"; import { useToast } from "@/composables/useToast"; import { isTauriRuntime } from "@/lib/backend/tauriRuntime"; -import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage"; import { resolveDefaultDatabase } from "@/lib/database/defaultDatabase"; import { copyToClipboard } from "@/lib/common/clipboard"; import * as api from "@/lib/backend/api"; import type { SqlFileEntry } from "@/lib/backend/api"; - -const STORAGE_KEY = "dbx-sql-file-folders"; +import { getSqlFileFolderPaths, saveSqlFileFolderPaths, notifySqlFileFoldersChanged } from "@/lib/sqlFile/sqlFileFolders"; const emit = defineEmits<{ close: []; @@ -52,19 +50,12 @@ function selectPath(path: string | null) { } function loadSavedFolders(): string[] { - try { - const raw = safeLocalStorageGet(STORAGE_KEY); - if (!raw) return []; - const parsed = JSON.parse(raw); - return Array.isArray(parsed) ? parsed.filter((p): p is string => typeof p === "string") : []; - } catch { - return []; - } + return getSqlFileFolderPaths(); } function saveFolders() { const paths = folders.value.map((f) => f.path); - safeLocalStorageSet(STORAGE_KEY, JSON.stringify(paths)); + saveSqlFileFolderPaths(paths); } async function pickFolder() { @@ -141,11 +132,13 @@ function collectPaths(entries: SqlFileEntry[], into: Set) { async function refreshFolder(folderPath: string) { await loadFolderEntries(folderPath); + notifySqlFileFoldersChanged(); toast(t("sqlFileTree.refreshed"), 1500); } async function refreshAll() { await Promise.all(folders.value.map((f) => loadFolderEntries(f.path))); + notifySqlFileFoldersChanged(); toast(t("sqlFileTree.refreshed"), 1500); } diff --git a/apps/desktop/src/components/quick-open/QuickOpenDialog.vue b/apps/desktop/src/components/quick-open/QuickOpenDialog.vue index e69d4b93a5..109f28e21d 100644 --- a/apps/desktop/src/components/quick-open/QuickOpenDialog.vue +++ b/apps/desktop/src/components/quick-open/QuickOpenDialog.vue @@ -1,7 +1,7 @@