From fdecd0fb6e574a67f3dd57325b989844fa1862d1 Mon Sep 17 00:00:00 2001 From: hypercross Date: Fri, 27 Feb 2026 13:55:20 +0800 Subject: [PATCH] refactor: store based md-pins --- src/components/md-pins.tsx | 120 ++++++----------------------- src/components/stores/pinsStore.ts | 107 +++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 95 deletions(-) create mode 100644 src/components/stores/pinsStore.ts diff --git a/src/components/md-pins.tsx b/src/components/md-pins.tsx index a31ac35..f566837 100644 --- a/src/components/md-pins.tsx +++ b/src/components/md-pins.tsx @@ -1,78 +1,22 @@ import { customElement, noShadowDOM } from "solid-element"; -import { createSignal, onMount, onCleanup, Show, For, createResource, createMemo } from "solid-js"; +import {Show, For, createResource, createMemo, createSignal} from "solid-js"; import { resolvePath } from "./utils/path"; - -interface Pin { - x: number; - y: number; - label: string; -} - -// 生成标签 A-Z, AA-ZZ, AAA-ZZZ ... -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[] -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" -function formatPins(pins: Pin[]): string { - return pins.map(pin => `${pin.label}:${pin.x},${pin.y}`).join(' '); -} - -// 找到最早未使用的标签 -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); -} +import { createPinsStore } from "./stores/pinsStore"; customElement("md-pins", { pins: "", fixed: false }, (props, { element }) => { noShadowDOM(); - const [pins, setPins] = createSignal([]); const [showToast, setShowToast] = createSignal(false); - let editorContainer: HTMLDivElement | undefined; + + // 创建 store + const store = createPinsStore(props.pins, props.fixed); // 从 element 的 textContent 获取图片路径 const rawSrc = element?.textContent?.trim() || ''; + // 设置 rawSrc 到 store + store.setRawSrc(rawSrc); + // 隐藏原始文本内容 if (element) { element.textContent = ""; @@ -98,22 +42,12 @@ customElement("md-pins", { pins: "", fixed: false }, (props, { element }) => { const [image] = createResource(resolvedSrc, loadImage); const visible = createMemo(() => !image.loading && !!image()); - // 从 props.pins 初始化 pins - onMount(() => { - if (props.pins) { - const parsed = parsePins(props.pins); - if (parsed.length > 0) { - setPins(parsed); - } - } - }); - // 添加 pin const addPin = (e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); - if (isFixed()) return; + if (props.fixed) return; const imgRect = (e.target as Element).getBoundingClientRect(); const clickX = ((e.clientX - imgRect.left) / imgRect.width) * 100; @@ -121,25 +55,23 @@ customElement("md-pins", { pins: "", fixed: false }, (props, { element }) => { const x = Math.round(clickX); const y = Math.round(clickY); - const label = findNextUnusedLabel(pins()); - setPins([...pins(), { x, y, label }]); + store.addPin(x, y); }; // 删除 pin const removePin = (index: number, e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); - - if (isFixed()) return; - - setPins(pins().filter((_, i) => i !== index)); + + if (props.fixed) return; + + store.removePin(index); }; // 复制所有 pin 为 :md-editor-pin 格式 const copyPins = () => { - const pinsStr = formatPins(pins()); - const text = `:md-pins[${rawSrc}]{pins="${pinsStr}" fixed}`; + const text = store.getCopyText(); navigator.clipboard.writeText(text).then(() => { setShowToast(true); @@ -149,26 +81,24 @@ customElement("md-pins", { pins: "", fixed: false }, (props, { element }) => { }); }; - const isFixed = () => props.fixed; - return ( -
- +
+ {/* 图片容器 */}
{/* 显示图片 */} - + {/* 透明遮罩层 */} - +
- +
{/* 复制按钮 HUD */} - +
diff --git a/src/components/stores/pinsStore.ts b/src/components/stores/pinsStore.ts new file mode 100644 index 0000000..841bfcc --- /dev/null +++ b/src/components/stores/pinsStore.ts @@ -0,0 +1,107 @@ +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({ + 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 + }; +}