feat(theme): add custom UI color palettes
This commit is contained in:
1 parent
c81466d0db
commit
8996da7d9a
15 files changed
+618
-37
No files matched your search
@@ -125,7 +125,7 @@ import ChangelogPanel from "@/components/settings/ChangelogPanel.vue";
|
||||
import McpConnectionScopePicker from "@/components/settings/McpConnectionScopePicker.vue";
|
||||
import ScheduledDatabaseBackupSettings from "@/components/backup/ScheduledDatabaseBackupSettings.vue";
|
||||
import SqlFormatterSettingsPanel from "./SqlFormatterSettingsPanel.vue";
|
||||
import { APP_THEME_PALETTES, type AppCornerStyle, type AppThemeAppearance, type AppThemeMode, type AppThemePalette } from "@/lib/app/appTheme";
|
||||
import { APP_CUSTOM_UI_COLOR_DEFS, APP_THEME_PALETTES, type AppCornerStyle, type AppCustomUiColors, type AppThemeAppearance, type AppThemeMode, type AppThemePalette } from "@/lib/app/appTheme";
|
||||
import { editorSettingsDraftChanged, editorSettingsDraftFromSettings, editorSettingsPatchFromDraft, normalizeQueryResultMaxRowsDraft, normalizeTableOpenPageSizeDraft, type EditorSettingsDraft } from "@/lib/settings/editorSettingsDraft";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useSavedSqlStore } from "@/stores/savedSqlStore";
|
||||
@@ -152,7 +152,11 @@ const connectionStore = useConnectionStore();
|
||||
const savedSqlStore = useSavedSqlStore();
|
||||
const promptTemplateStore = usePromptTemplateStore();
|
||||
const tunnelProfileStore = useTunnelProfileStore();
|
||||
const { isDark, themeMode, themePalette, cornerStyle, setThemeMode, setThemePalette, setCornerStyle } = useTheme();
|
||||
const { isDark, themeMode, themePalette, activeCustomUiColors, cornerStyle, setThemeMode, setThemePalette, setCustomUiColors, resetCustomUiColors, setCornerStyle } = useTheme();
|
||||
|
||||
function updateCustomUiColor(key: keyof AppCustomUiColors, value: string) {
|
||||
setCustomUiColors({ ...activeCustomUiColors.value, [key]: value });
|
||||
}
|
||||
|
||||
const appThemePaletteOptions = computed(
|
||||
(): Array<{ value: AppThemePalette; label: string; previewColor: string }> =>
|
||||
@@ -4157,29 +4161,36 @@ onUnmounted(() => {
|
||||
<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 class="flex items-center gap-2">
|
||||
<div class="min-w-0 flex-1">
|
||||
<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>
|
||||
<Button v-if="themePalette === 'custom'" type="button" variant="outline" size="sm" class="h-8 shrink-0" @click="resetCustomUiColors">
|
||||
{{ t("settings.customUiReset") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-appearance-field min-w-0">
|
||||
@@ -4208,6 +4219,20 @@ onUnmounted(() => {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<template v-if="themePalette === 'custom'">
|
||||
<div class="settings-appearance-field min-w-0">
|
||||
<div class="flex h-9 items-center gap-1">
|
||||
<Label class="truncate text-xs text-muted-foreground">{{ isDark ? t("settings.customUiEditingDark") : t("settings.customUiEditingLight") }}</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="def in APP_CUSTOM_UI_COLOR_DEFS" :key="def.key" class="settings-appearance-field min-w-0">
|
||||
<div class="flex h-9 items-center justify-between gap-2">
|
||||
<Label :for="`custom-ui-${def.key}`" class="truncate text-xs text-muted-foreground">{{ t(def.labelKey) }}</Label>
|
||||
<input :id="`custom-ui-${def.key}`" type="color" class="h-6 w-10 shrink-0 cursor-pointer rounded border border-border bg-transparent p-0.5" :value="activeCustomUiColors[def.key]" @input="updateCustomUiColor(def.key, ($event.target as HTMLInputElement).value)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
|
||||
@@ -100,7 +100,7 @@ import {
|
||||
type QueryEditorTableReferencePayload,
|
||||
} from "@/lib/editor/queryEditorTableDrop";
|
||||
import type { SqlHighlighter } from "@/lib/sql/sqlHighlighter";
|
||||
import { EDITOR_FONT_FAMILY_CSS_VAR, EDITOR_FONT_SIZE_CSS_VAR, loadEditorTheme, editorFontTheme, sqlCompletionTheme, sqlSemanticHighlightTheme } from "@/lib/editor/editorThemes";
|
||||
import { EDITOR_FONT_FAMILY_CSS_VAR, EDITOR_FONT_SIZE_CSS_VAR, editorDiagnosticColors, editorThemeAppearanceFor, loadEditorTheme, editorFontTheme, sqlCompletionTheme, sqlSemanticHighlightTheme } from "@/lib/editor/editorThemes";
|
||||
import { createStatementGutterMarkerDom, shouldShowStatementGutter } from "@/lib/editor/codemirrorStatementGutter";
|
||||
import { createQueryEditorSearchKeymap } from "@/lib/editor/queryEditorSearchKeymap";
|
||||
import { searchKeymapWithoutModD } from "@/lib/editor/codemirrorSearchKeymap";
|
||||
@@ -225,7 +225,7 @@ function sqlStatementParameterOptions() {
|
||||
const toggles = resolveSqlVariableSyntaxToggles(settingsStore.editorSettings.sqlVariableSyntaxOverrides, props.databaseType, settingsStore.editorSettings.sqlVariableSubstitutionEnabled);
|
||||
return { databaseType: props.databaseType, enabledSyntaxes: enabledSqlParameterSyntaxes(toggles) };
|
||||
}
|
||||
const { isDark, themePalette } = useTheme();
|
||||
const { isDark, themePalette, activeCustomUiColors } = useTheme();
|
||||
const { t } = useI18n();
|
||||
const { toast } = useToast();
|
||||
const snippetDatabaseType = computed(() => {
|
||||
@@ -595,7 +595,7 @@ const tableNavigationHoverClass = "query-editor--table-navigation-hover";
|
||||
const DBX_VIM_SAVE_EVENT = "dbx-vim-save";
|
||||
|
||||
function editorThemeAppearance() {
|
||||
return isDark.value ? "dark" : "light";
|
||||
return editorThemeAppearanceFor(isDark.value ? "dark" : "light", themePalette.value, themePalette.value === "custom" ? activeCustomUiColors.value : undefined);
|
||||
}
|
||||
|
||||
// Completion cache
|
||||
@@ -681,6 +681,16 @@ function syncEditorFontCssVars(fontSize = liveFontSize.value, fontFamily = setti
|
||||
editorRef.value.style.setProperty(EDITOR_FONT_FAMILY_CSS_VAR, fontFamily);
|
||||
}
|
||||
|
||||
// Diagnostics render on the editor surface, so their marker colors follow the
|
||||
// resolved editor appearance (which already adapts to custom backgrounds) via
|
||||
// editor-scoped variables instead of the app-level warning/destructive tokens.
|
||||
function syncEditorDiagnosticCssVars() {
|
||||
if (!editorRef.value) return;
|
||||
const colors = editorDiagnosticColors(editorThemeAppearance());
|
||||
editorRef.value.style.setProperty("--dbx-editor-diagnostic-error", colors.error);
|
||||
editorRef.value.style.setProperty("--dbx-editor-diagnostic-warning", colors.warning);
|
||||
}
|
||||
|
||||
let pendingFontReconfig: { size: number; family: string } | null = null;
|
||||
let fontReconfigScheduled = false;
|
||||
|
||||
@@ -4488,11 +4498,11 @@ onMounted(async () => {
|
||||
|
||||
const diagnosticTheme = EditorView.baseTheme({
|
||||
".cm-sql-error": {
|
||||
textDecoration: "underline wavy var(--destructive)",
|
||||
textDecoration: "underline wavy var(--dbx-editor-diagnostic-error, var(--destructive))",
|
||||
textUnderlineOffset: "3px",
|
||||
},
|
||||
".cm-sql-semantic-warning": {
|
||||
textDecoration: "underline wavy var(--warning)",
|
||||
textDecoration: "underline wavy var(--dbx-editor-diagnostic-warning, var(--warning))",
|
||||
textUnderlineOffset: "3px",
|
||||
},
|
||||
});
|
||||
@@ -5309,6 +5319,7 @@ onMounted(async () => {
|
||||
restoreEditorViewport();
|
||||
syncContextMenuState(view.value);
|
||||
syncEditorFontCssVars(liveFontSize.value, initialSettings.fontFamily);
|
||||
syncEditorDiagnosticCssVars();
|
||||
registerTableReferenceDropListener();
|
||||
|
||||
cachedTables = [];
|
||||
@@ -5476,7 +5487,7 @@ function getCurrentCustomThemeColors() {
|
||||
|
||||
// Reactively apply editor settings changes
|
||||
watch(
|
||||
[queryEditorAppearanceSettings, () => isDark.value, () => themePalette.value],
|
||||
[queryEditorAppearanceSettings, () => isDark.value, () => themePalette.value, editorThemeAppearance],
|
||||
async ([ss]) => {
|
||||
if (!view.value || !codeMirrorTheme || !fontThemeComp || !wordWrapComp || !vimModeComp || !closeBracketsComp || !runGutterComp || !runKeymapComp || !editorViewModule) {
|
||||
return;
|
||||
@@ -5485,6 +5496,7 @@ watch(
|
||||
liveFontSize.value = ss.fontSize;
|
||||
}
|
||||
syncEditorFontCssVars(liveFontSize.value, ss.fontFamily);
|
||||
syncEditorDiagnosticCssVars();
|
||||
const themeColors = getCurrentCustomThemeColors();
|
||||
const [themeExt] = await Promise.all([loadEditorTheme(ss.theme, editorThemeAppearance(), themeColors, themePalette.value), ss.vimModeEnabled ? ensureCodeMirrorVim() : Promise.resolve(false)]);
|
||||
if (!view.value || !codeMirrorTheme || !wordWrapComp || !vimModeComp || !closeBracketsComp || !runGutterComp || !runKeymapComp || !editorViewModule) {
|
||||
|
||||
Reference in new issue
Block a user