From e49fa03fede5fe749601391426c69c6737a9e35c Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 20 Jul 2025 14:14:46 -0600 Subject: [PATCH 01/61] Rename the elements folder to components --- module/Apps/{elements => components}/Icon.mjs | 0 module/Apps/{elements => components}/RipCryptBorder.mjs | 0 module/Apps/{elements => components}/_index.mjs | 0 .../{elements => components}/mixins/StyledShadowElement.mjs | 0 module/Apps/{elements => components}/svgLoader.mjs | 0 module/hooks/init.mjs | 2 +- 6 files changed, 1 insertion(+), 1 deletion(-) rename module/Apps/{elements => components}/Icon.mjs (100%) rename module/Apps/{elements => components}/RipCryptBorder.mjs (100%) rename module/Apps/{elements => components}/_index.mjs (100%) rename module/Apps/{elements => components}/mixins/StyledShadowElement.mjs (100%) rename module/Apps/{elements => components}/svgLoader.mjs (100%) diff --git a/module/Apps/elements/Icon.mjs b/module/Apps/components/Icon.mjs similarity index 100% rename from module/Apps/elements/Icon.mjs rename to module/Apps/components/Icon.mjs diff --git a/module/Apps/elements/RipCryptBorder.mjs b/module/Apps/components/RipCryptBorder.mjs similarity index 100% rename from module/Apps/elements/RipCryptBorder.mjs rename to module/Apps/components/RipCryptBorder.mjs diff --git a/module/Apps/elements/_index.mjs b/module/Apps/components/_index.mjs similarity index 100% rename from module/Apps/elements/_index.mjs rename to module/Apps/components/_index.mjs diff --git a/module/Apps/elements/mixins/StyledShadowElement.mjs b/module/Apps/components/mixins/StyledShadowElement.mjs similarity index 100% rename from module/Apps/elements/mixins/StyledShadowElement.mjs rename to module/Apps/components/mixins/StyledShadowElement.mjs diff --git a/module/Apps/elements/svgLoader.mjs b/module/Apps/components/svgLoader.mjs similarity index 100% rename from module/Apps/elements/svgLoader.mjs rename to module/Apps/components/svgLoader.mjs diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 645a5be..0bbabd1 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -30,7 +30,7 @@ import { RipCryptToken } from "../documents/token.mjs"; // Misc import helpers from "../handlebarHelpers/_index.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { registerCustomComponents } from "../Apps/elements/_index.mjs"; +import { registerCustomComponents } from "../Apps/components/_index.mjs"; import { registerDevSettings } from "../settings/devSettings.mjs"; import { registerMetaSettings } from "../settings/metaSettings.mjs"; import { registerUserSettings } from "../settings/userSettings.mjs"; From 26a2e0f3ff7f8273ffec10ffdbc8014996c40afc Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 20 Jul 2025 21:28:25 -0600 Subject: [PATCH 02/61] Cleanup a few logs --- module/Apps/ActorSheets/CombinedHeroSheet.mjs | 1 - module/data/Item/Armour.mjs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/module/Apps/ActorSheets/CombinedHeroSheet.mjs b/module/Apps/ActorSheets/CombinedHeroSheet.mjs index 75bde71..3c18491 100644 --- a/module/Apps/ActorSheets/CombinedHeroSheet.mjs +++ b/module/Apps/ActorSheets/CombinedHeroSheet.mjs @@ -106,7 +106,6 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi }; }; - Logger.debug(`Context keys:`, Object.keys(ctx)); return ctx; }; // #endregion diff --git a/module/data/Item/Armour.mjs b/module/data/Item/Armour.mjs index 2669b4c..3602978 100644 --- a/module/data/Item/Armour.mjs +++ b/module/data/Item/Armour.mjs @@ -94,7 +94,7 @@ export class ArmourData extends CommonItemData { }; const slots = parent.parent.system.equippedArmour ?? {}; - Logger.debug(`slots`, slots); + for (const locationTag of this.location) { if (slots[locationTag.toLowerCase()] != null) { Logger.error(`Unable to equip multiple items in the same slot`); From 2b88bcc2eff0d4701030963127a956cce5968e6c Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 20 Jul 2025 21:32:46 -0600 Subject: [PATCH 03/61] Add a component that handles displaying the person silhouette with some content inside of it --- module/Apps/components/ArmourSummary.mjs | 56 +++++++++++++++++++++ module/Apps/components/_index.mjs | 2 + templates/components/armour-summary.hbs | 54 ++++++++++++++++++++ templates/css/components/armour-summary.css | 34 +++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 module/Apps/components/ArmourSummary.mjs create mode 100644 templates/components/armour-summary.hbs create mode 100644 templates/css/components/armour-summary.css diff --git a/module/Apps/components/ArmourSummary.mjs b/module/Apps/components/ArmourSummary.mjs new file mode 100644 index 0000000..15ddf16 --- /dev/null +++ b/module/Apps/components/ArmourSummary.mjs @@ -0,0 +1,56 @@ +import { filePath } from "../../consts.mjs"; +import { StyledShadowElement } from "./mixins/StyledShadowElement.mjs"; + +const { renderTemplate } = foundry.applications.handlebars; + +export class ArmourSummary extends StyledShadowElement(HTMLElement) { + static elementName = `armour-summary`; + static formAssociated = false; + + /* Stuff for the mixin to use */ + static _stylePath = `css/components/armour-summary.css`; + #container; + + get type() { + return this.getAttribute(`type`) ?? `hero`; + }; + + set type(newValue) { + this.setAttribute(`type`, newValue); + }; + + _mounted = false; + async connectedCallback() { + super.connectedCallback(); + if (this._mounted) { return }; + + /* + 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); + }; + }; + + this.#container = document.createElement(`div`); + this.#container.classList = `person`; + + this.#container.innerHTML = await renderTemplate( + filePath(`templates/components/armour-summary.hbs`), + { type: this.type }, + ); + + this._shadow.appendChild(this.#container); + + this._mounted = true; + }; + + disconnectedCallback() { + super.disconnectedCallback(); + if (!this._mounted) { return }; + this._mounted = false; + }; +}; diff --git a/module/Apps/components/_index.mjs b/module/Apps/components/_index.mjs index 3568481..8400462 100644 --- a/module/Apps/components/_index.mjs +++ b/module/Apps/components/_index.mjs @@ -1,9 +1,11 @@ +import { ArmourSummary } from "./ArmourSummary.mjs"; import { Logger } from "../../utils/Logger.mjs"; import { RipCryptBorder } from "./RipCryptBorder.mjs"; import { RipCryptIcon } from "./Icon.mjs"; import { RipCryptSVGLoader } from "./svgLoader.mjs"; const components = [ + ArmourSummary, RipCryptIcon, RipCryptSVGLoader, RipCryptBorder, diff --git a/templates/components/armour-summary.hbs b/templates/components/armour-summary.hbs new file mode 100644 index 0000000..cb9c563 --- /dev/null +++ b/templates/components/armour-summary.hbs @@ -0,0 +1,54 @@ +{{#if (eq type "hero")}} + +{{else}} + +{{/if}} +{{!-- {{#each armours as | slot |}} +
+ + {{ slot.defense }} + + {{#if slot.shielded}} + + {{/if}} +
+{{/each}} --}} +
+ + {{ rc-i18n "RipCrypt.common.anatomy.head" }} +
+
+ + {{ rc-i18n "RipCrypt.common.anatomy.body" }} +
+
+ + {{ rc-i18n "RipCrypt.common.anatomy.arms" }} +
+
+ + {{ rc-i18n "RipCrypt.common.anatomy.legs" }} +
diff --git a/templates/css/components/armour-summary.css b/templates/css/components/armour-summary.css new file mode 100644 index 0000000..fafb3a5 --- /dev/null +++ b/templates/css/components/armour-summary.css @@ -0,0 +1,34 @@ +:host { + display: inline-block; +} + +.person { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-rows: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.2fr); + justify-items: center; + align-items: center; + position: relative; + row-gap: var(--row-gap); + + > div { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 1; + } + + > rc-svg { + position: absolute; + bottom: 0; + left: 0; + width: 58%; + } + + .head, .body, .legs { grid-column: 1; } + .arms { grid-column: 2; } + .head { grid-row: 1; } + .body, .arms { grid-row: 2; } + .legs { grid-row: 3; } +} From 94942c8eab48f3a67eb3b45db04e0f4ba40995bc Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 20 Jul 2025 21:35:27 -0600 Subject: [PATCH 04/61] Make a unique ArmourSheet so that it can have a better UX for indicating protection locations --- module/Apps/ItemSheets/ArmourSheet.mjs | 115 +++++++++++++++++++++ module/hooks/init.mjs | 13 ++- templates/Apps/ArmourSheet/content.hbs | 109 ++++++++++++++++++++ templates/Apps/ArmourSheet/style.css | 131 ++++++++++++++++++++++++ templates/Apps/apps.css | 1 + templates/Apps/partials/item-header.hbs | 27 +++++ templates/css/elements/input.css | 1 + templates/css/main.css | 5 +- templates/css/partials/item-header.css | 11 ++ 9 files changed, 407 insertions(+), 6 deletions(-) create mode 100644 module/Apps/ItemSheets/ArmourSheet.mjs create mode 100644 templates/Apps/ArmourSheet/content.hbs create mode 100644 templates/Apps/ArmourSheet/style.css create mode 100644 templates/Apps/partials/item-header.hbs create mode 100644 templates/css/partials/item-header.css diff --git a/module/Apps/ItemSheets/ArmourSheet.mjs b/module/Apps/ItemSheets/ArmourSheet.mjs new file mode 100644 index 0000000..990771a --- /dev/null +++ b/module/Apps/ItemSheets/ArmourSheet.mjs @@ -0,0 +1,115 @@ +import { filePath } from "../../consts.mjs"; +import { gameTerms } from "../../gameTerms.mjs"; +import { GenericAppMixin } from "../GenericApp.mjs"; +import { Logger } from "../../utils/Logger.mjs"; + +const { HandlebarsApplicationMixin } = foundry.applications.api; +const { ItemSheetV2 } = foundry.applications.sheets; + +export class ArmourSheet extends GenericAppMixin(HandlebarsApplicationMixin(ItemSheetV2)) { + + // #region Options + static DEFAULT_OPTIONS = { + classes: [ + `ripcrypt--item`, + `ArmourSheet`, + ], + position: { + width: `auto`, + height: `auto`, + }, + window: { + resizable: false, + }, + form: { + submitOnChange: true, + closeOnSubmit: false, + }, + }; + + static PARTS = { + header: { + template: filePath(`templates/Apps/partials/item-header.hbs`), + }, + content: { + template: filePath(`templates/Apps/ArmourSheet/content.hbs`), + }, + }; + // #endregion + + // #region Lifecycle + async _onRender() { + // remove the flag if it exists when we render the sheet + delete this.document?.system?.forceRerender; + }; + + /** + * Used to make it so that items that don't get updated because of the + * _preUpdate hook removing/changing the data submitted, can still get + * re-rendered when the diff is empty. If the document does get updated, + * this rerendering does not happen. + * + * @override + */ + async _processSubmitData(...args) { + await super._processSubmitData(...args); + + if (this.document.system.forceRerender) { + await this.render(false); + }; + }; + // #endregion + + // #region Data Prep + async _preparePartContext(partId, _, opts) { + const ctx = await super._preparePartContext(partId, {}, opts); + + ctx.item = this.document; + ctx.system = this.document.system; + + switch (partId) { + case `content`: { + this._prepareContentContext(ctx, opts); + break; + }; + }; + + Logger.debug(`Context:`, ctx); + return ctx; + }; + + async _prepareContentContext(ctx) { + ctx.weights = [ + { + label: `RipCrypt.common.empty`, + value: null, + }, + ...Object.values(gameTerms.WeightRatings).map(opt => ({ + label: `RipCrypt.common.weightRatings.${opt}`, + value: opt, + })), + ]; + + ctx.accesses = [ + { + label: `RipCrypt.common.empty`, + value: ``, + }, + ...gameTerms.Access.map(opt => ({ + label: `RipCrypt.common.accessLevels.${opt}`, + value: opt, + })), + ]; + + ctx.protects = { + head: this.document.system.location.has(gameTerms.Anatomy.HEAD), + body: this.document.system.location.has(gameTerms.Anatomy.BODY), + arms: this.document.system.location.has(gameTerms.Anatomy.ARMS), + legs: this.document.system.location.has(gameTerms.Anatomy.LEGS), + }; + }; + // #endregion + + // #region Actions + // #endregion +}; diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 0bbabd1..684ba7c 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -1,5 +1,6 @@ // Applications import { AllItemSheetV1 } from "../Apps/ItemSheets/AllItemSheetV1.mjs"; +import { ArmourSheet } from "../Apps/ItemSheets/ArmourSheet.mjs"; import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs"; import { CraftCardV1 } from "../Apps/ActorSheets/CraftCardV1.mjs"; import { DelveDiceHUD } from "../Apps/DelveDiceHUD.mjs"; @@ -37,7 +38,6 @@ import { registerUserSettings } from "../settings/userSettings.mjs"; import { registerWorldSettings } from "../settings/worldSettings.mjs"; const { Items, Actors } = foundry.documents.collections; -const { ItemSheet, ActorSheet } = foundry.appv1.sheets; Hooks.once(`init`, () => { Logger.log(`Initializing`); @@ -74,10 +74,6 @@ Hooks.once(`init`, () => { // #endregion // #region Sheets - // Unregister core sheets - Items.unregisterSheet(`core`, ItemSheet); - Actors.unregisterSheet(`core`, ActorSheet); - // #region Actors Actors.registerSheet(game.system.id, CombinedHeroSheet, { makeDefault: true, @@ -114,6 +110,13 @@ Hooks.once(`init`, () => { label: `RipCrypt.sheet-names.AllItemsSheetV1`, themes: AllItemSheetV1.themes, }); + + Items.registerSheet(game.system.id, ArmourSheet, { + makeDefault: true, + types: [`armour`], + label: `RipCrypt.sheet-names.ArmourSheet`, + themes: ArmourSheet.themes, + }); // #endregion // #endregion diff --git a/templates/Apps/ArmourSheet/content.hbs b/templates/Apps/ArmourSheet/content.hbs new file mode 100644 index 0000000..ac3f7a5 --- /dev/null +++ b/templates/Apps/ArmourSheet/content.hbs @@ -0,0 +1,109 @@ +
+
+ + + + + + Cost +
+ + + + + + +
+
+
+
+
+ Location + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
diff --git a/templates/Apps/ArmourSheet/style.css b/templates/Apps/ArmourSheet/style.css new file mode 100644 index 0000000..9751c53 --- /dev/null +++ b/templates/Apps/ArmourSheet/style.css @@ -0,0 +1,131 @@ +.ripcrypt.ArmourSheet > .window-content { + --input-height: 1rem; + --input-underline: none; + --col-gap: 8px; + --row-gap: 8px; + + --string-tags-tag-text: var(--header-text); + --string-tags-tag-background: var(--header-background); + --string-tags-add-text: white; + --string-tags-add-background: var(--accent-1); + --string-tags-input-text: white; + --string-tags-input-background: var(--accent-2); + + --input-text: white; + --input-background: var(--accent-2); + --button-text: white; + --button-background: var(--accent-2); + + --pill-width: 100%; + --pill-border-radius: 4px; + + display: flex; + flex-direction: column; + gap: 8px; + + padding: 8px; + background: var(--base-background); + color: var(--base-text); + + hr { + background: var(--accent-1); + grid-column: 1 / -1; + height: 1px; + width: 90%; + margin: 0 auto; + + &.vertical { + grid-column: unset; + height: 100%; + } + } + + label, .label { + display: flex; + align-items: center; + box-sizing: border-box; + + padding: 2px 4px; + text-transform: uppercase; + font-size: var(--font-size-14); + overflow: hidden; + text-overflow: ellipsis; + font-weight: bold; + } + + button, input, select, .value { + border-radius: 4px; + padding: 2px 4px; + } + .value { + border: 2px solid var(--accent-2); + } + + .contents { + display: grid; + grid-template-columns: 300px 1px 118px; + gap: 8px; + + > .contents__left { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); + column-gap: var(--col-gap); + row-gap: var(--row-gap); + } + + > .contents__right { + display: flex; + flex-direction: column; + gap: 8px; + } + } + + .section-pill { + background: var(--section-header-background); + color: var(--section-header-text); + padding: 0 4px; + border-radius: 999px; + text-align: right; + } + + rc-border { + grid-column: 1 / -1; + + > .content { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); + column-gap: var(--col-gap); + row-gap: var(--row-gap); + } + + .label { + background: purple; + } + } + + .center { + text-align: center; + } + + .compass { + --size: 35px; + width: var(--size); + height: var(--size); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border: 2px solid var(--accent-1); + border-radius: 50%; + font-size: 1.1rem; + position: relative; + background: var(--base-background); + + > .value { + background: none; + width: 70%; + text-align: center; + padding: 0; + } + } +} diff --git a/templates/Apps/apps.css b/templates/Apps/apps.css index 30af300..db88f7e 100644 --- a/templates/Apps/apps.css +++ b/templates/Apps/apps.css @@ -6,6 +6,7 @@ @import url("./StatsCardV1/style.css"); @import url("./SkillsCardV1/style.css"); @import url("./RichEditor/style.css"); +@import url("./ArmourSheet/style.css"); @import url("./popover.css"); @import url("./popovers/AmmoTracker/style.css"); diff --git a/templates/Apps/partials/item-header.hbs b/templates/Apps/partials/item-header.hbs new file mode 100644 index 0000000..3f6abd6 --- /dev/null +++ b/templates/Apps/partials/item-header.hbs @@ -0,0 +1,27 @@ +{{!-- +Required parameters: + "name" : the name of the item + "system.quantity" : the quantity of the item + "meta.idp" : the ID Prefix for the application +--}} +
+
+ + + +
+
diff --git a/templates/css/elements/input.css b/templates/css/elements/input.css index b12e45e..b8d7b46 100644 --- a/templates/css/elements/input.css +++ b/templates/css/elements/input.css @@ -21,6 +21,7 @@ all: revert-layer; --checkbox-checked-color: var(--accent-3); --checkbox-background-color: var(--accent-2); + --checkbox-checkmark-color: black; } &::placeholder { diff --git a/templates/css/main.css b/templates/css/main.css index 9b06147..a8c65a3 100644 --- a/templates/css/main.css +++ b/templates/css/main.css @@ -1,4 +1,4 @@ -@layer resets, themes, elements, components, apps, exceptions; +@layer resets, themes, elements, partials, apps, exceptions; /* Resets */ @import url("./resets/inputs.css") layer(resets); @@ -19,6 +19,9 @@ @import url("./elements/string-tags.css") layer(elements); @import url("./elements/table.css") layer(elements); +/* Partials */ +@import url("./partials/item-header.css") layer(partials); + /* Applications */ @import url("../Apps/apps.css") layer(apps); diff --git a/templates/css/partials/item-header.css b/templates/css/partials/item-header.css new file mode 100644 index 0000000..810c58f --- /dev/null +++ b/templates/css/partials/item-header.css @@ -0,0 +1,11 @@ +.item-header { + .name-row { + display: grid; + grid-template-columns: minmax(0, 1fr) min-content 50px; + gap: 4px; + + .quantity { + text-align: center; + } + } +} From caa3fbbda0eb71d0e6efe07681e4530bf555c2be Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 20 Jul 2025 21:35:39 -0600 Subject: [PATCH 05/61] Localize name based on the core translation --- templates/Apps/AllItemSheetV1/content.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/Apps/AllItemSheetV1/content.hbs b/templates/Apps/AllItemSheetV1/content.hbs index 0fb9599..523c483 100644 --- a/templates/Apps/AllItemSheetV1/content.hbs +++ b/templates/Apps/AllItemSheetV1/content.hbs @@ -1,6 +1,6 @@
{{#if meta.editable}} - + Date: Sun, 20 Jul 2025 21:43:23 -0600 Subject: [PATCH 06/61] Add lang entry for the new sheet --- langs/en-ca.json | 1 + 1 file changed, 1 insertion(+) diff --git a/langs/en-ca.json b/langs/en-ca.json index d606190..bcd494d 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -17,6 +17,7 @@ "RipCrypt": { "sheet-names": { "AllItemsSheetV1": "RipCrypt Item Sheet", + "ArmourSheet": "Armour Sheet", "CombinedHeroSheet": "Hero Sheet", "StatsCardV1": "Hero Stat Card", "CraftCardV1": "Hero Craft Card", From bfa26edd5bd9a0d92712945aff1e773257570b85 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 20 Jul 2025 21:43:41 -0600 Subject: [PATCH 07/61] Prevent the AllItemSheet from being used on armour/shields --- module/hooks/init.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 684ba7c..548e8e2 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -113,10 +113,13 @@ Hooks.once(`init`, () => { Items.registerSheet(game.system.id, ArmourSheet, { makeDefault: true, - types: [`armour`], + types: [`armour`, `shield`], label: `RipCrypt.sheet-names.ArmourSheet`, themes: ArmourSheet.themes, }); + Items.unregisterSheet(game.system.id, AllItemSheetV1, { + types: [`armour`, `shield`], + }); // #endregion // #endregion From 2215ce503b3dc91526b1f9b1fb7b4c92a7648a0d Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 20 Jul 2025 21:44:27 -0600 Subject: [PATCH 08/61] Remove the sheet inputs from the data model --- module/data/Item/Armour.mjs | 86 ------------------------------------- 1 file changed, 86 deletions(-) diff --git a/module/data/Item/Armour.mjs b/module/data/Item/Armour.mjs index 3602978..2d95562 100644 --- a/module/data/Item/Armour.mjs +++ b/module/data/Item/Armour.mjs @@ -110,90 +110,4 @@ export class ArmourData extends CommonItemData { return [...this.location].join(`, `); }; // #endregion - - // #region Sheet Data - getFormFields(_ctx) { - const fields = [ - { - id: `quantity`, - type: `integer`, - label: `RipCrypt.common.quantity`, - path: `system.quantity`, - value: this.quantity, - min: 0, - }, - { - id: `access`, - type: `dropdown`, - label: `RipCrypt.common.access`, - path: `system.access`, - value: this.access, - limited: false, - options: [ - { - label: `RipCrypt.common.empty`, - value: ``, - }, - ...gameTerms.Access.map(opt => ({ - label: `RipCrypt.common.accessLevels.${opt}`, - value: opt, - })), - ], - }, - { - id: `cost`, - type: `cost`, - label: `RipCrypt.common.cost`, - gold: this.cost.gold, - silver: this.cost.silver, - copper: this.cost.copper, - }, - { - id: `weight`, - type: `dropdown`, - label: `RipCrypt.common.weightRating`, - path: `system.weight`, - value: this.weight, - options: [ - { - label: `RipCrypt.common.empty`, - value: null, - }, - ...Object.values(gameTerms.WeightRatings).map(opt => ({ - label: `RipCrypt.common.weightRatings.${opt}`, - value: opt, - })), - ], - }, - { - id: `location`, - type: `string-set`, - label: `RipCrypt.common.location`, - placeholder: `RipCrypt.Apps.location-placeholder`, - path: `system.location`, - value: this.locationString, - }, - { - id: `protection`, - type: `integer`, - label: `RipCrypt.common.protection`, - value: this.protection, - path: `system.protection`, - min: 0, - }, - ]; - - if (this.parent.isEmbedded) { - fields.push({ - id: `equipped`, - type: `boolean`, - label: `RipCrypt.common.equipped`, - value: this.equipped, - path: `system.equipped`, - }); - }; - - return fields; - }; - // #endregion }; From 0bd099cc28d0baf5e9b89bd3a314ff744905ddb7 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 20 Jul 2025 21:45:10 -0600 Subject: [PATCH 09/61] Add todo so I don't forget --- templates/Apps/ArmourSheet/content.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/Apps/ArmourSheet/content.hbs b/templates/Apps/ArmourSheet/content.hbs index ac3f7a5..89a9373 100644 --- a/templates/Apps/ArmourSheet/content.hbs +++ b/templates/Apps/ArmourSheet/content.hbs @@ -12,6 +12,7 @@ + {{!-- TODO: Add equipped boolean control --}} From b72c9d9739b1dba5da700bf5ca11af28220159d4 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Wed, 23 Jul 2025 22:17:05 -0600 Subject: [PATCH 10/61] Add meta properties that indicate if the document is embedded, and if the user is able to edit it --- module/Apps/GenericApp.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index 1fe5edf..b1a1162 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -91,8 +91,9 @@ export function GenericAppMixin(HandlebarsApp) { ctx.meta.idp = this.document?.uuid ?? this.id; if (this.document) { ctx.meta.limited = this.document.limited; - ctx.meta.editable = ctx.editable; - } + ctx.meta.editable = this.document.isOwner; + ctx.meta.embedded = this.document.isEmbedded; + }; delete ctx.editable; return ctx; From 3c582c77bb6949aba9fd6e3a5df6ccba1f4385df Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Wed, 23 Jul 2025 23:52:07 -0600 Subject: [PATCH 11/61] Update the ArmourSheets to allow for the equipped toggle to be present and work --- module/Apps/ItemSheets/ArmourSheet.mjs | 22 ++++++++++++++++++++++ module/data/Item/Armour.mjs | 13 ++++++++----- templates/Apps/ArmourSheet/content.hbs | 12 +++++++++++- templates/Apps/ArmourSheet/style.css | 4 ++++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/module/Apps/ItemSheets/ArmourSheet.mjs b/module/Apps/ItemSheets/ArmourSheet.mjs index 990771a..453f4c4 100644 --- a/module/Apps/ItemSheets/ArmourSheet.mjs +++ b/module/Apps/ItemSheets/ArmourSheet.mjs @@ -5,6 +5,7 @@ import { Logger } from "../../utils/Logger.mjs"; const { HandlebarsApplicationMixin } = foundry.applications.api; const { ItemSheetV2 } = foundry.applications.sheets; +const { getProperty, hasProperty, setProperty } = foundry.utils; export class ArmourSheet extends GenericAppMixin(HandlebarsApplicationMixin(ItemSheetV2)) { @@ -58,6 +59,27 @@ export class ArmourSheet extends GenericAppMixin(HandlebarsApplicationMixin(Item await this.render(false); }; }; + + /** + * Customize how form data is extracted into an expanded object. + * @param {SubmitEvent|null} event The originating form submission event + * @param {HTMLFormElement} form The form element that was submitted + * @param {FormDataExtended} formData Processed data for the submitted form + * @returns {object} An expanded object of processed form data + * @throws {Error} Subclasses may throw validation errors here to prevent form submission + * @protected + */ + _processFormData(event, form, formData) { + const data = super._processFormData(event, form, formData); + + if (hasProperty(data, `system.location`)) { + let locations = getProperty(data, `system.location`); + locations = locations.filter(value => value != null); + setProperty(data, `system.location`, locations); + }; + + return data; + }; // #endregion // #region Data Prep diff --git a/module/data/Item/Armour.mjs b/module/data/Item/Armour.mjs index 2d95562..726c929 100644 --- a/module/data/Item/Armour.mjs +++ b/module/data/Item/Armour.mjs @@ -5,7 +5,7 @@ import { Logger } from "../../utils/Logger.mjs"; import { requiredInteger } from "../helpers.mjs"; const { fields } = foundry.data; -const { hasProperty, diffObject, mergeObject } = foundry.utils; +const { getProperty, diffObject, mergeObject } = foundry.utils; /** Used for Armour and Shields */ export class ArmourData extends CommonItemData { @@ -19,16 +19,16 @@ export class ArmourData extends CommonItemData { blank: false, trim: true, nullable: false, + required: true, options: Object.values(gameTerms.Anatomy), }), { nullable: false, - required: true, + initial: [], }, ), equipped: new fields.BooleanField({ initial: false, - required: true, nullable: false, }), weight: new fields.StringField({ @@ -59,7 +59,7 @@ export class ArmourData extends CommonItemData { const diff = diffObject(this.parent._source, changes); let valid = await super._preUpdate(changes, options, user); - if (hasProperty(diff, `system.equipped`) && !this._canEquip()) { + if (getProperty(diff, `system.equipped`) && !this._canEquip()) { ui.notifications.error( localizer( `RipCrypt.notifs.error.cannot-equip`, @@ -80,7 +80,10 @@ export class ArmourData extends CommonItemData { return valid; }; - /** Used to tell the preUpdate logic whether or not to prevent the */ + /** + * Used to tell the preUpdate logic whether or not to prevent the item from + * being equipped or not. + */ _canEquip() { const parent = this.parent; if (!parent.isEmbedded || !(parent.parent instanceof Actor)) { diff --git a/templates/Apps/ArmourSheet/content.hbs b/templates/Apps/ArmourSheet/content.hbs index 89a9373..b3ad0b3 100644 --- a/templates/Apps/ArmourSheet/content.hbs +++ b/templates/Apps/ArmourSheet/content.hbs @@ -12,7 +12,17 @@ - {{!-- TODO: Add equipped boolean control --}} + {{#if meta.embedded}} + + + {{/if}} diff --git a/templates/Apps/ArmourSheet/style.css b/templates/Apps/ArmourSheet/style.css index 9751c53..24535ce 100644 --- a/templates/Apps/ArmourSheet/style.css +++ b/templates/Apps/ArmourSheet/style.css @@ -57,6 +57,10 @@ border-radius: 4px; padding: 2px 4px; } + input[type="checkbox"] { + justify-self: end; + padding: 0; + } .value { border: 2px solid var(--accent-2); } From ca0c793b561ce0a7654026d60d1b69ec0858889d Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Wed, 23 Jul 2025 23:52:23 -0600 Subject: [PATCH 12/61] Add cursor pointer to events --- templates/css/elements/input.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/css/elements/input.css b/templates/css/elements/input.css index b8d7b46..f7d60f8 100644 --- a/templates/css/elements/input.css +++ b/templates/css/elements/input.css @@ -11,6 +11,7 @@ background: var(--input-background); color: var(--input-text); padding: 0px 4px; + cursor: pointer; &[type="text"], &[type="number"] { @@ -19,6 +20,7 @@ &[type="checkbox"] { all: revert-layer; + cursor: pointer; --checkbox-checked-color: var(--accent-3); --checkbox-background-color: var(--accent-2); --checkbox-checkmark-color: black; From e90e90bfe04cb06863cc606b313e4311403c766c Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 31 Jul 2025 10:29:56 -0600 Subject: [PATCH 13/61] Remove log --- module/Apps/ItemSheets/ArmourSheet.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/module/Apps/ItemSheets/ArmourSheet.mjs b/module/Apps/ItemSheets/ArmourSheet.mjs index 453f4c4..0c4ca54 100644 --- a/module/Apps/ItemSheets/ArmourSheet.mjs +++ b/module/Apps/ItemSheets/ArmourSheet.mjs @@ -96,7 +96,6 @@ export class ArmourSheet extends GenericAppMixin(HandlebarsApplicationMixin(Item }; }; - Logger.debug(`Context:`, ctx); return ctx; }; From 7dfc1bd0c03b41185d66be652815d0507bee0a0d Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 31 Jul 2025 10:32:42 -0600 Subject: [PATCH 14/61] Add IDs for the label associations --- templates/Apps/ArmourSheet/content.hbs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/templates/Apps/ArmourSheet/content.hbs b/templates/Apps/ArmourSheet/content.hbs index b3ad0b3..ead4f41 100644 --- a/templates/Apps/ArmourSheet/content.hbs +++ b/templates/Apps/ArmourSheet/content.hbs @@ -1,15 +1,15 @@
-