145 wiersze
3.6 KiB
TypeScript
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!");
|
|
}
|