diff --git a/src/components/card-preview.tsx b/src/components/card-preview.tsx index 31280fd..5db8861 100644 --- a/src/components/card-preview.tsx +++ b/src/components/card-preview.tsx @@ -1,56 +1,48 @@ import { Show, For } from 'solid-js'; import { marked } from '../markdown'; -import type { CardData, LayerConfig, Dimensions } from './types'; import { getLayerStyle } from './utils/dimensions'; -import { getSelectionBoxStyle } from './hooks/use-selection'; +import {getSelectionBoxStyle, useSelection} from './hooks/use-selection'; +import {DeckStore} from "./stores/deckStore"; export interface CardPreviewProps { - cards: CardData[]; - activeTab: number; - layerConfigs: LayerConfig[]; - dimensions: Dimensions; - isEditing: boolean; - isFixed: boolean; - editingLayer: string | null; - isSelecting: boolean; - selectStart: { x: number; y: number } | null; - selectEnd: { x: number; y: number } | null; - onMouseDown: (e: MouseEvent) => void; - onMouseMove: (e: MouseEvent) => void; - onMouseUp: () => void; - onMouseLeave: () => void; + store: DeckStore; } /** * 渲染 layer 内容 */ -function renderLayer(layer: { prop: string }, cardData: CardData): string { +function renderLayer(layer: { prop: string }, cardData: DeckStore['cards'][number]): string { const content = cardData[layer.prop] || ''; return marked.parse(content) as string; } export function CardPreview(props: CardPreviewProps) { - const currentCard = () => props.cards[props.activeTab]; - const visibleLayers = () => props.layerConfigs.filter(l => l.visible); + const currentCard = () => props.store.cards[props.store.activeTab]; + const visibleLayers = () => props.store.layerConfigs.filter((l) => l.visible); const selectionStyle = () => - getSelectionBoxStyle(props.selectStart, props.selectEnd, props.dimensions); + getSelectionBoxStyle(props.store.selectStart, props.store.selectEnd, props.store.dimensions); + + const selection = useSelection(props.store); + + let cardRef: HTMLDivElement | undefined; return (
- +
selection.onMouseDown(e, cardRef!)} + onMouseMove={(e) => selection.onMouseMove(e, cardRef!)} + onMouseUp={selection.onMouseUp} + onMouseLeave={selection.onMouseLeave} > {/* 框选遮罩 */} - +
{/* 编辑模式下的网格线 */} - +
- + {(_, i) => (
)} - + {(_, i) => (
)} @@ -92,12 +84,12 @@ export function CardPreview(props: CardPreviewProps) { {/* 渲染每个 layer */} {(layer) => { - const style = getLayerStyle(layer, props.dimensions); + const style = getLayerStyle(layer, props.store.dimensions!); return (
void; + cards: DeckStore['cards']; + updateCardData: DeckStore['updateCardData']; } export interface PropertiesEditorPanelProps { - localSize: string; - localGrid: string; - localBleed: string; - localPadding: string; - layerConfigs: LayerConfig[]; - editingLayer: string | null; - onSizeChange: (value: string) => void; - onGridChange: (value: string) => void; - onBleedChange: (value: string) => void; - onPaddingChange: (value: string) => void; - onToggleLayerVisible: (prop: string) => void; - onStartEditingLayer: (prop: string) => void; - onCopyCode: () => void; + store: DeckStore; } /** @@ -39,7 +27,7 @@ export function DataEditorPanel(props: DataEditorPanelProps) { class="w-full border border-gray-300 rounded px-2 py-1 text-sm" rows={3} value={props.cards[props.activeTab]?.[key] || ''} - onInput={(e) => props.updateCardData(key, e.target.value)} + onInput={(e) => props.updateCardData(props.activeTab, key, e.target.value)} />
)} @@ -53,6 +41,8 @@ export function DataEditorPanel(props: DataEditorPanelProps) { * 右侧:卡牌属性编辑面板 */ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) { + const { store } = props; + return (

卡牌属性

@@ -63,8 +53,8 @@ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) { props.onSizeChange(e.target.value)} + value={store.size} + onInput={(e) => store.setSize(e.target.value)} />
@@ -73,8 +63,8 @@ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) { props.onGridChange(e.target.value)} + value={store.grid} + onInput={(e) => store.setGrid(e.target.value)} />
@@ -83,8 +73,8 @@ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) { props.onBleedChange(e.target.value)} + value={store.bleed} + onInput={(e) => store.setBleed(e.target.value)} />
@@ -93,33 +83,33 @@ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) { props.onPaddingChange(e.target.value)} + value={store.padding} + onInput={(e) => store.setPadding(e.target.value)} />

图层

- + {(layer) => (
props.onToggleLayerVisible(layer.prop)} + onChange={() => store.toggleLayerVisible(layer.prop)} class="cursor-pointer" /> {layer.prop}
)} @@ -128,7 +118,7 @@ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) {
-
- +
+ {(card, index) => (
{/* 卡牌预览 */} - 0}> - + 0}> +
{/* 右侧:属性编辑表单 */} - - + +
); diff --git a/src/components/stores/deckStore.ts b/src/components/stores/deckStore.ts new file mode 100644 index 0000000..a84ede5 --- /dev/null +++ b/src/components/stores/deckStore.ts @@ -0,0 +1,182 @@ +import { createStore, produce } from 'solid-js/store'; +import type { CardData, LayerConfig, Dimensions } from '../types'; + +export interface DeckState { + // 基本属性 + size: string; + grid: string; + bleed: string; + padding: string; + fixed: boolean; + src: string; + + // 解析后的尺寸 + dimensions: Dimensions | null; + + // 卡牌数据 + cards: CardData[]; + activeTab: number; + + // 图层配置 + layerConfigs: LayerConfig[]; + + // 编辑状态 + isEditing: boolean; + editingLayer: string | null; + + // 框选状态 + isSelecting: boolean; + selectStart: { x: number; y: number } | null; + selectEnd: { x: number; y: number } | null; +} + +export interface DeckActions { + // 基本属性设置 + setSize: (size: string) => void; + setGrid: (grid: string) => void; + setBleed: (bleed: string) => void; + setPadding: (padding: string) => void; + + // 数据设置 + setCards: (cards: CardData[]) => void; + setActiveTab: (index: number) => void; + updateCardData: (index: number, key: string, value: string) => void; + + // 图层操作 + setLayerConfigs: (configs: LayerConfig[]) => void; + updateLayerConfig: (prop: string, updates: Partial) => void; + toggleLayerVisible: (prop: string) => void; + + // 编辑状态 + setIsEditing: (editing: boolean) => void; + setEditingLayer: (layer: string | null) => void; + updateLayerPosition: (x1: number, y1: number, x2: number, y2: number) => void; + + // 框选操作 + setIsSelecting: (selecting: boolean) => void; + setSelectStart: (pos: { x: number; y: number } | null) => void; + setSelectEnd: (pos: { x: number; y: number } | null) => void; + cancelSelection: () => void; + + // 初始化 + initialize: (props: Record, csvPath: string) => void; + + // 生成代码 + generateCode: () => string; + copyCode: () => void; +} + +export interface DeckStore extends DeckState, DeckActions {} + +/** + * 创建 deck store + */ +export function createDeckStore(): DeckStore { + const [state, setState] = createStore({ + size: '54x86', + grid: '5x8', + bleed: '1', + padding: '2', + fixed: false, + src: '', + dimensions: null, + cards: [], + activeTab: 0, + layerConfigs: [], + isEditing: false, + editingLayer: null, + isSelecting: false, + selectStart: null, + selectEnd: null + }); + + const setSize = (size: string) => setState({ size }); + const setGrid = (grid: string) => setState({ grid }); + const setBleed = (bleed: string) => setState({ bleed }); + const setPadding = (padding: string) => setState({ padding }); + + const setCards = (cards: CardData[]) => setState({ cards }); + const setActiveTab = (index: number) => setState({ activeTab: index }); + const updateCardData = (index: number, key: string, value: string) => { + setState('cards', index, key, value); + }; + + const setLayerConfigs = (configs: LayerConfig[]) => setState({ layerConfigs: configs }); + const updateLayerConfig = (prop: string, updates: Partial) => { + setState('layerConfigs', (prev) => prev.map((config) => config.prop === prop ? { ...config, ...updates } : config)); + }; + const toggleLayerVisible = (prop: string) => { + setState('layerConfigs', (prev) => prev.map((config) => + config.prop === prop ? { ...config, visible: !config.visible } : config + )); + }; + + const setIsEditing = (editing: boolean) => setState({ isEditing: editing }); + const setEditingLayer = (layer: string | null) => setState({ editingLayer: layer }); + const updateLayerPosition = (x1: number, y1: number, x2: number, y2: number) => { + const layer = state.editingLayer; + if (!layer) return; + setState('layerConfigs', (prev) => prev.map((config) => + config.prop === layer ? { ...config, x1, y1, x2, y2 } : config + )); + setState({ editingLayer: null }); + }; + + 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 initialize = (props: Record, csvPath: string) => { + setState({ + size: props.size as string || '54x86', + grid: props.grid as string || '5x8', + bleed: props.bleed as string || '1', + padding: props.padding as string || '2', + fixed: props.fixed === true || props.fixed === 'true', + src: csvPath + }); + }; + + const generateCode = () => { + const layersStr = state.layerConfigs + .map(l => `${l.prop}=${l.x1},${l.y1},${l.x2},${l.y2}`) + .join('|'); + return `:md-deck[${state.src}]{size="${state.size}" grid="${state.grid}" bleed="${state.bleed}" padding="${state.padding}" layers="${layersStr}"}`; + }; + + const copyCode = () => { + const code = generateCode(); + navigator.clipboard.writeText(code).then(() => { + alert('已复制到剪贴板!'); + }).catch(err => { + console.error('复制失败:', err); + }); + }; + + return { + ...state, + setSize, + setGrid, + setBleed, + setPadding, + setCards, + setActiveTab, + updateCardData, + setLayerConfigs, + updateLayerConfig, + toggleLayerVisible, + setIsEditing, + setEditingLayer, + updateLayerPosition, + setIsSelecting, + setSelectStart, + setSelectEnd, + cancelSelection, + initialize, + generateCode, + copyCode + }; +}