Feature:新增多套应用配色主题 (#2559)
Co-authored-by: staff <staff@qimaos-MacBook-Pro.local>
This commit is contained in:
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" />
|
||||
|
||||
Reference in new issue
Block a user