feat(quick-open): add SQL Library and SQL Files
This commit is contained in:
1 parent
fa26a56ab0
commit
438164dd64
13 files changed
+409
-37
No files matched your search
@@ -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<string>) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch, nextTick } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Command } from "@lucide/vue";
|
||||
import { Command, FileCode, FileText } from "@lucide/vue";
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useQuickOpen, type QuickOpenItem } from "@/composables/useQuickOpen";
|
||||
@@ -16,7 +16,7 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const { searchQuery, filteredItems, selectedIndex, selectedItem, selectNext, selectPrevious, setQuery } = useQuickOpen();
|
||||
const { searchQuery, filteredItems, selectedIndex, selectedItem, selectNext, selectPrevious, setQuery, loadExternalSqlFiles } = useQuickOpen();
|
||||
const inputRef = ref<HTMLInputElement | null>(null);
|
||||
|
||||
const dialogOpen = computed({
|
||||
@@ -107,16 +107,28 @@ function getTypeLabel(type: string): string {
|
||||
return t("common.package");
|
||||
case "package-body":
|
||||
return t("common.packageBody");
|
||||
case "sql_file":
|
||||
return t("quickOpen.sqlFile");
|
||||
case "sql_library_file":
|
||||
return t("quickOpen.sqlLibraryFile");
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
function getItemIcon(type: string) {
|
||||
if (type === "sql_file") return FileCode;
|
||||
if (type === "sql_library_file") return FileText;
|
||||
return null;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(newOpen) => {
|
||||
if (newOpen) {
|
||||
setQuery("");
|
||||
// Eagerly load external SQL files so they appear in the initial list
|
||||
void loadExternalSqlFiles();
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus();
|
||||
});
|
||||
@@ -145,17 +157,20 @@ watch(
|
||||
<div v-else class="divide-y">
|
||||
<div v-for="(item, index) in filteredItems" :key="item.id" :class="['px-4 py-2 cursor-pointer', index === selectedIndex ? 'bg-accent' : 'hover:bg-muted']" @click="handleSelect(item)" @mouseenter="selectedIndex = index">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium truncate">
|
||||
<template v-for="(part, i) in getHighlightedLabel(item)" :key="i">
|
||||
<span v-if="typeof part === 'object'" :class="{ 'bg-yellow-200 dark:bg-yellow-800 font-semibold': part.highlight }">
|
||||
{{ part.text }}
|
||||
</span>
|
||||
<span v-else>{{ part }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="item.description" class="text-xs text-muted-foreground truncate">
|
||||
{{ item.description }}
|
||||
<div class="flex items-center gap-2 flex-1 min-w-0">
|
||||
<component v-if="getItemIcon(item.type)" :is="getItemIcon(item.type)" class="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium truncate">
|
||||
<template v-for="(part, i) in getHighlightedLabel(item)" :key="i">
|
||||
<span v-if="typeof part === 'object'" :class="{ 'bg-yellow-200 dark:bg-yellow-800 font-semibold': part.highlight }">
|
||||
{{ part.text }}
|
||||
</span>
|
||||
<span v-else>{{ part }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="item.description" class="text-xs text-muted-foreground truncate">
|
||||
{{ item.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs px-2 py-1 rounded bg-muted text-muted-foreground whitespace-nowrap">
|
||||
|
||||
Reference in new issue
Block a user