diff --git a/lib/browser-main.tsx b/lib/browser-main.tsx
index 3120fea..616d0d4 100644
--- a/lib/browser-main.tsx
+++ b/lib/browser-main.tsx
@@ -2,40 +2,67 @@ import React, { useState } from "react";
import ReactDOM from "react-dom";
import _SvgVocabulary from "./svg-vocabulary.json";
-import type { SvgSymbolData } from "./vocabulary";
+import type { SvgSymbolData, SvgSymbolElement } from "./vocabulary";
const APP_ID = "app";
const appEl = document.getElementById(APP_ID);
-const SvgVocabulary: SvgSymbolData[] = _SvgVocabulary;
+const SvgVocabulary: SvgSymbolData[] = _SvgVocabulary as any;
if (!appEl) {
throw new Error(`Unable to find #${APP_ID}!`);
}
-type SvgSymbolProps = {
- data: SvgSymbolData;
- scale?: number;
+type SvgSymbolContext = {
stroke: string;
fill: string;
};
+type SvgSymbolProps = {
+ data: SvgSymbolData;
+ scale?: number;
+} & SvgSymbolContext;
+
const px = (value: number) => `${value}px`;
+function reactifySvgSymbolElement(
+ ctx: SvgSymbolContext,
+ el: SvgSymbolElement,
+ key: number
+): JSX.Element {
+ let { fill, stroke } = el.props;
+ if (fill && fill !== "none") {
+ fill = ctx.fill;
+ }
+ if (stroke && stroke !== "none") {
+ stroke = ctx.stroke;
+ }
+ return React.createElement(
+ el.tagName,
+ {
+ ...el.props,
+ id: undefined,
+ fill,
+ stroke,
+ key,
+ },
+ el.children.map(reactifySvgSymbolElement.bind(null, ctx))
+ );
+}
+
const SvgSymbol: React.FC = (props) => {
const d = props.data;
const scale = props.scale || 1;
return (
+ >
+ {props.data.layers.map(reactifySvgSymbolElement.bind(null, props))}
+
);
};
@@ -64,6 +91,7 @@ const App: React.FC<{}> = () => {
{SvgVocabulary.map((symbolData) => (
;
+ }
+ | {
+ tagName: "path";
+ props: SVGProps;
+ }
+) & {
+ children: SvgSymbolElement[];
+};
+
+const SUPPORTED_SVG_TAG_ARRAY: SvgSymbolElement["tagName"][] = ["g", "path"];
+const SUPPORTED_SVG_TAGS = new Set(SUPPORTED_SVG_TAG_ARRAY);
+
const MY_DIR = __dirname;
const SVG_DIR = path.join(MY_DIR, "..", "svg");
const VOCAB_PATH = path.join(MY_DIR, "svg-vocabulary.json");
const SVG_EXT = ".svg";
-function removeAttrIfNotNone(
- attr: string,
- $: cheerio.Root,
- g: cheerio.Cheerio
-) {
- const items = g.find(`[${attr}]`);
- items.each(function (this: any, i, el) {
- if ($(this).attr(attr) !== "none") {
- $(this).removeAttr(attr);
+function onlyTags(
+ elements: cheerio.Element[] | cheerio.Cheerio
+): cheerio.TagElement[] {
+ const result: cheerio.TagElement[] = [];
+
+ for (let i = 0; i < elements.length; i++) {
+ const el = elements[i];
+ if (el.type === "tag") {
+ result.push(el);
}
- });
+ }
+
+ return result;
+}
+
+function isSupportedSvgTag(
+ tagName: string
+): tagName is SvgSymbolElement["tagName"] {
+ return SUPPORTED_SVG_TAGS.has(tagName as any);
+}
+
+const SVG_ATTRIB_TO_PROP_MAP: {
+ [key: string]: keyof SvgSymbolElement["props"] | undefined;
+} = {
+ id: "id",
+ fill: "fill",
+ stroke: "stroke",
+ d: "d",
+ "stroke-linejoin": "strokeLinejoin",
+ "stroke-linecap": "strokeLinecap",
+ "stroke-width": "strokeWidth",
+ "fill-rule": "fillRule",
+};
+
+function attribsToProps(el: cheerio.TagElement): any {
+ const { attribs } = el;
+ const result: any = {};
+
+ for (let attrib of Object.keys(attribs)) {
+ const prop = SVG_ATTRIB_TO_PROP_MAP[attrib];
+
+ if (!prop) {
+ throw new Error(`Unknown SVG attribute '${attrib}' in <${el.tagName}>!`);
+ }
+
+ result[prop] = attribs[attrib];
+ }
+
+ return result;
+}
+
+function serializeSvgSymbolElement(
+ $: cheerio.Root,
+ el: cheerio.TagElement
+): SvgSymbolElement {
+ let children = onlyTags(el.children).map((child) =>
+ serializeSvgSymbolElement($, child)
+ );
+ const { tagName } = el;
+ if (isSupportedSvgTag(tagName)) {
+ return {
+ tagName,
+ props: attribsToProps(el) as any,
+ children,
+ };
+ }
+ throw new Error(`Unsupported SVG element: <${tagName}>`);
}
function getSvgPixelDimension($: cheerio.Root, attr: string): number {
@@ -35,7 +108,7 @@ function getSvgPixelDimension($: cheerio.Root, attr: string): number {
const match = value.match(/^(\d+)px$/);
if (!match) {
throw new Error(
- `unable to parse