4578b65965
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m30s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m37s
CI/CD Pipeline / Frontend Lint (push) Successful in 3m3s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m18s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m18s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 3m17s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m33s
CI/CD Pipeline / Integration Tests (push) Successful in 2m22s
CI/CD Pipeline / Build Staging API Image (push) Successful in 6m19s
CI/CD Pipeline / Unit Tests (push) Failing after 6m57s
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m31s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 16s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m46s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m59s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
55 lines
1.5 KiB
TypeScript
55 lines
1.5 KiB
TypeScript
import { useState, useRef, useCallback, useEffect } from "react"
|
|
import type { MediaAsset } from "@/api/template-editor"
|
|
|
|
interface UseAssetPreviewReturn {
|
|
previewAsset: MediaAsset | null
|
|
previewPos: { x: number; y: number }
|
|
handleMouseEnter: (asset: MediaAsset, e: React.MouseEvent) => void
|
|
handleMouseLeave: () => void
|
|
}
|
|
|
|
/**
|
|
* 悬浮预览 Hook —— 延迟 400ms 显示预览浮层
|
|
*/
|
|
const useAssetPreview = (): UseAssetPreviewReturn => {
|
|
const [previewAsset, setPreviewAsset] = useState<MediaAsset | null>(null)
|
|
const [previewPos, setPreviewPos] = useState({ x: 0, y: 0 })
|
|
const previewTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
|
|
const handleMouseEnter = useCallback((asset: MediaAsset, e: React.MouseEvent) => {
|
|
if (previewTimer.current) clearTimeout(previewTimer.current)
|
|
previewTimer.current = setTimeout(() => {
|
|
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
|
|
setPreviewAsset(asset)
|
|
setPreviewPos({
|
|
x: rect.right + 12,
|
|
y: Math.max(8, rect.top - 20),
|
|
})
|
|
}, 400)
|
|
}, [])
|
|
|
|
const handleMouseLeave = useCallback(() => {
|
|
if (previewTimer.current) {
|
|
clearTimeout(previewTimer.current)
|
|
previewTimer.current = null
|
|
}
|
|
setPreviewAsset(null)
|
|
}, [])
|
|
|
|
// 清理定时器
|
|
useEffect(() => {
|
|
return () => {
|
|
if (previewTimer.current) clearTimeout(previewTimer.current)
|
|
}
|
|
}, [])
|
|
|
|
return {
|
|
previewAsset,
|
|
previewPos,
|
|
handleMouseEnter,
|
|
handleMouseLeave,
|
|
}
|
|
}
|
|
|
|
export default useAssetPreview
|