import type { CanvasClipboard, CanvasContextMenuState, CanvasLayer, } from './ImageCanvasEditorTypes'; export type CanvasLayerMoveMode = 'up' | 'down' | 'top' | 'bottom'; export type CanvasLayerFlipAxis = 'x' | 'y'; type CanvasPoint = { x: number; y: number; }; function cloneLayer(layer: CanvasLayer): CanvasLayer { return { ...layer, generationInputs: layer.generationInputs ? { fields: layer.generationInputs.fields.map((field) => ({ ...field })), references: layer.generationInputs.references.map((reference) => ({ ...reference, })), } : layer.generationInputs, }; } export function resolveContextTargetLayerIds( menu: CanvasContextMenuState | null, selectedLayerIds: string[], ) { if (menu?.kind !== 'layer') { return []; } return selectedLayerIds.includes(menu.layerId) ? [...selectedLayerIds] : [menu.layerId]; } export function getCanvasLayersByIds( layers: CanvasLayer[], targetIds: string[], ) { return layers.filter((layer) => targetIds.includes(layer.id)); } export function duplicateCanvasLayers({ sourceLayers, allLayers, canvasPoint, renameCopies = true, stamp = Date.now(), }: { sourceLayers: CanvasLayer[]; allLayers: CanvasLayer[]; canvasPoint?: CanvasPoint; renameCopies?: boolean; stamp?: number | string; }) { if (!sourceLayers.length) { return []; } const minX = Math.min(...sourceLayers.map((layer) => layer.x)); const minY = Math.min(...sourceLayers.map((layer) => layer.y)); const maxZIndex = allLayers.reduce( (maxZ, layer) => Math.max(maxZ, layer.zIndex), 0, ); return sourceLayers.map((layer, index) => ({ ...cloneLayer(layer), id: `layer-copy-${stamp}-${index}`, resourceId: `local-resource-copy-${stamp}-${index}`, title: renameCopies ? `${layer.title} 副本` : layer.title, x: canvasPoint ? canvasPoint.x + (layer.x - minX) : layer.x + 32, y: canvasPoint ? canvasPoint.y + (layer.y - minY) : layer.y + 32, zIndex: maxZIndex + index + 1, groupId: null, })); } export function createCanvasLayerClipboard( layers: CanvasLayer[], targetIds: string[], mode: CanvasClipboard['mode'], ): CanvasClipboard | null { const targetLayers = getCanvasLayersByIds(layers, targetIds); if (!targetLayers.length) { return null; } return { layers: targetLayers.map(cloneLayer), mode, }; } export function removeCanvasLayers(layers: CanvasLayer[], targetIds: string[]) { if (!targetIds.length) { return layers; } return layers.filter((layer) => !targetIds.includes(layer.id)); } export function updateCanvasLayersByIds( layers: CanvasLayer[], targetIds: string[], updater: (layer: CanvasLayer, targetIds: string[]) => CanvasLayer, ) { if (!targetIds.length) { return layers; } return layers.map((layer) => targetIds.includes(layer.id) ? updater(layer, targetIds) : layer, ); } export function moveCanvasLayers( layers: CanvasLayer[], targetIds: string[], mode: CanvasLayerMoveMode, ) { if (!targetIds.length) { return layers; } const maxZIndex = layers.reduce( (maxZ, layer) => Math.max(maxZ, layer.zIndex), 0, ); const minZIndex = layers.reduce( (minZ, layer) => Math.min(minZ, layer.zIndex), 0, ); let offsetIndex = 0; return updateCanvasLayersByIds(layers, targetIds, (layer) => { if (mode === 'up') { return { ...layer, zIndex: layer.zIndex + 1 }; } if (mode === 'down') { return { ...layer, zIndex: layer.zIndex - 1 }; } offsetIndex += 1; if (mode === 'top') { return { ...layer, zIndex: maxZIndex + offsetIndex }; } return { ...layer, zIndex: minZIndex - (targetIds.length - offsetIndex + 1), }; }); } export function groupCanvasLayers( layers: CanvasLayer[], targetIds: string[], groupId: string, ) { return updateCanvasLayersByIds(layers, targetIds, (layer) => ({ ...layer, groupId, })); } export function ungroupCanvasLayers( layers: CanvasLayer[], targetIds: string[], ) { return updateCanvasLayersByIds(layers, targetIds, (layer) => ({ ...layer, groupId: null, })); } export function toggleCanvasLayersVisibility( layers: CanvasLayer[], targetIds: string[], ) { const shouldHide = getCanvasLayersByIds(layers, targetIds).some( (layer) => !layer.hidden, ); return updateCanvasLayersByIds(layers, targetIds, (layer) => ({ ...layer, hidden: shouldHide, })); } export function toggleCanvasLayersLock( layers: CanvasLayer[], targetIds: string[], ) { const shouldLock = getCanvasLayersByIds(layers, targetIds).some( (layer) => !layer.locked, ); return updateCanvasLayersByIds(layers, targetIds, (layer) => ({ ...layer, locked: shouldLock, })); } export function flipCanvasLayers( layers: CanvasLayer[], targetIds: string[], axis: CanvasLayerFlipAxis, ) { return updateCanvasLayersByIds(layers, targetIds, (layer) => axis === 'x' ? { ...layer, flipX: !layer.flipX, } : { ...layer, flipY: !layer.flipY, }, ); }