feat(editor): Add dnd reordering and layer add/remove
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user