diff --git a/src/layouts/layout.types.ts b/src/layouts/layout.types.ts index 958e683..f3a3f6e 100644 --- a/src/layouts/layout.types.ts +++ b/src/layouts/layout.types.ts @@ -20,7 +20,7 @@ export const StatblockItemTypes = [ "action" ] as const; -export const TypeNames: Array<[(typeof StatblockItemTypes)[number], string]> = [ +export const TypeNames: Array<[(typeof StatblockItemTypes)[number] | null, string]> = [ ["group", "Group"], ["inline", "Inline Group"], ["ifelse", "If/Else"], diff --git a/src/view/statblock.ts b/src/view/statblock.ts index 3bcca8b..f47b6cd 100644 --- a/src/view/statblock.ts +++ b/src/view/statblock.ts @@ -45,17 +45,17 @@ type RendererParameters = { ); export default class StatBlockRenderer extends MarkdownRenderChild { - topBar: HTMLDivElement; - bottomBar: HTMLDivElement; + topBar!: HTMLDivElement; + bottomBar!: HTMLDivElement; loaded: boolean = false; - statblockEl: HTMLDivElement; - contentEl: HTMLDivElement; + statblockEl!: HTMLDivElement; + contentEl!: HTMLDivElement; container: HTMLElement; - monster: Monster; + monster!: Monster; plugin: StatBlockPlugin; - params: Partial; + params!: Partial; context: string; - layout: Layout; + layout!: Layout; constructor( public rendererParameters: RendererParameters, public icons = true @@ -166,86 +166,57 @@ export default class StatBlockRenderer extends MarkdownRenderChild { } switch (block.type) { case "traits": { + /** + * Traits can be defined directly, as additive (+) or subtractive (-). + * + * Directly defined traits can be overidden by name up the extension tree. + * Parameters > `creature` > `extends` + * Directly defined parameter traits are *always shown*. + * + * Additive traits are *always* displayed, no matter where they originate. + * + * Subtractive traits are *always* removed, unless the trait is directly defined in the parameters. + * Subtractive traits only work on directly defined traits. + * + */ const $TRAIT_MAP: Map = new Map(); - let $ADDITIVE_TRAITS: Trait[] = []; - let $DELETE_TRAITS: Set = new Set(); - /** Add traits from the extensions group first. */ - for (const extension of extensions) { - let traits = getTraitsList(property, extension); - for (const trait of traits) { - $TRAIT_MAP.set(trait.name, trait); - } + const $ADDITIVE_TRAITS: Trait[] = []; - traits = getTraitsList( - `${property}+` as keyof Monster, - extension - ); - for (const trait of traits) { - $ADDITIVE_TRAITS.push(trait); - } - traits = getTraitsList( + /** + * Resolve extension traits first. + */ + for (const creature of [...extensions]) { + /** + * Deleted traits. These are always removed. + */ + for (const trait of getTraitsList( `${property}-` as keyof Monster, - extension - ); - for (const trait of traits) { - $DELETE_TRAITS.add(trait.name); - } - } - //next, underlying monster object - let traits = getTraitsList(property, built); - for (const trait of traits) { - if (!(property in this.params)) { + creature + )) { $TRAIT_MAP.delete(trait.name); - $ADDITIVE_TRAITS.push(trait); - } else { + } + /** + * Directly defined traits. + * + * Because these can be overridden, they go into a map by name. + */ + for (const trait of getTraitsList( + property, + creature + )) { $TRAIT_MAP.set(trait.name, trait); } - } - traits = getTraitsList( - `${property}+` as keyof Monster, - built - ); - for (const trait of traits) { - $ADDITIVE_TRAITS.push(trait); - } - traits = getTraitsList( - `${property}-` as keyof Monster, - built - ); - for (const trait of traits) { - $DELETE_TRAITS.add(trait.name); - } - - /** Remove these traits first, so you don't get hit by the params */ - traits = getTraitsList( - `${property}-` as keyof Monster, - this.params - ); - for (const trait of traits) { - $DELETE_TRAITS.add(trait.name); - } - for (const trait of $DELETE_TRAITS) { - $TRAIT_MAP.delete(trait); - $ADDITIVE_TRAITS = $ADDITIVE_TRAITS.filter( - (t) => t.name !== trait - ); - } - //finally, the parameters should always be added - traits = getTraitsList(property, this.params); - for (const trait of traits) { - $TRAIT_MAP.delete(trait.name); - $ADDITIVE_TRAITS.push(trait); - } - - traits = getTraitsList( - `${property}+` as keyof Monster, - this.params - ); - for (const trait of traits) { - $ADDITIVE_TRAITS.push(trait); + /** + * Additive traits. These traits are always shown. + */ + for (const trait of getTraitsList( + `${property}+` as keyof Monster, + creature + )) { + $ADDITIVE_TRAITS.push(trait); + } } - Object.assign(built, { [property]: [ ...$TRAIT_MAP.values(), @@ -398,7 +369,7 @@ export default class StatBlockRenderer extends MarkdownRenderChild { } } - $ui: Statblock; + $ui!: Statblock; async init() { this.containerEl.empty(); this.monster = (await this.build()) as Monster; @@ -434,7 +405,7 @@ export default class StatBlockRenderer extends MarkdownRenderChild { this.$ui.$on("export", () => { this.plugin.exportAsPng( this.monster.name, - this.containerEl.firstElementChild + this.containerEl.firstElementChild! ); }); diff --git a/src/view/ui/ColumnContainer.svelte b/src/view/ui/ColumnContainer.svelte index 845a90a..a5bed5b 100644 --- a/src/view/ui/ColumnContainer.svelte +++ b/src/view/ui/ColumnContainer.svelte @@ -129,7 +129,7 @@ container: target, classes: item.cls ? [...(classes ?? []), item.cls] - : classes ?? [] + : (classes ?? []) }); targets.push(...element); @@ -231,7 +231,7 @@ "statblock-item-inline", ...(item.cls ? [...(classes ?? []), item.cls] - : classes ?? []) + : (classes ?? [])) ] }); for (const nested of item.nested ?? []) { @@ -266,7 +266,7 @@ type: "group", nested: layout.blocks, id: item.layout, - properties: null + properties: [] }, { classes: [ @@ -306,58 +306,55 @@ item.properties[0] ] as Spell[]; - if (!Array.isArray(blocks) || !blocks.length) return; - let spellBlocks: Array = blocks.reduce( - (acc, current) => { - if ( - typeof current === "string" && - (current.charAt(current.length - 1) == ":" || - !current.includes(":")) - ) { - const newBlock: SpellBlock = { - header: ensureColon(current), - spells: [] - }; - acc.push(newBlock); - return acc; - } - const lastBlock: SpellBlock = acc[acc.length - 1]; - let spell: Spell; - if (typeof current == "string") { + if (!Array.isArray(blocks) || !blocks.length) return []; + let spellBlocks: Array = blocks.reduce< + SpellBlock[] + >((acc, current) => { + if ( + typeof current === "string" && + (current.charAt(current.length - 1) == ":" || + !current.includes(":")) + ) { + const newBlock: SpellBlock = { + header: ensureColon(current), + spells: [] + }; + acc.push(newBlock); + return acc; + } + const lastBlock: SpellBlock = acc[acc.length - 1]; + let spell: Spell; + if (typeof current == "string") { + spell = { + spells: Linkifier.linkifySpells( + current, + context.get("context") as string + ) + }; + } else { + try { spell = { + level: Object.keys(current).shift(), spells: Linkifier.linkifySpells( - current, + stringify(Object.values(current).shift()!), context.get("context") as string ) }; - } else { - try { - spell = { - level: Object.keys(current).shift(), - spells: Linkifier.linkifySpells( - stringify( - Object.values(current).shift() - ), - context.get("context") as string - ) - }; - } catch (e) { - return acc; - } - } - if (lastBlock) { - lastBlock.spells.push(spell); - } else { - const missingHeaderBlock: SpellBlock = { - header: `${monster.name} knows the following spells:`, - spells: [spell] - }; - acc.push(missingHeaderBlock); + } catch (e) { + return acc; } - return acc; - }, - [] - ); + } + if (lastBlock) { + lastBlock.spells.push(spell); + } else { + const missingHeaderBlock: SpellBlock = { + header: `${monster.name} knows the following spells:`, + spells: [spell] + }; + acc.push(missingHeaderBlock); + } + return acc; + }, []); for ( let blockIndex = 0; @@ -371,7 +368,7 @@ props: { name: blockIndex == 0 - ? item.heading ?? "Spellcasting" + ? (item.heading ?? "Spellcasting") : "", property: item.properties[0], desc: block.header, @@ -515,10 +512,12 @@ } } } catch (e) { + console.error(e); return []; } break; } + } if ("hasRule" in item && item.hasRule) { const rule = createDiv( @@ -535,7 +534,7 @@ return targets.filter((el) => el.hasChildNodes()); }; $: maxHeight = - !isNaN(Number(monster.columnHeight)) && monster.columnHeight > 0 + !isNaN(Number(monster.columnHeight)) && monster.columnHeight! > 0 ? monster.columnHeight : Infinity; @@ -582,7 +581,7 @@ } }); contentContainer.$on("built", () => { - const columnEl = temp.querySelector(".column"); + const columnEl = temp.querySelector(".column")!; for (let target of targets) { heights.push(target.scrollHeight); } @@ -597,7 +596,7 @@ } else { split = Math.max( 600, - Math.min(columnEl.scrollHeight / columns, maxHeight) + Math.min(columnEl.scrollHeight / columns, maxHeight!) ); } diff --git a/src/view/ui/MarkdownHolder.svelte b/src/view/ui/MarkdownHolder.svelte index 8af333a..18e390c 100644 --- a/src/view/ui/MarkdownHolder.svelte +++ b/src/view/ui/MarkdownHolder.svelte @@ -9,6 +9,7 @@ import { Linkifier } from "src/parser/linkify"; import { parseForDice } from "src/parser/dice-parsing"; import type { Writable } from "svelte/store"; + import { stringify } from "src/util/util"; export let property: string; @@ -38,7 +39,7 @@ ) { split = [{ text: monster[item.diceProperty] as string }]; } else { - const parsed = parseForDice(layout, property, monster); + const parsed = parseForDice(layout, stringify(property), monster); if (Array.isArray(parsed)) { split = parsed; } else { @@ -62,7 +63,7 @@ new Notice( `There was an error executing the provided dice callback for [${item.properties.join( ", " - )}]\n\n${e.message}` + )}]\n\n${(e as Error).message}` ); console.error(e); }