diff --git a/src/characters/character-block.ts b/src/characters/character-block.ts
new file mode 100644
index 00000000..3e2f2dfc
--- /dev/null
+++ b/src/characters/character-block.ts
@@ -0,0 +1,215 @@
+import { html, render } from "lit-html";
+import { map } from "lit-html/directives/map.js";
+// import { range } from "lit-html/directives/range.js";
+
+import IronVaultPlugin from "index";
+import { EventRef, TFile } from "obsidian";
+// import { vaultProcess } from "utils/obsidian";
+import { CharacterContext } from "../character-tracker";
+import { md } from "utils/ui/directives";
+
+export default function registerCharacterBlock(plugin: IronVaultPlugin): void {
+ plugin.registerMarkdownCodeBlockProcessor(
+ "iron-vault-character",
+ async (source: string, el: CharacterContainerEl, ctx) => {
+ // We can't render blocks until datastore is ready.
+ await plugin.datastore.waitForReady;
+ if (!el.characterRenderer) {
+ el.characterRenderer = new CharacterRenderer(el, source, plugin);
+ }
+ const file = plugin.app.vault.getFileByPath(ctx.sourcePath);
+ await el.characterRenderer.render(file);
+ },
+ );
+}
+
+interface CharacterContainerEl extends HTMLElement {
+ characterRenderer?: CharacterRenderer;
+}
+
+class CharacterRenderer {
+ contentEl: HTMLElement;
+ source: string;
+ plugin: IronVaultPlugin;
+ fileWatcher?: EventRef;
+
+ constructor(contentEl: HTMLElement, source: string, plugin: IronVaultPlugin) {
+ this.contentEl = contentEl;
+ this.source = source;
+ this.plugin = plugin;
+ }
+
+ async render(file: TFile | undefined | null) {
+ if (!file) {
+ render(
+ html`
Error rendering character: no file found.
`,
+ this.contentEl,
+ );
+ return;
+ }
+ const character = this.plugin.characters.get(file.path);
+ if (this.fileWatcher) {
+ this.plugin.app.metadataCache.offref(this.fileWatcher);
+ }
+ this.fileWatcher = this.plugin.app.metadataCache.on(
+ "changed",
+ (moddedFile) => {
+ if (moddedFile.path === file.path) {
+ this.render(moddedFile);
+ }
+ },
+ );
+ this.plugin.registerEvent(this.fileWatcher);
+ if (!character || character.isLeft()) {
+ render(
+ // TODO: we should preserve the error?
+ //html`Error rendering clock: ${res.error.message}
`,
+ html`
+Error rendering character: character file is invalid${character
+ ? ": " + character.error.message
+ : ""}
`,
+ this.contentEl,
+ );
+ return;
+ } else if (character.isRight()) {
+ await this.renderCharacter(character.value, file);
+ }
+ }
+
+ async renderCharacter(charCtx: CharacterContext, file: TFile) {
+ const lens = charCtx.lens;
+ const raw = charCtx.character;
+ const tpl = html`
+
+ ${md(this.plugin, lens.name.get(raw), file.path)}
+ Stats
+ ${this.renderStats(charCtx, file)}
+ Meters
+ ${this.renderMeters(charCtx, file)}
+ Impacts
+ ${this.renderImpacts(charCtx, file)}
+ Special Tracks
+ ${this.renderSpecialTracks(charCtx, file)}
+ Assets
+ ${this.renderAssets(charCtx, file)}
+
+ `;
+ render(tpl, this.contentEl);
+ }
+
+ renderStats(charCtx: CharacterContext, _file: TFile) {
+ const lens = charCtx.lens;
+ const raw = charCtx.character;
+ return html`
+
+ ${map(
+ Object.entries(lens.stats),
+ ([stat, value]) => html`
+ -
+
+ - ${stat}
+ - ${value.get(raw)}
+
+
+ `,
+ )}
+
+ `;
+ }
+
+ renderMeters(charCtx: CharacterContext, _file: TFile) {
+ const lens = charCtx.lens;
+ const raw = charCtx.character;
+ return html`
+
+ ${map(
+ Object.entries(lens.condition_meters),
+ ([meter, value]) => html`
+ -
+
+ - ${meter}
+ - ${value.get(raw)}
+
+
+ `,
+ )}
+
+ `;
+ }
+
+ renderImpacts(charCtx: CharacterContext, _file: TFile) {
+ const lens = charCtx.lens;
+ const raw = charCtx.character;
+ return html`
+
+ ${map(
+ Object.entries(lens.impacts.get(raw)),
+ ([impact, value]) => html`
+ -
+
+ - ${impact}
+ - ${value}
+
+
+ `,
+ )}
+
+ `;
+ }
+
+ renderSpecialTracks(charCtx: CharacterContext, _file: TFile) {
+ const lens = charCtx.lens;
+ const raw = charCtx.character;
+ return html`
+
+ ${map(
+ Object.entries(lens.special_tracks),
+ ([track, value]) => html`
+ -
+
+ - ${track}
+ - ${JSON.stringify(value.get(raw))}
+
+
+ `,
+ )}
+
+ `;
+ }
+
+ renderAssets(charCtx: CharacterContext, _file: TFile) {
+ const lens = charCtx.lens;
+ const raw = charCtx.character;
+ return html`
+
+ ${map(
+ Object.values(lens.assets.get(raw)),
+ (asset) => html`
+ -
+
+ - ${asset.id}
+ - ${JSON.stringify(asset)}
+
+
+ `,
+ )}
+
+ `;
+ }
+
+ // async updateClockProgress(
+ // file: TFile,
+ // { steps, progress }: { steps?: number; progress?: number },
+ // ) {
+ // const newProg = await clockUpdater(
+ // vaultProcess(this.plugin.app, file.path),
+ // (clockFile) =>
+ // clockFile.updatingClock((clock) =>
+ // progress != null ? clock.withProgress(progress) : clock.tick(steps!),
+ // ),
+ // );
+
+ // await this.renderCharacter(newProg, file);
+ // }
+}
diff --git a/src/characters/css/assets.css b/src/characters/css/assets.css
new file mode 100644
index 00000000..84b43ae9
--- /dev/null
+++ b/src/characters/css/assets.css
@@ -0,0 +1,2 @@
+.iron-vault-character .assets {
+}
diff --git a/src/characters/css/characters.css b/src/characters/css/characters.css
new file mode 100644
index 00000000..eb42f79d
--- /dev/null
+++ b/src/characters/css/characters.css
@@ -0,0 +1,9 @@
+@import url("assets.css");
+@import url("impacts.css");
+@import url("meters.css");
+@import url("special_tracks.css");
+@import url("assets.css");
+
+.iron-vault-character {
+
+}
diff --git a/src/characters/css/impacts.css b/src/characters/css/impacts.css
new file mode 100644
index 00000000..9d7742ed
--- /dev/null
+++ b/src/characters/css/impacts.css
@@ -0,0 +1,2 @@
+.iron-vault-character .impacts {
+}
diff --git a/src/characters/css/meters.css b/src/characters/css/meters.css
new file mode 100644
index 00000000..76e4dc21
--- /dev/null
+++ b/src/characters/css/meters.css
@@ -0,0 +1,2 @@
+.iron-vault-character .meters {
+}
diff --git a/src/characters/css/special_tracks.css b/src/characters/css/special_tracks.css
new file mode 100644
index 00000000..28d56eed
--- /dev/null
+++ b/src/characters/css/special_tracks.css
@@ -0,0 +1,2 @@
+.iron-vault-character .special-tracks {
+}
diff --git a/src/characters/css/stats.css b/src/characters/css/stats.css
new file mode 100644
index 00000000..9f422fc5
--- /dev/null
+++ b/src/characters/css/stats.css
@@ -0,0 +1,2 @@
+.iron-vault-character .stats {
+}
diff --git a/src/characters/lens.test.ts b/src/characters/lens.test.ts
index 8aad0346..225ebbcc 100644
--- a/src/characters/lens.test.ts
+++ b/src/characters/lens.test.ts
@@ -7,7 +7,6 @@ import { Lens, updating } from "../utils/lens";
import {
BaseIronVaultSchema,
IronVaultSheetAssetInput,
- ImpactStatus,
characterLens,
momentumOps,
movesReader,
@@ -171,40 +170,40 @@ describe("characterLens", () => {
});
actsLikeLens(lens.impacts, character, {
- wounded: ImpactStatus.Marked,
- disappointed: ImpactStatus.Unmarked,
+ wounded: true,
+ disappointed: false,
});
+ // TODO(@zkat)
it("treats any string other than ⬢ as unmarked", () => {
expect(lens.impacts.get(character)).toEqual({
- wounded: ImpactStatus.Unmarked,
- disappointed: ImpactStatus.Unmarked,
+ wounded: false,
+ disappointed: false,
});
});
it("treats a missing key as unmarked", () => {
const character = validater({ ...VALID_INPUT });
expect(lens.impacts.get(character)).toEqual({
- wounded: ImpactStatus.Unmarked,
- disappointed: ImpactStatus.Unmarked,
+ wounded: false,
+ disappointed: false,
});
});
+ // TODO(@zkat)
it("treats ⬢ as marked", () => {
expect(
- lens.impacts.get(
- lens.impacts.update(character, { wounded: ImpactStatus.Marked }),
- ),
+ lens.impacts.get(lens.impacts.update(character, { wounded: true })),
).toEqual({
- wounded: ImpactStatus.Marked,
- disappointed: ImpactStatus.Unmarked,
+ wounded: true,
+ disappointed: false,
});
});
it("rejects an invalid impact type", () => {
- expect(() =>
- lens.impacts.update(character, { foobar: ImpactStatus.Marked }),
- ).toThrow("unexpected key in impacts: foobar");
+ expect(() => lens.impacts.update(character, { foobar: true })).toThrow(
+ "unexpected key in impacts: foobar",
+ );
});
});
});
@@ -248,9 +247,7 @@ describe("momentumOps", () => {
const character = validater({
...VALID_INPUT,
momentum: 3,
- ...Object.fromEntries(
- impactKeys.map((key) => [key, ImpactStatus.Marked]),
- ),
+ ...Object.fromEntries(impactKeys.map((key) => [key, true])),
});
it(`caps momentum to ${max}`, () => {
expect(lens.momentum.get(take(8)(character))).toBe(max);
diff --git a/src/characters/lens.ts b/src/characters/lens.ts
index f1345697..1d71c520 100644
--- a/src/characters/lens.ts
+++ b/src/characters/lens.ts
@@ -55,10 +55,10 @@ export const baseIronVaultSchema = z
export type BaseIronVaultSchema = z.input;
-export enum ImpactStatus {
- Unmarked = "⬡",
- Marked = "⬢",
-}
+// export enum ImpactStatus {
+// Unmarked = "marked",
+// Marked = "un",
+// }
export interface CharacterLens {
name: Lens;
@@ -66,7 +66,7 @@ export interface CharacterLens {
stats: Record>;
condition_meters: Record>;
assets: Lens;
- impacts: Lens>;
+ impacts: Lens>;
special_tracks: Record>;
ruleset: Ruleset;
}
@@ -156,7 +156,7 @@ export function validated(
function createImpactLens(
ruleset: Ruleset,
-): Lens, Record> {
+): Lens, Record> {
// function createImpactLens(data: Record, ruleset: Ruleset): Lens, Record> {
// const impactProps: Record = {};
// const dataKeys = Object.fromEntries(Object.keys(data).map((key) => [key.toLowerCase(), key]));
@@ -169,17 +169,13 @@ function createImpactLens(
return {
get(source) {
return objectMap(impactProps, (dataKey) => {
- const val = source[dataKey];
- if (val === "⬢") {
- return ImpactStatus.Marked;
- } else {
- return ImpactStatus.Unmarked;
- }
+ // TODO(@zkat): this needs validation.
+ return !!source[dataKey];
});
},
update(source, newval) {
const original = this.get(source);
- const updates: [string, ImpactStatus][] = [];
+ const updates: [string, boolean][] = [];
for (const key in newval) {
if (!(key in impactProps)) {
throw new Error(`unexpected key in impacts: ${key}`);
@@ -283,10 +279,9 @@ export function momentumTrackerReader(
return { get: momentumTrackerLens(characterLens).get };
}
-export function countMarked(impacts: Record): number {
+export function countMarked(impacts: Record): number {
return Object.values(impacts).reduce(
- (count, impactStatus) =>
- count + (impactStatus === ImpactStatus.Marked ? 1 : 0),
+ (count, impactStatus) => count + (impactStatus ? 1 : 0),
0,
);
}
diff --git a/src/index.ts b/src/index.ts
index a15e348a..ce72cd3c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -35,6 +35,7 @@ import { MoveModal } from "moves/move-modal";
import { ViewPlugin, ViewUpdate } from "@codemirror/view";
import registerTrackBlock from "tracks/track-block";
import registerClockBlock from "tracks/clock-block";
+import registerCharacterBlock from "characters/character-block";
export default class IronVaultPlugin extends Plugin {
settings!: IronVaultPluginSettings;
@@ -222,6 +223,7 @@ export default class IronVaultPlugin extends Plugin {
registerMechanicsBlock(this);
registerTrackBlock(this);
registerClockBlock(this);
+ registerCharacterBlock(this);
}
async activateView() {
diff --git a/src/styles.css b/src/styles.css
index 5e0dfae4..e72197c1 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -3,6 +3,7 @@
@import url("oracles/css/oracles.css");
@import url("tracks/css/tracks.css");
@import url("tracks/css/clocks.css");
+@import url("characters/css/characters.css");
.theme-light {
--color-code: 0, 0, 0;
diff --git a/test-vault/characters/Ash Barlowe.md b/test-vault/characters/Ash Barlowe.md
index 65332123..ff845422 100644
--- a/test-vault/characters/Ash Barlowe.md
+++ b/test-vault/characters/Ash Barlowe.md
@@ -38,5 +38,6 @@ Bonds_Progress: 0
Discoveries_XPEarned: 0
Discoveries_Progress: 0
---
+```iron-vault-character
+```
-asdf