= (props) => {
- const d = props.data;
- const bbox = dilateBoundingBox(d.bbox, BBOX_DILATION);
- const scale = props.scale || 1;
- const [width, height] = getBoundingBoxSize(bbox);
-
- return (
-
- );
-};
-
-const App: React.FC<{}> = () => {
- const [stroke, setStroke] = useState("#000000");
- const [fill, setFill] = useState("#ffffff");
- const [showSpecs, setShowSpecs] = useState(false);
+const App: React.FC<{}> = (props) => {
+ const page = new URLSearchParams(window.location.search);
+ const currPageName = toPageName(page.get("p") || "", "vocabulary");
+ const PageComponent = Pages[currPageName];
return (
<>
- Mystic Symbolic Vocabulary
-
-
- setStroke(e.target.value)}
- id="stroke"
- />{" "}
-
- setFill(e.target.value)}
- id="fill"
- />{" "}
-
-
- {SvgVocabulary.map((symbolData) => (
-
-
- {symbolData.name}
-
-
-
-
-
- ))}
+
+
+
+
>
);
};
ReactDOM.render(, appEl);
+
+function isPageName(page: string): page is PageName {
+ return pageNames.includes(page as any);
+}
+
+function toPageName(page: string, defaultValue: PageName): PageName {
+ if (isPageName(page)) return page;
+ return defaultValue;
+}
diff --git a/lib/pages/about-page.tsx b/lib/pages/about-page.tsx
new file mode 100644
index 0000000..5eb89a1
--- /dev/null
+++ b/lib/pages/about-page.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+
+export const AboutPage: React.FC<{}> = () => (
+ <>
+ About
+ This is an about page.
+ >
+);
diff --git a/lib/pages/vocabulary-page.tsx b/lib/pages/vocabulary-page.tsx
new file mode 100644
index 0000000..933741e
--- /dev/null
+++ b/lib/pages/vocabulary-page.tsx
@@ -0,0 +1,241 @@
+import React, { useState } from "react";
+import { BBox } from "../../vendor/bezier-js";
+import { dilateBoundingBox, getBoundingBoxSize } from "../bounding-box";
+import { FILL_REPLACEMENT_COLOR, STROKE_REPLACEMENT_COLOR } from "../colors";
+import * as colors from "../colors";
+import { PointWithNormal, Specs } from "../specs";
+
+import _SvgVocabulary from "../svg-vocabulary.json";
+import type { SvgSymbolData, SvgSymbolElement } from "../vocabulary";
+
+const SvgVocabulary: SvgSymbolData[] = _SvgVocabulary as any;
+
+type SvgSymbolContext = {
+ stroke: string;
+ fill: string;
+ showSpecs: boolean;
+};
+
+type SvgSymbolProps = {
+ data: SvgSymbolData;
+ scale?: number;
+} & SvgSymbolContext;
+
+const px = (value: number) => `${value}px`;
+
+function getColor(
+ ctx: SvgSymbolContext,
+ color: string | undefined
+): string | undefined {
+ switch (color) {
+ case STROKE_REPLACEMENT_COLOR:
+ return ctx.stroke;
+ case FILL_REPLACEMENT_COLOR:
+ return ctx.fill;
+ }
+ return color;
+}
+
+function reactifySvgSymbolElement(
+ ctx: SvgSymbolContext,
+ el: SvgSymbolElement,
+ key: number
+): JSX.Element {
+ let { fill, stroke } = el.props;
+ fill = getColor(ctx, fill);
+ stroke = getColor(ctx, stroke);
+ return React.createElement(
+ el.tagName,
+ {
+ ...el.props,
+ id: undefined,
+ fill,
+ stroke,
+ key,
+ },
+ el.children.map(reactifySvgSymbolElement.bind(null, ctx))
+ );
+}
+
+const ATTACHMENT_POINT_RADIUS = 20;
+
+const ATTACHMENT_POINT_NORMAL_LENGTH = 50;
+
+const ATTACHMENT_POINT_NORMAL_STROKE = 4;
+
+const AttachmentPoints: React.FC<{
+ color: string;
+ points: PointWithNormal[];
+}> = (props) => (
+ <>
+ {props.points.map((pwn, i) => {
+ const { x, y } = pwn.point;
+ const x2 = x + pwn.normal.x * ATTACHMENT_POINT_NORMAL_LENGTH;
+ const y2 = y + pwn.normal.y * ATTACHMENT_POINT_NORMAL_LENGTH;
+ return (
+
+
+
+
+ );
+ })}
+ >
+);
+
+const BoundingBoxes: React.FC<{ fill: string; bboxes: BBox[] }> = (props) => (
+ <>
+ {props.bboxes.map((b, i) => {
+ const [width, height] = getBoundingBoxSize(b);
+ return (
+
+ );
+ })}
+ >
+);
+
+const SvgSymbolSpecs: React.FC<{ specs: Specs }> = ({ specs }) => {
+ return (
+ <>
+ {specs.tail && (
+
+ )}
+ {specs.leg && (
+
+ )}
+ {specs.arm && (
+
+ )}
+ {specs.horn && (
+
+ )}
+ {specs.crown && (
+
+ )}
+ {specs.nesting && (
+
+ )}
+ >
+ );
+};
+
+const BBOX_DILATION = 50;
+
+const SvgSymbol: React.FC = (props) => {
+ const d = props.data;
+ const bbox = dilateBoundingBox(d.bbox, BBOX_DILATION);
+ const scale = props.scale || 1;
+ const [width, height] = getBoundingBoxSize(bbox);
+
+ return (
+
+ );
+};
+
+export const VocabularyPage: React.FC<{}> = () => {
+ const [stroke, setStroke] = useState("#000000");
+ const [fill, setFill] = useState("#ffffff");
+ const [showSpecs, setShowSpecs] = useState(false);
+
+ return (
+ <>
+ Mystic Symbolic Vocabulary
+
+
+ setStroke(e.target.value)}
+ id="stroke"
+ />{" "}
+
+ setFill(e.target.value)}
+ id="fill"
+ />{" "}
+
+
+ {SvgVocabulary.map((symbolData) => (
+
+
+ {symbolData.name}
+
+
+
+
+
+ ))}
+ >
+ );
+};