返回 oh-my-ppt
usePreviewWindow.ts
根目录 / src / renderer / src / components / session-detail / hooks / usePreviewWindow.ts
1 import { useCallback, useEffect, useRef, useState } from 'react'
2 import { selectPreviewWindowIds } from './preview-window-utils'
3
4 interface UsePreviewWindowOptions {
5 enabled: boolean
6 itemIds: readonly string[]
7 limit: number
8 }
9
10 interface UsePreviewWindowResult {
11 activePreviewIds: Set<string>
12 viewportRef: React.RefObject<HTMLDivElement | null>
13 schedulePreviewWindowUpdate: () => void
14 }
15
16 export function usePreviewWindow({
17 enabled,
18 itemIds,
19 limit
20 }: UsePreviewWindowOptions): UsePreviewWindowResult {
21 const [activePreviewIds, setActivePreviewIds] = useState<Set<string>>(() => new Set())
22 const viewportRef = useRef<HTMLDivElement>(null)
23 const updateRafRef = useRef<number | null>(null)
24
25 const updatePreviewWindow = useCallback((): void => {
26 const viewport = viewportRef.current
27 if (!enabled || !viewport || itemIds.length === 0) {
28 setActivePreviewIds((current) => (current.size === 0 ? current : new Set()))
29 return
30 }
31
32 const viewportRect = viewport.getBoundingClientRect()
33 const viewportCenterX = viewportRect.left + viewportRect.width / 2
34 const viewportCenterY = viewportRect.top + viewportRect.height / 2
35 const candidates = Array.from(
36 viewport.querySelectorAll<HTMLElement>('[data-preview-window-id]')
37 ).map((node) => {
38 const rect = node.getBoundingClientRect()
39 return {
40 id: node.dataset.previewWindowId || '',
41 distance: Math.hypot(
42 rect.left + rect.width / 2 - viewportCenterX,
43 rect.top + rect.height / 2 - viewportCenterY
44 )
45 }
46 })
47 const next = selectPreviewWindowIds(candidates, limit)
48
49 setActivePreviewIds((current) => {
50 if (current.size === next.size && Array.from(next).every((id) => current.has(id))) {
51 return current
52 }
53 return next
54 })
55 }, [enabled, itemIds, limit])
56
57 const schedulePreviewWindowUpdate = useCallback((): void => {
58 if (updateRafRef.current !== null) return
59 updateRafRef.current = window.requestAnimationFrame(() => {
60 updateRafRef.current = null
61 updatePreviewWindow()
62 })
63 }, [updatePreviewWindow])
64
65 useEffect(() => {
66 schedulePreviewWindowUpdate()
67 window.addEventListener('resize', schedulePreviewWindowUpdate)
68 return () => {
69 window.removeEventListener('resize', schedulePreviewWindowUpdate)
70 if (updateRafRef.current !== null) {
71 window.cancelAnimationFrame(updateRafRef.current)
72 updateRafRef.current = null
73 }
74 }
75 }, [schedulePreviewWindowUpdate])
76
77 return { activePreviewIds, viewportRef, schedulePreviewWindowUpdate }
78 }
79
79 lines TYPESCRIPT