From 94942c8eab48f3a67eb3b45db04e0f4ba40995bc Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 20 Jul 2025 21:35:27 -0600 Subject: [PATCH] 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; + } + } +}