Skip to content

Commit

Permalink
Add support for asset ability controls. (#465)
Browse files Browse the repository at this point in the history
  • Loading branch information
cwegrzyn committed Aug 22, 2024
1 parent 64677b4 commit a2991a0
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 96 deletions.
93 changes: 88 additions & 5 deletions src/assets/asset-card.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import {
Asset,
AssetAbility,
AssetAbilityControlField,
AssetConditionMeter,
AssetControlField,
AssetOptionField,
DictKey,
} from "@datasworn/core/dist/Datasworn";
import { TemplateResult, html } from "lit-html";
import { TemplateResult, html, nothing } from "lit-html";
import { map } from "lit-html/directives/map.js";
import { range } from "lit-html/directives/range.js";

import { Clock } from "clocks/clock";
import { clockWidget } from "clocks/ui/clock-widget";
import { IDataContext } from "datastore/data-context";
import { produce } from "immer";
import IronVaultPlugin from "index";
import { repeat } from "lit-html/directives/repeat.js";
import { rootLogger } from "logger";
import { md } from "utils/ui/directives";
import { integratedAssetLens } from "../characters/assets";
import { IronVaultSheetAssetSchema } from "../characters/lens";

const logger = rootLogger.getLogger("asset-card");

export function makeDefaultSheetAsset(asset: Asset) {
return {
id: asset._id,
Expand Down Expand Up @@ -194,6 +200,14 @@ function renderAssetAbility(
}),
);
};
const updateControlField = (key: string) =>
updateAsset &&
((control: AssetAbilityControlField) =>
updateAsset(
produce(asset, (draft) => {
draft.abilities[i].controls![key] = control;
}),
));
return html`<li>
<label>
<input
Expand All @@ -204,6 +218,24 @@ function renderAssetAbility(
/>
<span>${md(plugin, ability.text)}</span>
</label>
${ability.controls
? html`<ul class="controls">
${repeat(
Object.entries(ability.controls),
([key]) => key,
([key, control]) => {
return html`<li>
<dl>
<dt>${key}</dt>
<dd class="control">
${renderControl(key, control, updateControlField(key))}
</dd>
</dl>
</li>`;
},
)}
</ul>`
: null}
</li>`;
}

Expand Down Expand Up @@ -242,10 +274,10 @@ function renderControls<T extends Asset | AssetConditionMeter>(
`;
}

function renderControl(
function renderControl<C extends AssetControlField | AssetAbilityControlField>(
key: string,
control: AssetControlField,
updateControl?: (asset: AssetControlField) => void,
control: C,
updateControl?: (control: C) => void,
) {
const updateControlValue = (e: Event) => {
const value = (e.target as HTMLInputElement).value;
Expand Down Expand Up @@ -275,6 +307,14 @@ function renderControl(
}),
);
};
const updateClockValue = (newProgress: number) => {
if (!updateControl) return;
updateControl(
produce(control, (draft) => {
draft.value = newProgress;
}),
);
};

switch (control.field_type) {
case "condition_meter": {
Expand All @@ -283,7 +323,12 @@ function renderControl(
@submit=${(ev: Event) => ev.preventDefault()}
>
${control.controls &&
renderControls(control, control.controls, updateControl)}
renderControls(
control,
control.controls,
// At this point we know this must be an updater for a condition meter
updateControl as (control: AssetConditionMeter) => void,
)}
<ul class="meter">
<li><span>${control.label}</span></li>
${repeat(
Expand Down Expand Up @@ -359,5 +404,43 @@ function renderControl(
</select>
</label>`;
}

case "clock": {
return clockWidget(
Clock.create({
active: true,
name: control.label,
progress: control.value,
segments: control.max,
}).unwrap(),
updateClockValue,
);
}
case "counter": {
return html`<input
type="number"
min="${control.min}"
max="${control.max ?? nothing}"
.value=${control.value}
?disabled=${!updateControl}
@change=${updateControlValueNumeric}
/>`;
}

case "text": {
return html`<input
type="text"
?disabled=${!updateControl}
placeholder=${control.label}
.value=${control.value}
@change=${updateControlValue}
/>`;
}
default:
logger.warn(
"Unsupported asset control: %s",
(control as { field_type: string }).field_type,
);
return html``;
}
}
5 changes: 5 additions & 0 deletions src/assets/css/asset-card.css
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,9 @@
}
}
}

& input[type=number]::-webkit-inner-spin-button {
appearance: auto;
-webkit-appearance: auto;
}
}
45 changes: 7 additions & 38 deletions src/clocks/clock-block.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { html, render, svg } from "lit-html";
import { map } from "lit-html/directives/map.js";
import { range } from "lit-html/directives/range.js";
import { html, render } from "lit-html";

