feat: add support for stat sheet SVG rendering
Introduces the ability to discover and render `*.sheet.svg` files.
These files can contain `<text>` elements with `${key}` templates
that reactively update based on the current journal stats.
- Add `StatSheet` type and CLI scanning logic
- Implement `SheetView` for rendering SVGs with live bindings
- Add `parseSheet` utility to extract template patterns from SVG
- Update file indexer to include `.svg` files
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Stat sheet template engine — parses *.sheet.svg files and extracts
|
||||
* <text> elements containing ${key} template patterns.
|
||||
*
|
||||
* The SVG element is parsed once via DOMParser. The caller (StatsView)
|
||||
* reactively updates the text nodes whenever the stats store changes.
|
||||
*/
|
||||
|
||||
/** Regex matching ${key} template patterns (captures the key name). */
|
||||
const TEMPLATE_RE = /\$\{(\w+)\}/g;
|
||||
|
||||
/** A single template binding in a <text> element. */
|
||||
export interface TextTemplate {
|
||||
el: SVGTextElement;
|
||||
template: string;
|
||||
keys: string[];
|
||||
}
|
||||
|
||||
/** The result of parsing a stat sheet SVG string. */
|
||||
export interface ParsedSheet {
|
||||
svgElement: SVGSVGElement;
|
||||
templates: TextTemplate[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an SVG string into a live DOM element and extract all text
|
||||
* elements that contain ${key} template patterns.
|
||||
*/
|
||||
export function parseSheet(svgString: string): ParsedSheet {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(svgString, "image/svg+xml");
|
||||
const svgElement = doc.documentElement as unknown as SVGSVGElement;
|
||||
|
||||
if (!svgElement || svgElement.tagName !== "svg") {
|
||||
return {
|
||||
svgElement: document.createElementNS(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"svg",
|
||||
) as SVGSVGElement,
|
||||
templates: [],
|
||||
};
|
||||
}
|
||||
|
||||
const templates: TextTemplate[] = [];
|
||||
const textEls = svgElement.querySelectorAll("text");
|
||||
|
||||
for (const el of textEls) {
|
||||
const content = el.textContent;
|
||||
if (!content) continue;
|
||||
|
||||
TEMPLATE_RE.lastIndex = 0;
|
||||
const keys: string[] = [];
|
||||
let m: RegExpExecArray | null;
|
||||
while ((m = TEMPLATE_RE.exec(content)) !== null) {
|
||||
keys.push(m[1]);
|
||||
}
|
||||
|
||||
if (keys.length > 0) {
|
||||
templates.push({ el: el as SVGTextElement, template: content, keys });
|
||||
}
|
||||
}
|
||||
|
||||
return { svgElement, templates };
|
||||
}
|
||||
Reference in New Issue
Block a user