44e6c8e10f
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
131 lines
3.7 KiB
TypeScript
131 lines
3.7 KiB
TypeScript
import { useState, useCallback, useMemo } from "react"
|
|
import type { PipConfig, PipLayer, PipGridPosition } from "../../../types"
|
|
import { DEFAULT_PIP_LAYER, DEFAULT_PIP_CONFIG } from "../../../types"
|
|
import { GRID_POSITION_MAP, genLayerId } from "../constants"
|
|
|
|
const MIN_DIMENSION = 10
|
|
const MAX_DIMENSION = 80
|
|
|
|
interface UsePipConfigPanelOptions {
|
|
config: PipConfig
|
|
onChange: (config: PipConfig) => void
|
|
}
|
|
|
|
export const usePipConfigPanel = ({ config, onChange }: UsePipConfigPanelOptions) => {
|
|
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 && selectedLayer.height > 0) {
|
|
const ratio = selectedLayer.width / selectedLayer.height
|
|
const newHeight = Math.round(val / ratio)
|
|
partial.height = Math.max(MIN_DIMENSION, Math.min(MAX_DIMENSION, newHeight))
|
|
}
|
|
updateLayer(selectedLayer.id, partial)
|
|
},
|
|
[selectedLayer, updateLayer],
|
|
)
|
|
|
|
const handleHeightChange = useCallback(
|
|
(val: number) => {
|
|
if (!selectedLayer) return
|
|
const partial: Partial<PipLayer> = { height: val }
|
|
if (selectedLayer.aspect_lock && selectedLayer.width > 0 && selectedLayer.height > 0) {
|
|
const ratio = selectedLayer.width / selectedLayer.height
|
|
const newWidth = Math.round(val * ratio)
|
|
partial.width = Math.max(MIN_DIMENSION, Math.min(MAX_DIMENSION, newWidth))
|
|
}
|
|
updateLayer(selectedLayer.id, partial)
|
|
},
|
|
[selectedLayer, updateLayer],
|
|
)
|
|
|
|
return {
|
|
selectedId,
|
|
setSelectedId,
|
|
selectedLayer,
|
|
handleAddLayer,
|
|
handleDeleteLayer,
|
|
updateLayer,
|
|
handleEnableToggle,
|
|
handleReset,
|
|
handleGridClick,
|
|
handleWidthChange,
|
|
handleHeightChange,
|
|
}
|
|
}
|