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>
66 lines
1.8 KiB
TypeScript
66 lines
1.8 KiB
TypeScript
import { useCallback, useRef, useMemo } from "react"
|
|
import type { MediaAsset } from "@/api/template-editor"
|
|
|
|
interface UseAssetSelectionOptions {
|
|
filteredAssets: MediaAsset[]
|
|
selectedIds: string[]
|
|
onSelectionChange?: (ids: string[]) => void
|
|
}
|
|
|
|
interface UseAssetSelectionReturn {
|
|
selectedSet: Set<string>
|
|
toggleSelect: (asset: MediaAsset, idx: number, shiftKey: boolean) => void
|
|
clearSelection: () => void
|
|
}
|
|
|
|
/**
|
|
* 素材选择 Hook —— 单选/多选/Shift 连选
|
|
*/
|
|
const useAssetSelection = ({
|
|
filteredAssets,
|
|
selectedIds,
|
|
onSelectionChange,
|
|
}: UseAssetSelectionOptions): UseAssetSelectionReturn => {
|
|
const lastClickedIdx = useRef<number | null>(null)
|
|
|
|
const selectedSet = useMemo(() => new Set(selectedIds), [selectedIds])
|
|
|
|
const toggleSelect = useCallback(
|
|
(asset: MediaAsset, idx: number, shiftKey: boolean) => {
|
|
if (!onSelectionChange) return
|
|
|
|
if (shiftKey && lastClickedIdx.current !== null) {
|
|
// Shift 连选
|
|
const start = Math.min(lastClickedIdx.current, idx)
|
|
const end = Math.max(lastClickedIdx.current, idx)
|
|
const rangeIds = filteredAssets.slice(start, end + 1).map((a) => a.id)
|
|
const newSet = new Set(selectedIds)
|
|
rangeIds.forEach((id) => newSet.add(id))
|
|
onSelectionChange(Array.from(newSet))
|
|
} else {
|
|
const newSet = new Set(selectedIds)
|
|
if (newSet.has(asset.id)) {
|
|
newSet.delete(asset.id)
|
|
} else {
|
|
newSet.add(asset.id)
|
|
}
|
|
onSelectionChange(Array.from(newSet))
|
|
}
|
|
lastClickedIdx.current = idx
|
|
},
|
|
[onSelectionChange, selectedIds, filteredAssets],
|
|
)
|
|
|
|
const clearSelection = useCallback(() => {
|
|
onSelectionChange?.([])
|
|
}, [onSelectionChange])
|
|
|
|
return {
|
|
selectedSet,
|
|
toggleSelect,
|
|
clearSelection,
|
|
}
|
|
}
|
|
|
|
export default useAssetSelection
|