Fix flickering tooltip when hovering modified in FileExplorer. Use the new, improved tooltip also in gallery to show full name, modified and size of an item.

This commit is contained in:
2026-01-22 00:49:50 +00:00
parent b6c21152e7
commit e55e11b399
3 changed files with 113 additions and 56 deletions
+86
View File
@@ -0,0 +1,86 @@
<template>
<Teleport to="body">
<div v-if="visible" class="cursor-tooltip" :style="tooltipStyle">
<slot></slot>
</div>
</Teleport>
</template>
<script lang="ts">
// Global activation state - shared across all instances
let globalActive = false
let globalDeactivateTimer: ReturnType<typeof setTimeout> | null = null
</script>
<script setup lang="ts">
import { computed, ref } from 'vue'
const props = defineProps<{
text: string
delay?: number
}>()
const visible = ref(false)
const mouseX = ref(0)
const mouseY = ref(0)
let hoverTimer: ReturnType<typeof setTimeout> | null = null
const tooltipStyle = computed(() => ({
left: `${mouseX.value + 12}px`,
top: `${mouseY.value + 12}px`,
}))
const startHover = (e: MouseEvent) => {
mouseX.value = e.clientX
mouseY.value = e.clientY
// Clear any pending deactivation
if (globalDeactivateTimer) {
clearTimeout(globalDeactivateTimer)
globalDeactivateTimer = null
}
const delay = globalActive ? 0 : (props.delay ?? 800)
hoverTimer = setTimeout(() => {
visible.value = true
globalActive = true
}, delay)
}
const updatePosition = (e: MouseEvent) => {
mouseX.value = e.clientX
mouseY.value = e.clientY
}
const endHover = () => {
if (hoverTimer) {
clearTimeout(hoverTimer)
hoverTimer = null
}
visible.value = false
// Deactivate global state after a short delay if no new tooltip started
if (globalDeactivateTimer) clearTimeout(globalDeactivateTimer)
globalDeactivateTimer = setTimeout(() => {
globalActive = false
}, 500)
}
defineExpose({
startHover,
updatePosition,
endHover,
})
</script>
<style scoped>
.cursor-tooltip {
position: fixed;
z-index: 10000;
padding: .5rem 1rem;
border-radius: 3rem 0 3rem 0;
box-shadow: 0 0 1rem var(--accent-color);
background-color: var(--accent-color);
color: var(--primary-color);
white-space: nowrap;
pointer-events: none;
font-size: 1rem;
}
</style>
+7 -54
View File
@@ -2,15 +2,11 @@
<td class="modified right"> <td class="modified right">
<time <time
:datetime=datetime :datetime=datetime
@mouseenter="startHover" @mouseenter="tooltip?.startHover"
@mousemove="updatePosition" @mousemove="tooltip?.updatePosition"
@mouseleave="endHover" @mouseleave="tooltip?.endHover"
>{{ modified }}</time> >{{ modified }}</time>
<Teleport to="body"> <CursorTooltip ref="tooltip" :text="tooltipText">{{ tooltipText }}</CursorTooltip>
<div v-if="showTooltip" class="cursor-tooltip" :style="tooltipStyle">
{{ tooltipText }}
</div>
</Teleport>
</td> </td>
</template> </template>
@@ -18,12 +14,15 @@
import { Doc } from '@/repositories/Document' import { Doc } from '@/repositories/Document'
import { formatUnixDate } from '@/utils' import { formatUnixDate } from '@/utils'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import CursorTooltip from './CursorTooltip.vue'
const props = defineProps<{ const props = defineProps<{
doc: Doc doc: Doc
now: number now: number
}>() }>()
const tooltip = ref<InstanceType<typeof CursorTooltip> | null>(null)
// Reference props.now to trigger reactivity when time updates // Reference props.now to trigger reactivity when time updates
const modified = computed(() => { const modified = computed(() => {
props.now // trigger reactivity props.now // trigger reactivity
@@ -37,50 +36,4 @@ const datetime = computed(() =>
const tooltipText = computed(() => const tooltipText = computed(() =>
datetime.value.replace('T', ' ').replace('Z', ' UTC') datetime.value.replace('T', ' ').replace('Z', ' UTC')
) )
const showTooltip = ref(false)
const mouseX = ref(0)
const mouseY = ref(0)
let hoverTimer: ReturnType<typeof setTimeout> | null = null
const tooltipStyle = computed(() => ({
left: `${mouseX.value + 12}px`,
top: `${mouseY.value + 12}px`,
}))
const startHover = (e: MouseEvent) => {
mouseX.value = e.clientX
mouseY.value = e.clientY
hoverTimer = setTimeout(() => {
showTooltip.value = true
}, 500)
}
const updatePosition = (e: MouseEvent) => {
mouseX.value = e.clientX
mouseY.value = e.clientY
}
const endHover = () => {
if (hoverTimer) {
clearTimeout(hoverTimer)
hoverTimer = null
}
showTooltip.value = false
}
</script> </script>
<style scoped>
.cursor-tooltip {
position: fixed;
z-index: 10000;
padding: .5rem 1rem;
border-radius: 3rem 0 3rem 0;
box-shadow: 0 0 1rem var(--accent-color);
background-color: var(--accent-color);
color: var(--primary-color);
white-space: pre;
pointer-events: none;
font-size: 1rem;
}
</style>
+20 -2
View File
@@ -4,6 +4,9 @@
@contextmenu.stop @contextmenu.stop
@focus.stop="store.cursor = doc.key" @focus.stop="store.cursor = doc.key"
@click=onclick @click=onclick
@mouseenter="tooltip?.startHover"
@mousemove="tooltip?.updatePosition"
@mouseleave="tooltip?.endHover"
> >
<figure> <figure>
<slot></slot> <slot></slot>
@@ -15,19 +18,24 @@
</template> </template>
<template v-else> <template v-else>
<SelectBox :doc=doc @click="store.cursor = doc.key"/> <SelectBox :doc=doc @click="store.cursor = doc.key"/>
<span :title="doc.name + '\n' + doc.modified + '\n' + doc.sizedisp">{{ doc.name }}</span> <span>{{ doc.name }}</span>
<div class=namespacer></div> <div class=namespacer></div>
</template> </template>
</figcaption> </figcaption>
</figure> </figure>
<CursorTooltip ref="tooltip" :text="tooltipText">
<div class="tooltip-name">{{ doc.name }}</div>
<div class="tooltip-details">{{ doc.modified }} {{ doc.sizedisp }}</div>
</CursorTooltip>
</a> </a>
</template> </template>
<script setup lang=ts> <script setup lang=ts>
import { ref } from 'vue' import { ref, computed } from 'vue'
import { useMainStore } from '@/stores/main' import { useMainStore } from '@/stores/main'
import { Doc } from '@/repositories/Document' import { Doc } from '@/repositories/Document'
import MediaPreview from '@/components/MediaPreview.vue' import MediaPreview from '@/components/MediaPreview.vue'
import CursorTooltip from './CursorTooltip.vue'
const store = useMainStore() const store = useMainStore()
type EditingProp = { type EditingProp = {
@@ -40,6 +48,9 @@ const props = defineProps<{
editing?: EditingProp, editing?: EditingProp,
}>() }>()
const m = ref<typeof MediaPreview | null>(null) const m = ref<typeof MediaPreview | null>(null)
const tooltip = ref<InstanceType<typeof CursorTooltip> | null>(null)
const tooltipText = computed(() => props.doc.key)
const onclick = (ev: Event) => { const onclick = (ev: Event) => {
if (m.value!.play()) ev.preventDefault() if (m.value!.play()) ev.preventDefault()
@@ -48,6 +59,13 @@ const onclick = (ev: Event) => {
</script> </script>
<style scoped> <style scoped>
.tooltip-name {
font-weight: 600;
text-align: center;
}
.tooltip-details {
text-align: center;
}
figure { figure {
max-height: 15em; max-height: 15em;
position: relative; position: relative;