ttrpg-tools/src/components/stores/pinsStore.ts

108 lines
2.3 KiB
TypeScript
Raw Normal View History

2026-02-27 13:55:20 +08:00
import { createStore } from "solid-js/store";
export interface Pin {
x: number;
y: number;
label: string;
}
interface PinsState {
pins: Pin[];
rawSrc: string;
isFixed: boolean;
}
// 生成标签 A-Z, AA-ZZ, AAA-ZZZ ...
export function generateLabel(index: number): string {
const labels: string[] = [];
let num = index;
do {
const remainder = num % 26;
labels.unshift(String.fromCharCode(65 + remainder));
num = Math.floor(num / 26) - 1;
} while (num >= 0);
return labels.join('');
}
// 解析 pins 字符串 "A:30,40 B:10,30" -> Pin[]
export function parsePins(pinsStr: string): Pin[] {
if (!pinsStr) return [];
const pins: Pin[] = [];
const regex = /([A-Z]+):(\d+),(\d+)/g;
let match;
while ((match = regex.exec(pinsStr)) !== null) {
pins.push({
label: match[1],
x: parseInt(match[2]),
y: parseInt(match[3])
});
}
return pins;
}
// 格式化 pins 为字符串 "A:30,40 B:10,30"
export function formatPins(pins: Pin[]): string {
return pins.map(pin => `${pin.label}:${pin.x},${pin.y}`).join(' ');
}
// 找到最早未使用的标签
export function findNextUnusedLabel(pins: Pin[]): string {
const usedLabels = new Set(pins.map(p => p.label));
let index = 0;
while (true) {
const label = generateLabel(index);
if (!usedLabels.has(label)) {
return label;
}
index++;
if (index > 10000) break; // 安全限制
}
return generateLabel(pins.length);
}
// 创建 store 实例
export function createPinsStore(initialPinsStr: string = "", initialFixed: boolean = false) {
const [state, setState] = createStore<PinsState>({
pins: parsePins(initialPinsStr),
rawSrc: "",
isFixed: initialFixed
});
const setRawSrc = (src: string) => {
setState("rawSrc", src);
};
const addPin = (x: number, y: number) => {
const label = findNextUnusedLabel(state.pins);
setState("pins", [...state.pins, { x, y, label }]);
};
const removePin = (index: number) => {
setState("pins", state.pins.filter((_, i) => i !== index));
};
const formatCurrentPins = () => formatPins(state.pins);
const getCopyText = () => {
const pinsStr = formatCurrentPins();
return `:md-pins[${state.rawSrc}]{pins="${pinsStr}" fixed}`;
};
return {
state,
setState,
setRawSrc,
addPin,
removePin,
formatCurrentPins,
getCopyText
};
}