perf(desktop): use lightweight sidebar tree tooltips
This commit is contained in:
parent
58b8552316
commit
26ed2b4aff
|
|
@ -122,15 +122,15 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import LightTooltip from "@/components/ui/LightTooltip.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
const labelRef = ref<HTMLElement>();
|
||||
const rowRef = ref<HTMLElement>();
|
||||
const isTruncated = computed(() => {
|
||||
function isLabelTruncated(): boolean {
|
||||
const el = labelRef.value;
|
||||
return !!el && el.scrollWidth > el.clientWidth;
|
||||
});
|
||||
}
|
||||
const connectionStore = useConnectionStore();
|
||||
const queryStore = useQueryStore();
|
||||
const savedSqlStore = useSavedSqlStore();
|
||||
|
|
@ -263,7 +263,7 @@ function visibleLabel(node: TreeNode): string {
|
|||
}
|
||||
|
||||
function isTooltipDisabled(node: TreeNode): boolean {
|
||||
return !isTruncated.value && visibleLabel(node) === displayLabel(node);
|
||||
return !isLabelTruncated() && visibleLabel(node) === displayLabel(node);
|
||||
}
|
||||
|
||||
async function toggle() {
|
||||
|
|
@ -1955,14 +1955,12 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
|||
}
|
||||
items.push({ label: "", separator: true });
|
||||
if (availableGroups.value.length > 0 || currentGroupId.value) {
|
||||
const groupChildren: ContextMenuItem[] = [
|
||||
...availableGroups.value.map((group: { id: string; name: string }) => ({
|
||||
label: group.name,
|
||||
action: () => moveToGroup(group.id),
|
||||
icon: FolderOpen,
|
||||
disabled: group.id === currentGroupId.value,
|
||||
})),
|
||||
];
|
||||
const groupChildren: ContextMenuItem[] = availableGroups.value.map((group: { id: string; name: string }) => ({
|
||||
label: group.name,
|
||||
action: () => moveToGroup(group.id),
|
||||
icon: FolderOpen,
|
||||
disabled: group.id === currentGroupId.value,
|
||||
}));
|
||||
if (currentGroupId.value) {
|
||||
groupChildren.push({ label: "", separator: true });
|
||||
groupChildren.push({ label: t("connectionGroup.ungrouped"), action: () => moveToGroup(null) });
|
||||
|
|
@ -2273,12 +2271,15 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
|||
@keydown.escape.prevent="isRenamingGroup = false"
|
||||
@click.stop
|
||||
/>
|
||||
<Tooltip v-else :disabled="isTooltipDisabled(node)">
|
||||
<TooltipTrigger as-child>
|
||||
<span ref="labelRef" class="min-w-0 flex-1 truncate">{{ visibleLabel(node) }}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" :side-offset="8">{{ displayLabel(node) }}</TooltipContent>
|
||||
</Tooltip>
|
||||
<LightTooltip
|
||||
v-else
|
||||
:text="displayLabel(node)"
|
||||
:disabled="isTooltipDisabled(node)"
|
||||
side="right"
|
||||
:side-offset="8"
|
||||
>
|
||||
<span ref="labelRef" class="min-w-0 flex-1 truncate">{{ visibleLabel(node) }}</span>
|
||||
</LightTooltip>
|
||||
<span
|
||||
v-if="
|
||||
(node.type === 'group-tables' ||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, ref } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
text: string;
|
||||
disabled?: boolean;
|
||||
side?: "top" | "right" | "bottom" | "left";
|
||||
sideOffset?: number;
|
||||
delay?: number;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
side: "top",
|
||||
sideOffset: 8,
|
||||
delay: 300,
|
||||
},
|
||||
);
|
||||
|
||||
const triggerRef = ref<HTMLElement>();
|
||||
const show = ref(false);
|
||||
const x = ref(0);
|
||||
const y = ref(0);
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function triggerElement(): HTMLElement | undefined {
|
||||
const root = triggerRef.value;
|
||||
const child = root?.firstElementChild;
|
||||
return child instanceof HTMLElement ? child : root;
|
||||
}
|
||||
|
||||
const tooltipTransformClass = computed(() => {
|
||||
switch (props.side) {
|
||||
case "right":
|
||||
return "-translate-y-1/2";
|
||||
case "left":
|
||||
return "-translate-x-full -translate-y-1/2";
|
||||
case "bottom":
|
||||
return "-translate-x-1/2";
|
||||
case "top":
|
||||
default:
|
||||
return "-translate-x-1/2 -translate-y-full";
|
||||
}
|
||||
});
|
||||
|
||||
const arrowClass = computed(() => {
|
||||
switch (props.side) {
|
||||
case "right":
|
||||
return "absolute -left-1 top-1/2 -translate-y-1/2";
|
||||
case "left":
|
||||
return "absolute -right-1 top-1/2 -translate-y-1/2";
|
||||
case "bottom":
|
||||
return "absolute -top-1 left-1/2 -translate-x-1/2";
|
||||
case "top":
|
||||
default:
|
||||
return "absolute -bottom-1 left-1/2 -translate-x-1/2";
|
||||
}
|
||||
});
|
||||
|
||||
function clearTimer() {
|
||||
if (!timer) return;
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
|
||||
function updatePosition() {
|
||||
const el = triggerElement();
|
||||
if (!el) return;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const offset = props.sideOffset;
|
||||
switch (props.side) {
|
||||
case "right":
|
||||
x.value = Math.min(window.innerWidth - 8, rect.right + offset);
|
||||
y.value = Math.min(Math.max(8, rect.top + rect.height / 2), window.innerHeight - 8);
|
||||
break;
|
||||
case "left":
|
||||
x.value = Math.max(8, rect.left - offset);
|
||||
y.value = Math.min(Math.max(8, rect.top + rect.height / 2), window.innerHeight - 8);
|
||||
break;
|
||||
case "bottom":
|
||||
x.value = Math.min(Math.max(8, rect.left + rect.width / 2), window.innerWidth - 8);
|
||||
y.value = Math.min(window.innerHeight - 8, rect.bottom + offset);
|
||||
break;
|
||||
case "top":
|
||||
default:
|
||||
x.value = Math.min(Math.max(8, rect.left + rect.width / 2), window.innerWidth - 8);
|
||||
y.value = Math.max(8, rect.top - offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
clearTimer();
|
||||
show.value = false;
|
||||
window.removeEventListener("scroll", close, true);
|
||||
window.removeEventListener("resize", close);
|
||||
}
|
||||
|
||||
function open() {
|
||||
if (props.disabled || !props.text) return;
|
||||
updatePosition();
|
||||
show.value = true;
|
||||
window.addEventListener("scroll", close, true);
|
||||
window.addEventListener("resize", close);
|
||||
}
|
||||
|
||||
function scheduleOpen() {
|
||||
if (props.disabled || !props.text) return;
|
||||
clearTimer();
|
||||
timer = setTimeout(open, props.delay);
|
||||
}
|
||||
|
||||
onBeforeUnmount(close);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
ref="triggerRef"
|
||||
class="contents"
|
||||
@mouseenter="scheduleOpen"
|
||||
@mouseleave="close"
|
||||
@focusin="scheduleOpen"
|
||||
@focusout="close"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="show"
|
||||
class="pointer-events-none fixed z-50 inline-flex w-fit max-w-xs items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background"
|
||||
:class="tooltipTransformClass"
|
||||
:style="{ left: `${x}px`, top: `${y}px` }"
|
||||
role="tooltip"
|
||||
>
|
||||
{{ text }}
|
||||
<span :class="[arrowClass, 'size-2.5 rotate-45 rounded-[2px] bg-foreground']" aria-hidden="true" />
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
Loading…
Reference in New Issue