64387c00bb
CI/CD Pipeline / Check if frontend-only change (push) Blocked by required conditions
CI/CD Pipeline / Validate - Code Quality (push) Blocked by required conditions
CI/CD Pipeline / Validate - Type Check (mypy) (push) Blocked by required conditions
CI/CD Pipeline / Validate - Migration (alembic) (push) Blocked by required conditions
CI/CD Pipeline / Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (push) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (push) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (push) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging API Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (push) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (push) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (push) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Deploy Production (push) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (push) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (push) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (push) Blocked by required conditions
130 lines
3.4 KiB
TypeScript
130 lines
3.4 KiB
TypeScript
/**
|
|
* 混剪图层管理 Hook
|
|
*/
|
|
import { useState, useCallback, useMemo } from "react"
|
|
import type { PipConfig, PipLayer, PipGridPosition } from "@/pages/editing-planner/types"
|
|
import { DEFAULT_PIP_LAYER, DEFAULT_PIP_CONFIG } from "@/pages/editing-planner/types"
|
|
import { GRID_POSITION_MAP } from "@/pages/editing-planner/constants/pipConfig"
|
|
|
|
let layerIdCounter = 0
|
|
const genLayerId = () => `pip_layer_${Date.now()}_${++layerIdCounter}`
|
|
|
|
interface UsePipLayersOptions {
|
|
config: PipConfig
|
|
onChange: (config: PipConfig) => void
|
|
}
|
|
|
|
export const usePipLayers = ({ config, onChange }: UsePipLayersOptions) => {
|
|
const [selectedId, setSelectedId] = useState<string>("")
|
|
|
|
const selectedLayer = useMemo(
|
|
() => config.layers.find((l) => l.id === selectedId) ?? null,
|
|
[config.layers, selectedId],
|
|
)
|
|
|
|
/* ── 添加图层 ── */
|
|
const handleAddLayer = useCallback(() => {
|
|
const newLayer: PipLayer = {
|
|
...DEFAULT_PIP_LAYER,
|
|
id: genLayerId(),
|
|
name: `图层 ${config.layers.length + 1}`,
|
|
z_index: config.layers.length + 1,
|
|
}
|
|
onChange({
|
|
...config,
|
|
layers: [...config.layers, newLayer],
|
|
})
|
|
setSelectedId(newLayer.id)
|
|
}, [config, onChange])
|
|
|
|
/* ── 删除图层 ── */
|
|
const handleDeleteLayer = useCallback(
|
|
(id: string) => {
|
|
const newLayers = config.layers.filter((l) => l.id !== id)
|
|
onChange({ ...config, layers: newLayers })
|
|
if (selectedId === id) {
|
|
setSelectedId(newLayers.length > 0 ? newLayers[0].id : "")
|
|
}
|
|
},
|
|
[config, onChange, selectedId],
|
|
)
|
|
|
|
/* ── 更新图层 ── */
|
|
const updateLayer = useCallback(
|
|
(id: string, partial: Partial<PipLayer>) => {
|
|
onChange({
|
|
...config,
|
|
layers: config.layers.map((l) => (l.id === id ? { ...l, ...partial } : l)),
|
|
})
|
|
},
|
|
[config, onChange],
|
|
)
|
|
|
|
/* ── 切换启用 ── */
|
|
const handleEnableToggle = useCallback(
|
|
(checked: boolean) => {
|
|
onChange({ ...config, enabled: checked })
|
|
},
|
|
[config, onChange],
|
|
)
|
|
|
|
/* ── 重置 ── */
|
|
const handleReset = useCallback(() => {
|
|
onChange({ ...DEFAULT_PIP_CONFIG })
|
|
setSelectedId("")
|
|
}, [onChange])
|
|
|
|
/* ── 九宫格点击 ── */
|
|
const handleGridClick = useCallback(
|
|
(pos: PipGridPosition) => {
|
|
if (!selectedLayer) return
|
|
const coords = GRID_POSITION_MAP[pos]
|
|
updateLayer(selectedLayer.id, {
|
|
grid_position: pos,
|
|
x: coords.x,
|
|
y: coords.y,
|
|
})
|
|
},
|
|
[selectedLayer, updateLayer],
|
|
)
|
|
|
|
/* ── 宽高比锁定 ── */
|
|
const handleWidthChange = useCallback(
|
|
(val: number) => {
|
|
if (!selectedLayer) return
|
|
const partial: Partial<PipLayer> = { width: val }
|
|
if (selectedLayer.aspect_lock) {
|
|
partial.height = val
|
|
}
|
|
updateLayer(selectedLayer.id, partial)
|
|
},
|
|
[selectedLayer, updateLayer],
|
|
)
|
|
|
|
const handleHeightChange = useCallback(
|
|
(val: number) => {
|
|
if (!selectedLayer) return
|
|
const partial: Partial<PipLayer> = { height: val }
|
|
if (selectedLayer.aspect_lock) {
|
|
partial.width = val
|
|
}
|
|
updateLayer(selectedLayer.id, partial)
|
|
},
|
|
[selectedLayer, updateLayer],
|
|
)
|
|
|
|
return {
|
|
selectedId,
|
|
setSelectedId,
|
|
selectedLayer,
|
|
handleAddLayer,
|
|
handleDeleteLayer,
|
|
updateLayer,
|
|
handleEnableToggle,
|
|
handleReset,
|
|
handleGridClick,
|
|
handleWidthChange,
|
|
handleHeightChange,
|
|
}
|
|
}
|