diff --git a/src/components/card-preview.tsx b/src/components/card-preview.tsx index 5db8861..6337d79 100644 --- a/src/components/card-preview.tsx +++ b/src/components/card-preview.tsx @@ -1,7 +1,7 @@ import { Show, For } from 'solid-js'; import { marked } from '../markdown'; import { getLayerStyle } from './utils/dimensions'; -import {getSelectionBoxStyle, useSelection} from './hooks/use-selection'; +import {getSelectionBoxStyle, useSelection} from './stores/use-selection'; import {DeckStore} from "./stores/deckStore"; export interface CardPreviewProps { diff --git a/src/components/hooks/types.ts b/src/components/hooks/types.ts deleted file mode 100644 index a5a7301..0000000 --- a/src/components/hooks/types.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * 属性编辑器 Schema 类型定义 - */ - -export type FieldType = 'string' | 'number' | 'boolean' | 'text' | 'select'; - -export interface FieldSchema { - /** 字段名 */ - key: string; - /** 字段类型 */ - type: FieldType; - /** 显示标签 */ - label?: string; - /** 占位符文本 */ - placeholder?: string; - /** 描述/提示文本 */ - description?: string; - /** 是否必需 */ - required?: boolean; - /** 最小值(用于 number 类型) */ - min?: number; - /** 最大值(用于 number 类型) */ - max?: number; - /** 选项(用于 select 类型) */ - options?: { label: string; value: string }[]; - /** 行数(用于 text 类型) */ - rows?: number; - /** 默认值 */ - default?: string | number | boolean; -} - -export interface Schema { - /** Schema 名称 */ - name?: string; - /** 字段定义列表 */ - fields: FieldSchema[]; -} - -/** - * 属性值类型 - */ -export type PropertyValues = Record; - -/** - * usePropertyEditor hook 返回类型 - */ -export interface UsePropertyEditorReturn { - /** 获取属性值 */ - get: (key: K) => T[K]; - /** 设置属性值 */ - set: (key: K, value: T[K]) => void; - /** 获取所有属性值 */ - getAll: () => T; - /** 设置所有属性值 */ - setAll: (values: T) => void; - /** 重置为默认值 */ - reset: () => void; -} diff --git a/src/components/md-deck.tsx b/src/components/md-deck.tsx index 6a6c29e..148d5e3 100644 --- a/src/components/md-deck.tsx +++ b/src/components/md-deck.tsx @@ -1,5 +1,5 @@ import { customElement, noShadowDOM } from 'solid-element'; -import { Show, createEffect, createResource, For } from 'solid-js'; +import { Show, createEffect, createResource, For, onCleanup } from 'solid-js'; import { resolvePath } from './utils/path'; import { loadCSV } from './utils/csv-loader'; import { initLayerConfigs } from './utils/layer-parser'; @@ -7,7 +7,16 @@ import { createDeckStore } from './stores/deckStore'; import { CardPreview } from './card-preview'; import { DataEditorPanel, PropertiesEditorPanel } from './editor-panel'; -customElement('md-deck', { +interface DeckProps { + size?: string; + grid?: string; + bleed?: string; + padding?: string; + layers?: string; + fixed?: boolean | string; +} + +customElement('md-deck', { size: '54x86', grid: '5x8', bleed: '1', @@ -21,7 +30,7 @@ customElement('md-deck', { const store = createDeckStore(); // 从 element 的 textContent 获取 CSV 路径 - const src = element?.textContent?.trim() || ''; + const csvPath = element?.textContent?.trim() || ''; // 隐藏原始文本内容 if (element) { @@ -33,20 +42,37 @@ customElement('md-deck', { const articlePath = articleEl?.getAttribute('data-src') || ''; // 解析相对路径 - const resolvedSrc = resolvePath(articlePath, src); + const resolvedSrc = resolvePath(articlePath, csvPath); - // 初始化 store - store.initialize(props, src); + // 初始化 store 属性 + store.setSize(props.size || '54x86'); + store.setGrid(props.grid || '5x8'); + store.setBleed(props.bleed || '1'); + store.setPadding(props.padding || '2'); // 加载 CSV 文件 - const [csvData] = createResource(() => resolvedSrc, loadCSV); + const [csvData, { refetch }] = createResource(() => resolvedSrc, loadCSV); + // 处理 CSV 数据加载结果 createEffect(() => { - const data = !csvData.loading && csvData(); - if (data) { - store.setCards(data); - store.setLayerConfigs(initLayerConfigs(data, props.layers as string || '')); + const data = csvData(); + const loading = csvData.loading; + const error = csvData.error; + + if (error) { + store.setError(`加载 CSV 失败:${error.message}`); + return; } + + if (!loading && data) { + store.loadCards(data); + store.setLayerConfigs(initLayerConfigs(data, (props.layers as string) || '')); + } + }); + + // 清理函数 + onCleanup(() => { + // 可以在这里清理资源 }); return ( @@ -92,9 +118,30 @@ customElement('md-deck', { + {/* 错误提示 */} + +
+ {store.error} +
+
+ + {/* 加载状态 */} + +
+ 加载卡牌数据中... +
+
+ {/* 卡牌预览 */} - 0}> - + 0 && !store.error}> + + + + {/* 空状态 */} + +
+ 暂无卡牌数据 +
diff --git a/src/components/stores/deckStore.ts b/src/components/stores/deckStore.ts index a84ede5..abe7483 100644 --- a/src/components/stores/deckStore.ts +++ b/src/components/stores/deckStore.ts @@ -1,4 +1,5 @@ -import { createStore, produce } from 'solid-js/store'; +import { createStore } from 'solid-js/store'; +import { calculateDimensions } from '../utils/dimensions'; import type { CardData, LayerConfig, Dimensions } from '../types'; export interface DeckState { @@ -9,25 +10,28 @@ export interface DeckState { 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; + + // 错误状态 + error: string | null; } export interface DeckActions { @@ -36,31 +40,32 @@ export interface DeckActions { 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; - + + // 初始化和数据加载 + loadCards: (cards: CardData[]) => void; + setError: (error: string | null) => void; + // 生成代码 generateCode: () => string; copyCode: () => void; @@ -87,20 +92,44 @@ export function createDeckStore(): DeckStore { editingLayer: null, isSelecting: false, selectStart: null, - selectEnd: null + selectEnd: null, + error: 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 }); + // 更新尺寸并重新计算 dimensions + const updateDimensions = () => { + const dims = calculateDimensions({ + size: state.size, + grid: state.grid, + bleed: state.bleed, + padding: state.padding + }); + setState({ dimensions: dims }); + }; + + const setSize = (size: string) => { + setState({ size }); + updateDimensions(); + }; + const setGrid = (grid: string) => { + setState({ grid }); + updateDimensions(); + }; + const setBleed = (bleed: string) => { + setState({ bleed }); + updateDimensions(); + }; + const setPadding = (padding: string) => { + setState({ padding }); + updateDimensions(); + }; + + const setCards = (cards: 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 setLayerConfigs = (configs: LayerConfig[]) => setState({ layerConfigs: configs }); const updateLayerConfig = (prop: string, updates: Partial) => { setState('layerConfigs', (prev) => prev.map((config) => config.prop === prop ? { ...config, ...updates } : config)); @@ -110,7 +139,7 @@ export function createDeckStore(): DeckStore { 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) => { @@ -121,32 +150,34 @@ export function createDeckStore(): DeckStore { )); 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 - }); + + // 加载卡牌数据并初始化 dimensions 和 layerConfigs + const loadCards = (cards: CardData[]) => { + if (cards.length === 0) { + setState({ error: 'CSV 文件为空或格式不正确' }); + return; + } + setState({ cards, activeTab: 0, error: null }); + updateDimensions(); }; - + + const setError = (error: string | null) => setState({ error }); + const generateCode = () => { const layersStr = state.layerConfigs - .map(l => `${l.prop}=${l.x1},${l.y1},${l.x2},${l.y2}`) - .join('|'); + .filter(l => l.visible) + .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(() => { @@ -175,7 +206,8 @@ export function createDeckStore(): DeckStore { setSelectStart, setSelectEnd, cancelSelection, - initialize, + loadCards, + setError, generateCode, copyCode }; diff --git a/src/components/hooks/use-selection.ts b/src/components/stores/use-selection.ts similarity index 98% rename from src/components/hooks/use-selection.ts rename to src/components/stores/use-selection.ts index 11e6872..479faec 100644 --- a/src/components/hooks/use-selection.ts +++ b/src/components/stores/use-selection.ts @@ -1,4 +1,4 @@ -import type { DeckStore } from '../stores/deckStore'; +import type { DeckStore } from './deckStore'; /** * 框选相关的操作(已整合到 deckStore)