Feature:新增多套应用配色主题 (#2559)

Co-authored-by: staff <staff@qimaos-MacBook-Pro.local>
This commit is contained in:
zipgandstaff authored and GitHub committed 2026-07-05 00:34:47 +08:00
1 parent 896580f1c5
commit 5c35755219
27 files changed
+1884 -99

No files matched your search

@@ -15,7 +15,7 @@ import { ArrowLeft, Copy, Download, Play, Loader2, PlusCircle, XCircle, ArrowRig
const { t } = useI18n();
const { toast } = useToast();
const settingsStore = useSettingsStore();
const { isDark } = useTheme();
const { isDark, themePalette } = useTheme();
const props = defineProps<{
deploySql: string;
@@ -120,7 +120,7 @@ async function initEditor() {
const fontSize = settingsStore.editorSettings.fontSize;
const fontFamily = settingsStore.editorSettings.fontFamily;
const themeExt = await loadEditorTheme(editorTheme, appAppearance);
const themeExt = await loadEditorTheme(editorTheme, appAppearance, undefined, themePalette.value);
const fontExt = editorFontTheme(EditorView, fontSize, fontFamily, { fixedHeight: true, scrollable: true });
const dialect = createDbxCodeMirrorSqlDialect(langSql, "postgres");
@@ -81,7 +81,7 @@ import { DEFAULT_SQL_SNIPPETS } from "@/lib/sql/sqlCompletion";
import AiProviderLogo from "@/components/icons/AiProviderLogo.vue";
import AppLogo from "@/components/icons/AppLogo.vue";
import SqlFormatterSettingsPanel from "./SqlFormatterSettingsPanel.vue";
import type { AppThemeAppearance } from "@/lib/app/appTheme";
import { APP_THEME_PALETTES, type AppThemeAppearance, type AppThemeMode, type AppThemePalette } from "@/lib/app/appTheme";
import { useConnectionStore } from "@/stores/connectionStore";
import { useSavedSqlStore } from "@/stores/savedSqlStore";
import { currentLocale, setLocale, type Locale } from "@/i18n";
@@ -95,7 +95,23 @@ const { toast } = useToast();
const settingsStore = useSettingsStore();
const connectionStore = useConnectionStore();
const savedSqlStore = useSavedSqlStore();
const { isDark, themeMode, setThemeMode } = useTheme();
const { isDark, themeMode, themePalette, setThemeMode, setThemePalette } = useTheme();
const appThemePaletteOptions = computed(
(): Array<{ value: AppThemePalette; label: string; previewColor: string }> =>
APP_THEME_PALETTES.map((palette) => ({
value: palette.value,
label: t(palette.labelKey),
previewColor: palette.previewColor,
})),
);
const selectedThemePaletteOption = computed(() => appThemePaletteOptions.value.find((option) => option.value === themePalette.value) ?? appThemePaletteOptions.value[0]);
const selectedLocaleOption = computed(() => LOCALE_OPTIONS.find((locale) => locale.value === currentLocale()) ?? LOCALE_OPTIONS[0]);
const appThemeModeOptions = computed(() => [
{ value: "light" as AppThemeMode, label: t("toolbar.themeLight"), icon: Sun },
{ value: "dark" as AppThemeMode, label: t("toolbar.themeDark"), icon: Moon },
{ value: "system" as AppThemeMode, label: t("toolbar.themeSystem"), icon: SunMoon },
]);
let cachedSystemFonts: string[] | null = null;
let pendingSystemFonts: Promise<string[]> | null = null;
@@ -274,7 +290,9 @@ const systemFontsLoaded = ref(false);
const uiScaleOptions = [0.75, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2];
const fontSearchTriggerClass =
"h-8 w-full max-w-none justify-between gap-1.5 rounded-[6px] border border-input bg-transparent py-2 pl-2.5 pr-2 text-sm font-normal shadow-none hover:bg-transparent focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-expanded:bg-transparent dark:bg-input/30 dark:hover:bg-input/50";
const appearanceFontSearchTriggerClass = `${fontSearchTriggerClass} gap-0 pl-2 pr-1.5`;
const fontSearchTriggerIconClass = "size-4 text-muted-foreground";
const appearanceFontSearchTriggerIconClass = "size-2.5 text-muted-foreground";
const disconnectTabHandlingModeDescriptionKey = computed(() => {
switch (editDisconnectTabHandlingMode.value) {
case "close-tabs":
@@ -1123,14 +1141,14 @@ function setSidebarActivation(value: "single" | "double") {
editSidebarActivation.value = value;
}
const activeSettingsTab = ref("editor");
const activeSettingsTab = ref("appearance");
const isWeb = !isTauriRuntime();
const displayedAppVersion = computed(() => (props.appVersion ? `v${props.appVersion}` : ""));
type SettingsCategory = "editor" | "formatter" | "appearance" | "navigation" | "data" | "shortcuts" | "snippets" | "sync" | "ai" | "mcp" | "security" | "about";
const settingsCategoryNav = computed<{ value: SettingsCategory; label: string }[]>(() => [
{ value: "appearance", label: t("settings.appearanceTab") },
{ value: "editor", label: t("settings.editorTab") },
{ value: "formatter", label: t("settings.sqlFormatterTab") },
{ value: "appearance", label: t("settings.appearanceTab") },
{ value: "navigation", label: t("settings.navigationTab") },
{ value: "data", label: t("settings.dataTab") },
{ value: "shortcuts", label: t("settings.shortcutsTab") },
@@ -1148,7 +1166,7 @@ function hasSettingsApplyFooter(value: SettingsCategory): boolean {
}
function settingsCategoryButton(value: SettingsCategory): string {
return ["w-full rounded-md px-3 py-2 text-left text-sm transition-colors", value === activeSettingsTab.value ? "bg-primary text-primary-foreground shadow-sm" : "text-muted-foreground hover:bg-muted hover:text-foreground"].join(" ");
return ["w-auto shrink-0 whitespace-nowrap rounded-md px-3 py-2 text-left text-sm transition-colors lg:w-full", value === activeSettingsTab.value ? "bg-primary text-primary-foreground shadow-sm" : "text-muted-foreground hover:bg-muted hover:text-foreground"].join(" ");
}
function openExternalUrl(url: string) {
@@ -1477,7 +1495,7 @@ watch(
() => settingsVisible.value,
async (open) => {
if (open) {
activeSettingsTab.value = props.initialTab || "editor";
activeSettingsTab.value = props.initialTab || "appearance";
passwordMessage.value = "";
oldPassword.value = "";
newPassword.value = "";
@@ -1962,12 +1980,14 @@ const previewSettings = computed<{
fontSize: number;
theme: EditorTheme;
appAppearance: AppThemeAppearance;
appPalette: AppThemePalette;
customColors?: CustomThemeColors;
}>(() => ({
fontFamily: editFontFamily.value,
fontSize: editFontSize.value,
theme: editTheme.value,
appAppearance: isDark.value ? "dark" : "light",
appPalette: themePalette.value,
customColors: getPreviewCustomThemeColors(),
}));
@@ -2017,7 +2037,7 @@ watch(
async ([ss]) => {
if (!previewView.value || !fontThemeComp || !themeComp || !editorViewModule) return;
const themeExt = await loadEditorTheme(ss.theme, ss.appAppearance, ss.customColors);
const themeExt = await loadEditorTheme(ss.theme, ss.appAppearance, ss.customColors, ss.appPalette);
previewView.value.dispatch({
effects: [themeComp.reconfigure(themeExt), fontThemeComp.reconfigure(editorFontTheme(editorViewModule.EditorView, ss.fontSize, ss.fontFamily))],
});
@@ -2065,7 +2085,7 @@ watch(previewRef, async (el) => {
previewSqlDiagnostics = previewDiagnosticsForSql(currentPreviewSql());
const ss = previewSettings.value;
const themeExt = await loadEditorTheme(ss.theme, ss.appAppearance, ss.customColors);
const themeExt = await loadEditorTheme(ss.theme, ss.appAppearance, ss.customColors, ss.appPalette);
const diagnosticTheme = EditorView.baseTheme({
".cm-settings-preview-sql-error": {
textDecoration: "underline wavy var(--destructive)",
@@ -2125,8 +2145,8 @@ onUnmounted(cleanupPreviewEditor);
</component>
</DialogHeader>
<div class="flex min-h-0 flex-1 flex-col gap-3 overflow-hidden sm:flex-row">
<nav class="settingsCategoryNav flex min-h-0 shrink-0 gap-1 overflow-x-auto border-b pb-3 sm:w-40 sm:flex-col sm:overflow-x-hidden sm:overflow-y-auto sm:border-b-0 sm:border-r sm:pb-0 sm:pr-3">
<div class="flex min-h-0 flex-1 flex-col gap-3 overflow-hidden lg:flex-row">
<nav class="settingsCategoryNav flex min-h-0 shrink-0 gap-1 overflow-x-auto border-b pb-3 lg:w-40 lg:flex-col lg:overflow-x-hidden lg:overflow-y-auto lg:border-b-0 lg:border-r lg:pb-0 lg:pr-3">
<button v-for="category in settingsCategoryNav" :key="category.value" type="button" :class="settingsCategoryButton(category.value)" @click="activeSettingsTab = category.value">
{{ category.label }}
</button>
@@ -2374,16 +2394,25 @@ onUnmounted(cleanupPreviewEditor);
</section>
<section v-else-if="activeSettingsTab === 'appearance'" class="flex flex-col gap-5 py-2">
<div class="grid gap-4 md:grid-cols-[minmax(220px,280px)_minmax(260px,1fr)]">
<div class="grid gap-x-1.5 gap-y-4 sm:grid-cols-[minmax(0,127fr)_minmax(0,127fr)_minmax(0,191fr)_minmax(0,130fr)]">
<div class="space-y-2 min-w-0">
<Label>{{ t("settings.languageTitle") }}</Label>
<div class="flex h-9 items-end">
<Label class="whitespace-normal leading-tight">{{ t("settings.languageTitle") }}</Label>
</div>
<Select :model-value="currentLocale()" @update:model-value="onLocaleChange">
<SelectTrigger class="h-8 w-full">
<SelectValue />
<SelectTrigger class="h-8 w-full gap-0.5 px-0.5">
<SelectValue>
<span v-if="selectedLocaleOption" class="flex min-w-0 items-center gap-0.5">
<span class="inline-flex h-5 shrink-0 items-center justify-center text-sm font-medium leading-none">
{{ selectedLocaleOption.flag }}
</span>
<span class="truncate">{{ selectedLocaleOption.label }}</span>
</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectContent class="w-[150px]">
<SelectItem v-for="locale in LOCALE_OPTIONS" :key="locale.value" :value="locale.value">
<div class="flex items-center gap-2">
<div class="flex items-center gap-1">
<span class="inline-flex h-5 w-6 shrink-0 items-center justify-center text-sm font-medium leading-none">
{{ locale.flag }}
</span>
@@ -2395,7 +2424,36 @@ onUnmounted(cleanupPreviewEditor);
</div>
<div class="space-y-2 min-w-0">
<Label>{{ t("settings.uiFontFamily") }}</Label>
<div class="flex h-9 items-end">
<Label class="whitespace-normal leading-tight">{{ t("settings.colorTheme") }}</Label>
</div>
<Select :model-value="themePalette" @update:model-value="(value) => setThemePalette(value as AppThemePalette)">
<SelectTrigger class="h-8 w-full gap-1">
<SelectValue :placeholder="t('settings.selectColorTheme')">
<span v-if="selectedThemePaletteOption" class="flex min-w-0 items-center gap-1">
<span class="h-3 w-3 shrink-0 rounded-full border border-border shadow-xs" :style="{ background: selectedThemePaletteOption.previewColor }" />
<span class="truncate">{{ selectedThemePaletteOption.label }}</span>
</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem v-for="option in appThemePaletteOptions" :key="option.value" :value="option.value">
<div class="flex items-center gap-2">
<span class="h-3 w-3 rounded-full border border-border shadow-xs" :style="{ background: option.previewColor }" />
{{ option.label }}
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
<div class="space-y-2 min-w-0">
<div class="flex h-9 items-end gap-1">
<Label class="min-w-0 whitespace-normal leading-tight">{{ t("settings.uiFontFamily") }}</Label>
<HelpTooltip :label="t('settings.uiFontFamily')" trigger-class="[&_svg]:h-3 [&_svg]:w-3" content-class="max-w-64">
<p>{{ t("settings.uiFontFamilyDescription") }}</p>
</HelpTooltip>
</div>
<SearchableSelect
:model-value="editUiFontFamily"
:options="uiFontOptions"
@@ -2406,8 +2464,8 @@ onUnmounted(cleanupPreviewEditor);
allow-custom
:display-name="displayUiFontFamily"
:normalize-custom="normalizeCustomFontFamilyInput"
:trigger-class="fontSearchTriggerClass"
:trigger-icon-class="fontSearchTriggerIconClass"
:trigger-class="appearanceFontSearchTriggerClass"
:trigger-icon-class="appearanceFontSearchTriggerIconClass"
content-class="w-[var(--reka-popover-trigger-width)] min-w-[260px]"
@update:model-value="onUiFontFamilyChange"
@update:open="(open: boolean) => open && loadSystemFontOptions()"
@@ -2426,26 +2484,46 @@ onUnmounted(cleanupPreviewEditor);
</span>
</template>
</SearchableSelect>
<p class="text-xs text-muted-foreground">{{ t("settings.uiFontFamilyDescription") }}</p>
</div>
<div class="space-y-2 min-w-0">
<div class="flex h-9 items-end gap-1">
<Label class="min-w-0 whitespace-normal leading-tight">{{ t("settings.uiScale") }}</Label>
<HelpTooltip :label="t('settings.uiScale')" trigger-class="[&_svg]:h-3 [&_svg]:w-3" content-class="max-w-64">
<p>{{ t("settings.uiScaleDescription") }}</p>
</HelpTooltip>
</div>
<Select
:model-value="String(editUiScale)"
@update:model-value="
(value: any) => {
const next = Number(value);
if (Number.isFinite(next)) editUiScale = next;
}
"
>
<SelectTrigger class="h-8 w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="scale in uiScaleOptions" :key="scale" :value="String(scale)" class="pl-2.5"> {{ Math.round(scale * 100) }}% </SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div class="space-y-2">
<Label>{{ t("settings.theme") }}</Label>
<div class="flex gap-2">
<div class="flex flex-wrap gap-2">
<Button
v-for="option in [
{ value: 'light', label: t('toolbar.themeLight'), icon: Sun },
{ value: 'dark', label: t('toolbar.themeDark'), icon: Moon },
{ value: 'system', label: t('toolbar.themeSystem'), icon: SunMoon },
]"
v-for="option in appThemeModeOptions"
:key="option.value"
type="button"
variant="outline"
size="sm"
class="h-auto gap-1.5 px-3 py-1.5"
:class="themeMode === option.value ? 'border-blue-300 ring-2 ring-blue-300/50' : ''"
@click="setThemeMode(option.value as 'light' | 'dark' | 'system')"
class="h-8 gap-1.5 rounded-[6px] px-3"
:class="themeMode === option.value ? 'border-primary/40 bg-primary/10 text-primary ring-1 ring-primary/30' : 'text-foreground'"
@click="setThemeMode(option.value)"
>
<component :is="option.icon" class="h-3.5 w-3.5" />
{{ option.label }}
@@ -2455,29 +2533,6 @@ onUnmounted(cleanupPreviewEditor);
<Separator />
<div class="space-y-2">
<Label>{{ t("settings.uiScale") }}</Label>
<Select
:model-value="String(editUiScale)"
@update:model-value="
(value: any) => {
const next = Number(value);
if (Number.isFinite(next)) editUiScale = next;
}
"
>
<SelectTrigger class="min-w-36">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="scale in uiScaleOptions" :key="scale" :value="String(scale)" class="pl-2.5"> {{ Math.round(scale * 100) }}% </SelectItem>
</SelectContent>
</Select>
<p class="text-xs text-muted-foreground">{{ t("settings.uiScaleDescription") }}</p>
</div>
<Separator />
<div class="space-y-2">
<Label>{{ t("settings.appLayout") }}</Label>
<div class="grid grid-cols-2 gap-2">
@@ -109,7 +109,7 @@ let latestViewport: { scrollTop: number; scrollLeft: number } | undefined = prop
let latestSelection: { anchor: number; head: number } | undefined = props.initialSelection;
const connectionStore = useConnectionStore();
const settingsStore = useSettingsStore();
const { isDark } = useTheme();
const { isDark, themePalette } = useTheme();
const { t } = useI18n();
const { toast } = useToast();
@@ -2295,7 +2295,7 @@ onMounted(async () => {
const dialect = createDbxCodeMirrorSqlDialect(langSql, props.dialect);
const initialSettings = settingsStore.editorSettings;
const theme = await loadEditorTheme(initialSettings.theme, editorThemeAppearance(), getCurrentCustomThemeColors());
const theme = await loadEditorTheme(initialSettings.theme, editorThemeAppearance(), getCurrentCustomThemeColors(), themePalette.value);
if (initialSettings.vimModeEnabled) {
await ensureCodeMirrorVim();
}
@@ -2643,7 +2643,7 @@ onMounted(async () => {
if (!view.value || !codeMirrorTheme) return;
const settings = settingsStore.editorSettings;
const themeColors = settings.theme === "custom" ? getCurrentCustomThemeColors() : settings.customThemeColors;
const themeExt = await loadEditorTheme(settings.theme, editorThemeAppearance(), themeColors);
const themeExt = await loadEditorTheme(settings.theme, editorThemeAppearance(), themeColors, themePalette.value);
view.value.dispatch({
effects: [codeMirrorTheme.reconfigure(themeExt)],
});
@@ -2724,7 +2724,7 @@ function getCurrentCustomThemeColors() {
// Reactively apply editor settings changes
watch(
[queryEditorAppearanceSettings, () => isDark.value],
[queryEditorAppearanceSettings, () => isDark.value, () => themePalette.value],
async ([ss]) => {
if (!view.value || !codeMirrorTheme || !fontThemeComp || !wordWrapComp || !vimModeComp || !runKeymapComp || !editorViewModule) {
return;
@@ -2734,7 +2734,7 @@ watch(
}
syncEditorFontCssVars(liveFontSize.value, ss.fontFamily);
const themeColors = getCurrentCustomThemeColors();
const [themeExt] = await Promise.all([loadEditorTheme(ss.theme, editorThemeAppearance(), themeColors), ss.vimModeEnabled ? ensureCodeMirrorVim() : Promise.resolve(false)]);
const [themeExt] = await Promise.all([loadEditorTheme(ss.theme, editorThemeAppearance(), themeColors, themePalette.value), ss.vimModeEnabled ? ensureCodeMirrorVim() : Promise.resolve(false)]);
if (!view.value || !codeMirrorTheme || !wordWrapComp || !vimModeComp || !runKeymapComp || !editorViewModule) {
return;
}
@@ -169,7 +169,7 @@ const connectionStore = useConnectionStore();
const queryStore = useQueryStore();
const settingsStore = useSettingsStore();
const tableFontSize = computed(() => settingsStore.editorSettings.tableFontSize);
const { isDark } = useTheme();
const { isDark, themePalette } = useTheme();
const { toast } = useToast();
const { highlight } = useSqlHighlighter();
const binaryCellDownloadMenuItems = computed(() =>
@@ -4655,6 +4655,7 @@ let dialogJsonPreviewEditor: UseCellDetailEditorReturn | null = null;
const editorThemeAccessor = () => settingsStore.editorSettings.theme;
const editorAppAppearance = () => (isDark.value ? "dark" : "light") as import("@/lib/app/appTheme").AppThemeAppearance;
const editorAppPalette = () => themePalette.value;
const editorFontSize = () => settingsStore.editorSettings.fontSize;
const editorFontFamily = () => settingsStore.editorSettings.fontFamily;
const SIDE_DETAIL_EDITOR_MIN_HEIGHT = 160;
@@ -4681,6 +4682,7 @@ watch(detailsEditorContainer, async (el) => {
onEscape: () => cancelDetailEdit(),
editorTheme: editorThemeAccessor,
appAppearance: editorAppAppearance,
appPalette: editorAppPalette,
fontSize: editorFontSize,
fontFamily: editorFontFamily,
});
@@ -4701,6 +4703,7 @@ watch(valueEditorContainer, async (el) => {
onBlur: () => commitValueEditorEdit(),
editorTheme: editorThemeAccessor,
appAppearance: editorAppAppearance,
appPalette: editorAppPalette,
fontSize: editorFontSize,
fontFamily: editorFontFamily,
});
@@ -4718,6 +4721,7 @@ watch(sideJsonPreviewContainer, async (el) => {
readOnly: true,
editorTheme: editorThemeAccessor,
appAppearance: editorAppAppearance,
appPalette: editorAppPalette,
fontSize: editorFontSize,
fontFamily: editorFontFamily,
});
@@ -4735,6 +4739,7 @@ watch(dialogJsonPreviewContainer, async (el) => {
readOnly: true,
editorTheme: editorThemeAccessor,
appAppearance: editorAppAppearance,
appPalette: editorAppPalette,
fontSize: editorFontSize,
fontFamily: editorFontFamily,
});
@@ -5180,7 +5185,7 @@ const canvasSurfaceWidth = computed(() => {
if (vw <= 0) return total;
return Math.min(vw, total);
});
const canvasRenderStyleKey = computed(() => `${settingsStore.editorSettings.theme}:${settingsStore.editorSettings.uiScale}:${canvasBackingPixelRatio.value}:${isDark.value}:${settingsStore.editorSettings.fontFamily}:${tableFontSize.value}`);
const canvasRenderStyleKey = computed(() => `${settingsStore.editorSettings.theme}:${settingsStore.editorSettings.uiScale}:${canvasBackingPixelRatio.value}:${isDark.value}:${themePalette.value}:${settingsStore.editorSettings.fontFamily}:${tableFontSize.value}`);
const CANVAS_MOUSE_WHEEL_SCROLL_MULTIPLIER = 1.5;
const CANVAS_TRACKPAD_DELTA_THRESHOLD = 40;
let canvasResizeObserver: ResizeObserver | null = null;
@@ -148,9 +148,9 @@ defineExpose({ focusSearch });
</script>
<template>
<div class="h-full shrink-0 relative select-none" :class="classicLayout ? '' : 'rounded-md border border-border/80 bg-background'" :style="{ width: sidebarWidth + 'px' }">
<div class="app-sidebar-panel h-full shrink-0 relative select-none" :class="classicLayout ? '' : 'rounded-md border border-border/80 bg-background'" :style="{ width: sidebarWidth + 'px' }">
<div class="h-full flex flex-col overflow-hidden">
<div class="flex items-center gap-px px-3 text-xs font-medium text-muted-foreground border-b bg-muted/20" :class="classicLayout ? 'h-9' : 'h-10'">
<div class="app-sidebar-toolbar flex items-center gap-px px-3 text-xs font-medium text-muted-foreground border-b bg-muted/20" :class="classicLayout ? 'h-9' : 'h-10'">
<span class="flex self-stretch items-center truncate" data-tauri-drag-region>{{ t("sidebar.connections") }}</span>
<span class="flex-1 self-stretch" data-tauri-drag-region />
<template v-if="showConnectionMultiSelectToolbar">
@@ -524,7 +524,7 @@ function onOverflowItemKeydown(event: KeyboardEvent, tabId: string, kind: "regul
</script>
<template>
<div v-if="queryStore.tabs.length > 0 || driverStoreOpen || settingsPageOpen" class="relative flex w-full min-w-0 shrink-0 overflow-hidden border-b" :class="[isClassicLayout ? 'bg-muted' : 'bg-background', hasFixedTabs ? 'flex-col' : '']">
<div v-if="queryStore.tabs.length > 0 || driverStoreOpen || settingsPageOpen" class="app-tab-bar relative flex w-full min-w-0 shrink-0 overflow-hidden border-b" :class="[isClassicLayout ? 'bg-muted' : 'bg-background', hasFixedTabs ? 'flex-col' : '']">
<div class="flex w-full min-w-0 shrink-0 overflow-hidden" :class="isClassicLayout ? 'h-9 items-stretch' : 'h-10 items-center px-2'">
<div class="app-tab-strip relative h-full min-w-0 flex-1 overflow-hidden">
<div v-if="showRegularTabScrollbar" class="app-tab-scrollbar" :class="{ 'app-tab-scrollbar--dragging': isScrollbarDragging }" @pointerdown="startScrollbarDrag">
@@ -10,7 +10,7 @@ import ExportProgressPopover from "@/components/export/ExportProgressPopover.vue
import { shouldReserveMacTrafficLightInset, useWindowControls } from "@/composables/useWindowControls";
import { useToast } from "@/composables/useToast";
import { useSettingsStore } from "@/stores/settingsStore";
import type { AppThemeMode } from "@/lib/app/appTheme";
import { isSystemAppThemeMode, type AppThemeMode } from "@/lib/app/appTheme";
const GithubIcon = {
render() {
@@ -61,14 +61,15 @@ const toolbarItems = computed(() => settingsStore.editorSettings.toolbarItems);
const { isMac, isDesktop, showControls, isMaximized, isFullscreen, minimize, toggleMaximize, close } = useWindowControls();
const themeTriggerIcon = computed(() => {
if (props.themeMode === "system") return SunMoon;
if (isSystemAppThemeMode(props.themeMode)) return SunMoon;
return props.isDark ? Moon : Sun;
});
const themeCycle: AppThemeMode[] = ["light", "dark", "system"];
function nextThemeMode(mode: AppThemeMode): AppThemeMode {
if (mode === "light") return "dark";
if (mode === "dark") return "system";
return "light";
const index = themeCycle.indexOf(mode);
return themeCycle[(index + 1) % themeCycle.length] ?? themeCycle[0];
}
function themeModeLabel(mode: AppThemeMode): string {
@@ -362,7 +363,7 @@ const toolbarDropdownTriggerClass = `inline-flex h-8 items-center gap-1 rounded-
</script>
<template>
<div ref="toolbarEl" class="h-10 flex items-center gap-1 px-2 border-b bg-muted/30 shrink-0 overflow-hidden" :class="{ 'pl-17.5': shouldReserveMacTrafficLightInset(isMac, isFullscreen, isDesktop) }" data-tauri-drag-region @dblclick="onToolbarDblClick">
<div ref="toolbarEl" class="app-toolbar h-10 flex items-center gap-1 px-2 border-b bg-muted/30 shrink-0 overflow-hidden" :class="{ 'pl-17.5': shouldReserveMacTrafficLightInset(isMac, isFullscreen, isDesktop) }" data-tauri-drag-region @dblclick="onToolbarDblClick">
<Button variant="ghost" size="sm" :class="toolbarTextButtonClass" @click="emit('new-connection')">
<DatabaseZap class="h-3.5 w-3.5" />
<span :class="toolbarTextLabelClass">{{ t("toolbar.newConnection") }}</span>
@@ -145,7 +145,7 @@ function connectionById(connectionId: string): ConnectionConfig | undefined {
</script>
<template>
<div class="h-9 shrink-0 border-b bg-background/80 px-3 flex items-center gap-1 text-xs text-muted-foreground relative z-10" :style="toolbarStyle">
<div class="app-editor-toolbar h-9 shrink-0 border-b bg-background/80 px-3 flex items-center gap-1 text-xs text-muted-foreground relative z-10" :style="toolbarStyle">
<div class="flex items-center gap-0.5">
<Tooltip>
<TooltipTrigger as-child>
@@ -41,7 +41,7 @@ const { toast } = useToast();
const { t } = useI18n();
const settingsStore = useSettingsStore();
const connectionStore = useConnectionStore();
const { isDark } = useTheme();
const { isDark, themePalette } = useTheme();
const activeTab = ref<AdminTab>("configs");
const connectionInfo = ref<NacosConnectionInfo | null>(null);
const connectionError = ref("");
@@ -191,7 +191,7 @@ async function mountConfigEditor() {
configLanguageExtension(configType.value),
]);
const editorSettings = settingsStore.editorSettings;
const theme = await loadEditorTheme(editorSettings.theme, editorThemeAppearance(), currentCustomThemeColors());
const theme = await loadEditorTheme(editorSettings.theme, editorThemeAppearance(), currentCustomThemeColors(), themePalette.value);
const view = new EditorView({
parent: configEditorHost.value,
state: EditorState.create({
@@ -819,11 +819,11 @@ watch(historyCompareOpen, (value) => {
});
watch(
[() => settingsStore.editorSettings, () => isDark.value],
[() => settingsStore.editorSettings, () => isDark.value, () => themePalette.value],
async ([settings]) => {
const view = configEditorView.value;
if (!view) return;
const [{ EditorView }, theme] = await Promise.all([import("@codemirror/view"), loadEditorTheme(settings.theme, editorThemeAppearance(), currentCustomThemeColors())]);
const [{ EditorView }, theme] = await Promise.all([import("@codemirror/view"), loadEditorTheme(settings.theme, editorThemeAppearance(), currentCustomThemeColors(), themePalette.value)]);
if (configEditorView.value !== view) return;
view.dispatch({
effects: [configEditorTheme.reconfigure(theme), configEditorFontTheme.reconfigure(editorFontTheme(EditorView, settings.fontSize, settings.fontFamily, { fixedHeight: true, scrollable: true }))],
@@ -36,7 +36,7 @@ const emit = defineEmits<{
const { t } = useI18n();
const { toast } = useToast();
const { isDark } = useTheme();
const { isDark, themePalette } = useTheme();
const settingsStore = useSettingsStore();
const ddlContent = ref("");
@@ -84,7 +84,7 @@ async function initDdlEditor(content: string) {
const appAppearance = isDark.value ? "dark" : "light";
const fontSize = settingsStore.editorSettings.fontSize;
const fontFamily = settingsStore.editorSettings.fontFamily;
const themeExt = await loadEditorTheme(editorTheme, appAppearance);
const themeExt = await loadEditorTheme(editorTheme, appAppearance, undefined, themePalette.value);
const fontExt = editorFontTheme(EditorView, fontSize, fontFamily, { fixedHeight: true, scrollable: true });
const dialect = createDbxCodeMirrorSqlDialect(langSql, props.dialect);
const state = EditorState.create({
@@ -991,7 +991,7 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes });
<template>
<div ref="rootRef" class="h-full min-h-0 flex flex-col text-sm select-none" @pointerenter="pointerInsideTree = true" @pointerleave="pointerInsideTree = false">
<div class="sticky top-0 z-10 bg-background px-2 py-1">
<div class="connection-tree-search sticky top-0 z-10 bg-background px-2 py-1">
<div class="relative flex items-center gap-1">
<div class="relative flex-1">
<Search class="absolute left-2 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground" />