From 28c836321dd5432f8c830b4c5a2c1a8ddeb7229f Mon Sep 17 00:00:00 2001 From: Jeremy Valentine <38669521+valentine195@users.noreply.github.com> Date: Tue, 5 Mar 2024 15:35:25 -0500 Subject: [PATCH] feat!: switch to new Fantasy Statblocks API --- package-lock.json | 8 +- package.json | 2 +- src/builder/view.ts | 37 ++++-- src/builder/view/creatures/Creatures.svelte | 3 +- src/encounter/ui/Creature.svelte | 10 +- src/encounter/ui/EncounterLine.svelte | 25 ++-- src/main.css | 37 ++++++ src/main.ts | 125 ++++++++++++++++---- src/utils/index.ts | 12 ++ 9 files changed, 207 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index 627fc2a5..2ec311e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "dotenv": "^16.3.1", "esbuild": "^0.19.3", "esbuild-svelte": "^0.8.0", - "fantasy-statblocks": "^4.0.1", + "fantasy-statblocks": "^4.0.3", "fast-copy": "^3.0.1", "jest": "^29.7.0", "obsidian": "^1.4.11", @@ -3366,9 +3366,9 @@ } }, "node_modules/fantasy-statblocks": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/fantasy-statblocks/-/fantasy-statblocks-4.0.1.tgz", - "integrity": "sha512-DoBlgkU2DYuM5YZU355BH+lRFj94boRe+t6nbXo5dilta0m9p4CjaRBAB/9UkUDCp3XH1PfQgcQrg784hjDxBg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fantasy-statblocks/-/fantasy-statblocks-4.0.3.tgz", + "integrity": "sha512-cgCwruJeKwj3LJKI7lQzSuo96/eirlAvkb+x2/BEiGxyKsq1igXHL5S3tdQgYFvPoj+1JOcArfdo+ezcJsbsJw==", "dev": true, "dependencies": { "yaml": "^2.1.3" diff --git a/package.json b/package.json index f33e2fa9..d98d4d43 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "dotenv": "^16.3.1", "esbuild": "^0.19.3", "esbuild-svelte": "^0.8.0", - "fantasy-statblocks": "^4.0.1", + "fantasy-statblocks": "^4.0.3", "fast-copy": "^3.0.1", "jest": "^29.7.0", "obsidian": "^1.4.11", diff --git a/src/builder/view.ts b/src/builder/view.ts index 184b95d5..5a3b0f46 100644 --- a/src/builder/view.ts +++ b/src/builder/view.ts @@ -1,6 +1,6 @@ import { ItemView, type ViewStateResult, WorkspaceLeaf } from "obsidian"; import type InitiativeTracker from "src/main"; -import { BUILDER_VIEW } from "../utils"; +import { BUILDER_VIEW, buildLoader } from "../utils"; import Builder from "./view/Builder.svelte"; import { encounter } from "./stores/encounter"; @@ -37,15 +37,36 @@ export default class BuilderView extends ItemView { } ui: Builder; async onOpen() { - this.ui = new Builder({ - target: this.contentEl, - props: { - plugin: this.plugin - } - }); + + if ( + this.plugin.canUseStatBlocks && + !window["FantasyStatblocks"].isResolved() + ) { + this.contentEl.addClasses(["waiting-for-bestiary", "is-loading"]); + const loading = this.contentEl.createEl("p", { + text: "Waiting for Fantasy Statblocks Bestiary..." + }); + window["FantasyStatblocks"].onResolved(() => { + this.contentEl.removeClasses(["waiting-for-bestiary", "is-loading"]); + loading.detach(); + this.ui = new Builder({ + target: this.contentEl, + props: { + plugin: this.plugin + } + }); + }); + } else { + this.ui = new Builder({ + target: this.contentEl, + props: { + plugin: this.plugin + } + }); + } } async onClose() { - this.ui.$destroy(); + this.ui?.$destroy(); } getDisplayText(): string { return "Encounter Builder"; diff --git a/src/builder/view/creatures/Creatures.svelte b/src/builder/view/creatures/Creatures.svelte index c2b8375d..96177be3 100644 --- a/src/builder/view/creatures/Creatures.svelte +++ b/src/builder/view/creatures/Creatures.svelte @@ -12,6 +12,7 @@ import type InitiativeTracker from "src/main"; import Ajv from "ajv"; import schema from "../../stores/filter/filter-schema.json"; + import type { BuilderState } from "obsidian-overload"; const table = getContext("table"); const { sortDir, allHeaders } = table; @@ -62,7 +63,7 @@ const ajv = new Ajv(); // validate is a type guard for MyData - type is inferred from schema type - const validate = ajv.compile(schema); + const validate = ajv.compile(schema); const input = createEl("input", { attr: { type: "file", diff --git a/src/encounter/ui/Creature.svelte b/src/encounter/ui/Creature.svelte index e27320ef..c272cf75 100644 --- a/src/encounter/ui/Creature.svelte +++ b/src/encounter/ui/Creature.svelte @@ -3,7 +3,7 @@ import type InitiativeTracker from "src/main"; import { FRIENDLY, HIDDEN, RANDOM_HP, getRpgSystem } from "src/utils"; import type { Creature } from "src/utils/creature"; - import { getContext } from "svelte/internal"; + import { getContext } from "svelte"; const plugin = getContext("plugin"); const rpgSystem = getRpgSystem(plugin); @@ -34,9 +34,9 @@ {/if} plugin.openCombatant(creature)}> {#if creature.display && creature.display != creature.name} -  {creature.display}{count == 1 ? "" : "s"} ({creature.name}) + {creature.display}{count == 1 ? "" : "s"} ({creature.name}) {:else} -  {creature.name}{count == 1 ? "" : "s"} + {creature.name}{count == 1 ? "" : "s"} {/if} {#if shouldShowRoll && creature.hit_dice?.length} @@ -46,7 +46,9 @@  ( - {rpgSystem.formatDifficultyValue(xp)} + {rpgSystem.formatDifficultyValue(xp)} {rpgSystem.valueUnit} ) diff --git a/src/encounter/ui/EncounterLine.svelte b/src/encounter/ui/EncounterLine.svelte index 186b92e3..328eea52 100644 --- a/src/encounter/ui/EncounterLine.svelte +++ b/src/encounter/ui/EncounterLine.svelte @@ -121,20 +121,21 @@ {#if creatures.size} {#each [...creatures] as [creature, count], index} - - {joiner(index, creatures.size)} -   - {joiner(index, creatures.size)} plugin.openCombatant(creature)} - > - {#if creature.display && creature.display != creature.name} - {creature.display}{count == 1 ? "" : "s"} ({creature.name}) - {:else} - {creature.name}{count == 1 ? "" : "s"} - {/if} - - + >{#if creature.display && creature.display != creature.name}{creature.display}{count == + 1 + ? "" + : "s"} ({creature.name}){:else}{creature.name}{count == + 1 + ? "" + : "s"}{/if} {/each} {:else} - diff --git a/src/main.css b/src/main.css index 0e13ad96..f98f89e4 100644 --- a/src/main.css +++ b/src/main.css @@ -253,3 +253,40 @@ body { align-items: center; gap: 0.25rem; } + +.waiting-for-bestiary.is-loading { + display: flex; + justify-content: center; + font-style: italic; + color: var(--text-muted); +} + +.waiting-for-bestiary.inline { + display: flex; + align-items: center; + gap: 0.25rem; + font-size: var(--font-small); + --icon-size: var(--icon-s); +} + +.waiting-for-bestiary.inline .icon { + display: flex; + align-items: center; + animation: rotation 1s ease infinite; +} + + +@keyframes rotation { + 0% { + transform: scale(1) rotate(-60deg); + } + 20% { + transform: /* scale(1.1) */ rotate(-90deg); + } + 50% { + transform: /* scale(0.9) */ rotate(690deg); + } + 100% { + transform: /* scale(1) */ rotate(660deg); + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index f7da8012..b33d1b7c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,7 +4,8 @@ import { parseYaml, Plugin, TFile, - WorkspaceLeaf + WorkspaceLeaf, + setIcon } from "obsidian"; import { @@ -153,9 +154,10 @@ export default class InitiativeTracker extends Plugin { } get canUseStatBlocks(): boolean { + return true; if (this.app.plugins.enabledPlugins.has("obsidian-5e-statblocks")) { return (window["FantasyStatblocks"]?.getVersion()?.major ?? 0) >= 4; - } + } return false; } get statblockVersion() { @@ -281,7 +283,9 @@ export default class InitiativeTracker extends Plugin { e.createEl("p", { text: `Initiative Tracker v${this.manifest.version} requires Fantasy Statblocks v4.0.0 or later.` }); - e.createEl("span", { text: "Please update Fantasy Statblocks to use the Fantasy Statblocks integration."}) + e.createEl("span", { + text: "Please update Fantasy Statblocks to use the Fantasy Statblocks integration." + }); }) ); } @@ -314,14 +318,49 @@ export default class InitiativeTracker extends Plugin { this.registerEditorSuggest(new EncounterSuggester(this)); this.registerMarkdownCodeBlockProcessor("encounter", (src, el, ctx) => { - const handler = new EncounterBlock(this, src, el); - ctx.addChild(handler); + if ( + this.canUseStatBlocks && + !window["FantasyStatblocks"].isResolved() + ) { + el.addClasses(["waiting-for-bestiary", "is-loading"]); + const loading = el.createEl("p", { + text: "Waiting for Fantasy Statblocks Bestiary..." + }); + window["FantasyStatblocks"].onResolved(() => { + el.removeClasses(["waiting-for-bestiary", "is-loading"]); + loading.detach(); + const handler = new EncounterBlock(this, src, el); + ctx.addChild(handler); + }); + } else { + const handler = new EncounterBlock(this, src, el); + ctx.addChild(handler); + } }); this.registerMarkdownCodeBlockProcessor( "encounter-table", (src, el, ctx) => { - const handler = new EncounterBlock(this, src, el, true); - ctx.addChild(handler); + if ( + this.canUseStatBlocks && + !window["FantasyStatblocks"].isResolved() + ) { + el.addClasses(["waiting-for-bestiary", "is-loading"]); + const loading = el.createEl("p", { + text: "Waiting for Fantasy Statblocks Bestiary..." + }); + window["FantasyStatblocks"].onResolved(() => { + el.removeClasses([ + "waiting-for-bestiary", + "is-loading" + ]); + loading.detach(); + const handler = new EncounterBlock(this, src, el, true); + ctx.addChild(handler); + }); + } else { + const handler = new EncounterBlock(this, src, el, true); + ctx.addChild(handler); + } } ); @@ -337,25 +376,67 @@ export default class InitiativeTracker extends Plugin { if (!codes.length) return; for (const code of codes) { - const definitions = code.innerText.replace(`encounter:`, ""); + const target = createSpan("initiative-tracker-encounter-line"); - const creatures = parseYaml("[" + definitions.trim() + "]"); - const parser = new EncounterParser(this); - const parsed = await parser.parse({ creatures }); + code.replaceWith(target); - if (!parsed || !parsed.creatures || !parsed.creatures.size) - continue; + const buildEncounter = async () => { + const definitions = code.innerText.replace( + `encounter:`, + "" + ); - const target = createSpan("initiative-tracker-encounter-line"); - new EncounterLine({ - target, - props: { - ...parsed, - plugin: this - } - }); + const creatures = parseYaml("[" + definitions.trim() + "]"); + const parser = new EncounterParser(this); + const parsed = await parser.parse({ creatures }); - code.replaceWith(target); + if ( + !parsed || + !parsed.creatures || + !parsed.creatures.size + ) { + target.setText("No creatures found."); + return; + } + new EncounterLine({ + target, + props: { + ...parsed, + plugin: this + } + }); + }; + if ( + true + /* this.canUseStatBlocks && + !window["FantasyStatblocks"].isResolved() */ + ) { + const loading = target.createSpan( + "waiting-for-bestiary inline" + ); + const delay = Math.floor(200 * Math.random()); + console.log("🚀 ~ file: main.ts:417 ~ duration:", delay); + + setIcon( + loading.createDiv({ + cls: "icon", + attr: { + style: `animation-delay: ${delay}ms` + } + }), + "loader-2" + ); + loading.createEl("em", { + text: "Loading Bestiary..." + }); + window["FantasyStatblocks"].onResolved(() => { + el.removeClasses(["waiting-for-bestiary", "inline"]); + loading.detach(); + buildEncounter(); + }); + } else { + buildEncounter(); + } } }); diff --git a/src/utils/index.ts b/src/utils/index.ts index f7bba0cc..cc85b8c8 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,6 +2,7 @@ import type InitiativeTracker from "src/main"; import type { SRDMonster } from "index"; import type { Creature } from "./creature"; import { DECIMAL_TO_VULGAR_FRACTION } from "./constants"; +import { setIcon } from "obsidian"; export * from "./constants"; export * from "./icons"; @@ -47,3 +48,14 @@ export function getFromCreatureOrBestiary( if (fromBase) return fromBase; return getter(plugin.getCreatureFromBestiary(creature.name)); } + +export const buildLoader = (text: string): HTMLDivElement => { + const loading = createDiv({ + cls: "is-loading" + }); + setIcon(loading.createDiv("spinner"), "loader-2"); + loading.createEl("em", { + text + }); + return loading; +};