import IronVaultPlugin from "index";
import { vaultProcess } from "utils/obsidian";
Expand All @@ -9,6 +7,7 @@ import { TrackedEntityRenderer } from "utils/ui/tracked-entity-renderer";
import { ZodError } from "zod";
import { Clock } from "./clock";
import { ClockFileAdapter, clockUpdater } from "./clock-file";
import { clockWidget } from "./ui/clock-widget";

export default function registerClockBlock(plugin: IronVaultPlugin): void {
plugin.registerMarkdownCodeBlockProcessor(
Expand Down Expand Up @@ -74,19 +73,11 @@ class ClockRenderer extends TrackedEntityRenderer<ClockFileAdapter, ZodError> {
>`}
</header>
<svg
class="clock-widget"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
viewBox="-55 -55 110 110"
aria-valuenow=${clockFile.clock.progress}
aria-valuetext="${clockFile.clock.progress}${clockFile.clock
.segments}"
>
${map(range(clockFile.clock.segments), (i) =>
this.renderPath(i, clockFile),
)}
</svg>
${clockWidget(clockFile.clock, (newProgress) =>
this.updateClockProgress({
progress: newProgress,
}),
)}
<div
class="clock-segments"
Expand Down Expand Up @@ -149,15 +140,6 @@ class ClockRenderer extends TrackedEntityRenderer<ClockFileAdapter, ZodError> {
render(tpl, this.containerEl);
}

renderPath(i: number, clockFile: ClockFileAdapter) {
return svg`<path
d="${pathString(i, clockFile.clock.segments)}"
class="clock-segment svg"
aria-selected="${clockFile.clock.progress === i + 1}"
@click=${() => this.updateClockProgress({ progress: i === 0 && clockFile.clock.progress === 1 ? 0 : i + 1 })}
></path>`;
}

async updateClockProgress({
steps,
progress,
Expand All @@ -174,16 +156,3 @@ class ClockRenderer extends TrackedEntityRenderer<ClockFileAdapter, ZodError> {
);
}
}

const R = 50;

function pathString(wedgeIdx: number, numWedges: number) {
const wedgeAngle = (2 * Math.PI) / numWedges;
const startAngle = wedgeIdx * wedgeAngle - Math.PI / 2;
const x1 = R * Math.cos(startAngle);
const y1 = R * Math.sin(startAngle);
const x2 = R * Math.cos(startAngle + wedgeAngle);
const y2 = R * Math.sin(startAngle + wedgeAngle);

return `M0,0 L${x1},${y1} A${R},${R} 0 0,1 ${x2},${y2} z`;
}
107 changes: 54 additions & 53 deletions src/clocks/css/clocks.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,60 @@
.block-language-iron-vault-clock {
container: iron-vault-clock / inline-size;
}
.iron-vault-clock-widget {
& .clock-segment {
vector-effect: non-scaling-stroke;
}
max-width: 200px;
@container iron-vault-clock (min-width: 300px) {
max-width: 300px;
}
pointer-events: none;
fill: var(--interactive-accent);
fill-opacity: 0.8;
stroke: var(--background-modifier-border);
stroke-width: 4;
aspect-ratio: 1;

&:hover {
fill-opacity: 0.8;

& .clock-segment {
&:hover {
fill: var(--interactive-accent-hover);
& ~ .clock-segment {
fill: var(--interactive-normal);
}
}
}
}

&:not(:hover) {
& .clock-segment {
&[aria-selected="true"] {
& ~ .clock-segment {
fill: var(--interactive-normal);
}
}
}
}

&[aria-valuenow="0"]:not(:hover) {
& .clock-segment {
fill: var(--interactive-normal);
}
}

& .clock-segment {
transition: fill 0.3s ease;
cursor: pointer;
pointer-events: visible;

&:active {
fill-opacity: 1;
}
}
}
.iron-vault-clock {
margin: 0.5em auto;
& > .clock-name {
Expand Down Expand Up @@ -29,58 +83,5 @@
gap: 0.4em;
}
}
& .clock-widget {
& .clock-segment {
vector-effect: non-scaling-stroke;
}
max-width: 200px;
@container iron-vault-clock (min-width: 300px) {
max-width: 300px;
}
pointer-events: none;
fill: var(--interactive-accent);
fill-opacity: 0.8;
stroke: var(--background-modifier-border);
stroke-width: 4;
aspect-ratio: 1;

&:hover {
fill-opacity: 0.8;

& .clock-segment {
&:hover {
fill: var(--interactive-accent-hover);
& ~ .clock-segment {
fill: var(--interactive-normal);
}
}
}
}

&:not(:hover) {
& .clock-segment {
&[aria-selected="true"] {
& ~ .clock-segment {
fill: var(--interactive-normal);
}
}
}
}

&[aria-valuenow="0"]:not(:hover) {
& .clock-segment {
fill: var(--interactive-normal);
}
}

& .clock-segment {
transition: fill 0.3s ease;
cursor: pointer;
pointer-events: visible;

&:active {
fill-opacity: 1;
}
}
}
}
Loading

0 comments on commit a2991a0

Please sign in to comment.