Factor out vocabulary.ts.
rodzic
a19a560b8f
commit
51f68656ad
|
@ -6,6 +6,7 @@ import {
|
|||
} from "./creature-symbol";
|
||||
import { AttachmentPointType } from "./specs";
|
||||
import { SvgSymbolData } from "./svg-symbol";
|
||||
import { Vocabulary } from "./vocabulary";
|
||||
|
||||
type AttachmentIndices = {
|
||||
left?: boolean;
|
||||
|
@ -71,7 +72,7 @@ type SimpleCreatureSymbolFC = React.FC<SimpleCreatureSymbolProps> & {
|
|||
* render a `<CreatureSymbol>`.
|
||||
*/
|
||||
export function createCreatureSymbolFactory(
|
||||
getSymbol: (name: string) => SvgSymbolData
|
||||
vocabulary: Vocabulary<SvgSymbolData>
|
||||
) {
|
||||
/**
|
||||
* Returns a React component that renders a `<CreatureSymbol>`, using the symbol
|
||||
|
@ -80,7 +81,7 @@ export function createCreatureSymbolFactory(
|
|||
return function createCreatureSymbol(
|
||||
name: string
|
||||
): React.FC<SimpleCreatureSymbolProps> {
|
||||
const data = getSymbol(name);
|
||||
const data = vocabulary.get(name);
|
||||
const Component: SimpleCreatureSymbolFC = (props) => {
|
||||
const symbol = getCreatureSymbol(data, props);
|
||||
return <CreatureSymbol {...symbol} />;
|
||||
|
|
|
@ -25,7 +25,7 @@ import { NumericSlider } from "../numeric-slider";
|
|||
import { DEFAULT_BG_COLOR } from "../colors";
|
||||
|
||||
/** Symbols that can be the "root" (i.e., main body) of a creature. */
|
||||
const ROOT_SYMBOLS = SvgVocabulary.filter(
|
||||
const ROOT_SYMBOLS = SvgVocabulary.items.filter(
|
||||
(data) => data.meta?.always_be_nested !== true
|
||||
);
|
||||
|
||||
|
@ -41,7 +41,7 @@ const ATTACHMENT_SYMBOLS: AttachmentSymbolMap = (() => {
|
|||
const result = {} as AttachmentSymbolMap;
|
||||
|
||||
for (let type of ATTACHMENT_POINT_TYPES) {
|
||||
result[type] = SvgVocabulary.filter((data) => {
|
||||
result[type] = SvgVocabulary.items.filter((data) => {
|
||||
const { meta } = data;
|
||||
|
||||
// If we have no metadata whatsoever, it can attach anywhere.
|
||||
|
@ -66,7 +66,7 @@ const ATTACHMENT_SYMBOLS: AttachmentSymbolMap = (() => {
|
|||
})();
|
||||
|
||||
/** Symbols that can be nested within any part of a creature. */
|
||||
const NESTED_SYMBOLS = SvgVocabulary.filter(
|
||||
const NESTED_SYMBOLS = SvgVocabulary.items.filter(
|
||||
// Since we don't currently support recursive nesting, ignore anything that
|
||||
// wants nested children.
|
||||
(data) =>
|
||||
|
|
|
@ -5,10 +5,10 @@ import { createCreatureSymbolFactory } from "../creature-symbol-factory";
|
|||
import { HoverDebugHelper } from "../hover-debug-helper";
|
||||
import { createSvgSymbolContext } from "../svg-symbol";
|
||||
import { svgScale, SvgTransforms } from "../svg-transform";
|
||||
import { getSvgSymbol } from "../svg-vocabulary";
|
||||
import { SvgVocabulary } from "../svg-vocabulary";
|
||||
import { SymbolContextWidget } from "../symbol-context-widget";
|
||||
|
||||
const symbol = createCreatureSymbolFactory(getSvgSymbol);
|
||||
const symbol = createCreatureSymbolFactory(SvgVocabulary);
|
||||
|
||||
const Eye = symbol("eye");
|
||||
|
||||
|
|
|
@ -13,18 +13,18 @@ import {
|
|||
SvgSymbolContext,
|
||||
SvgSymbolData,
|
||||
} from "../svg-symbol";
|
||||
import { SvgSymbolWidget } from "../svg-symbol-widget";
|
||||
import { VocabularyWidget } from "../vocabulary-widget";
|
||||
import {
|
||||
svgRotate,
|
||||
svgScale,
|
||||
SvgTransforms,
|
||||
svgTranslate,
|
||||
} from "../svg-transform";
|
||||
import { getSvgSymbol, SvgVocabulary } from "../svg-vocabulary";
|
||||
import { SvgVocabulary } from "../svg-vocabulary";
|
||||
import { SymbolContextWidget } from "../symbol-context-widget";
|
||||
import { range } from "../util";
|
||||
|
||||
const EYE = getSvgSymbol("eye");
|
||||
const EYE = SvgVocabulary.get("eye");
|
||||
|
||||
const MandalaCircle: React.FC<
|
||||
{
|
||||
|
@ -72,7 +72,7 @@ export const MandalaPage: React.FC<{}> = () => {
|
|||
<ColorWidget label="Background" value={bgColor} onChange={setBgColor} />{" "}
|
||||
</SymbolContextWidget>
|
||||
<p>
|
||||
<SvgSymbolWidget
|
||||
<VocabularyWidget
|
||||
label="Symbol"
|
||||
value={symbol}
|
||||
onChange={setSymbol}
|
||||
|
|
|
@ -44,7 +44,7 @@ export const VocabularyPage: React.FC<{}> = () => {
|
|||
<h1>Mystic Symbolic Vocabulary</h1>
|
||||
<SymbolContextWidget ctx={ctx} onChange={setCtx} />
|
||||
<HoverDebugHelper>
|
||||
{SvgVocabulary.map((symbolData) => (
|
||||
{SvgVocabulary.items.map((symbolData) => (
|
||||
<div
|
||||
key={symbolData.name}
|
||||
style={{
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import React, { useMemo } from "react";
|
||||
import { SvgSymbolData } from "./svg-symbol";
|
||||
import { slugify } from "./util";
|
||||
|
||||
export type SvgSymbolWidgetProps = {
|
||||
id?: string;
|
||||
label: string;
|
||||
value: SvgSymbolData;
|
||||
onChange: (value: SvgSymbolData) => void;
|
||||
choices: SvgSymbolData[];
|
||||
};
|
||||
|
||||
export const SvgSymbolWidget: React.FC<SvgSymbolWidgetProps> = ({
|
||||
id,
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
choices,
|
||||
}) => {
|
||||
id = id || slugify(label);
|
||||
const symbolMap = useMemo(
|
||||
() => new Map(choices.map((symbol) => [symbol.name, symbol])),
|
||||
[choices]
|
||||
);
|
||||
const handleChange = (value: string) => {
|
||||
const symbol = symbolMap.get(value);
|
||||
if (!symbol) throw new Error(`Unable to find "${value}"`);
|
||||
onChange(symbol);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={id}>{label}: </label>
|
||||
<select
|
||||
id={id}
|
||||
onChange={(e) => handleChange(e.target.value)}
|
||||
value={value.name}
|
||||
>
|
||||
{choices.map((choice) => (
|
||||
<option key={choice.name} value={choice.name}>
|
||||
{choice.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,23 +1,7 @@
|
|||
import type { SvgSymbolData } from "./svg-symbol";
|
||||
import { Vocabulary } from "./vocabulary";
|
||||
import _SvgVocabulary from "./_svg-vocabulary.json";
|
||||
|
||||
export const SvgVocabulary: SvgSymbolData[] = _SvgVocabulary as any;
|
||||
|
||||
/**
|
||||
* Mapping from symbol names to symbol data, for quick and easy access.
|
||||
*/
|
||||
const SYMBOL_MAP = new Map(
|
||||
SvgVocabulary.map((symbol) => [symbol.name, symbol])
|
||||
export const SvgVocabulary = new Vocabulary<SvgSymbolData>(
|
||||
_SvgVocabulary as any
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the data for the given symbol, throwing an error
|
||||
* if it doesn't exist.
|
||||
*/
|
||||
export function getSvgSymbol(name: string): SvgSymbolData {
|
||||
const symbol = SYMBOL_MAP.get(name);
|
||||
if (!symbol) {
|
||||
throw new Error(`Unable to find the symbol "${name}"!`);
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import React from "react";
|
||||
import { slugify } from "./util";
|
||||
import { Vocabulary, VocabularyType } from "./vocabulary";
|
||||
|
||||
export type VocabularyWidgetProps<T extends VocabularyType> = {
|
||||
id?: string;
|
||||
label: string;
|
||||
value: T;
|
||||
onChange: (value: T) => void;
|
||||
choices: Vocabulary<T>;
|
||||
};
|
||||
|
||||
export function VocabularyWidget<T extends VocabularyType>({
|
||||
id,
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
choices,
|
||||
}: VocabularyWidgetProps<T>) {
|
||||
id = id || slugify(label);
|
||||
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={id}>{label}: </label>
|
||||
<select
|
||||
id={id}
|
||||
onChange={(e) => onChange(choices.get(e.target.value))}
|
||||
value={value.name}
|
||||
>
|
||||
{choices.items.map((choice) => (
|
||||
<option key={choice.name} value={choice.name}>
|
||||
{choice.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
export type VocabularyType = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export class Vocabulary<T extends VocabularyType> {
|
||||
readonly itemMap: Map<string, T>;
|
||||
|
||||
constructor(readonly items: T[]) {
|
||||
this.itemMap = new Map(items.map((item) => [item.name, item]));
|
||||
}
|
||||
|
||||
get(name: string): T {
|
||||
const item = this.itemMap.get(name);
|
||||
if (!item) {
|
||||
throw new Error(`Unable to find the item "${name}"!`);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue