RC-51 | Roll Abilities

This commit is contained in:
Oliver-Akins 2024-12-29 21:28:13 -07:00
parent 23cad8fcc3
commit 0319841a01
14 changed files with 327 additions and 4 deletions

View file

@ -5,6 +5,7 @@ import { Logger } from "../../utils/Logger.mjs";
const { HandlebarsApplicationMixin } = foundry.applications.api;
const { ActorSheetV2 } = foundry.applications.sheets;
const { Roll } = foundry.dice;
export class HeroSummaryCardV1 extends HandlebarsApplicationMixin(ActorSheetV2) {
@ -23,7 +24,9 @@ export class HeroSummaryCardV1 extends HandlebarsApplicationMixin(ActorSheetV2)
window: {
resizable: false,
},
actions: {},
actions: {
roll: this.rollDice,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
@ -131,5 +134,24 @@ export class HeroSummaryCardV1 extends HandlebarsApplicationMixin(ActorSheetV2)
// #endregion
// #region Actions
static async rollDice(_$e, el) {
const data = el.dataset;
const formula = data.formula;
Logger.debug(`Attempting to roll formula: ${formula}`);
let flavor;
if (data.flavor) {
flavor = localizer(
data.flavor,
);
}
const roll = new Roll(formula);
await roll.evaluate();
await roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
flavor,
});
};
// #endregion
};

View file

@ -0,0 +1,126 @@
import { Logger } from "../../utils/Logger.mjs";
import { StyledShadowElement } from "./mixins/StyledShadowElement.mjs";
/**
Attributes:
@property {string} name - The name of the icon, takes precedence over the path
@property {string} path - The path of the icon file
*/
export class RipCryptIcon extends StyledShadowElement(HTMLElement) {
static elementName = `rc-icon`;
static formAssociated = false;
/* Stuff for the mixin to use */
static _stylePath = `components/icon.css`;
static _cache = new Map();
#container;
/** @type {null | string} */
_name;
/** @type {null | string} */
_path;
/* Stored IDs for all of the hooks that are in this component */
#svgHmr;
constructor() {
super();
// this._shadow = this.attachShadow({ mode: `open`, delegatesFocus: true });
this.#container = document.createElement(`div`);
this._shadow.appendChild(this.#container);
};
_mounted = false;
async connectedCallback() {
super.connectedCallback();
if (this._mounted) { return };
this._name = this.getAttribute(`name`);
this._path = this.getAttribute(`path`);
/*
This converts all of the double-dash prefixed properties on the element to
CSS variables so that they don't all need to be provided by doing style=""
*/
for (const attrVar of this.attributes) {
if (attrVar.name?.startsWith(`var:`)) {
const prop = attrVar.name.replace(`var:`, ``);
this.style.setProperty(`--` + prop, attrVar.value);
};
};
/*
Try to retrieve the icon if it isn't present, try the path then default to
the slot content, as then we can have a default per-icon usage
*/
let content;
if (this._name) {
content = await this.#getIcon(`./systems/${game.system.id}/assets/${this._name}.svg`);
};
if (this._path && !content) {
content = await this.#getIcon(this._path);
};
if (content) {
this.#container.appendChild(content.cloneNode(true));
};
/*
This is so that when we get an HMR event from Foundry we can appropriately
handle it using our logic to update the component and the icon cache.
*/
if (game.settings.get(`ripcrypt`, `devMode`)) {
this.#svgHmr = Hooks.on(`${game.system.id}-hmr:svg`, (iconName, data) => {
if (this._name === iconName || this._path?.endsWith(data.path)) {
const svg = this.#parseSVG(data.content);
this.constructor._cache.set(iconName, svg);
this.#container.replaceChildren(svg.cloneNode(true));
};
});
};
this._mounted = true;
};
disconnectedCallback() {
super.disconnectedCallback();
if (!this._mounted) { return };
Hooks.off(`${game.system.id}-hmr:svg`, this.#svgHmr);
this._mounted = false;
};
async #getIcon(path) {
// Cache hit!
if (this.constructor._cache.has(path)) {
Logger.debug(`Icon ${path} cache hit`);
return this.constructor._cache.get(path);
};
const r = await fetch(path);
switch (r.status) {
case 200:
case 201:
break;
default:
Logger.error(`Failed to fetch icon: ${path}`);
return;
};
Logger.debug(`Adding icon ${path} to the cache`);
const svg = this.#parseSVG(await r.text());
this.constructor._cache.set(path, svg);
return svg;
};
/** Takes an SVG string and returns it as a DOM node */
#parseSVG(content) {
const temp = document.createElement(`div`);
temp.innerHTML = content;
return temp.querySelector(`svg`);
};
};

View file

@ -0,0 +1,22 @@
import { Logger } from "../../utils/Logger.mjs";
import { RipCryptIcon } from "./Icon.mjs";
const components = [
RipCryptIcon,
];
export function registerCustomComponents() {
(CONFIG.CACHE ??= {}).componentListeners ??= [];
for (const component of components) {
if (!window.customElements.get(component.elementName)) {
Logger.debug(`Registering component "${component.elementName}"`);
window.customElements.define(
component.elementName,
component,
);
if (component.formAssociated) {
CONFIG.CACHE.componentListeners.push(component.elementName);
}
};
}
};

View file

@ -0,0 +1,72 @@
/**
* @param {HTMLElement} Base
*/
export function StyledShadowElement(Base) {
return class extends Base {
/**
* The path to the CSS that is loaded
* @type {string}
*/
static _stylePath;
/**
* The stringified CSS to use
* @type {string}
*/
static _styles;
/**
* The HTML element of the stylesheet
* @type {HTMLStyleElement}
*/
_style;
/** @type {ShadowRoot} */
_shadow;
/**
* The hook ID for this element's CSS hot reload
* @type {number}
*/
#cssHmr;
constructor() {
super();
this._shadow = this.attachShadow({ mode: `open` });
this._style = document.createElement(`style`);
this._shadow.appendChild(this._style);
};
#mounted = false;
connectedCallback() {
if (this.#mounted) { return };
this._getStyles();
this.#mounted = true;
};
disconnectedCallback() {
if (!this.#mounted) { return };
if (this.#cssHmr != null) {
Hooks.off(`dd-hmr:css`, this.#cssHmr);
this.#cssHmr = null;
};
this.#mounted = false;
};
_getStyles() {
if (this.constructor._styles) {
this._style.innerHTML = this.constructor._styles;
} else {
fetch(`./systems/${game.system.id}/Apps/${this.constructor._stylePath}`)
.then(r => r.text())
.then(t => {
this.constructor._styles = t;
this._style.innerHTML = t;
});
}
};
};
};

View file

@ -11,6 +11,7 @@ import { CryptDie } from "../dice/CryptDie.mjs";
// Misc
import helpers from "../handlebarHelpers/_index.mjs";
import { Logger } from "../utils/Logger.mjs";
import { registerCustomComponents } from "../Apps/elements/_index.mjs";
import { registerDevSettings } from "../settings/devSettings.mjs";
import { registerUserSettings } from "../settings/userSettings.mjs";
@ -44,5 +45,6 @@ Hooks.once(`init`, () => {
// #region Token Attrs
CONFIG.Actor.trackableAttributes.hero = HeroData.trackableAttributes;
registerCustomComponents();
Handlebars.registerHelper(helpers);
});