- Split loadCSV into parseCSVString (content) and loadCSVFromPath (path); drop isCSV/looksLikeCsv heuristics - Delete coerceSparkTables and markedTable label-header magic; plain markdown tables now render as plain tables - Add explicit markdown role=spark-table fence syntax that converts pipe tables to CSV at scan time, with dice-header validation - Map ESM-only github-slugger and csv-parse browser build to CJS in jest config; add content-registry tests
670 lines
18 KiB
TypeScript
670 lines
18 KiB
TypeScript
import { createStore } from "solid-js/store";
|
|
import yaml from "js-yaml";
|
|
import { calculateDimensions } from "./dimensions";
|
|
import { loadCSVFromPath, CSV } from "../../utils/csv-loader";
|
|
import { formatLayers } from "./layer-parser";
|
|
import * as layerCrud from "./layer-crud";
|
|
import type {
|
|
CardData,
|
|
LayerConfig,
|
|
Dimensions,
|
|
CardSide,
|
|
CardShape,
|
|
} from "../types";
|
|
|
|
export const DECK_DEFAULTS = {
|
|
SIZE_W: 54,
|
|
SIZE_H: 86,
|
|
GRID_W: 5,
|
|
GRID_H: 8,
|
|
BLEED: 1,
|
|
PADDING: 2,
|
|
CORNER_RADIUS: 3,
|
|
} as const;
|
|
|
|
export interface DraggingState {
|
|
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 };
|
|
}
|
|
|
|
export interface DeckState {
|
|
sizeW: number;
|
|
sizeH: number;
|
|
gridW: number;
|
|
gridH: number;
|
|
bleed: number;
|
|
padding: number;
|
|
cornerRadius: number;
|
|
shape: CardShape;
|
|
fixed: boolean;
|
|
/** True when the deck was configured via a yaml role=tag codeblock (data-config). */
|
|
isYamlBlock: boolean;
|
|
src: string;
|
|
rawSrc: string;
|
|
|
|
dimensions: Dimensions | null;
|
|
|
|
cards: CSV<CardData>;
|
|
activeTab: number;
|
|
|
|
frontLayerConfigs: LayerConfig[];
|
|
backLayerConfigs: LayerConfig[];
|
|
|
|
isEditing: boolean;
|
|
selectedLayer: number | null;
|
|
activeSide: CardSide;
|
|
|
|
isSelecting: boolean;
|
|
selectStart: { x: number; y: number } | null;
|
|
selectEnd: { x: number; y: number } | null;
|
|
|
|
draggingState: DraggingState | null;
|
|
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
|
|
isExporting: boolean;
|
|
exportProgress: number;
|
|
exportError: string | null;
|
|
|
|
printOrientation: "portrait" | "landscape";
|
|
printFrontOddPageOffsetX: number;
|
|
printFrontOddPageOffsetY: number;
|
|
printDoubleSided: boolean;
|
|
pltDoubleCut: boolean;
|
|
}
|
|
|
|
export interface DeckActions {
|
|
setSizeW: (size: number) => void;
|
|
setSizeH: (size: number) => void;
|
|
setGridW: (grid: number) => void;
|
|
setGridH: (grid: number) => void;
|
|
setBleed: (bleed: number) => void;
|
|
setPadding: (padding: number) => void;
|
|
setCornerRadius: (cornerRadius: number) => void;
|
|
setShape: (shape: CardShape) => void;
|
|
setIsYamlBlock: (isYamlBlock: boolean) => void;
|
|
|
|
setCards: (cards: CSV<CardData>) => void;
|
|
setActiveTab: (index: number) => void;
|
|
updateCardData: (index: number, key: string, value: string) => void;
|
|
|
|
setFrontLayerConfigs: (configs: LayerConfig[]) => void;
|
|
updateFrontLayerConfig: (
|
|
index: number,
|
|
updates: Partial<LayerConfig>,
|
|
) => void;
|
|
toggleFrontLayerVisible: (index: number) => void;
|
|
|
|
setBackLayerConfigs: (configs: LayerConfig[]) => void;
|
|
updateBackLayerConfig: (index: number, updates: Partial<LayerConfig>) => 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: number | null) => void;
|
|
setActiveSide: (side: CardSide) => void;
|
|
|
|
setIsSelecting: (selecting: boolean) => void;
|
|
setSelectStart: (pos: { x: number; y: number } | null) => void;
|
|
setSelectEnd: (pos: { x: number; y: number } | null) => void;
|
|
cancelSelection: () => void;
|
|
|
|
setDraggingState: (state: DraggingState | null) => 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,
|
|
frontLayers?: LayerConfig[],
|
|
backLayers?: LayerConfig[],
|
|
) => Promise<void>;
|
|
setError: (error: string | null) => void;
|
|
clearError: () => void;
|
|
|
|
generateCode: (backLayersStr?: string) => string;
|
|
copyCode: (fallback?: (code: string) => void) => Promise<void>;
|
|
|
|
setExporting: (exporting: boolean) => void;
|
|
exportDeck: () => void;
|
|
setExportProgress: (progress: number) => void;
|
|
setExportError: (error: string | null) => void;
|
|
clearExportError: () => void;
|
|
|
|
setPrintOrientation: (orientation: "portrait" | "landscape") => void;
|
|
setPrintFrontOddPageOffsetX: (offset: number) => void;
|
|
setPrintFrontOddPageOffsetY: (offset: number) => void;
|
|
setPrintDoubleSided: (doubleSided: boolean) => void;
|
|
setPltDoubleCut: (doubleCut: boolean) => void;
|
|
}
|
|
|
|
export interface DeckStore {
|
|
state: DeckState;
|
|
actions: DeckActions;
|
|
}
|
|
|
|
export function createDeckStore(initialSrc: string = ""): DeckStore {
|
|
const [state, setState] = createStore<DeckState>({
|
|
sizeW: DECK_DEFAULTS.SIZE_W,
|
|
sizeH: DECK_DEFAULTS.SIZE_H,
|
|
gridW: DECK_DEFAULTS.GRID_W,
|
|
gridH: DECK_DEFAULTS.GRID_H,
|
|
bleed: DECK_DEFAULTS.BLEED,
|
|
padding: DECK_DEFAULTS.PADDING,
|
|
cornerRadius: DECK_DEFAULTS.CORNER_RADIUS,
|
|
shape: "rectangle",
|
|
fixed: false,
|
|
isYamlBlock: false,
|
|
src: initialSrc,
|
|
rawSrc: initialSrc,
|
|
dimensions: null,
|
|
cards: [] as any,
|
|
activeTab: 0,
|
|
frontLayerConfigs: [],
|
|
backLayerConfigs: [],
|
|
isEditing: false,
|
|
selectedLayer: null,
|
|
activeSide: "front",
|
|
isSelecting: false,
|
|
selectStart: null,
|
|
selectEnd: null,
|
|
draggingState: null,
|
|
isLoading: false,
|
|
error: null,
|
|
isExporting: false,
|
|
exportProgress: 0,
|
|
exportError: null,
|
|
printOrientation: "portrait",
|
|
printFrontOddPageOffsetX: 0,
|
|
printFrontOddPageOffsetY: 0,
|
|
printDoubleSided: false,
|
|
pltDoubleCut: false,
|
|
});
|
|
|
|
const updateDimensions = () => {
|
|
const dims = calculateDimensions({
|
|
sizeW: state.sizeW,
|
|
sizeH: state.sizeH,
|
|
gridW: state.gridW,
|
|
gridH: state.gridH,
|
|
bleed: state.bleed,
|
|
padding: state.padding,
|
|
});
|
|
setState({ dimensions: dims });
|
|
};
|
|
|
|
const setSizeW = (size: number) => {
|
|
setState({ sizeW: size });
|
|
updateDimensions();
|
|
};
|
|
const setSizeH = (size: number) => {
|
|
setState({ sizeH: size });
|
|
updateDimensions();
|
|
};
|
|
const setGridW = (grid: number) => {
|
|
setState({ gridW: grid });
|
|
updateDimensions();
|
|
};
|
|
const setGridH = (grid: number) => {
|
|
setState({ gridH: grid });
|
|
updateDimensions();
|
|
};
|
|
const setBleed = (bleed: number) => {
|
|
setState({ bleed });
|
|
updateDimensions();
|
|
};
|
|
const setPadding = (padding: number) => {
|
|
setState({ padding });
|
|
updateDimensions();
|
|
};
|
|
const setCornerRadius = (cornerRadius: number) => {
|
|
setState({ cornerRadius });
|
|
};
|
|
const setShape = (shape: CardShape) => {
|
|
setState({ shape });
|
|
};
|
|
const setIsYamlBlock = (isYamlBlock: boolean) => {
|
|
setState({ isYamlBlock });
|
|
};
|
|
|
|
const setCards = (cards: CSV<CardData>) => setState({ cards, activeTab: 0 });
|
|
const setActiveTab = (index: number) => setState({ activeTab: index });
|
|
const updateCardData = (index: number, key: string, value: string) => {
|
|
setState("cards", index, key, value);
|
|
};
|
|
|
|
const setFrontLayerConfigs = (configs: LayerConfig[]) =>
|
|
setState({ frontLayerConfigs: configs });
|
|
const updateFrontLayerConfig = (
|
|
index: number,
|
|
updates: Partial<LayerConfig>,
|
|
) => {
|
|
setState("frontLayerConfigs", index, (config) => ({
|
|
...config,
|
|
...updates,
|
|
}));
|
|
};
|
|
const toggleFrontLayerVisible = (index: number) => {
|
|
setState("frontLayerConfigs", index, "visible", (v) => !v);
|
|
};
|
|
|
|
const setBackLayerConfigs = (configs: LayerConfig[]) =>
|
|
setState({ backLayerConfigs: configs });
|
|
const updateBackLayerConfig = (
|
|
index: number,
|
|
updates: Partial<LayerConfig>,
|
|
) => {
|
|
setState("backLayerConfigs", index, (config) => ({
|
|
...config,
|
|
...updates,
|
|
}));
|
|
};
|
|
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 configs = getLayerConfigs(side);
|
|
setLayerConfigs(side, layerCrud.addLayer(configs, prop));
|
|
setState({
|
|
selectedLayer: layerCrud.adjustSelectedLayer(
|
|
state.selectedLayer,
|
|
"add",
|
|
configs.length,
|
|
),
|
|
});
|
|
};
|
|
|
|
const removeLayer = (side: CardSide, index: number) => {
|
|
setLayerConfigs(side, layerCrud.removeLayer(getLayerConfigs(side), index));
|
|
setState({
|
|
selectedLayer: layerCrud.adjustSelectedLayer(
|
|
state.selectedLayer,
|
|
"remove",
|
|
index,
|
|
),
|
|
});
|
|
};
|
|
|
|
const reorderLayers = (side: CardSide, from: number, to: number) => {
|
|
setLayerConfigs(
|
|
side,
|
|
layerCrud.reorderLayers(getLayerConfigs(side), from, to),
|
|
);
|
|
setState({
|
|
selectedLayer: layerCrud.adjustSelectedLayer(
|
|
state.selectedLayer,
|
|
"reorder",
|
|
from,
|
|
to,
|
|
),
|
|
});
|
|
};
|
|
|
|
const setIsEditing = (editing: boolean) => setState({ isEditing: editing });
|
|
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 cancelSelection = () => {
|
|
setState({ isSelecting: false, selectStart: null, selectEnd: null });
|
|
};
|
|
|
|
const setDraggingState = (draggingState: DraggingState | null) =>
|
|
setState({ draggingState });
|
|
|
|
const getLayerConfig = (index: number): LayerConfig | undefined => {
|
|
const configs =
|
|
state.activeSide === "front"
|
|
? state.frontLayerConfigs
|
|
: state.backLayerConfigs;
|
|
return configs[index];
|
|
};
|
|
|
|
const updateLayerConfig = (index: number, updates: Partial<LayerConfig>) => {
|
|
if (state.activeSide === "front") {
|
|
updateFrontLayerConfig(index, updates);
|
|
} else {
|
|
updateBackLayerConfig(index, updates);
|
|
}
|
|
};
|
|
|
|
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(index, {
|
|
x1: grid.x1 + dxGrid,
|
|
x2: grid.x2 + dxGrid,
|
|
y1: grid.y1 + dyGrid,
|
|
y2: grid.y2 + dyGrid,
|
|
});
|
|
};
|
|
|
|
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<LayerConfig> = {};
|
|
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") {
|
|
updates.x2 = Math.max(startGrid.x1, startGrid.x2 + dxGrid);
|
|
updates.y1 = Math.min(startGrid.y2, startGrid.y1 + dyGrid);
|
|
} 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") {
|
|
updates.x2 = Math.max(startGrid.x1, startGrid.x2 + dxGrid);
|
|
updates.y2 = Math.max(startGrid.y1, startGrid.y2 + dyGrid);
|
|
}
|
|
updateLayerConfig(index, updates);
|
|
};
|
|
|
|
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<LayerConfig> = {};
|
|
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,
|
|
frontLayers: LayerConfig[] = [],
|
|
backLayers: LayerConfig[] = [],
|
|
) => {
|
|
if (!path) {
|
|
setState({ error: "未指定 CSV 文件路径" });
|
|
return;
|
|
}
|
|
|
|
setState({ isLoading: true, error: null, src: path, rawSrc: rawSrc });
|
|
|
|
try {
|
|
const data = await loadCSVFromPath(path);
|
|
|
|
if (data.length === 0) {
|
|
setState({
|
|
error: `CSV ${path} 文件为空或格式不正确`,
|
|
isLoading: false,
|
|
});
|
|
return;
|
|
}
|
|
|
|
setState({
|
|
cards: data,
|
|
activeTab: 0,
|
|
frontLayerConfigs: layerCrud.withKeys(frontLayers),
|
|
backLayerConfigs: layerCrud.withKeys(backLayers),
|
|
isLoading: false,
|
|
});
|
|
updateDimensions();
|
|
} catch (err) {
|
|
setState({
|
|
error: `加载 CSV ${path} 失败:${err instanceof Error ? err.message : "未知错误"}`,
|
|
isLoading: false,
|
|
});
|
|
}
|
|
};
|
|
|
|
const setError = (error: string | null) => setState({ error });
|
|
const clearError = () => setState({ error: null });
|
|
|
|
const generateCode = (backLayersStr?: string) => {
|
|
if (state.isYamlBlock) {
|
|
return generateYamlCode();
|
|
}
|
|
const frontLayersStr = formatLayers(state.frontLayerConfigs);
|
|
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}" `,
|
|
];
|
|
|
|
if (state.bleed !== DECK_DEFAULTS.BLEED) {
|
|
parts.push(`bleed="${state.bleed}" `);
|
|
}
|
|
if (state.padding !== DECK_DEFAULTS.PADDING) {
|
|
parts.push(`padding="${state.padding}" `);
|
|
}
|
|
if (state.shape !== "rectangle") {
|
|
parts.push(`shape="${state.shape}" `);
|
|
}
|
|
|
|
parts.push(`layers="${frontLayersStr}" `);
|
|
if (backLayersString) {
|
|
parts.push(`back-layers="${backLayersString}" `);
|
|
}
|
|
parts.push("}");
|
|
return parts.join("");
|
|
};
|
|
|
|
/** Serialize the deck back to a yaml codeblock (round-trips templates). */
|
|
const generateYamlCode = () => {
|
|
const toYamlLayer = (l: LayerConfig) => {
|
|
const out: Record<string, unknown> = {};
|
|
if (l.template) {
|
|
out.template = l.template;
|
|
} else {
|
|
out.prop = l.prop ?? "";
|
|
}
|
|
out.pos = `${l.x1},${l.y1}-${l.x2},${l.y2}`;
|
|
if (l.fontSize) out.font = l.fontSize;
|
|
if (l.orientation && l.orientation !== "n") out.orientation = l.orientation;
|
|
if (l.align) out.align = l.align;
|
|
if (!l.visible) out.visible = false;
|
|
return out;
|
|
};
|
|
|
|
const config: Record<string, unknown> = {
|
|
size: `${state.sizeW}x${state.sizeH}`,
|
|
grid: `${state.gridW}x${state.gridH}`,
|
|
layers: state.frontLayerConfigs.map(toYamlLayer),
|
|
};
|
|
if (state.bleed !== DECK_DEFAULTS.BLEED) config.bleed = state.bleed;
|
|
if (state.padding !== DECK_DEFAULTS.PADDING) config.padding = state.padding;
|
|
if (state.shape !== "rectangle") config.shape = state.shape;
|
|
if (state.backLayerConfigs.length > 0) {
|
|
config.back_layers = state.backLayerConfigs.map(toYamlLayer);
|
|
}
|
|
|
|
const doc = {
|
|
tag: "md-deck",
|
|
body: state.rawSrc || state.src,
|
|
"data-config": config,
|
|
};
|
|
const yamlStr = yaml.dump(doc, {
|
|
indent: 2,
|
|
lineWidth: -1,
|
|
noRefs: true,
|
|
});
|
|
const fence = "```yaml role=tag";
|
|
const backtick = "`";
|
|
return `${fence}\n${yamlStr}${backtick.repeat(3)}`;
|
|
};
|
|
|
|
const copyCode = async (fallback?: (code: string) => void) => {
|
|
const code = generateCode();
|
|
try {
|
|
await navigator.clipboard.writeText(code);
|
|
alert("已复制到剪贴板!");
|
|
} catch (err) {
|
|
console.error("复制失败:", err);
|
|
if (fallback) {
|
|
fallback(code);
|
|
} else {
|
|
alert("复制失败,请手动复制");
|
|
}
|
|
}
|
|
};
|
|
|
|
const setExporting = (exporting: boolean) =>
|
|
setState({ isExporting: exporting });
|
|
|
|
const exportDeck = () => {
|
|
setState({ isExporting: true, exportProgress: 0, exportError: null });
|
|
};
|
|
|
|
const setExportProgress = (progress: number) =>
|
|
setState({ exportProgress: progress });
|
|
|
|
const setExportError = (error: string | null) =>
|
|
setState({ exportError: error });
|
|
|
|
const clearExportError = () => setState({ exportError: null });
|
|
|
|
const setPrintOrientation = (orientation: "portrait" | "landscape") => {
|
|
setState({ printOrientation: orientation });
|
|
};
|
|
|
|
const setPrintFrontOddPageOffsetX = (offset: number) => {
|
|
setState({ printFrontOddPageOffsetX: offset });
|
|
};
|
|
|
|
const setPrintFrontOddPageOffsetY = (offset: number) => {
|
|
setState({ printFrontOddPageOffsetY: offset });
|
|
};
|
|
|
|
const setPrintDoubleSided = (doubleSided: boolean) => {
|
|
setState({ printDoubleSided: doubleSided });
|
|
};
|
|
|
|
const setPltDoubleCut = (doubleCut: boolean) => {
|
|
setState({ pltDoubleCut: doubleCut });
|
|
};
|
|
|
|
const actions: DeckActions = {
|
|
setSizeW,
|
|
setSizeH,
|
|
setGridW,
|
|
setGridH,
|
|
setBleed,
|
|
setPadding,
|
|
setCornerRadius,
|
|
setShape,
|
|
setIsYamlBlock,
|
|
setCards,
|
|
setActiveTab,
|
|
updateCardData,
|
|
setFrontLayerConfigs,
|
|
updateFrontLayerConfig,
|
|
toggleFrontLayerVisible,
|
|
setBackLayerConfigs,
|
|
updateBackLayerConfig,
|
|
toggleBackLayerVisible,
|
|
addLayer,
|
|
removeLayer,
|
|
reorderLayers,
|
|
setIsEditing,
|
|
setSelectedLayer,
|
|
setActiveSide,
|
|
setIsSelecting,
|
|
setSelectStart,
|
|
setSelectEnd,
|
|
cancelSelection,
|
|
setDraggingState,
|
|
moveLayer,
|
|
resizeLayerCorner,
|
|
resizeLayerEdge,
|
|
loadCardsFromPath,
|
|
setError,
|
|
clearError,
|
|
generateCode,
|
|
copyCode,
|
|
setExporting,
|
|
exportDeck,
|
|
setExportProgress,
|
|
setExportError,
|
|
clearExportError,
|
|
setPrintOrientation,
|
|
setPrintFrontOddPageOffsetX,
|
|
setPrintFrontOddPageOffsetY,
|
|
setPrintDoubleSided,
|
|
setPltDoubleCut,
|
|
};
|
|
|
|
return { state, actions };
|
|
}
|