refactor: clean up

This commit is contained in:
hypercross 2026-02-27 14:32:43 +08:00
parent 72285e093f
commit 0aaadea2da
5 changed files with 133 additions and 112 deletions

View File

@ -1,7 +1,7 @@
import { Show, For } from 'solid-js'; import { Show, For } from 'solid-js';
import { marked } from '../markdown'; import { marked } from '../markdown';
import { getLayerStyle } from './utils/dimensions'; import { getLayerStyle } from './utils/dimensions';
import {getSelectionBoxStyle, useSelection} from './hooks/use-selection'; import {getSelectionBoxStyle, useSelection} from './stores/use-selection';
import {DeckStore} from "./stores/deckStore"; import {DeckStore} from "./stores/deckStore";
export interface CardPreviewProps { export interface CardPreviewProps {

View File

@ -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<string, string | number | boolean>;
/**
* usePropertyEditor hook
*/
export interface UsePropertyEditorReturn<T extends PropertyValues> {
/** 获取属性值 */
get: <K extends keyof T>(key: K) => T[K];
/** 设置属性值 */
set: <K extends keyof T>(key: K, value: T[K]) => void;
/** 获取所有属性值 */
getAll: () => T;
/** 设置所有属性值 */
setAll: (values: T) => void;
/** 重置为默认值 */
reset: () => void;
}

View File

@ -1,5 +1,5 @@
import { customElement, noShadowDOM } from 'solid-element'; 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 { resolvePath } from './utils/path';
import { loadCSV } from './utils/csv-loader'; import { loadCSV } from './utils/csv-loader';
import { initLayerConfigs } from './utils/layer-parser'; import { initLayerConfigs } from './utils/layer-parser';
@ -7,7 +7,16 @@ import { createDeckStore } from './stores/deckStore';
import { CardPreview } from './card-preview'; import { CardPreview } from './card-preview';
import { DataEditorPanel, PropertiesEditorPanel } from './editor-panel'; 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<DeckProps>('md-deck', {
size: '54x86', size: '54x86',
grid: '5x8', grid: '5x8',
bleed: '1', bleed: '1',
@ -21,7 +30,7 @@ customElement('md-deck', {
const store = createDeckStore(); const store = createDeckStore();
// 从 element 的 textContent 获取 CSV 路径 // 从 element 的 textContent 获取 CSV 路径
const src = element?.textContent?.trim() || ''; const csvPath = element?.textContent?.trim() || '';
// 隐藏原始文本内容 // 隐藏原始文本内容
if (element) { if (element) {
@ -33,20 +42,37 @@ customElement('md-deck', {
const articlePath = articleEl?.getAttribute('data-src') || ''; const articlePath = articleEl?.getAttribute('data-src') || '';
// 解析相对路径 // 解析相对路径
const resolvedSrc = resolvePath(articlePath, src); const resolvedSrc = resolvePath(articlePath, csvPath);
// 初始化 store // 初始化 store 属性
store.initialize(props, src); store.setSize(props.size || '54x86');
store.setGrid(props.grid || '5x8');
store.setBleed(props.bleed || '1');
store.setPadding(props.padding || '2');
// 加载 CSV 文件 // 加载 CSV 文件
const [csvData] = createResource(() => resolvedSrc, loadCSV); const [csvData, { refetch }] = createResource(() => resolvedSrc, loadCSV);
// 处理 CSV 数据加载结果
createEffect(() => { createEffect(() => {
const data = !csvData.loading && csvData(); const data = csvData();
if (data) { const loading = csvData.loading;
store.setCards(data); const error = csvData.error;
store.setLayerConfigs(initLayerConfigs(data, props.layers as string || ''));
if (error) {
store.setError(`加载 CSV 失败:${error.message}`);
return;
} }
if (!loading && data) {
store.loadCards(data);
store.setLayerConfigs(initLayerConfigs(data, (props.layers as string) || ''));
}
});
// 清理函数
onCleanup(() => {
// 可以在这里清理资源
}); });
return ( return (
@ -92,9 +118,30 @@ customElement('md-deck', {
</div> </div>
</div> </div>
{/* 错误提示 */}
<Show when={store.error}>
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded mb-4">
{store.error}
</div>
</Show>
{/* 加载状态 */}
<Show when={csvData.loading}>
<div class="text-center text-gray-500 py-8">
...
</div>
</Show>
{/* 卡牌预览 */} {/* 卡牌预览 */}
<Show when={!csvData.loading && store.cards.length > 0}> <Show when={!csvData.loading && store.cards.length > 0 && !store.error}>
<CardPreview store={store}/> <CardPreview store={store} />
</Show>
{/* 空状态 */}
<Show when={!csvData.loading && store.cards.length === 0 && !store.error}>
<div class="text-center text-gray-500 py-8">
</div>
</Show> </Show>
</div> </div>

View File

@ -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'; import type { CardData, LayerConfig, Dimensions } from '../types';
export interface DeckState { export interface DeckState {
@ -9,25 +10,28 @@ export interface DeckState {
padding: string; padding: string;
fixed: boolean; fixed: boolean;
src: string; src: string;
// 解析后的尺寸 // 解析后的尺寸
dimensions: Dimensions | null; dimensions: Dimensions | null;
// 卡牌数据 // 卡牌数据
cards: CardData[]; cards: CardData[];
activeTab: number; activeTab: number;
// 图层配置 // 图层配置
layerConfigs: LayerConfig[]; layerConfigs: LayerConfig[];
// 编辑状态 // 编辑状态
isEditing: boolean; isEditing: boolean;
editingLayer: string | null; editingLayer: string | null;
// 框选状态 // 框选状态
isSelecting: boolean; isSelecting: boolean;
selectStart: { x: number; y: number } | null; selectStart: { x: number; y: number } | null;
selectEnd: { x: number; y: number } | null; selectEnd: { x: number; y: number } | null;
// 错误状态
error: string | null;
} }
export interface DeckActions { export interface DeckActions {
@ -36,31 +40,32 @@ export interface DeckActions {
setGrid: (grid: string) => void; setGrid: (grid: string) => void;
setBleed: (bleed: string) => void; setBleed: (bleed: string) => void;
setPadding: (padding: string) => void; setPadding: (padding: string) => void;
// 数据设置 // 数据设置
setCards: (cards: CardData[]) => void; setCards: (cards: CardData[]) => void;
setActiveTab: (index: number) => void; setActiveTab: (index: number) => void;
updateCardData: (index: number, key: string, value: string) => void; updateCardData: (index: number, key: string, value: string) => void;
// 图层操作 // 图层操作
setLayerConfigs: (configs: LayerConfig[]) => void; setLayerConfigs: (configs: LayerConfig[]) => void;
updateLayerConfig: (prop: string, updates: Partial<LayerConfig>) => void; updateLayerConfig: (prop: string, updates: Partial<LayerConfig>) => void;
toggleLayerVisible: (prop: string) => void; toggleLayerVisible: (prop: string) => void;
// 编辑状态 // 编辑状态
setIsEditing: (editing: boolean) => void; setIsEditing: (editing: boolean) => void;
setEditingLayer: (layer: string | null) => void; setEditingLayer: (layer: string | null) => void;
updateLayerPosition: (x1: number, y1: number, x2: number, y2: number) => void; updateLayerPosition: (x1: number, y1: number, x2: number, y2: number) => void;
// 框选操作 // 框选操作
setIsSelecting: (selecting: boolean) => void; setIsSelecting: (selecting: boolean) => void;
setSelectStart: (pos: { x: number; y: number } | null) => void; setSelectStart: (pos: { x: number; y: number } | null) => void;
setSelectEnd: (pos: { x: number; y: number } | null) => void; setSelectEnd: (pos: { x: number; y: number } | null) => void;
cancelSelection: () => void; cancelSelection: () => void;
// 初始化 // 初始化和数据加载
initialize: (props: Record<string, any>, csvPath: string) => void; loadCards: (cards: CardData[]) => void;
setError: (error: string | null) => void;
// 生成代码 // 生成代码
generateCode: () => string; generateCode: () => string;
copyCode: () => void; copyCode: () => void;
@ -87,20 +92,44 @@ export function createDeckStore(): DeckStore {
editingLayer: null, editingLayer: null,
isSelecting: false, isSelecting: false,
selectStart: null, selectStart: null,
selectEnd: null selectEnd: null,
error: null
}); });
const setSize = (size: string) => setState({ size }); // 更新尺寸并重新计算 dimensions
const setGrid = (grid: string) => setState({ grid }); const updateDimensions = () => {
const setBleed = (bleed: string) => setState({ bleed }); const dims = calculateDimensions({
const setPadding = (padding: string) => setState({ padding }); size: state.size,
grid: state.grid,
const setCards = (cards: CardData[]) => setState({ cards }); 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 setActiveTab = (index: number) => setState({ activeTab: index });
const updateCardData = (index: number, key: string, value: string) => { const updateCardData = (index: number, key: string, value: string) => {
setState('cards', index, key, value); setState('cards', index, key, value);
}; };
const setLayerConfigs = (configs: LayerConfig[]) => setState({ layerConfigs: configs }); const setLayerConfigs = (configs: LayerConfig[]) => setState({ layerConfigs: configs });
const updateLayerConfig = (prop: string, updates: Partial<LayerConfig>) => { const updateLayerConfig = (prop: string, updates: Partial<LayerConfig>) => {
setState('layerConfigs', (prev) => prev.map((config) => config.prop === prop ? { ...config, ...updates } : config)); 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 config.prop === prop ? { ...config, visible: !config.visible } : config
)); ));
}; };
const setIsEditing = (editing: boolean) => setState({ isEditing: editing }); const setIsEditing = (editing: boolean) => setState({ isEditing: editing });
const setEditingLayer = (layer: string | null) => setState({ editingLayer: layer }); const setEditingLayer = (layer: string | null) => setState({ editingLayer: layer });
const updateLayerPosition = (x1: number, y1: number, x2: number, y2: number) => { const updateLayerPosition = (x1: number, y1: number, x2: number, y2: number) => {
@ -121,32 +150,34 @@ export function createDeckStore(): DeckStore {
)); ));
setState({ editingLayer: null }); setState({ editingLayer: null });
}; };
const setIsSelecting = (selecting: boolean) => setState({ isSelecting: selecting }); const setIsSelecting = (selecting: boolean) => setState({ isSelecting: selecting });
const setSelectStart = (pos: { x: number; y: number } | null) => setState({ selectStart: pos }); const setSelectStart = (pos: { x: number; y: number } | null) => setState({ selectStart: pos });
const setSelectEnd = (pos: { x: number; y: number } | null) => setState({ selectEnd: pos }); const setSelectEnd = (pos: { x: number; y: number } | null) => setState({ selectEnd: pos });
const cancelSelection = () => { const cancelSelection = () => {
setState({ isSelecting: false, selectStart: null, selectEnd: null }); setState({ isSelecting: false, selectStart: null, selectEnd: null });
}; };
const initialize = (props: Record<string, any>, csvPath: string) => { // 加载卡牌数据并初始化 dimensions 和 layerConfigs
setState({ const loadCards = (cards: CardData[]) => {
size: props.size as string || '54x86', if (cards.length === 0) {
grid: props.grid as string || '5x8', setState({ error: 'CSV 文件为空或格式不正确' });
bleed: props.bleed as string || '1', return;
padding: props.padding as string || '2', }
fixed: props.fixed === true || props.fixed === 'true', setState({ cards, activeTab: 0, error: null });
src: csvPath updateDimensions();
});
}; };
const setError = (error: string | null) => setState({ error });
const generateCode = () => { const generateCode = () => {
const layersStr = state.layerConfigs const layersStr = state.layerConfigs
.map(l => `${l.prop}=${l.x1},${l.y1},${l.x2},${l.y2}`) .filter(l => l.visible)
.join('|'); .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}"}`; return `:md-deck[${state.src}]{size="${state.size}" grid="${state.grid}" bleed="${state.bleed}" padding="${state.padding}" layers="${layersStr}"}`;
}; };
const copyCode = () => { const copyCode = () => {
const code = generateCode(); const code = generateCode();
navigator.clipboard.writeText(code).then(() => { navigator.clipboard.writeText(code).then(() => {
@ -175,7 +206,8 @@ export function createDeckStore(): DeckStore {
setSelectStart, setSelectStart,
setSelectEnd, setSelectEnd,
cancelSelection, cancelSelection,
initialize, loadCards,
setError,
generateCode, generateCode,
copyCode copyCode
}; };

View File

@ -1,4 +1,4 @@
import type { DeckStore } from '../stores/deckStore'; import type { DeckStore } from './deckStore';
/** /**
* deckStore * deckStore