diff --git a/package-lock.json b/package-lock.json index 2bfab38..fdae3f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@modelcontextprotocol/sdk": "^0.5.0", "@solidjs/router": "^0.15.0", + "@thisbeyond/solid-dnd": "^0.7.5", "@types/three": "^0.183.1", "chokidar": "^5.0.0", "commander": "^14.0.3", @@ -2867,6 +2868,19 @@ "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, + "node_modules/@thisbeyond/solid-dnd": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@thisbeyond/solid-dnd/-/solid-dnd-0.7.5.tgz", + "integrity": "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0", + "pnpm": ">=8.6.0" + }, + "peerDependencies": { + "solid-js": "^1.5" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", diff --git a/package.json b/package.json index 6681a96..6ec9ec5 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "dependencies": { "@modelcontextprotocol/sdk": "^0.5.0", "@solidjs/router": "^0.15.0", + "@thisbeyond/solid-dnd": "^0.7.5", "@types/three": "^0.183.1", "chokidar": "^5.0.0", "commander": "^14.0.3", diff --git a/src/components/md-deck/CardLayer.tsx b/src/components/md-deck/CardLayer.tsx index 6e6ee49..cf56797 100644 --- a/src/components/md-deck/CardLayer.tsx +++ b/src/components/md-deck/CardLayer.tsx @@ -1,11 +1,11 @@ -import { createMemo, For, Show } from 'solid-js'; -import { parseMarkdown } from '../../markdown'; -import { getLayerStyle } from './hooks/dimensions'; -import type { CardData, CardSide, LayerConfig } from './types'; +import { createMemo, For, Show } from "solid-js"; +import { parseMarkdown } from "../../markdown"; +import { getLayerStyle } from "./hooks/dimensions"; +import type { CardData, CardSide, LayerConfig } from "./types"; import { DeckStore } from "./hooks/deckStore"; import { processVariables } from "../utils/csv-loader"; import { resolvePath } from "../utils/path"; -import type { LayerInteractionHandlers } from './hooks/useLayerInteraction'; +import type { LayerInteractionHandlers } from "./hooks/useLayerInteraction"; export interface CardLayerProps { cardData: CardData; @@ -15,28 +15,35 @@ export interface CardLayerProps { } export function CardLayer(props: CardLayerProps) { - const side = () => props.side || 'front'; - const layers = createMemo(() => - side() === 'front' + const side = () => props.side || "front"; + const layers = createMemo(() => + side() === "front" ? props.store.state.frontLayerConfigs.filter((l) => l.visible) - : props.store.state.backLayerConfigs.filter((l) => l.visible) + : props.store.state.backLayerConfigs.filter((l) => l.visible), ); const dimensions = () => props.store.state.dimensions!; const selectedLayer = () => props.store.state.selectedLayer; const draggingState = () => props.store.state.draggingState; function renderLayerContent(content: string) { - const iconPath = resolvePath(props.store.state.cards.sourcePath, props.cardData.iconPath ?? "./assets"); - return parseMarkdown(processVariables(content, props.cardData, props.store.state.cards), iconPath) as string; + const iconPath = resolvePath( + props.store.state.cards.sourcePath, + props.cardData.iconPath ?? "./assets", + ); + return parseMarkdown( + processVariables(content, props.cardData, props.store.state.cards), + iconPath, + ) as string; } - const getAlignStyle = (align?: 'l' | 'c' | 'r') => { - if (align === 'l') return 'left'; - if (align === 'r') return 'right'; - return 'center'; + const getAlignStyle = (align?: "l" | "c" | "r") => { + if (align === "l") return "left"; + if (align === "r") return "right"; + return "center"; }; - const isLayerSelected = (layer: LayerConfig) => selectedLayer() === layer.prop; + const isLayerSelected = (layerIndex: number) => + selectedLayer() === layerIndex; const getFrameBounds = (layer: LayerConfig) => { const dims = dimensions(); @@ -47,17 +54,17 @@ export function CardLayer(props: CardLayerProps) { return { left, top, width, height }; }; - const handleLayerClick = (layerProp: string, e: MouseEvent) => { + const handleLayerClick = (layerIndex: number, e: MouseEvent) => { if (props.interaction) { - props.interaction.onLayerClick(layerProp, e); + props.interaction.onLayerClick(layerIndex, e); } }; const handleFrameMouseDown = ( - action: 'drag' | 'resize-corner' | 'resize-edge', - anchor?: 'nw' | 'ne' | 'sw' | 'se', - edge?: 'n' | 's' | 'e' | 'w', - e?: MouseEvent + action: "drag" | "resize-corner" | "resize-edge", + anchor?: "nw" | "ne" | "sw" | "se", + edge?: "n" | "s" | "e" | "w", + e?: MouseEvent, ) => { if (props.interaction) { props.interaction.onFrameMouseDown(action, anchor, edge, e); @@ -66,24 +73,25 @@ export function CardLayer(props: CardLayerProps) { return ( - {(layer) => { + {(layer, index) => { const bounds = () => getFrameBounds(layer); - const isSelected = () => isLayerSelected(layer); - + const isSelected = () => isLayerSelected(index()); + return ( <> -
handleLayerClick(layer.prop, e)} + onClick={(e) => handleLayerClick(index(), e)} />
handleFrameMouseDown('drag', undefined, undefined, e)} + onMouseDown={(e) => + handleFrameMouseDown("drag", undefined, undefined, e) + } /> - +
handleFrameMouseDown('resize-corner', 'nw', undefined, e)} + onMouseDown={(e) => + handleFrameMouseDown("resize-corner", "nw", undefined, e) + } />
handleFrameMouseDown('resize-corner', 'ne', undefined, e)} + onMouseDown={(e) => + handleFrameMouseDown("resize-corner", "ne", undefined, e) + } />
handleFrameMouseDown('resize-corner', 'sw', undefined, e)} + onMouseDown={(e) => + handleFrameMouseDown("resize-corner", "sw", undefined, e) + } />
handleFrameMouseDown('resize-corner', 'se', undefined, e)} + onMouseDown={(e) => + handleFrameMouseDown("resize-corner", "se", undefined, e) + } />
handleFrameMouseDown('resize-edge', undefined, 'n', e)} + onMouseDown={(e) => + handleFrameMouseDown("resize-edge", undefined, "n", e) + } />
handleFrameMouseDown('resize-edge', undefined, 's', e)} + onMouseDown={(e) => + handleFrameMouseDown("resize-edge", undefined, "s", e) + } />
handleFrameMouseDown('resize-edge', undefined, 'w', e)} + onMouseDown={(e) => + handleFrameMouseDown("resize-edge", undefined, "w", e) + } />
handleFrameMouseDown('resize-edge', undefined, 'e', e)} + onMouseDown={(e) => + handleFrameMouseDown("resize-edge", undefined, "e", e) + } /> @@ -181,4 +209,4 @@ export function CardLayer(props: CardLayerProps) { }} ); -} \ No newline at end of file +} diff --git a/src/components/md-deck/editor-panel/LayerEditorPanel.tsx b/src/components/md-deck/editor-panel/LayerEditorPanel.tsx index f589982..97a4ca5 100644 --- a/src/components/md-deck/editor-panel/LayerEditorPanel.tsx +++ b/src/components/md-deck/editor-panel/LayerEditorPanel.tsx @@ -1,267 +1,206 @@ -import { For, createSignal, onCleanup, onMount } from 'solid-js'; -import type { DeckStore } from '../hooks/deckStore'; -import alignLeftIcon from './icons/align-left.png'; -import alignCenterIcon from './icons/align-center.png'; -import alignRightIcon from './icons/align-right.png'; +import { For, createSignal, createMemo, onCleanup, onMount } from "solid-js"; +import type { DeckStore } from "../hooks/deckStore"; +import { + DragDropProvider, + DragDropSensors, + createSortable, + maybeTransformStyle, + closestCenter, +} from "@thisbeyond/solid-dnd"; +import alignLeftIcon from "./icons/align-left.png"; +import alignCenterIcon from "./icons/align-center.png"; +import alignRightIcon from "./icons/align-right.png"; export interface LayerEditorPanelProps { store: DeckStore; } const ORIENTATION_OPTIONS = [ - { value: 'n', label: '↑ 北' }, - { value: 'e', label: '→ 东' }, - { value: 's', label: '↓ 南' }, - { value: 'w', label: '← 西' } + { value: "n", label: "↑ 北" }, + { value: "e", label: "→ 东" }, + { value: "s", label: "↓ 南" }, + { value: "w", label: "← 西" }, ] as const; const ALIGN_OPTIONS = [ - { value: '', label: '默认', icon: alignCenterIcon }, - { value: 'l', label: '左对齐', icon: alignLeftIcon }, - { value: 'c', label: '居中', icon: alignCenterIcon }, - { value: 'r', label: '右对齐', icon: alignRightIcon } + { value: "", label: "默认", icon: alignCenterIcon }, + { value: "l", label: "左对齐", icon: alignLeftIcon }, + { value: "c", label: "居中", icon: alignCenterIcon }, + { value: "r", label: "右对齐", icon: alignRightIcon }, ] as const; const FONT_PRESETS = [3, 5, 8, 12] as const; function OrientationIcon(value: string): string { switch (value) { - case 'n': return '↑'; - case 'e': return '→'; - case 's': return '↓'; - case 'w': return '←'; - default: return '↑'; + case "n": + return "↑"; + case "e": + return "→"; + case "s": + return "↓"; + case "w": + return "←"; + default: + return "↑"; } } function AlignIconSrc(value: string): string { switch (value) { - case 'l': return alignLeftIcon; - case 'r': return alignRightIcon; - default: return alignCenterIcon; + case "l": + return alignLeftIcon; + case "r": + return alignRightIcon; + default: + return alignCenterIcon; } } function LayerEditorPanel(props: LayerEditorPanelProps) { const { store } = props; const [openDropdown, setOpenDropdown] = createSignal(null); - let dropdownRef: HTMLDivElement | undefined; + const [hoveredIndex, setHoveredIndex] = createSignal(null); + const [addMenuOpen, setAddMenuOpen] = createSignal(false); + let addMenuRef: HTMLDivElement | undefined; - const currentLayerConfigs = () => - store.state.activeSide === 'front' - ? store.state.frontLayerConfigs + const side = () => store.state.activeSide; + + const currentLayerConfigs = () => + side() === "front" + ? store.state.frontLayerConfigs : store.state.backLayerConfigs; - const updateLayerOrientation = (layerProp: string, orientation: 'n' | 's' | 'e' | 'w') => { - const updateFn = store.state.activeSide === 'front' - ? store.actions.updateFrontLayerConfig + // Available CSV fields not yet used in the current side + const availableFields = createMemo(() => { + const cards = store.state.cards; + if (!cards || cards.length === 0) return []; + const usedProps = new Set(currentLayerConfigs().map((l) => l.prop)); + return Object.keys(cards[0]).filter( + (k) => k !== "label" && !usedProps.has(k), + ); + }); + + const updateFn = () => + side() === "front" + ? store.actions.updateFrontLayerConfig : store.actions.updateBackLayerConfig; - updateFn(layerProp, { orientation }); + + const updateLayerOrientation = ( + index: number, + orientation: "n" | "s" | "e" | "w", + ) => { + updateFn()(index, { orientation }); setOpenDropdown(null); }; - const updateLayerFontSize = (layerProp: string, fontSize?: number) => { - const updateFn = store.state.activeSide === 'front' - ? store.actions.updateFrontLayerConfig - : store.actions.updateBackLayerConfig; - updateFn(layerProp, { fontSize }); + const updateLayerFontSize = (index: number, fontSize?: number) => { + updateFn()(index, { fontSize }); }; - const updateLayerAlign = (layerProp: string, align?: 'l' | 'c' | 'r') => { - const updateFn = store.state.activeSide === 'front' - ? store.actions.updateFrontLayerConfig - : store.actions.updateBackLayerConfig; - updateFn(layerProp, { align }); + const updateLayerAlign = (index: number, align?: "l" | "c" | "r") => { + updateFn()(index, { align }); setOpenDropdown(null); }; - const toggleLayerVisible = (layerProp: string) => { - const toggleFn = store.state.activeSide === 'front' - ? store.actions.toggleFrontLayerVisible - : store.actions.toggleBackLayerVisible; - toggleFn(layerProp); - }; - - const selectLayer = (layerProp: string) => { + const selectLayer = (index: number) => { store.actions.setSelectedLayer( - store.state.selectedLayer === layerProp ? null : layerProp + store.state.selectedLayer === index ? null : index, ); }; - const handleDropdownClick = (e: MouseEvent) => { - e.stopPropagation(); - }; - const handleClickOutside = (e: MouseEvent) => { - if (dropdownRef && !dropdownRef.contains(e.target as Node)) { - setOpenDropdown(null); + if (addMenuRef && !addMenuRef.contains(e.target as Node)) { + setAddMenuOpen(false); } }; onMount(() => { - document.addEventListener('click', handleClickOutside); + document.addEventListener("click", handleClickOutside); }); onCleanup(() => { - document.removeEventListener('click', handleClickOutside); + document.removeEventListener("click", handleClickOutside); }); - const layerCount = () => currentLayerConfigs().length; + const onDragEnd: import("@thisbeyond/solid-dnd").DragEventHandler = ({ + draggable, + droppable, + }) => { + if (droppable) { + const fromIndex = draggable.id as number; + const toIndex = droppable.id as number; + if (fromIndex !== toIndex) { + store.actions.reorderLayers(side(), fromIndex, toIndex); + } + } + }; return (

- 图层 ({store.state.activeSide === 'front' ? '正面' : '背面'}) + 图层 ({side() === "front" ? "正面" : "背面"})

-
- - {(layer, index) => ( -
- toggleLayerVisible(layer.prop)} - class="cursor-pointer" - /> - selectLayer(layer.prop)} - > - {layer.prop} - -
- - {openDropdown() === `orient-${layer.prop}` && ( -
+ + {addMenuOpen() && ( +
e.stopPropagation()} + > + {availableFields().length === 0 ? ( +
所有字段已添加
+ ) : ( + + {(field) => ( + - )} - -
+ {field} + )} -
- -
- - {openDropdown() === `align-${layer.prop}` && ( -
- - {(opt) => ( - - )} - -
- )} -
- -
- - {openDropdown() === `font-${layer.prop}` && ( -
-
- { - const value = e.target.value; - updateLayerFontSize(layer.prop, value ? Number(value) : undefined); - }} - class="w-14 text-xs px-1.5 py-1 rounded border border-gray-300" - step="0.1" - min="0.1" - /> - mm -
-
- - {(preset) => ( - - )} - -
- -
- )} -
-
- )} - + + )} +
+ )}
+ {/* Drag and drop list */} + + + + {(layer, index) => ( + updateLayerOrientation(index(), o)} + updateFontSize={(fs) => updateLayerFontSize(index(), fs)} + updateAlign={(a) => updateLayerAlign(index(), a)} + onSelect={() => selectLayer(index())} + onRemove={() => store.actions.removeLayer(side(), index())} + /> + )} + + + +
+ + {/* Visibility toggle */} + + + {/* Layer name */} + + {props.layer.prop} + + + {/* Orientation */} +
+ + {props.openDropdown === `orient-${props.index}` && ( +
e.stopPropagation()} + > + + {(opt) => ( + + )} + +
+ )} +
+ + {/* Align */} +
+ + {props.openDropdown === `align-${props.index}` && ( +
e.stopPropagation()} + > + + {(opt) => ( + + )} + +
+ )} +
+ + {/* Font size */} +
+ + {fontOpen() && ( +
e.stopPropagation()} + > +
+ { + const value = e.target.value; + props.updateFontSize(value ? Number(value) : undefined); + }} + class="w-14 text-xs px-1.5 py-1 rounded border border-gray-300" + step="0.1" + min="0.1" + /> + mm +
+
+ + {(preset) => ( + + )} + +
+ +
+ )} +
+ + {/* Delete (visible on hover) */} + +
+ ); +} + export { LayerEditorPanel }; diff --git a/src/components/md-deck/hooks/deckStore.ts b/src/components/md-deck/hooks/deckStore.ts index 055bfa1..ba64335 100644 --- a/src/components/md-deck/hooks/deckStore.ts +++ b/src/components/md-deck/hooks/deckStore.ts @@ -1,8 +1,18 @@ -import { createStore } from 'solid-js/store'; -import { calculateDimensions } from './dimensions'; -import { loadCSV, CSV } from '../../utils/csv-loader'; -import { initLayerConfigs, formatLayers, initLayerConfigsForSide } from './layer-parser'; -import type { CardData, LayerConfig, Dimensions, CardSide, CardShape } from '../types'; +import { createStore } from "solid-js/store"; +import { calculateDimensions } from "./dimensions"; +import { loadCSV, CSV } from "../../utils/csv-loader"; +import { + initLayerConfigs, + formatLayers, + initLayerConfigsForSide, +} from "./layer-parser"; +import type { + CardData, + LayerConfig, + Dimensions, + CardSide, + CardShape, +} from "../types"; export const DECK_DEFAULTS = { SIZE_W: 54, @@ -11,14 +21,14 @@ export const DECK_DEFAULTS = { GRID_H: 8, BLEED: 1, PADDING: 2, - CORNER_RADIUS: 3 + CORNER_RADIUS: 3, } as const; export interface DraggingState { - layer: string; - action: 'drag' | 'resize-corner' | 'resize-edge'; - anchor?: 'nw' | 'ne' | 'sw' | 'se'; - edge?: 'n' | 's' | 'e' | 'w'; + layer: number; + action: "drag" | "resize-corner" | "resize-edge"; + anchor?: "nw" | "ne" | "sw" | "se"; + edge?: "n" | "s" | "e" | "w"; startX: number; startY: number; startGrid: { x1: number; y1: number; x2: number; y2: number }; @@ -46,7 +56,7 @@ export interface DeckState { backLayerConfigs: LayerConfig[]; isEditing: boolean; - selectedLayer: string | null; + selectedLayer: number | null; activeSide: CardSide; isSelecting: boolean; @@ -62,7 +72,7 @@ export interface DeckState { exportProgress: number; exportError: string | null; - printOrientation: 'portrait' | 'landscape'; + printOrientation: "portrait" | "landscape"; printFrontOddPageOffsetX: number; printFrontOddPageOffsetY: number; printDoubleSided: boolean; @@ -83,15 +93,22 @@ export interface DeckActions { updateCardData: (index: number, key: string, value: string) => void; setFrontLayerConfigs: (configs: LayerConfig[]) => void; - updateFrontLayerConfig: (prop: string, updates: Partial) => void; - toggleFrontLayerVisible: (prop: string) => void; + updateFrontLayerConfig: ( + index: number, + updates: Partial, + ) => void; + toggleFrontLayerVisible: (index: number) => void; setBackLayerConfigs: (configs: LayerConfig[]) => void; - updateBackLayerConfig: (prop: string, updates: Partial) => void; - toggleBackLayerVisible: (prop: string) => void; + updateBackLayerConfig: (index: number, updates: Partial) => void; + toggleBackLayerVisible: (index: number) => void; + + addLayer: (side: CardSide, prop: string) => void; + removeLayer: (side: CardSide, index: number) => void; + reorderLayers: (side: CardSide, fromIndex: number, toIndex: number) => void; setIsEditing: (editing: boolean) => void; - setSelectedLayer: (layer: string | null) => void; + setSelectedLayer: (layer: number | null) => void; setActiveSide: (side: CardSide) => void; setIsSelecting: (selecting: boolean) => void; @@ -100,11 +117,32 @@ export interface DeckActions { cancelSelection: () => void; setDraggingState: (state: DraggingState | null) => void; - moveLayer: (layerProp: string, dxGrid: number, dyGrid: number, startGrid?: { x1: number; y1: number; x2: number; y2: number }) => void; - resizeLayerCorner: (layerProp: string, anchor: 'nw' | 'ne' | 'sw' | 'se', dxGrid: number, dyGrid: number, startGrid: { x1: number; y1: number; x2: number; y2: number }) => void; - resizeLayerEdge: (layerProp: string, edge: 'n' | 's' | 'e' | 'w', delta: number, startGrid: { x1: number; y1: number; x2: number; y2: number }) => void; + moveLayer: ( + index: number, + dxGrid: number, + dyGrid: number, + startGrid?: { x1: number; y1: number; x2: number; y2: number }, + ) => void; + resizeLayerCorner: ( + index: number, + anchor: "nw" | "ne" | "sw" | "se", + dxGrid: number, + dyGrid: number, + startGrid: { x1: number; y1: number; x2: number; y2: number }, + ) => void; + resizeLayerEdge: ( + index: number, + edge: "n" | "s" | "e" | "w", + delta: number, + startGrid: { x1: number; y1: number; x2: number; y2: number }, + ) => void; - loadCardsFromPath: (path: string, rawSrc: string, layersStr?: string, backLayersStr?: string) => Promise; + loadCardsFromPath: ( + path: string, + rawSrc: string, + layersStr?: string, + backLayersStr?: string, + ) => Promise; setError: (error: string | null) => void; clearError: () => void; @@ -117,7 +155,7 @@ export interface DeckActions { setExportError: (error: string | null) => void; clearExportError: () => void; - setPrintOrientation: (orientation: 'portrait' | 'landscape') => void; + setPrintOrientation: (orientation: "portrait" | "landscape") => void; setPrintFrontOddPageOffsetX: (offset: number) => void; setPrintFrontOddPageOffsetY: (offset: number) => void; setPrintDoubleSided: (doubleSided: boolean) => void; @@ -128,9 +166,7 @@ export interface DeckStore { actions: DeckActions; } -export function createDeckStore( - initialSrc: string = '', -): DeckStore { +export function createDeckStore(initialSrc: string = ""): DeckStore { const [state, setState] = createStore({ sizeW: DECK_DEFAULTS.SIZE_W, sizeH: DECK_DEFAULTS.SIZE_H, @@ -139,7 +175,7 @@ export function createDeckStore( bleed: DECK_DEFAULTS.BLEED, padding: DECK_DEFAULTS.PADDING, cornerRadius: DECK_DEFAULTS.CORNER_RADIUS, - shape: 'rectangle', + shape: "rectangle", fixed: false, src: initialSrc, rawSrc: initialSrc, @@ -150,7 +186,7 @@ export function createDeckStore( backLayerConfigs: [], isEditing: false, selectedLayer: null, - activeSide: 'front', + activeSide: "front", isSelecting: false, selectStart: null, selectEnd: null, @@ -160,10 +196,10 @@ export function createDeckStore( isExporting: false, exportProgress: 0, exportError: null, - printOrientation: 'portrait', + printOrientation: "portrait", printFrontOddPageOffsetX: 0, printFrontOddPageOffsetY: 0, - printDoubleSided: false + printDoubleSided: false, }); const updateDimensions = () => { @@ -173,7 +209,7 @@ export function createDeckStore( gridW: state.gridW, gridH: state.gridH, bleed: state.bleed, - padding: state.padding + padding: state.padding, }); setState({ dimensions: dims }); }; @@ -212,105 +248,217 @@ export function createDeckStore( const setCards = (cards: CSV) => setState({ cards, activeTab: 0 }); const setActiveTab = (index: number) => setState({ activeTab: index }); const updateCardData = (index: number, key: string, value: string) => { - setState('cards', index, key, value); + setState("cards", index, key, value); }; - const setFrontLayerConfigs = (configs: LayerConfig[]) => setState({ frontLayerConfigs: configs }); - const updateFrontLayerConfig = (prop: string, updates: Partial) => { - setState('frontLayerConfigs', (prev) => prev.map((config) => config.prop === prop ? { ...config, ...updates } : config)); + const setFrontLayerConfigs = (configs: LayerConfig[]) => + setState({ frontLayerConfigs: configs }); + const updateFrontLayerConfig = ( + index: number, + updates: Partial, + ) => { + setState("frontLayerConfigs", index, (config) => ({ + ...config, + ...updates, + })); }; - const toggleFrontLayerVisible = (prop: string) => { - setState('frontLayerConfigs', (prev) => prev.map((config) => - config.prop === prop ? { ...config, visible: !config.visible } : config - )); + const toggleFrontLayerVisible = (index: number) => { + setState("frontLayerConfigs", index, "visible", (v) => !v); }; - const setBackLayerConfigs = (configs: LayerConfig[]) => setState({ backLayerConfigs: configs }); - const updateBackLayerConfig = (prop: string, updates: Partial) => { - setState('backLayerConfigs', (prev) => prev.map((config) => config.prop === prop ? { ...config, ...updates } : config)); + const setBackLayerConfigs = (configs: LayerConfig[]) => + setState({ backLayerConfigs: configs }); + const updateBackLayerConfig = ( + index: number, + updates: Partial, + ) => { + setState("backLayerConfigs", index, (config) => ({ + ...config, + ...updates, + })); }; - const toggleBackLayerVisible = (prop: string) => { - setState('backLayerConfigs', (prev) => prev.map((config) => - config.prop === prop ? { ...config, visible: !config.visible } : config - )); + const toggleBackLayerVisible = (index: number) => { + setState("backLayerConfigs", index, "visible", (v) => !v); + }; + + const getLayerConfigs = (side: CardSide): LayerConfig[] => + side === "front" ? state.frontLayerConfigs : state.backLayerConfigs; + + const setLayerConfigs = (side: CardSide, configs: LayerConfig[]) => { + if (side === "front") setFrontLayerConfigs(configs); + else setBackLayerConfigs(configs); + }; + + const addLayer = (side: CardSide, prop: string) => { + const newLayer: LayerConfig = { + prop, + visible: true, + x1: 1, + y1: 1, + x2: 2, + y2: 2, + }; + const configs = getLayerConfigs(side); + setLayerConfigs(side, [...configs, newLayer]); + }; + + const removeLayer = (side: CardSide, index: number) => { + const configs = getLayerConfigs(side); + setLayerConfigs( + side, + configs.filter((_, i) => i !== index), + ); + if (state.selectedLayer === index) { + setState({ selectedLayer: null }); + } else if ( + state.selectedLayer !== null && + (state.selectedLayer as number) > index + ) { + setState({ selectedLayer: (state.selectedLayer as number) - 1 }); + } + }; + + const reorderLayers = ( + side: CardSide, + fromIndex: number, + toIndex: number, + ) => { + const configs = [...getLayerConfigs(side)]; + const [item] = configs.splice(fromIndex, 1); + configs.splice(toIndex, 0, item); + setLayerConfigs(side, configs); + if (state.selectedLayer === fromIndex) { + setState({ selectedLayer: toIndex }); + } else if (state.selectedLayer !== null) { + const sel = state.selectedLayer as number; + if (fromIndex < toIndex && sel > fromIndex && sel <= toIndex) { + setState({ selectedLayer: sel - 1 }); + } else if (fromIndex > toIndex && sel >= toIndex && sel < fromIndex) { + setState({ selectedLayer: sel + 1 }); + } + } }; const setIsEditing = (editing: boolean) => setState({ isEditing: editing }); - const setSelectedLayer = (layer: string | null) => setState({ selectedLayer: layer }); + const setSelectedLayer = (layer: number | null) => + setState({ selectedLayer: layer }); const setActiveSide = (side: CardSide) => setState({ activeSide: side }); - const setIsSelecting = (selecting: boolean) => setState({ isSelecting: selecting }); - const setSelectStart = (pos: { x: number; y: number } | null) => setState({ selectStart: pos }); - const setSelectEnd = (pos: { x: number; y: number } | null) => setState({ selectEnd: pos }); + const setIsSelecting = (selecting: boolean) => + setState({ isSelecting: selecting }); + const setSelectStart = (pos: { x: number; y: number } | null) => + setState({ selectStart: pos }); + const setSelectEnd = (pos: { x: number; y: number } | null) => + setState({ selectEnd: pos }); const cancelSelection = () => { setState({ isSelecting: false, selectStart: null, selectEnd: null }); }; - const setDraggingState = (draggingState: DraggingState | null) => setState({ draggingState }); + const setDraggingState = (draggingState: DraggingState | null) => + setState({ draggingState }); - const getLayerConfig = (layerProp: string): LayerConfig | undefined => { - const configs = state.activeSide === 'front' ? state.frontLayerConfigs : state.backLayerConfigs; - return configs.find(c => c.prop === layerProp); + const getLayerConfig = (index: number): LayerConfig | undefined => { + const configs = + state.activeSide === "front" + ? state.frontLayerConfigs + : state.backLayerConfigs; + return configs[index]; }; - const updateLayerConfig = (layerProp: string, updates: Partial) => { - if (state.activeSide === 'front') { - updateFrontLayerConfig(layerProp, updates); + const updateLayerConfig = (index: number, updates: Partial) => { + if (state.activeSide === "front") { + updateFrontLayerConfig(index, updates); } else { - updateBackLayerConfig(layerProp, updates); + updateBackLayerConfig(index, updates); } }; - const moveLayer = (layerProp: string, dxGrid: number, dyGrid: number, startGrid?: { x1: number; y1: number; x2: number; y2: number }) => { - const layer = getLayerConfig(layerProp); + const moveLayer = ( + index: number, + dxGrid: number, + dyGrid: number, + startGrid?: { x1: number; y1: number; x2: number; y2: number }, + ) => { + const layer = getLayerConfig(index); if (!layer) return; - const grid = startGrid ?? { x1: layer.x1, y1: layer.y1, x2: layer.x2, y2: layer.y2 }; - - updateLayerConfig(layerProp, { + const grid = startGrid ?? { + x1: layer.x1, + y1: layer.y1, + x2: layer.x2, + y2: layer.y2, + }; + + updateLayerConfig(index, { x1: grid.x1 + dxGrid, x2: grid.x2 + dxGrid, y1: grid.y1 + dyGrid, - y2: grid.y2 + dyGrid + y2: grid.y2 + dyGrid, }); }; - const resizeLayerCorner = (layerProp: string, anchor: 'nw' | 'ne' | 'sw' | 'se', dxGrid: number, dyGrid: number, startGrid: { x1: number; y1: number; x2: number; y2: number }) => { - const layer = getLayerConfig(layerProp); + const resizeLayerCorner = ( + index: number, + anchor: "nw" | "ne" | "sw" | "se", + dxGrid: number, + dyGrid: number, + startGrid: { x1: number; y1: number; x2: number; y2: number }, + ) => { + const layer = getLayerConfig(index); if (!layer) return; const updates: Partial = {}; - if (anchor === 'nw') { + if (anchor === "nw") { updates.x1 = Math.min(startGrid.x2, startGrid.x1 + dxGrid); updates.y1 = Math.min(startGrid.y2, startGrid.y1 + dyGrid); - } else if (anchor === 'ne') { + } else if (anchor === "ne") { updates.x2 = Math.max(startGrid.x1, startGrid.x2 + dxGrid); updates.y1 = Math.min(startGrid.y2, startGrid.y1 + dyGrid); - } else if (anchor === 'sw') { + } else if (anchor === "sw") { updates.x1 = Math.min(startGrid.x2, startGrid.x1 + dxGrid); updates.y2 = Math.max(startGrid.y1, startGrid.y2 + dyGrid); - } else if (anchor === 'se') { + } else if (anchor === "se") { updates.x2 = Math.max(startGrid.x1, startGrid.x2 + dxGrid); updates.y2 = Math.max(startGrid.y1, startGrid.y2 + dyGrid); } - updateLayerConfig(layerProp, updates); + updateLayerConfig(index, updates); }; - const resizeLayerEdge = (layerProp: string, edge: 'n' | 's' | 'e' | 'w', delta: number, startGrid: { x1: number; y1: number; x2: number; y2: number }) => { - const layer = getLayerConfig(layerProp); + const resizeLayerEdge = ( + index: number, + edge: "n" | "s" | "e" | "w", + delta: number, + startGrid: { x1: number; y1: number; x2: number; y2: number }, + ) => { + const layer = getLayerConfig(index); if (!layer) return; const updates: Partial = {}; - if (edge === 'n') updates.y1 = Math.min(startGrid.y2, Math.max(1, startGrid.y1 + delta)); - if (edge === 's') updates.y2 = Math.max(startGrid.y1, Math.min(state.gridH, startGrid.y2 + delta)); - if (edge === 'w') updates.x1 = Math.min(startGrid.x2, Math.max(1, startGrid.x1 + delta)); - if (edge === 'e') updates.x2 = Math.max(startGrid.x1, Math.min(state.gridW, startGrid.x2 + delta)); - updateLayerConfig(layerProp, updates); + if (edge === "n") + updates.y1 = Math.min(startGrid.y2, Math.max(1, startGrid.y1 + delta)); + if (edge === "s") + updates.y2 = Math.max( + startGrid.y1, + Math.min(state.gridH, startGrid.y2 + delta), + ); + if (edge === "w") + updates.x1 = Math.min(startGrid.x2, Math.max(1, startGrid.x1 + delta)); + if (edge === "e") + updates.x2 = Math.max( + startGrid.x1, + Math.min(state.gridW, startGrid.x2 + delta), + ); + updateLayerConfig(index, updates); }; - const loadCardsFromPath = async (path: string, rawSrc: string, layersStr: string = '', backLayersStr: string = '') => { + const loadCardsFromPath = async ( + path: string, + rawSrc: string, + layersStr: string = "", + backLayersStr: string = "", + ) => { if (!path) { - setState({ error: '未指定 CSV 文件路径' }); + setState({ error: "未指定 CSV 文件路径" }); return; } @@ -322,7 +470,7 @@ export function createDeckStore( if (data.length === 0) { setState({ error: `CSV ${path} 文件为空或格式不正确`, - isLoading: false + isLoading: false, }); return; } @@ -332,13 +480,13 @@ export function createDeckStore( activeTab: 0, frontLayerConfigs: initLayerConfigsForSide(data, layersStr), backLayerConfigs: initLayerConfigsForSide(data, backLayersStr), - isLoading: false + isLoading: false, }); updateDimensions(); } catch (err) { setState({ - error: `加载 CSV ${path} 失败:${err instanceof Error ? err.message : '未知错误'}`, - isLoading: false + error: `加载 CSV ${path} 失败:${err instanceof Error ? err.message : "未知错误"}`, + isLoading: false, }); } }; @@ -348,11 +496,12 @@ export function createDeckStore( const generateCode = (backLayersStr?: string) => { const frontLayersStr = formatLayers(state.frontLayerConfigs); - const backLayersString = backLayersStr || formatLayers(state.backLayerConfigs); + const backLayersString = + backLayersStr || formatLayers(state.backLayerConfigs); const parts = [ `:md-deck[${state.rawSrc || state.src}]`, `{size="${state.sizeW}x${state.sizeH}" `, - `grid="${state.gridW}x${state.gridH}" ` + `grid="${state.gridW}x${state.gridH}" `, ]; if (state.bleed !== DECK_DEFAULTS.BLEED) { @@ -361,7 +510,7 @@ export function createDeckStore( if (state.padding !== DECK_DEFAULTS.PADDING) { parts.push(`padding="${state.padding}" `); } - if (state.shape !== 'rectangle') { + if (state.shape !== "rectangle") { parts.push(`shape="${state.shape}" `); } @@ -369,34 +518,37 @@ export function createDeckStore( if (backLayersString) { parts.push(`back-layers="${backLayersString}" `); } - parts.push('}'); - return parts.join(''); + parts.push("}"); + return parts.join(""); }; const copyCode = async (backLayersStr?: string) => { const code = generateCode(backLayersStr); try { await navigator.clipboard.writeText(code); - alert('已复制到剪贴板!'); + alert("已复制到剪贴板!"); } catch (err) { - console.error('复制失败:', err); - alert('复制失败,请手动复制'); + console.error("复制失败:", err); + alert("复制失败,请手动复制"); } }; - const setExporting = (exporting: boolean) => setState({ isExporting: exporting }); + const setExporting = (exporting: boolean) => + setState({ isExporting: exporting }); const exportDeck = () => { setState({ isExporting: true, exportProgress: 0, exportError: null }); }; - const setExportProgress = (progress: number) => setState({ exportProgress: progress }); + const setExportProgress = (progress: number) => + setState({ exportProgress: progress }); - const setExportError = (error: string | null) => setState({ exportError: error }); + const setExportError = (error: string | null) => + setState({ exportError: error }); const clearExportError = () => setState({ exportError: null }); - const setPrintOrientation = (orientation: 'portrait' | 'landscape') => { + const setPrintOrientation = (orientation: "portrait" | "landscape") => { setState({ printOrientation: orientation }); }; @@ -430,6 +582,9 @@ export function createDeckStore( setBackLayerConfigs, updateBackLayerConfig, toggleBackLayerVisible, + addLayer, + removeLayer, + reorderLayers, setIsEditing, setSelectedLayer, setActiveSide, @@ -454,8 +609,8 @@ export function createDeckStore( setPrintOrientation, setPrintFrontOddPageOffsetX, setPrintFrontOddPageOffsetY, - setPrintDoubleSided + setPrintDoubleSided, }; return { state, actions }; -} \ No newline at end of file +} diff --git a/src/components/md-deck/hooks/layer-parser.ts b/src/components/md-deck/hooks/layer-parser.ts index 4404914..7f50642 100644 --- a/src/components/md-deck/hooks/layer-parser.ts +++ b/src/components/md-deck/hooks/layer-parser.ts @@ -1,5 +1,5 @@ -import type { Layer, LayerConfig } from '../types'; -import {CSV} from "../../utils/csv-loader"; +import type { Layer, LayerConfig } from "../types"; +import { CSV } from "../../utils/csv-loader"; /** * 解析 layers 字符串 @@ -11,7 +11,8 @@ export function parseLayers(layersStr: string): Layer[] { const layers: Layer[] = []; // 匹配:prop:x1,y1-x2,y2[ffontSize][direction][align] - const regex = /([^: ]+):(\d+),(\d+)-(\d+),(\d+)(?:f([\d.]+))?([nsew])?([lcr])?/g; + const regex = + /([^: ]+):(\d+),(\d+)-(\d+),(\d+)(?:f([\d.]+))?([nsew])?([lcr])?/g; let match; while ((match = regex.exec(layersStr)) !== null) { @@ -22,8 +23,8 @@ export function parseLayers(layersStr: string): Layer[] { x2: parseInt(match[4]), y2: parseInt(match[5]), fontSize: match[6] ? parseFloat(match[6]) : undefined, - orientation: match[7] as 'n' | 's' | 'e' | 'w' | undefined, - align: match[8] as 'l' | 'c' | 'r' | undefined + orientation: match[7] as "n" | "s" | "e" | "w" | undefined, + align: match[8] as "l" | "c" | "r" | undefined, }); } @@ -35,13 +36,13 @@ export function parseLayers(layersStr: string): Layer[] { */ export function formatLayers(layers: LayerConfig[]): string { return layers - .filter(l => l.visible) - .map(l => { + .filter((l) => l.visible) + .map((l) => { let str = `${l.prop}:${l.x1},${l.y1}-${l.x2},${l.y2}`; if (l.fontSize) { str += `f${l.fontSize}`; } - if (l.orientation && l.orientation !== 'n') { + if (l.orientation && l.orientation !== "n") { str += l.orientation; } if (l.align) { @@ -49,33 +50,29 @@ export function formatLayers(layers: LayerConfig[]): string { } return str; }) - .join(' '); + .join(" "); } /** * 初始化图层配置(用于特定面) */ export function initLayerConfigsForSide( - data: CSV, - layersStr: string + _data: CSV, + layersStr: string, ): LayerConfig[] { const parsed = parseLayers(layersStr); - const allProps = Object.keys(data[0] || {}).filter(k => k !== 'label'); - return allProps.map(prop => { - const existing = parsed.find(l => l.prop === prop); - return { - prop, - visible: !!existing, - x1: existing?.x1 || 1, - y1: existing?.y1 || 1, - x2: existing?.x2 || 2, - y2: existing?.y2 || 2, - orientation: existing?.orientation, - fontSize: existing?.fontSize, - align: existing?.align - }; - }); + return parsed.map((layer) => ({ + prop: layer.prop, + visible: true, + x1: layer.x1 || 1, + y1: layer.y1 || 1, + x2: layer.x2 || 2, + y2: layer.y2 || 2, + orientation: layer.orientation, + fontSize: layer.fontSize, + align: layer.align, + })); } /** @@ -83,7 +80,7 @@ export function initLayerConfigsForSide( */ export function initLayerConfigs( data: CSV, - existingLayersStr: string + existingLayersStr: string, ): LayerConfig[] { return initLayerConfigsForSide(data, existingLayersStr); } diff --git a/src/components/md-deck/hooks/useLayerInteraction.ts b/src/components/md-deck/hooks/useLayerInteraction.ts index 4eee2e6..8089daf 100644 --- a/src/components/md-deck/hooks/useLayerInteraction.ts +++ b/src/components/md-deck/hooks/useLayerInteraction.ts @@ -1,6 +1,6 @@ -import { createMemo } from 'solid-js'; -import type { DeckStore, DraggingState } from './deckStore'; -import type { LayerConfig } from '../types'; +import { createMemo } from "solid-js"; +import type { DeckStore, DraggingState } from "./deckStore"; +import type { LayerConfig } from "../types"; export interface LayerClickInfo { layerProp: string; @@ -9,21 +9,38 @@ export interface LayerClickInfo { } export interface LayerInteractionHandlers { - onLayerClick: (layerProp: string, e: MouseEvent) => void; - onFrameMouseDown: (action: 'drag' | 'resize-corner' | 'resize-edge', anchor?: 'nw' | 'ne' | 'sw' | 'se', edge?: 'n' | 's' | 'e' | 'w', e?: MouseEvent) => void; + onLayerClick: (index: number, e: MouseEvent) => void; + onFrameMouseDown: ( + action: "drag" | "resize-corner" | "resize-edge", + anchor?: "nw" | "ne" | "sw" | "se", + edge?: "n" | "s" | "e" | "w", + e?: MouseEvent, + ) => void; onCardMouseMove: (e: MouseEvent, cardEl: HTMLElement) => void; onCardMouseUp: () => void; onCardClick: (e: MouseEvent, cardEl: HTMLElement) => void; getOverlappingLayers: (gridX: number, gridY: number) => LayerConfig[]; } -export function useLayerInteraction(store: DeckStore): LayerInteractionHandlers { - let clickStack: { gridX: number; gridY: number; layers: string[]; currentIndex: number } | null = null; +export function useLayerInteraction( + store: DeckStore, +): LayerInteractionHandlers { + let clickStack: { + gridX: number; + gridY: number; + layers: number[]; + currentIndex: number; + } | null = null; const currentLayerConfigs = () => - store.state.activeSide === 'front' - ? store.state.frontLayerConfigs.filter(l => l.visible) - : store.state.backLayerConfigs.filter(l => l.visible); + store.state.activeSide === "front" + ? store.state.frontLayerConfigs.filter((l) => l.visible) + : store.state.backLayerConfigs.filter((l) => l.visible); + + const allLayerConfigs = () => + store.state.activeSide === "front" + ? store.state.frontLayerConfigs + : store.state.backLayerConfigs; const calculateGridCoords = (e: MouseEvent, cardEl: HTMLElement) => { const dims = store.state.dimensions; @@ -36,39 +53,54 @@ export function useLayerInteraction(store: DeckStore): LayerInteractionHandlers const offsetY = (e.clientY - rect.top) / mmToPxRatio; const gridX = Math.floor((offsetX - dims.gridOriginX) / dims.cellWidth) + 1; - const gridY = Math.floor((offsetY - dims.gridOriginY) / dims.cellHeight) + 1; + const gridY = + Math.floor((offsetY - dims.gridOriginY) / dims.cellHeight) + 1; return { gridX: Math.max(1, Math.min(dims.gridW, gridX)), - gridY: Math.max(1, Math.min(dims.gridH, gridY)) + gridY: Math.max(1, Math.min(dims.gridH, gridY)), }; }; - const isPointInLayer = (gridX: number, gridY: number, layer: LayerConfig): boolean => { - return gridX >= layer.x1 && gridX <= layer.x2 && gridY >= layer.y1 && gridY <= layer.y2; + const isPointInLayer = ( + gridX: number, + gridY: number, + layer: LayerConfig, + ): boolean => { + return ( + gridX >= layer.x1 && + gridX <= layer.x2 && + gridY >= layer.y1 && + gridY <= layer.y2 + ); }; - const getOverlappingLayers = (gridX: number, gridY: number): LayerConfig[] => { - return currentLayerConfigs().filter(layer => isPointInLayer(gridX, gridY, layer)); + const getOverlappingLayers = ( + gridX: number, + gridY: number, + ): LayerConfig[] => { + return currentLayerConfigs().filter((layer) => + isPointInLayer(gridX, gridY, layer), + ); }; - const handleLayerClick = (layerProp: string, e: MouseEvent) => { + const handleLayerClick = (index: number, e: MouseEvent) => { e.stopPropagation(); - + const currentlySelected = store.state.selectedLayer; - - if (currentlySelected === layerProp) { + + if (currentlySelected === index) { store.actions.setSelectedLayer(null); } else { - store.actions.setSelectedLayer(layerProp); + store.actions.setSelectedLayer(index); } - + clickStack = null; }; const handleCardClick = (e: MouseEvent, cardEl: HTMLElement) => { if (store.state.draggingState) return; - + const { gridX, gridY } = calculateGridCoords(e, cardEl); const overlapping = getOverlappingLayers(gridX, gridY); @@ -78,40 +110,55 @@ export function useLayerInteraction(store: DeckStore): LayerInteractionHandlers return; } - const overlappingProps = overlapping.map(l => l.prop); + const allConfigs = allLayerConfigs(); + const overlappingIndices = overlapping + .map((l) => allConfigs.findIndex((c) => c.prop === l.prop && c.visible)) + .filter((i) => i !== -1); - if (clickStack && clickStack.gridX === gridX && clickStack.gridY === gridY) { - clickStack.currentIndex = (clickStack.currentIndex + 1) % overlappingProps.length; - const nextLayer = overlappingProps[clickStack.currentIndex]; - store.actions.setSelectedLayer(nextLayer); + if (overlappingIndices.length === 0) { + store.actions.setSelectedLayer(null); + clickStack = null; + return; + } + + if ( + clickStack && + clickStack.gridX === gridX && + clickStack.gridY === gridY + ) { + clickStack.currentIndex = + (clickStack.currentIndex + 1) % overlappingIndices.length; + const nextIndex = overlappingIndices[clickStack.currentIndex]; + store.actions.setSelectedLayer(nextIndex); } else { clickStack = { gridX, gridY, - layers: overlappingProps, - currentIndex: 0 + layers: overlappingIndices, + currentIndex: 0, }; - store.actions.setSelectedLayer(overlappingProps[0]); + store.actions.setSelectedLayer(overlappingIndices[0]); } }; const handleFrameMouseDown = ( - action: 'drag' | 'resize-corner' | 'resize-edge', - anchor?: 'nw' | 'ne' | 'sw' | 'se', - edge?: 'n' | 's' | 'e' | 'w', - e?: MouseEvent + action: "drag" | "resize-corner" | "resize-edge", + anchor?: "nw" | "ne" | "sw" | "se", + edge?: "n" | "s" | "e" | "w", + e?: MouseEvent, ) => { - if (!store.state.selectedLayer) return; + if (store.state.selectedLayer === null) return; if (e) e.stopPropagation(); - const layer = currentLayerConfigs().find(l => l.prop === store.state.selectedLayer); + const index = store.state.selectedLayer; + const layer = allLayerConfigs()[index]; if (!layer) return; const startX = e?.clientX ?? 0; const startY = e?.clientY ?? 0; const draggingState: DraggingState = { - layer: store.state.selectedLayer, + layer: index, action, anchor, edge, @@ -121,8 +168,8 @@ export function useLayerInteraction(store: DeckStore): LayerInteractionHandlers x1: layer.x1, y1: layer.y1, x2: layer.x2, - y2: layer.y2 - } + y2: layer.y2, + }, }; store.actions.setDraggingState(draggingState); @@ -149,24 +196,42 @@ export function useLayerInteraction(store: DeckStore): LayerInteractionHandlers const startGrid = dragging.startGrid; - if (dragging.action === 'drag') { - store.actions.moveLayer(dragging.layer, Math.round(deltaGridX), Math.round(deltaGridY), startGrid); - } else if (dragging.action === 'resize-corner' && dragging.anchor) { - store.actions.resizeLayerCorner(dragging.layer, dragging.anchor, Math.round(deltaGridX), Math.round(deltaGridY), startGrid); - } else if (dragging.action === 'resize-edge' && dragging.edge) { - const delta = dragging.edge === 'n' || dragging.edge === 's' ? Math.round(deltaGridY) : Math.round(deltaGridX); - store.actions.resizeLayerEdge(dragging.layer, dragging.edge, delta, startGrid); + if (dragging.action === "drag") { + store.actions.moveLayer( + dragging.layer, + Math.round(deltaGridX), + Math.round(deltaGridY), + startGrid, + ); + } else if (dragging.action === "resize-corner" && dragging.anchor) { + store.actions.resizeLayerCorner( + dragging.layer, + dragging.anchor, + Math.round(deltaGridX), + Math.round(deltaGridY), + startGrid, + ); + } else if (dragging.action === "resize-edge" && dragging.edge) { + const delta = + dragging.edge === "n" || dragging.edge === "s" + ? Math.round(deltaGridY) + : Math.round(deltaGridX); + store.actions.resizeLayerEdge( + dragging.layer, + dragging.edge, + delta, + startGrid, + ); } }; const handleCardMouseUp = () => { if (store.state.draggingState) { - const layer = store.state.draggingState.layer; const dragging = store.state.draggingState; - + store.actions.setDraggingState(null); - - const newLayerConfig = currentLayerConfigs().find(l => l.prop === layer); + + const newLayerConfig = allLayerConfigs()[dragging.layer]; if (newLayerConfig && dragging.startGrid) { store.actions.setDraggingState({ ...dragging, @@ -174,8 +239,8 @@ export function useLayerInteraction(store: DeckStore): LayerInteractionHandlers x1: newLayerConfig.x1, y1: newLayerConfig.y1, x2: newLayerConfig.x2, - y2: newLayerConfig.y2 - } + y2: newLayerConfig.y2, + }, }); store.actions.setDraggingState(null); } @@ -188,6 +253,6 @@ export function useLayerInteraction(store: DeckStore): LayerInteractionHandlers onCardMouseMove: handleCardMouseMove, onCardMouseUp: handleCardMouseUp, onCardClick: handleCardClick, - getOverlappingLayers + getOverlappingLayers, }; -} \ No newline at end of file +}