RC-51 | Roll Abilities
This commit is contained in:
parent
23cad8fcc3
commit
0319841a01
14 changed files with 327 additions and 4 deletions
|
|
@ -76,6 +76,20 @@
|
|||
{{else}}
|
||||
<span>{{ability.value}}</span>
|
||||
{{/unless}}
|
||||
{{#if @root.meta.editable}}
|
||||
<button
|
||||
type="button"
|
||||
class="roll"
|
||||
data-action="roll"
|
||||
data-formula="{{ability.value}}d8rc4"
|
||||
data-flavor="{{ability.name}} Roll (Difficulty: 4)"
|
||||
>
|
||||
<rc-icon
|
||||
var:size="20px"
|
||||
name="icons/roll-2"
|
||||
></rc-icon>
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#unless ability.readonly}}
|
||||
<label
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@
|
|||
grid-template-rows: minmax(0, 3fr) minmax(0, 1fr);
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
label, .label {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
|
@ -106,6 +108,7 @@
|
|||
border: 2px solid black;
|
||||
border-radius: 50%;
|
||||
font-size: 1.5rem;
|
||||
position: relative;
|
||||
|
||||
> .value {
|
||||
background: none;
|
||||
|
|
@ -113,8 +116,15 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
> .roll {
|
||||
--distance: -15%;
|
||||
position: absolute;
|
||||
top: var(--distance);
|
||||
right: var(--distance);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&.dual {
|
||||
position: relative;
|
||||
font-size: var(--font-size-14);
|
||||
--distance-from-edge: 4px;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@import url("./elements/button.css");
|
||||
|
||||
.ripcrypt {
|
||||
.window-content {
|
||||
padding: 0;
|
||||
|
|
|
|||
18
Apps/components/icon.css
Normal file
18
Apps/components/icon.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
:host {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: var(--size, 1rem);
|
||||
height: var(--size, 1rem);
|
||||
fill: var(--fill);
|
||||
stroke: var(--stroke);
|
||||
}
|
||||
22
Apps/elements/button.css
Normal file
22
Apps/elements/button.css
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
.ripcrypt > .window-content button {
|
||||
all: revert;
|
||||
padding: 2px 4px;
|
||||
|
||||
&.roll {
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
outline: none;
|
||||
border: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
5
assets/README.md
Normal file
5
assets/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
Some of the assets provided in this repository may be Creative Commons by Attribution 3, or they may have some other license.
|
||||
|
||||
Oliver Akins does not grant any permission to use these assets outside of what the licenses allow. Make sure that anything you do with these assets is within the permitted usage of their respective licenses if they are outside of this repository.
|
||||
|
||||
For a detailed overview of what icons have what licenses, please see [the credit file](_credit.txt).
|
||||
2
assets/_credit.txt
Normal file
2
assets/_credit.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Soetarman Atmodjo:
|
||||
- icons/roll.svg : Rights Purchased.
|
||||
6
assets/icons/roll.svg
Normal file
6
assets/icons/roll.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="83" height="95" viewBox="0 0 83 95" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<path id="svg_1" d="m77.95688,69.37862c0,-1.375 1.1133,-2.4883 2.4883,-2.4883s2.4883,1.1133 2.4883,2.4883l0,0.53516c0,1.375 -1.1133,2.4883 -2.4883,2.4883s-2.4883,-1.1133 -2.4883,-2.4883l0,-0.53516zm-49.355,-34.766c1.4609,-1.4648 3.832,-1.4648 5.2969,0c1.4609,1.4609 1.4609,3.832 0,5.2969c-1.4648,1.4609 -3.832,1.4609 -5.2969,0c-1.4648,-1.4648 -1.4648,-3.832 0,-5.2969zm20.453,20.453c1.4648,-1.4609 3.832,-1.4609 5.2969,0c1.4648,1.4648 1.4648,3.832 0,5.2969c-1.4609,1.4648 -3.832,1.4648 -5.2969,0c-1.4609,-1.4609 -1.4609,-3.832 0,-5.2969zm-10.227,-10.227c1.4609,-1.4648 3.832,-1.4648 5.293,0c1.4648,1.4609 1.4648,3.832 0,5.293c-1.4609,1.4648 -3.832,1.4648 -5.293,0c-1.4648,-1.4609 -1.4648,-3.832 0,-5.293zm-9.8594,-20.359l25.016,0c2.8828,0 5.5039,1.1797 7.4062,3.082l0.00781,0.00781c1.9023,1.9023 3.082,4.5273 3.082,7.4062l0,25.016c0,2.8828 -1.1797,5.5039 -3.082,7.4062l-0.00781,0.00782c-1.9023,1.90229 -4.5273,3.08199 -7.4062,3.08199l-25.016,0c-2.8828,0 -5.5039,-1.1797 -7.4062,-3.08199l-0.00781,-0.00782c-1.9023,-1.9023 -3.082,-4.5273 -3.082,-7.4062l0,-25.016c0,-2.8828 1.1797,-5.5039 3.082,-7.4062l0.00781,-0.00781c1.9023,-1.9023 4.5273,-3.082 7.4062,-3.082zm25.016,5l-25.016,0c-1.5156,0 -2.8906,0.61719 -3.8867,1.6094c-0.99219,0.99609 -1.6094,2.375 -1.6094,3.8867l0,25.016c0,1.5156 0.61719,2.8906 1.6094,3.8867c0.99609,0.99219 2.375,1.6094 3.8867,1.6094l25.016,0c1.5156,0 2.8906,-0.61719 3.8867,-1.6094c0.99219,-0.99609 1.6094,-2.375 1.6094,-3.8867l0,-25.016c0,-1.5156 -0.61719,-2.8906 -1.6094,-3.8867c-0.99609,-0.99219 -2.375,-1.6094 -3.8867,-1.6094zm18.355,42.309c1.1914,-0.69141 2.7188,-0.28516 3.4062,0.91016c0.69141,1.1914 0.28516,2.7188 -0.91016,3.4062l-32.113,18.539c-0.84766,0.49219 -1.8672,0.42578 -2.6328,-0.08203l-38.828,-22.418c-0.80078,-0.46094 -1.25,-1.2969 -1.25,-2.1562l-0.01172,-45c0,-1.0117 0.60156,-1.8867 1.4688,-2.2773l38.766,-22.379c0.80859,-0.46484 1.7695,-0.42578 2.5195,0.02344l38.934,22.477c0.80078,0.46094 1.25,1.2969 1.25,2.1562l0,36.395c0,1.375 -1.1133,2.4883 -2.4883,2.4883s-2.4883,-1.1133 -2.4883,-2.4883l0,-34.961l-36.48,-21.062l-36.473,21.059l0,42.141l36.469,21.055l30.867,-17.82l-0.00522,-0.00647z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
126
module/Apps/elements/Icon.mjs
Normal file
126
module/Apps/elements/Icon.mjs
Normal 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`);
|
||||
};
|
||||
};
|
||||
22
module/Apps/elements/_index.mjs
Normal file
22
module/Apps/elements/_index.mjs
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
72
module/Apps/elements/mixins/StyledShadowElement.mjs
Normal file
72
module/Apps/elements/mixins/StyledShadowElement.mjs
Normal 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;
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
"flags": {
|
||||
"hotReload": {
|
||||
"extensions": ["css", "hbs", "json", "mjs", "svg"],
|
||||
"paths": ["Apps", "langs", "module"]
|
||||
"paths": ["assets", "Apps", "langs", "module"]
|
||||
}
|
||||
},
|
||||
"documentTypes": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue