fix(ui): fix appearance setting page display with text truncation tooltips

* chore: test main release token push

* fix(desktop): unify toolbar dropdown tooltips

* chore: test main release token push

* fix(desktop): unify toolbar dropdown tooltips

* fix(ui) Appearance setting page displayed incorrectly.

* fix(ui) Appearance setting page displayed incorrectly.

* fix(ui) Appearance setting page displayed incorrectly.

* Remove unused import

---------

Co-authored-by: Echocde <udixt@outlook.com>
This commit is contained in:
echocde 2026-06-15 11:08:27 +08:00 committed by GitHub
parent fb4e65d325
commit 7dc27f8f6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 153 additions and 24 deletions

View File

@ -2,7 +2,6 @@
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import PasswordInput from "@/components/ui/PasswordInput.vue";
import { Lock, Loader2, ShieldCheck } from "@lucide/vue";
import AppLogo from "@/components/icons/AppLogo.vue";

View File

@ -3,7 +3,6 @@ import { computed, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { Lock } from "@lucide/vue";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import PasswordInput from "@/components/ui/PasswordInput.vue";
import { Label } from "@/components/ui/label";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref, watch, shallowRef, computed, onMounted } from "vue";
import { ref, watch, shallowRef, computed, onMounted, onUnmounted, nextTick } from "vue";
import type { Ref } from "vue";
import type { EditorView as EditorViewType } from "@codemirror/view";
import { useI18n } from "vue-i18n";
import { AlertTriangle, CheckCircle2, CircleHelp, Cloud, Copy, Download, ExternalLink, Loader2, Moon, PackageSearch, Pencil, RefreshCw, RotateCcw, Settings, Sun, SunMoon, Terminal, Trash2, Upload, X } from "@lucide/vue";
@ -28,6 +29,7 @@ import {
type AiAuthMethod,
type EditorTheme,
type DesktopIconTheme,
type InterfaceLayout,
type DisconnectTabHandlingMode,
type CustomThemeColors,
type CustomTheme,
@ -146,6 +148,85 @@ const snippetDialogOpen = ref(false);
const snippetEditingId = ref<string | null>(null);
const snippetForm = ref({ label: "", prefix: "", body: "" });
const snippetFormPrefixError = ref("");
const iconThemeDescTruncated = { default: ref<boolean>(false), black: ref<boolean>(false) };
const iconThemeDescRef = {
default: ref<HTMLElement | null>(null),
black: ref<HTMLElement | null>(null),
};
const layoutDescTruncated = { separated: ref<boolean>(false), classic: ref<boolean>(false) };
const layoutDescRefs = {
separated: ref<HTMLElement | null>(null),
classic: ref<HTMLElement | null>(null),
};
let layoutDescObservers: Record<InterfaceLayout, ResizeObserver | undefined> = {
separated: undefined,
classic: undefined,
};
let iconThemeDescObservers: Record<DesktopIconTheme, ResizeObserver | undefined> = {
default: undefined,
black: undefined,
};
function observeElementTruncation(el: Ref<HTMLElement | null>, truncated: Ref<boolean>) {
if (!el.value) return;
const observer = new ResizeObserver(() => {
truncated.value = el.value!.scrollWidth > el.value!.clientWidth;
});
observer.observe(el.value);
return observer;
}
function initTruncationObservers() {
layoutDescObservers.separated = observeElementTruncation(layoutDescRefs.separated, layoutDescTruncated.separated);
layoutDescObservers.classic = observeElementTruncation(layoutDescRefs.classic, layoutDescTruncated.classic);
iconThemeDescObservers.default = observeElementTruncation(iconThemeDescRef.default, iconThemeDescTruncated.default);
iconThemeDescObservers.black = observeElementTruncation(iconThemeDescRef.black, iconThemeDescTruncated.black);
}
function cleanupTruncationObservers() {
layoutDescObservers.separated?.disconnect();
layoutDescObservers.classic?.disconnect();
iconThemeDescObservers.default?.disconnect();
iconThemeDescObservers.black?.disconnect();
}
function setLayoutDescRef(layout: InterfaceLayout, el: unknown) {
layoutDescRefs[layout].value = el instanceof HTMLElement ? el : null;
}
function setIconThemeDescRef(theme: DesktopIconTheme, el: unknown) {
iconThemeDescRef[theme].value = el instanceof HTMLElement ? el : null;
}
function checkLayoutDescTruncation() {
checkTruncationForRefs([
{ el: layoutDescRefs.separated, truncated: layoutDescTruncated.separated },
{ el: layoutDescRefs.classic, truncated: layoutDescTruncated.classic },
]);
}
function checkIconThemeDescTruncation() {
checkTruncationForRefs([
{ el: iconThemeDescRef.default, truncated: iconThemeDescTruncated.default },
{ el: iconThemeDescRef.black, truncated: iconThemeDescTruncated.black },
]);
}
function checkTruncationForRefs(items: Array<{ el: Ref<HTMLElement | null>; truncated: Ref<boolean> }>) {
nextTick(() => {
for (const item of items) {
if (item.el.value) {
item.truncated.value = checkElementTruncation(item.el.value);
}
}
});
}
function checkElementTruncation(el: HTMLElement | null) {
return el ? el.scrollWidth > el.clientWidth : false;
}
function openAddSnippetDialog() {
snippetEditingId.value = null;
@ -568,7 +649,7 @@ function clearShortcut(actionId: ShortcutActionId) {
editShortcuts.value = { ...editShortcuts.value, [actionId]: "" };
}
function setAppLayout(value: "separated" | "classic") {
function setAppLayout(value: InterfaceLayout) {
editAppLayout.value = value;
}
@ -919,10 +1000,21 @@ watch(webdavRememberPassword, (val) => {
watch(activeSettingsTab, (tab) => {
if (tab === "mcp" && !mcpStatus.value && !mcpStatusLoading.value) void refreshMcpStatus();
if (tab === "appearance") {
checkLayoutDescTruncation();
checkIconThemeDescTruncation();
}
});
onMounted(() => {
void refreshWebDavPasswordStatus();
checkLayoutDescTruncation();
checkIconThemeDescTruncation();
initTruncationObservers();
});
onUnmounted(() => {
cleanupTruncationObservers();
});
async function changePassword() {
@ -1552,39 +1644,76 @@ watch(
<Label>{{ t("settings.appLayout") }}</Label>
<div class="grid grid-cols-2 gap-2">
<Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editAppLayout === 'separated' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setAppLayout('separated')">
<div class="text-left">
<div class="text-sm font-medium">{{ t("settings.appLayoutSeparated") }}</div>
<div class="text-xs text-muted-foreground">{{ t("settings.appLayoutSeparatedDescription") }}</div>
</div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger as-child>
<div class="w-full min-w-0 text-left">
<div class="text-sm font-medium">{{ t("settings.appLayoutSeparated") }}</div>
<div :ref="(el) => setLayoutDescRef('separated', el)" class="text-xs text-muted-foreground truncate">{{ t("settings.appLayoutSeparatedDescription") }}</div>
</div>
</TooltipTrigger>
<TooltipContent v-if="layoutDescTruncated.separated.value" class="max-w-[320px] text-xs leading-relaxed">
{{ t("settings.appLayoutSeparatedDescription") }}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Button>
<Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editAppLayout === 'classic' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setAppLayout('classic')">
<div class="text-left">
<div class="text-sm font-medium">{{ t("settings.appLayoutClassic") }}</div>
<div class="text-xs text-muted-foreground">{{ t("settings.appLayoutClassicDescription") }}</div>
</div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger as-child>
<div class="w-full min-w-0 text-left">
<div class="text-sm font-medium">{{ t("settings.appLayoutClassic") }}</div>
<div :ref="(el) => setLayoutDescRef('classic', el)" class="text-xs text-muted-foreground truncate">{{ t("settings.appLayoutClassicDescription") }}</div>
</div>
</TooltipTrigger>
<TooltipContent v-if="layoutDescTruncated.classic.value" class="max-w-[320px] text-xs leading-relaxed">
{{ t("settings.appLayoutClassicDescription") }}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Button>
</div>
</div>
<div v-if="!isWeb" class="space-y-2">
<!-- <div v-if="!isWeb" class="space-y-2"> -->
<div class="space-y-2">
<Label>{{ t("settings.iconTheme") }}</Label>
<div class="grid grid-cols-2 gap-2">
<Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editIconTheme === 'default' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setIconTheme('default')">
<div class="flex items-center gap-3 text-left">
<div class="flex items-center gap-3 text-left w-full min-w-0">
<img src="/logo.png" alt="DBX" class="h-8 w-8 rounded-md" />
<div>
<div class="text-sm font-medium">{{ t("settings.iconThemeDefault") }}</div>
<div class="text-xs text-muted-foreground">{{ t("settings.iconThemeDefaultDescription") }}</div>
</div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger as-child>
<div class="w-full min-w-0 text-left">
<div class="text-sm font-medium">{{ t("settings.iconThemeDefault") }}</div>
<div :ref="(el) => setIconThemeDescRef('default', el)" class="text-xs text-muted-foreground truncate">{{ t("settings.iconThemeDefaultDescription") }}</div>
</div>
</TooltipTrigger>
<TooltipContent v-if="iconThemeDescTruncated.default.value" class="max-w-[320px] text-xs leading-relaxed">
{{ t("settings.iconThemeDefaultDescription") }}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</Button>
<Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editIconTheme === 'black' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setIconTheme('black')">
<div class="flex items-center gap-3 text-left">
<img src="/logo-black.png" alt="DBX" class="h-8 w-8 dark:invert" />
<div>
<div class="text-sm font-medium">{{ t("settings.iconThemeBlack") }}</div>
<div class="text-xs text-muted-foreground">{{ t("settings.iconThemeBlackDescription") }}</div>
</div>
<div class="flex items-center gap-3 text-left w-full min-w-0">
<img src="/logo-black.png" alt="DBX" class="h-8 w-8 dark:invert shrink-0" />
<TooltipProvider>
<Tooltip>
<TooltipTrigger as-child>
<div class="w-full min-w-0 text-left">
<div class="text-sm font-medium">{{ t("settings.iconThemeBlack") }}</div>
<div :ref="(el) => setIconThemeDescRef('black', el)" class="text-xs text-muted-foreground truncate">{{ t("settings.iconThemeBlackDescription") }}</div>
</div>
</TooltipTrigger>
<TooltipContent v-if="iconThemeDescTruncated.black.value" class="max-w-[320px] text-xs leading-relaxed">
{{ t("settings.iconThemeBlackDescription") }}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</Button>
</div>

View File

@ -47,6 +47,8 @@ export interface DesktopSettings {
export type DesktopIconTheme = "default" | "black";
export type InterfaceLayout = "separated" | "classic";
export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
show_tray_icon: true,
icon_theme: "default",