mysticsymbolic.github.io/lib/creature-symbol-factory.tsx

145 wiersze
3.6 KiB
TypeScript

import React from "react";
import {
AttachedCreatureSymbol,
CreatureSymbol,
NestedCreatureSymbol,
} from "./creature-symbol";
import { AttachmentPointType } from "./specs";
import { SvgSymbolData } from "./svg-symbol";
import { Vocabulary } from "./vocabulary";
type AttachmentIndices = {
left?: boolean;
right?: boolean;
};
type AttachmentChildren = JSX.Element | JSX.Element[];
type SimpleCreatureSymbolProps = AttachmentIndices & {
nestInside?: boolean;
children?: AttachmentChildren;
attachTo?: AttachmentPointType;
invert?: boolean;
indices?: number[];
};
function getAttachmentIndices(ai: AttachmentIndices): number[] {
const result: number[] = [];
if (ai.left) {
result.push(0);
}
if (ai.right) {
result.push(1);
}
if (result.length === 0) {
result.push(0);
}
return result;
}
type SplitCreatureSymbolChildren = {
attachments: JSX.Element[];
nests: JSX.Element[];
};
function splitCreatureSymbolChildren(
children?: AttachmentChildren
): SplitCreatureSymbolChildren {
const result: SplitCreatureSymbolChildren = {
attachments: [],
nests: [],
};
if (!children) return result;
React.Children.forEach(children, (child) => {
if (child.props.nestInside) {
result.nests.push(child);
} else {
result.attachments.push(child);
}
});
return result;
}
type SimpleCreatureSymbolFC = React.FC<SimpleCreatureSymbolProps> & {
creatureSymbolData: SvgSymbolData;
};
/**
* Create a factory that can be used to return React components to
* render a `<CreatureSymbol>`.
*/
export function createCreatureSymbolFactory(
vocabulary: Vocabulary<SvgSymbolData>
) {
/**
* Returns a React component that renders a `<CreatureSymbol>`, using the symbol
* with the given name as its default data.
*/
return function createCreatureSymbol(
name: string
): React.FC<SimpleCreatureSymbolProps> {
const data = vocabulary.get(name);
const Component: SimpleCreatureSymbolFC = (props) => {
const symbol = getCreatureSymbol(data, props);
return <CreatureSymbol {...symbol} />;
};
Component.creatureSymbolData = data;
return Component;
};
}
function isSimpleCreatureSymbolFC(fn: any): fn is SimpleCreatureSymbolFC {
return !!fn.creatureSymbolData;
}
function extractNestedCreatureSymbol(el: JSX.Element): NestedCreatureSymbol {
const base = extractCreatureSymbolFromElement(el);
const props: SimpleCreatureSymbolProps = el.props;
const indices = props.indices || getAttachmentIndices(props);
const result: NestedCreatureSymbol = {
...base,
indices,
};
return result;
}
function extractAttachedCreatureSymbol(
el: JSX.Element
): AttachedCreatureSymbol {
const base = extractNestedCreatureSymbol(el);
const props: SimpleCreatureSymbolProps = el.props;
const { attachTo } = props;
if (!attachTo) {
throw new Error("Expected attachment to have `attachTo` prop!");
}
const result: AttachedCreatureSymbol = {
...base,
attachTo,
};
return result;
}
function getCreatureSymbol(
data: SvgSymbolData,
props: SimpleCreatureSymbolProps
): CreatureSymbol {
const { attachments, nests } = splitCreatureSymbolChildren(props.children);
const result: CreatureSymbol = {
data,
attachments: attachments.map(extractAttachedCreatureSymbol),
nests: nests.map(extractNestedCreatureSymbol),
invertColors: props.invert ?? false,
};
return result;
}
function extractCreatureSymbolFromElement(el: JSX.Element): CreatureSymbol {
if (isSimpleCreatureSymbolFC(el.type)) {
return getCreatureSymbol(el.type.creatureSymbolData, el.props);
}
throw new Error("Found unknown component type!");
}