fix: state tracking
This commit is contained in:
parent
0aaadea2da
commit
8ddc2a672a
|
|
@ -2,7 +2,7 @@ import { Show, For } from 'solid-js';
|
|||
import { marked } from '../markdown';
|
||||
import { getLayerStyle } from './utils/dimensions';
|
||||
import { getSelectionBoxStyle, useSelection } from './stores/use-selection';
|
||||
import {DeckStore} from "./stores/deckStore";
|
||||
import type { DeckStore } from "./stores/deckStore";
|
||||
|
||||
export interface CardPreviewProps {
|
||||
store: DeckStore;
|
||||
|
|
@ -11,16 +11,16 @@ export interface CardPreviewProps {
|
|||
/**
|
||||
* 渲染 layer 内容
|
||||
*/
|
||||
function renderLayer(layer: { prop: string }, cardData: DeckStore['cards'][number]): string {
|
||||
function renderLayer(layer: { prop: string }, cardData: DeckStore['state']['cards'][number]): string {
|
||||
const content = cardData[layer.prop] || '';
|
||||
return marked.parse(content) as string;
|
||||
}
|
||||
|
||||
export function CardPreview(props: CardPreviewProps) {
|
||||
const currentCard = () => props.store.cards[props.store.activeTab];
|
||||
const visibleLayers = () => props.store.layerConfigs.filter((l) => l.visible);
|
||||
const currentCard = () => props.store.state.cards[props.store.state.activeTab];
|
||||
const visibleLayers = () => props.store.state.layerConfigs.filter((l) => l.visible);
|
||||
const selectionStyle = () =>
|
||||
getSelectionBoxStyle(props.store.selectStart, props.store.selectEnd, props.store.dimensions);
|
||||
getSelectionBoxStyle(props.store.state.selectStart, props.store.state.selectEnd, props.store.state.dimensions);
|
||||
|
||||
const selection = useSelection(props.store);
|
||||
|
||||
|
|
@ -28,13 +28,13 @@ export function CardPreview(props: CardPreviewProps) {
|
|||
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<Show when={props.store.activeTab < props.store.cards.length}>
|
||||
<Show when={props.store.state.activeTab < props.store.state.cards.length}>
|
||||
<div
|
||||
ref={cardRef}
|
||||
class="relative bg-white border border-gray-300 shadow-lg"
|
||||
style={{
|
||||
width: `${props.store.dimensions?.cardWidth}mm`,
|
||||
height: `${props.store.dimensions?.cardHeight}mm`
|
||||
width: `${props.store.state.dimensions?.cardWidth}mm`,
|
||||
height: `${props.store.state.dimensions?.cardHeight}mm`
|
||||
}}
|
||||
onMouseDown={(e) => selection.onMouseDown(e, cardRef!)}
|
||||
onMouseMove={(e) => selection.onMouseMove(e, cardRef!)}
|
||||
|
|
@ -42,7 +42,7 @@ export function CardPreview(props: CardPreviewProps) {
|
|||
onMouseLeave={selection.onMouseLeave}
|
||||
>
|
||||
{/* 框选遮罩 */}
|
||||
<Show when={props.store.isSelecting && selectionStyle()}>
|
||||
<Show when={props.store.state.isSelecting && selectionStyle()}>
|
||||
<div
|
||||
class="absolute bg-blue-500/30 border-2 border-blue-500 pointer-events-none"
|
||||
style={selectionStyle()!}
|
||||
|
|
@ -53,28 +53,28 @@ export function CardPreview(props: CardPreviewProps) {
|
|||
<div
|
||||
class="absolute"
|
||||
style={{
|
||||
left: `${props.store.dimensions?.gridOriginX}mm`,
|
||||
top: `${props.store.dimensions?.gridOriginY}mm`,
|
||||
width: `${props.store.dimensions?.gridAreaWidth}mm`,
|
||||
height: `${props.store.dimensions?.gridAreaHeight}mm`
|
||||
left: `${props.store.state.dimensions?.gridOriginX}mm`,
|
||||
top: `${props.store.state.dimensions?.gridOriginY}mm`,
|
||||
width: `${props.store.state.dimensions?.gridAreaWidth}mm`,
|
||||
height: `${props.store.state.dimensions?.gridAreaHeight}mm`
|
||||
}}
|
||||
>
|
||||
{/* 编辑模式下的网格线 */}
|
||||
<Show when={props.store.isEditing && !props.store.fixed}>
|
||||
<Show when={props.store.state.isEditing && !props.store.state.fixed}>
|
||||
<div class="absolute inset-0 pointer-events-none">
|
||||
<For each={Array.from({ length: (props.store.dimensions?.gridW || 0) - 1 })}>
|
||||
<For each={Array.from({ length: (props.store.state.dimensions?.gridW || 0) - 1 })}>
|
||||
{(_, i) => (
|
||||
<div
|
||||
class="absolute top-0 bottom-0 border-r border-dashed border-gray-300"
|
||||
style={{ left: `${(i() + 1) * (props.store.dimensions?.cellWidth || 0)}mm` }}
|
||||
style={{ left: `${(i() + 1) * (props.store.state.dimensions?.cellWidth || 0)}mm` }}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
<For each={Array.from({ length: (props.store.dimensions?.gridH || 0) - 1 })}>
|
||||
<For each={Array.from({ length: (props.store.state.dimensions?.gridH || 0) - 1 })}>
|
||||
{(_, i) => (
|
||||
<div
|
||||
class="absolute left-0 right-0 border-b border-dashed border-gray-300"
|
||||
style={{ top: `${(i() + 1) * (props.store.dimensions?.cellHeight || 0)}mm` }}
|
||||
style={{ top: `${(i() + 1) * (props.store.state.dimensions?.cellHeight || 0)}mm` }}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
|
|
@ -84,12 +84,12 @@ export function CardPreview(props: CardPreviewProps) {
|
|||
{/* 渲染每个 layer */}
|
||||
<For each={visibleLayers()}>
|
||||
{(layer) => {
|
||||
const style = getLayerStyle(layer, props.store.dimensions!);
|
||||
const style = getLayerStyle(layer, props.store.state.dimensions!);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={`absolute flex items-center justify-center text-center prose prose-sm ${
|
||||
props.store.isEditing ? 'bg-blue-500/20 ring-2 ring-blue-500' : ''
|
||||
props.store.state.isEditing ? 'bg-blue-500/20 ring-2 ring-blue-500' : ''
|
||||
}`}
|
||||
style={style}
|
||||
innerHTML={renderLayer(layer, currentCard())}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { For } from 'solid-js';
|
||||
import { DeckStore } from './stores/deckStore';
|
||||
import type { DeckStore } from './stores/deckStore';
|
||||
|
||||
export interface DataEditorPanelProps {
|
||||
activeTab: number;
|
||||
cards: DeckStore['cards'];
|
||||
updateCardData: DeckStore['updateCardData'];
|
||||
cards: DeckStore['state']['cards'];
|
||||
updateCardData: DeckStore['actions']['updateCardData'];
|
||||
}
|
||||
|
||||
export interface PropertiesEditorPanelProps {
|
||||
|
|
@ -53,8 +53,8 @@ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) {
|
|||
<input
|
||||
type="text"
|
||||
class="w-full border border-gray-300 rounded px-2 py-1 text-sm"
|
||||
value={store.size}
|
||||
onInput={(e) => store.setSize(e.target.value)}
|
||||
value={store.state.size}
|
||||
onInput={(e) => store.actions.setSize(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -63,8 +63,8 @@ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) {
|
|||
<input
|
||||
type="text"
|
||||
class="w-full border border-gray-300 rounded px-2 py-1 text-sm"
|
||||
value={store.grid}
|
||||
onInput={(e) => store.setGrid(e.target.value)}
|
||||
value={store.state.grid}
|
||||
onInput={(e) => store.actions.setGrid(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -73,8 +73,8 @@ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) {
|
|||
<input
|
||||
type="text"
|
||||
class="w-full border border-gray-300 rounded px-2 py-1 text-sm"
|
||||
value={store.bleed}
|
||||
onInput={(e) => store.setBleed(e.target.value)}
|
||||
value={store.state.bleed}
|
||||
onInput={(e) => store.actions.setBleed(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -83,33 +83,33 @@ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) {
|
|||
<input
|
||||
type="text"
|
||||
class="w-full border border-gray-300 rounded px-2 py-1 text-sm"
|
||||
value={store.padding}
|
||||
onInput={(e) => store.setPadding(e.target.value)}
|
||||
value={store.state.padding}
|
||||
onInput={(e) => store.actions.setPadding(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr class="my-4" />
|
||||
|
||||
<h4 class="font-medium text-sm text-gray-700">图层</h4>
|
||||
<For each={store.layerConfigs}>
|
||||
<For each={store.state.layerConfigs}>
|
||||
{(layer) => (
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={layer.visible}
|
||||
onChange={() => store.toggleLayerVisible(layer.prop)}
|
||||
onChange={() => store.actions.toggleLayerVisible(layer.prop)}
|
||||
class="cursor-pointer"
|
||||
/>
|
||||
<span class="text-sm flex-1">{layer.prop}</span>
|
||||
<button
|
||||
onClick={() => store.setEditingLayer(layer.prop)}
|
||||
onClick={() => store.actions.setEditingLayer(layer.prop)}
|
||||
class={`text-xs px-2 py-0.5 rounded cursor-pointer ${
|
||||
store.editingLayer === layer.prop
|
||||
store.state.editingLayer === layer.prop
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
|
||||
}`}
|
||||
>
|
||||
{store.editingLayer === layer.prop ? '✓ 框选' : '编辑位置'}
|
||||
{store.state.editingLayer === layer.prop ? '✓ 框选' : '编辑位置'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -118,7 +118,7 @@ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) {
|
|||
<hr class="my-4" />
|
||||
|
||||
<button
|
||||
onClick={store.copyCode}
|
||||
onClick={store.actions.copyCode}
|
||||
class="w-full bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded text-sm font-medium cursor-pointer"
|
||||
>
|
||||
📋 复制代码
|
||||
|
|
|
|||
|
|
@ -45,10 +45,10 @@ customElement<DeckProps>('md-deck', {
|
|||
const resolvedSrc = resolvePath(articlePath, csvPath);
|
||||
|
||||
// 初始化 store 属性
|
||||
store.setSize(props.size || '54x86');
|
||||
store.setGrid(props.grid || '5x8');
|
||||
store.setBleed(props.bleed || '1');
|
||||
store.setPadding(props.padding || '2');
|
||||
store.actions.setSize(props.size || '54x86');
|
||||
store.actions.setGrid(props.grid || '5x8');
|
||||
store.actions.setBleed(props.bleed || '1');
|
||||
store.actions.setPadding(props.padding || '2');
|
||||
|
||||
// 加载 CSV 文件
|
||||
const [csvData, { refetch }] = createResource(() => resolvedSrc, loadCSV);
|
||||
|
|
@ -60,13 +60,13 @@ customElement<DeckProps>('md-deck', {
|
|||
const error = csvData.error;
|
||||
|
||||
if (error) {
|
||||
store.setError(`加载 CSV 失败:${error.message}`);
|
||||
store.actions.setError(`加载 CSV 失败:${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loading && data) {
|
||||
store.loadCards(data);
|
||||
store.setLayerConfigs(initLayerConfigs(data, (props.layers as string) || ''));
|
||||
store.actions.loadCards(data);
|
||||
store.actions.setLayerConfigs(initLayerConfigs(data, (props.layers as string) || ''));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -78,11 +78,11 @@ customElement<DeckProps>('md-deck', {
|
|||
return (
|
||||
<div class="md-deck flex gap-4">
|
||||
{/* 左侧:CSV 数据编辑 */}
|
||||
<Show when={store.isEditing && !store.fixed}>
|
||||
<Show when={store.state.isEditing && !store.state.fixed}>
|
||||
<DataEditorPanel
|
||||
activeTab={store.activeTab}
|
||||
cards={store.cards}
|
||||
updateCardData={store.updateCardData}
|
||||
activeTab={store.state.activeTab}
|
||||
cards={store.state.cards}
|
||||
updateCardData={store.actions.updateCardData}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
|
|
@ -91,22 +91,22 @@ customElement<DeckProps>('md-deck', {
|
|||
{/* Tab 选择器 */}
|
||||
<div class="flex items-center gap-2 border-b border-gray-200 pb-2 mb-4">
|
||||
<button
|
||||
onClick={() => store.setIsEditing(!store.isEditing)}
|
||||
onClick={() => store.actions.setIsEditing(!store.state.isEditing)}
|
||||
class={`px-3 py-1 rounded text-sm font-medium transition-colors ${
|
||||
store.isEditing && !store.fixed
|
||||
store.state.isEditing && !store.state.fixed
|
||||
? 'bg-blue-100 text-blue-600'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
} cursor-pointer`}
|
||||
>
|
||||
{store.isEditing ? '✓ 编辑中' : '✏️ 编辑'}
|
||||
{store.state.isEditing ? '✓ 编辑中' : '✏️ 编辑'}
|
||||
</button>
|
||||
<div class="flex gap-1 overflow-x-auto flex-1 min-w-0 flex-wrap">
|
||||
<For each={store.cards}>
|
||||
<For each={store.state.cards}>
|
||||
{(card, index) => (
|
||||
<button
|
||||
onClick={() => store.setActiveTab(index())}
|
||||
onClick={() => store.actions.setActiveTab(index())}
|
||||
class={`font-medium transition-colors flex-shrink-0 min-w-[1.6em] cursor-pointer px-2 py-1 rounded ${
|
||||
store.activeTab === index()
|
||||
store.state.activeTab === index()
|
||||
? 'bg-blue-100 text-blue-600 border-b-2 border-blue-600'
|
||||
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-100'
|
||||
}`}
|
||||
|
|
@ -119,9 +119,9 @@ customElement<DeckProps>('md-deck', {
|
|||
</div>
|
||||
|
||||
{/* 错误提示 */}
|
||||
<Show when={store.error}>
|
||||
<Show when={store.state.error}>
|
||||
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded mb-4">
|
||||
{store.error}
|
||||
{store.state.error}
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
|
|
@ -133,12 +133,12 @@ customElement<DeckProps>('md-deck', {
|
|||
</Show>
|
||||
|
||||
{/* 卡牌预览 */}
|
||||
<Show when={!csvData.loading && store.cards.length > 0 && !store.error}>
|
||||
<Show when={!csvData.loading && store.state.cards.length > 0 && !store.state.error}>
|
||||
<CardPreview store={store} />
|
||||
</Show>
|
||||
|
||||
{/* 空状态 */}
|
||||
<Show when={!csvData.loading && store.cards.length === 0 && !store.error}>
|
||||
<Show when={!csvData.loading && store.state.cards.length === 0 && !store.state.error}>
|
||||
<div class="text-center text-gray-500 py-8">
|
||||
暂无卡牌数据
|
||||
</div>
|
||||
|
|
@ -146,7 +146,7 @@ customElement<DeckProps>('md-deck', {
|
|||
</div>
|
||||
|
||||
{/* 右侧:属性编辑表单 */}
|
||||
<Show when={store.isEditing && !store.fixed}>
|
||||
<Show when={store.state.isEditing && !store.state.fixed}>
|
||||
<PropertiesEditorPanel store={store} />
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,10 @@ export interface DeckActions {
|
|||
copyCode: () => void;
|
||||
}
|
||||
|
||||
export interface DeckStore extends DeckState, DeckActions {}
|
||||
export interface DeckStore {
|
||||
state: DeckState;
|
||||
actions: DeckActions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 deck store
|
||||
|
|
@ -187,8 +190,7 @@ export function createDeckStore(): DeckStore {
|
|||
});
|
||||
};
|
||||
|
||||
return {
|
||||
...state,
|
||||
const actions: DeckActions = {
|
||||
setSize,
|
||||
setGrid,
|
||||
setBleed,
|
||||
|
|
@ -211,4 +213,6 @@ export function createDeckStore(): DeckStore {
|
|||
generateCode,
|
||||
copyCode
|
||||
};
|
||||
|
||||
return { state, actions };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type { DeckStore } from './deckStore';
|
|||
* 此 hook 保留用于向后兼容或提取特定逻辑
|
||||
*/
|
||||
export function useSelection(store: DeckStore) {
|
||||
const calculateGridCoords = (e: MouseEvent, cardEl: HTMLElement, dimensions: DeckStore['dimensions']) => {
|
||||
const calculateGridCoords = (e: MouseEvent, cardEl: HTMLElement, dimensions: DeckStore['state']['dimensions']) => {
|
||||
if (!dimensions) return { gridX: 1, gridY: 1 };
|
||||
|
||||
const rect = cardEl.getBoundingClientRect();
|
||||
|
|
@ -23,36 +23,36 @@ export function useSelection(store: DeckStore) {
|
|||
};
|
||||
|
||||
const handleMouseDown = (e: MouseEvent, cardEl: HTMLElement) => {
|
||||
if (!store.isEditing || !store.editingLayer) return;
|
||||
if (!store.state.isEditing || !store.state.editingLayer) return;
|
||||
|
||||
const { gridX, gridY } = calculateGridCoords(e, cardEl, store.dimensions);
|
||||
const { gridX, gridY } = calculateGridCoords(e, cardEl, store.state.dimensions);
|
||||
|
||||
store.setSelectStart({ x: gridX, y: gridY });
|
||||
store.setSelectEnd({ x: gridX, y: gridY });
|
||||
store.setIsSelecting(true);
|
||||
store.actions.setSelectStart({ x: gridX, y: gridY });
|
||||
store.actions.setSelectEnd({ x: gridX, y: gridY });
|
||||
store.actions.setIsSelecting(true);
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent, cardEl: HTMLElement) => {
|
||||
if (!store.isSelecting) return;
|
||||
if (!store.state.isSelecting) return;
|
||||
|
||||
const { gridX, gridY } = calculateGridCoords(e, cardEl, store.dimensions);
|
||||
const { gridX, gridY } = calculateGridCoords(e, cardEl, store.state.dimensions);
|
||||
|
||||
store.setSelectEnd({ x: gridX, y: gridY });
|
||||
store.actions.setSelectEnd({ x: gridX, y: gridY });
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (!store.isSelecting || !store.editingLayer) return;
|
||||
if (!store.state.isSelecting || !store.state.editingLayer) return;
|
||||
|
||||
const start = store.selectStart!;
|
||||
const end = store.selectEnd!;
|
||||
const start = store.state.selectStart!;
|
||||
const end = store.state.selectEnd!;
|
||||
|
||||
const x1 = Math.min(start.x, end.x);
|
||||
const y1 = Math.min(start.y, end.y);
|
||||
const x2 = Math.max(start.x, end.x);
|
||||
const y2 = Math.max(start.y, end.y);
|
||||
|
||||
store.updateLayerPosition(x1, y1, x2, y2);
|
||||
store.cancelSelection();
|
||||
store.actions.updateLayerPosition(x1, y1, x2, y2);
|
||||
store.actions.cancelSelection();
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
|||
Loading…
Reference in New Issue