Improve message timeline tooltip positioning
This commit is contained in:
@@ -239,6 +239,9 @@ const MessageTimeline: Component<MessageTimelineProps> = (props) => {
|
|||||||
const store = () => messageStoreBus.getOrCreate(props.instanceId)
|
const store = () => messageStoreBus.getOrCreate(props.instanceId)
|
||||||
const [hoveredSegment, setHoveredSegment] = createSignal<TimelineSegment | null>(null)
|
const [hoveredSegment, setHoveredSegment] = createSignal<TimelineSegment | null>(null)
|
||||||
const [tooltipCoords, setTooltipCoords] = createSignal<{ top: number; left: number }>({ top: 0, left: 0 })
|
const [tooltipCoords, setTooltipCoords] = createSignal<{ top: number; left: number }>({ top: 0, left: 0 })
|
||||||
|
const [hoverAnchorRect, setHoverAnchorRect] = createSignal<{ top: number; left: number; width: number; height: number } | null>(null)
|
||||||
|
const [tooltipSize, setTooltipSize] = createSignal<{ width: number; height: number }>({ width: 360, height: 420 })
|
||||||
|
const [tooltipElement, setTooltipElement] = createSignal<HTMLDivElement | null>(null)
|
||||||
let hoverTimer: number | null = null
|
let hoverTimer: number | null = null
|
||||||
const showTools = () => props.showToolSegments ?? true
|
const showTools = () => props.showToolSegments ?? true
|
||||||
|
|
||||||
@@ -261,31 +264,40 @@ const MessageTimeline: Component<MessageTimelineProps> = (props) => {
|
|||||||
if (typeof window === "undefined") return
|
if (typeof window === "undefined") return
|
||||||
clearHoverTimer()
|
clearHoverTimer()
|
||||||
const target = event.currentTarget as HTMLButtonElement
|
const target = event.currentTarget as HTMLButtonElement
|
||||||
hoverTimer = window.setTimeout(() => {
|
hoverTimer = window.setTimeout(() => {
|
||||||
const rect = target.getBoundingClientRect()
|
const rect = target.getBoundingClientRect()
|
||||||
const tooltipWidth = 360
|
setHoverAnchorRect({ top: rect.top, left: rect.left, width: rect.width, height: rect.height })
|
||||||
const tooltipHeight = 420
|
setHoveredSegment(segment)
|
||||||
const verticalGap = 16
|
}, 200)
|
||||||
const horizontalGap = 16
|
|
||||||
const preferredTop = rect.top + rect.height / 2 - tooltipHeight / 2
|
|
||||||
const clampedTop = Math.min(window.innerHeight - tooltipHeight - verticalGap, Math.max(verticalGap, preferredTop))
|
|
||||||
const preferredLeft = rect.left - tooltipWidth - horizontalGap
|
|
||||||
const clampedLeft = Math.max(horizontalGap, preferredLeft)
|
|
||||||
setTooltipCoords({ top: clampedTop, left: clampedLeft })
|
|
||||||
setHoveredSegment(segment)
|
|
||||||
}, 200)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
clearHoverTimer()
|
clearHoverTimer()
|
||||||
setHoveredSegment(null)
|
setHoveredSegment(null)
|
||||||
|
setHoverAnchorRect(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (typeof window === "undefined") return
|
||||||
|
const anchor = hoverAnchorRect()
|
||||||
|
const segment = hoveredSegment()
|
||||||
|
if (!anchor || !segment) return
|
||||||
|
const { width, height } = tooltipSize()
|
||||||
|
const verticalGap = 16
|
||||||
|
const horizontalGap = 16
|
||||||
|
const preferredTop = anchor.top + anchor.height / 2 - height / 2
|
||||||
|
const maxTop = window.innerHeight - height - verticalGap
|
||||||
|
const clampedTop = Math.min(maxTop, Math.max(verticalGap, preferredTop))
|
||||||
|
const preferredLeft = anchor.left - width - horizontalGap
|
||||||
|
const clampedLeft = Math.max(horizontalGap, preferredLeft)
|
||||||
|
setTooltipCoords({ top: clampedTop, left: clampedLeft })
|
||||||
|
})
|
||||||
|
|
||||||
onCleanup(() => clearHoverTimer())
|
onCleanup(() => clearHoverTimer())
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const activeId = props.activeMessageId
|
const activeId = props.activeMessageId
|
||||||
|
|
||||||
if (!activeId) return
|
if (!activeId) return
|
||||||
const targetSegment = props.segments.find((segment) => segment.messageId === activeId)
|
const targetSegment = props.segments.find((segment) => segment.messageId === activeId)
|
||||||
if (!targetSegment) return
|
if (!targetSegment) return
|
||||||
@@ -300,8 +312,23 @@ const MessageTimeline: Component<MessageTimelineProps> = (props) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const element = tooltipElement()
|
||||||
|
if (!element || typeof window === "undefined") return
|
||||||
|
const updateSize = () => {
|
||||||
|
const rect = element.getBoundingClientRect()
|
||||||
|
setTooltipSize({ width: rect.width, height: rect.height })
|
||||||
|
}
|
||||||
|
updateSize()
|
||||||
|
if (typeof ResizeObserver === "undefined") return
|
||||||
|
const observer = new ResizeObserver(() => updateSize())
|
||||||
|
observer.observe(element)
|
||||||
|
onCleanup(() => observer.disconnect())
|
||||||
|
})
|
||||||
|
|
||||||
const previewData = createMemo(() => {
|
const previewData = createMemo(() => {
|
||||||
|
|
||||||
const segment = hoveredSegment()
|
const segment = hoveredSegment()
|
||||||
if (!segment) return null
|
if (!segment) return null
|
||||||
const record = store().getMessage(segment.messageId)
|
const record = store().getMessage(segment.messageId)
|
||||||
@@ -343,16 +370,23 @@ const MessageTimeline: Component<MessageTimelineProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
</For>
|
</For>
|
||||||
<Show when={previewData()}>
|
<Show when={previewData()}>
|
||||||
{(data) => (
|
{(data) => {
|
||||||
<div class="message-timeline-tooltip" style={{ top: `${tooltipCoords().top}px`, left: `${tooltipCoords().left}px` }}>
|
onCleanup(() => setTooltipElement(null))
|
||||||
<MessagePreview
|
return (
|
||||||
messageId={data().messageId}
|
<div
|
||||||
instanceId={props.instanceId}
|
ref={(element) => setTooltipElement(element)}
|
||||||
sessionId={props.sessionId}
|
class="message-timeline-tooltip"
|
||||||
store={store}
|
style={{ top: `${tooltipCoords().top}px`, left: `${tooltipCoords().left}px` }}
|
||||||
/>
|
>
|
||||||
</div>
|
<MessagePreview
|
||||||
)}
|
messageId={data().messageId}
|
||||||
|
instanceId={props.instanceId}
|
||||||
|
sessionId={props.sessionId}
|
||||||
|
store={store}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user