Add attach_to metadata. (#52)
This fixes #49 by implementing the `attach_to` metadata property.pull/54/head
rodzic
d67848fb67
commit
612195e2f2
|
@ -1,7 +1,11 @@
|
|||
import React, { useContext, useRef, useState } from "react";
|
||||
import { SvgVocabulary } from "../svg-vocabulary";
|
||||
import { createSvgSymbolContext, SvgSymbolData } from "../svg-symbol";
|
||||
import { iterAttachmentPoints } from "../specs";
|
||||
import {
|
||||
AttachmentPointType,
|
||||
ATTACHMENT_POINT_TYPES,
|
||||
iterAttachmentPoints,
|
||||
} from "../specs";
|
||||
import { Random } from "../random";
|
||||
import { SymbolContextWidget } from "../symbol-context-widget";
|
||||
import { range } from "../util";
|
||||
|
@ -34,8 +38,41 @@ const ROOT_SYMBOLS = SvgVocabulary.filter(
|
|||
(data) => data.meta?.always_be_nested !== true
|
||||
);
|
||||
|
||||
/** Symbols that can be attached to the main body of a creature. */
|
||||
const ATTACHMENT_SYMBOLS = ROOT_SYMBOLS;
|
||||
type AttachmentSymbolMap = {
|
||||
[key in AttachmentPointType]: SvgSymbolData[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Symbols that can be attached to the main body of a creature,
|
||||
* at a particular attachment point.
|
||||
*/
|
||||
const ATTACHMENT_SYMBOLS: AttachmentSymbolMap = (() => {
|
||||
const result = {} as AttachmentSymbolMap;
|
||||
|
||||
for (let type of ATTACHMENT_POINT_TYPES) {
|
||||
result[type] = SvgVocabulary.filter((data) => {
|
||||
const { meta } = data;
|
||||
|
||||
// If we have no metadata whatsoever, it can attach anywhere.
|
||||
if (!meta) return true;
|
||||
|
||||
if (meta.always_be_nested === true) {
|
||||
// This symbol should *only* ever be nested, so return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we have no "attach_to", it can attach anywhere.
|
||||
if (!meta.attach_to) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only attach to points listed in "attach_to".
|
||||
return meta.attach_to.includes(type);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
})();
|
||||
|
||||
/** Symbols that can be nested within any part of a creature. */
|
||||
const NESTED_SYMBOLS = SvgVocabulary.filter(
|
||||
|
@ -113,7 +150,7 @@ function getSymbolWithAttachments(
|
|||
numAttachmentKinds
|
||||
);
|
||||
for (let kind of attachmentKinds) {
|
||||
const attachment = rng.choice(ATTACHMENT_SYMBOLS);
|
||||
const attachment = rng.choice(ATTACHMENT_SYMBOLS[kind]);
|
||||
const indices = range(root.specs[kind]?.length ?? 0);
|
||||
result.attachments.push({
|
||||
data: attachment,
|
||||
|
|
|
@ -43,6 +43,14 @@ export const ATTACHMENT_POINT_TYPES: AttachmentPointType[] = [
|
|||
"crown",
|
||||
];
|
||||
|
||||
const ATTACHMENT_POINT_SET = new Set(ATTACHMENT_POINT_TYPES);
|
||||
|
||||
export function isAttachmentPointType(
|
||||
value: any
|
||||
): value is AttachmentPointType {
|
||||
return ATTACHMENT_POINT_SET.has(value);
|
||||
}
|
||||
|
||||
export function* iterAttachmentPoints(specs: Specs): Iterable<AttachmentPoint> {
|
||||
for (let type of ATTACHMENT_POINT_TYPES) {
|
||||
const points = specs[type];
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import path from "path";
|
||||
import fs from "fs";
|
||||
import toml from "toml";
|
||||
import { validateSvgSymbolMetadata } from "./svg-symbol-metadata";
|
||||
import {
|
||||
validateAttachTo,
|
||||
validateSvgSymbolMetadata,
|
||||
} from "./svg-symbol-metadata";
|
||||
import { withMockConsoleLog } from "./test-util";
|
||||
|
||||
const templatePath = path.join(__dirname, "..", "svg", "_template.toml");
|
||||
|
||||
|
@ -51,3 +55,24 @@ describe("validateSvgSymbolMetadata()", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateAttachTo()", () => {
|
||||
it("works", () => {
|
||||
expect(validateAttachTo(["tail", "leg"])).toEqual(["tail", "leg"]);
|
||||
});
|
||||
|
||||
it("works", () => {
|
||||
withMockConsoleLog((mockLog) => {
|
||||
expect(validateAttachTo(["beanbag"])).toEqual([]);
|
||||
expect(mockLog).toHaveBeenCalledWith(
|
||||
"Item 'beanbag' in \"attach_to\" is not a valid attachment point."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("raises error when value is not an array", () => {
|
||||
expect(() => validateAttachTo("blah")).toThrow(
|
||||
'Expected "attach_to" to be an array, but it is a string!'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { AttachmentPointType, isAttachmentPointType } from "./specs";
|
||||
|
||||
type SvgSymbolMetadataBooleans = {
|
||||
/**
|
||||
* If true, this indicates that the symbol should always have
|
||||
|
@ -37,7 +39,14 @@ function isSvgSymbolMetadataBoolean(
|
|||
return METADATA_BOOLEANS.has(key as any);
|
||||
}
|
||||
|
||||
export type SvgSymbolMetadata = SvgSymbolMetadataBooleans;
|
||||
export type SvgSymbolMetadata = SvgSymbolMetadataBooleans & {
|
||||
/**
|
||||
* If defined, this indicates the kinds of attachment points
|
||||
* that this symbol can attach to. If not defined, it will
|
||||
* be able to attach to any symbol.
|
||||
*/
|
||||
attach_to?: AttachmentPointType[];
|
||||
};
|
||||
|
||||
export function validateSvgSymbolMetadata(
|
||||
obj: any
|
||||
|
@ -53,9 +62,33 @@ export function validateSvgSymbolMetadata(
|
|||
);
|
||||
}
|
||||
metadata[key] = value;
|
||||
} else if (key === "attach_to") {
|
||||
metadata.attach_to = validateAttachTo(obj[key]);
|
||||
} else {
|
||||
unknownProperties.push(key);
|
||||
}
|
||||
}
|
||||
return { metadata, unknownProperties };
|
||||
}
|
||||
|
||||
export function validateAttachTo(value: unknown): AttachmentPointType[] {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new Error(
|
||||
`Expected "attach_to" to be an array, but it is a ${typeof value}!`
|
||||
);
|
||||
}
|
||||
|
||||
const result: AttachmentPointType[] = [];
|
||||
|
||||
for (let item of value) {
|
||||
if (isAttachmentPointType(item)) {
|
||||
result.push(item);
|
||||
} else {
|
||||
console.log(
|
||||
`Item '${item}' in "attach_to" is not a valid attachment point.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
export function withMockConsoleLog(fn: (mock: jest.Mock) => void) {
|
||||
const originalLog = console.log;
|
||||
const mockLog = jest.fn();
|
||||
console.log = mockLog;
|
||||
try {
|
||||
fn(mockLog);
|
||||
} finally {
|
||||
console.log = originalLog;
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import { withMockConsoleLog } from "./test-util";
|
||||
import { convertSvgMarkupToSymbolData } from "./vocabulary-builder";
|
||||
|
||||
const CIRCLE = `<path fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" d="M 360.000 29.751 C 542.791 29.751 690.249 177.209 690.249 360.000 C 690.249 542.791 542.791 690.249 360.000 690.249 C 177.209 690.249 29.751 542.791 29.751 360.000 C 29.751 177.209 177.209 29.751 360.000 29.751 Z"/>`;
|
||||
|
@ -6,17 +7,6 @@ function arrow(color: string) {
|
|||
return `<path fill="${color}" fill-rule="evenodd" stroke="none" d="M 360.000 679.153 C 360.001 679.156 372.114 713.074 372.116 713.077 C 372.114 713.076 360.001 701.805 360.000 701.804 C 359.999 701.805 347.886 713.076 347.884 713.077 C 347.886 713.074 359.999 679.156 360.000 679.153 Z"/>`;
|
||||
}
|
||||
|
||||
function withMockConsoleLog(fn: (mock: jest.Mock) => void) {
|
||||
const originalLog = console.log;
|
||||
const mockLog = jest.fn();
|
||||
console.log = mockLog;
|
||||
try {
|
||||
fn(mockLog);
|
||||
} finally {
|
||||
console.log = originalLog;
|
||||
}
|
||||
}
|
||||
|
||||
describe("convertSvgMarkupToSymbolData()", () => {
|
||||
it("works with SVGs that just have a path and no specs", () => {
|
||||
expect(
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
#
|
||||
# Also note that any lines that begin with a "#" are comments,
|
||||
# so they will be ignored by the code.
|
||||
#
|
||||
# Note also that all properties that are booleans
|
||||
# (i.e., true or false) will, unless otherwise noted,
|
||||
# always default to false if left undefined.
|
||||
|
||||
# If true, this indicates that the symbol should always have
|
||||
# a symbol nested within its nesting area(s).
|
||||
|
@ -23,3 +27,9 @@ never_be_nested = false
|
|||
# If true, this indicates that any symbols nested on this
|
||||
# symbol’s nesting area should have their colors inverted.
|
||||
invert_nested = false
|
||||
|
||||
# If set to an array of strings, this indicates the kinds of
|
||||
# attachment points that this symbol can attach to. If not
|
||||
# defined, this symbol will be able to attach to any attachment
|
||||
# point.
|
||||
attach_to = ["tail", "leg", "arm", "horn", "crown"]
|
||||
|
|
Ładowanie…
Reference in New Issue