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>
90 lines
2.6 KiB
TypeScript
90 lines
2.6 KiB
TypeScript
import { useState, useCallback } from "react"
|
|
import type { MediaAsset } from "@/api/template-editor"
|
|
|
|
interface UseDragReorderOptions {
|
|
filteredAssets: MediaAsset[]
|
|
selectedIds: string[]
|
|
onAssetDragStart?: (asset: MediaAsset) => void
|
|
onReorder?: (fromIdx: number, toIdx: number) => void
|
|
}
|
|
|
|
interface UseDragReorderReturn {
|
|
dragIdx: number | null
|
|
dragOverIdx: number | null
|
|
handleDragStart: (e: React.DragEvent, idx: number) => void
|
|
handleDragOver: (e: React.DragEvent, idx: number) => void
|
|
handleDrop: (e: React.DragEvent, toIdx: number) => void
|
|
handleDragEnd: () => void
|
|
}
|
|
|
|
/**
|
|
* 拖拽排序 Hook —— HTML5 DnD,支持批量拖拽携带数据
|
|
*/
|
|
const useDragReorder = ({
|
|
filteredAssets,
|
|
selectedIds,
|
|
onAssetDragStart,
|
|
onReorder,
|
|
}: UseDragReorderOptions): UseDragReorderReturn => {
|
|
const [dragIdx, setDragIdx] = useState<number | null>(null)
|
|
const [dragOverIdx, setDragOverIdx] = useState<number | null>(null)
|
|
|
|
const handleDragStart = useCallback(
|
|
(e: React.DragEvent, idx: number) => {
|
|
setDragIdx(idx)
|
|
e.dataTransfer.effectAllowed = "move"
|
|
e.dataTransfer.setData("text/plain", String(idx))
|
|
// 设置素材数据,供 TimelinePanel 接收
|
|
e.dataTransfer.setData("application/x-media-asset", JSON.stringify(filteredAssets[idx]))
|
|
// 批量拖拽:如果有多个选中素材,一起携带
|
|
if (selectedIds.length > 1 && selectedIds.includes(filteredAssets[idx].id)) {
|
|
const batchAssets = filteredAssets.filter((a) => selectedIds.includes(a.id))
|
|
e.dataTransfer.setData("application/x-media-assets", JSON.stringify(batchAssets))
|
|
}
|
|
// 通知父组件素材拖拽开始
|
|
if (onAssetDragStart) {
|
|
onAssetDragStart(filteredAssets[idx])
|
|
}
|
|
},
|
|
[filteredAssets, selectedIds, onAssetDragStart],
|
|
)
|
|
|
|
const handleDragOver = useCallback(
|
|
(e: React.DragEvent, idx: number) => {
|
|
e.preventDefault()
|
|
e.dataTransfer.dropEffect = "move"
|
|
if (dragIdx === null || dragIdx === idx) return
|
|
setDragOverIdx(idx)
|
|
},
|
|
[dragIdx],
|
|
)
|
|
|
|
const handleDrop = useCallback(
|
|
(e: React.DragEvent, toIdx: number) => {
|
|
e.preventDefault()
|
|
if (dragIdx !== null && dragIdx !== toIdx && onReorder) {
|
|
onReorder(dragIdx, toIdx)
|
|
}
|
|
setDragIdx(null)
|
|
setDragOverIdx(null)
|
|
},
|
|
[dragIdx, onReorder],
|
|
)
|
|
|
|
const handleDragEnd = useCallback(() => {
|
|
setDragIdx(null)
|
|
setDragOverIdx(null)
|
|
}, [])
|
|
|
|
return {
|
|
dragIdx,
|
|
dragOverIdx,
|
|
handleDragStart,
|
|
handleDragOver,
|
|
handleDrop,
|
|
handleDragEnd,
|
|
}
|
|
}
|
|
|
|
export default useDragReorder
|