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:
parent
fb4e65d325
commit
7dc27f8f6d
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue