From 1e416ff9bfb147df818961979fadc9961339a2c0 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 25 Jan 2025 23:49:51 -0700 Subject: [PATCH] RC-65 | Gear | Display Data --- langs/en-ca.json | 2 + module/Apps/ActorSheets/CombinedHeroSheet.mjs | 24 ++++++- module/Apps/ActorSheets/HeroSkillsCardV1.mjs | 68 +++++++++++++++++++ module/Apps/ActorSheets/HeroSummaryCardV1.mjs | 36 ++++------ module/Apps/GenericApp.mjs | 20 +++++- module/Apps/utils.mjs | 27 ++++++++ module/data/Item/Protector.mjs | 2 +- module/gameTerms.mjs | 6 ++ templates/Apps/HeroSkillsCardV1/content.hbs | 22 +++--- templates/Apps/HeroSkillsCardV1/style.css | 4 ++ 10 files changed, 172 insertions(+), 39 deletions(-) create mode 100644 module/Apps/utils.mjs diff --git a/langs/en-ca.json b/langs/en-ca.json index dd85173..fd6d2c6 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -17,6 +17,8 @@ "HeroSkillsCardV1": "Hero Skill Card" }, "common": { + "edit": "Edit", + "delete": "Delete", "empty": "---", "move": "Move", "run": "Run", diff --git a/module/Apps/ActorSheets/CombinedHeroSheet.mjs b/module/Apps/ActorSheets/CombinedHeroSheet.mjs index 28f6244..c27efcb 100644 --- a/module/Apps/ActorSheets/CombinedHeroSheet.mjs +++ b/module/Apps/ActorSheets/CombinedHeroSheet.mjs @@ -1,5 +1,6 @@ import { filePath } from "../../consts.mjs"; import { GenericAppMixin } from "../GenericApp.mjs"; +import { HeroSkillsCardV1 } from "./HeroSkillsCardV1.mjs"; import { HeroSummaryCardV1 } from "./HeroSummaryCardV1.mjs"; import { Logger } from "../../utils/Logger.mjs"; @@ -43,7 +44,26 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi // #region Lifecycle async _onRender(context, options) { await super._onRender(context, options); - HeroSummaryCardV1._onRender.bind(this)(context, options); + + const summaryElement = this.element.querySelector(`.HeroSummaryCardV1`); + HeroSummaryCardV1._onRender( + context, + { + ...options, + element: summaryElement, + isEditable: this.isEditable, + }, + ); + + const skillsElement = this.element.querySelector(`.HeroSkillsCardV1`); + HeroSkillsCardV1._onRender.bind(this)( + context, + { + ...options, + element: skillsElement, + isEditable: this.isEditable, + }, + ); }; async _preparePartContext(partId, ctx, opts) { @@ -58,6 +78,8 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi ctx = await HeroSummaryCardV1.prepareSpeed(ctx); ctx = await HeroSummaryCardV1.prepareLevelData(ctx); + ctx = await HeroSkillsCardV1.prepareGear(ctx); + Logger.debug(`Context:`, ctx); return ctx; }; diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index 9b55105..bad47c1 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -1,3 +1,4 @@ +import { deleteItemFromElement, editItemFromElement } from "../utils.mjs"; import { filePath } from "../../consts.mjs"; import { gameTerms } from "../../gameTerms.mjs"; import { GenericAppMixin } from "../GenericApp.mjs"; @@ -6,6 +7,7 @@ import { Logger } from "../../utils/Logger.mjs"; const { HandlebarsApplicationMixin } = foundry.applications.api; const { ActorSheetV2 } = foundry.applications.sheets; +const { ContextMenu } = foundry.applications.ui; export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(ActorSheetV2)) { @@ -38,13 +40,79 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin // #endregion // #region Lifecycle + async _onRender(context, options) { + await super._onRender(context, options); + HeroSkillsCardV1._onRender.bind(this)(context, options); + }; + + static async _onRender(_context, options) { + const { + element = this.element, + isEditable = this.isEditable, + } = options; + new ContextMenu( + element, + `[data-ctx-menu="gear"]`, + [ + { + name: localizer(`RipCrypt.common.edit`), + condition: (el) => { + const itemId = el.dataset.itemId; + return isEditable && itemId !== ``; + }, + callback: editItemFromElement, + }, + { + name: localizer(`RipCrypt.common.delete`), + condition: (el) => { + const itemId = el.dataset.itemId; + return isEditable && itemId !== ``; + }, + callback: deleteItemFromElement, + }, + ], + { jQuery: false, fixed: true }, + ); + }; + async _preparePartContext(partId, ctx, opts) { ctx = await super._preparePartContext(partId, ctx, opts); ctx.actor = this.document; + ctx = await HeroSkillsCardV1.prepareGear(ctx); + Logger.debug(`Context:`, ctx); return ctx; }; + + static async prepareGear(ctx) { + const limit = ctx.actor.system.limit.equipment; + ctx.gear = []; + const items = [...ctx.actor.items]; + for (const item of items) { + if (!gameTerms.gearItemTypes.has(item.type)) { continue }; + + if ( `equipped` in item.system && !item.system.equipped) { continue }; + ctx.gear.push({ + index: ctx.gear.length, + uuid: item.uuid, + name: item.name, + empty: false, + }); + + if (ctx.gear.length >= limit) { break }; + }; + + if (ctx.gear.length < limit) { + for (let i = ctx.gear.length - 1; i <= limit; i++) { + ctx.gear.push({ + index: ctx.gear.length, + uuid: ``, name: ``, empty: true }); + }; + }; + + return ctx; + }; // #endregion // #region Actions diff --git a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs index 4437e3c..d45de3a 100644 --- a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs @@ -1,3 +1,4 @@ +import { deleteItemFromElement, editItemFromElement } from "../utils.mjs"; import { filePath } from "../../consts.mjs"; import { gameTerms } from "../../gameTerms.mjs"; import { GenericAppMixin } from "../GenericApp.mjs"; @@ -24,7 +25,6 @@ export class HeroSummaryCardV1 extends GenericAppMixin(HandlebarsApplicationMixi resizable: false, }, actions: { - editItem: (_event, target) => this._editItem(target), }, form: { submitOnChange: true, @@ -45,32 +45,30 @@ export class HeroSummaryCardV1 extends GenericAppMixin(HandlebarsApplicationMixi HeroSummaryCardV1._onRender.bind(this)(context, options); }; - static async _onRender() { + static async _onRender(context, options) { + const { + element = this.element, + isEditable = this.isEditable, + } = options; new ContextMenu( - this.element, + element, `[data-ctx-menu="weapon"],[data-ctx-menu="armour"]`, [ { - name: `Edit`, + name: localizer(`RipCrypt.common.edit`), condition: (el) => { const itemId = el.dataset.itemId; - return this.isEditable && itemId !== ``; + return isEditable && itemId !== ``; }, - callback: HeroSummaryCardV1._editItem, + callback: editItemFromElement, }, { - name: `Delete`, + name: localizer(`RipCrypt.common.delete`), condition: (el) => { const itemId = el.dataset.itemId; - return this.isEditable && itemId !== ``; - }, - callback: async (el) => { - const itemEl = el.closest(`[data-item-id]`); - if (!itemEl) { return }; - const itemId = itemEl.dataset.itemId; - const item = await fromUuid(itemId); - await item.delete(); + return isEditable && itemId !== ``; }, + callback: deleteItemFromElement, }, ], { jQuery: false, fixed: true }, @@ -200,13 +198,5 @@ export class HeroSummaryCardV1 extends GenericAppMixin(HandlebarsApplicationMixi // #endregion // #region Actions - static async _editItem(target) { - const itemEl = target.closest(`[data-item-id]`); - if (!itemEl) { return }; - const itemId = itemEl.dataset.itemId; - if (!itemId) { return }; - const item = await fromUuid(itemId); - item.sheet.render({ force: true }); - }; // #endregion }; diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index 58aa790..edcb4a8 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -1,3 +1,4 @@ +import { deleteItemFromElement, editItemFromElement } from "./utils.mjs"; import { DicePool } from "./DicePool.mjs"; /** @@ -12,7 +13,9 @@ export function GenericAppMixin(HandlebarsApp) { `ripcrypt`, ], actions: { - roll: this.rollDice, + roll: this._rollDice, + editItem: (_event, target) => editItemFromElement(target), + deleteItem: (_event, target) => deleteItemFromElement(target), }, }; @@ -22,6 +25,19 @@ export function GenericAppMixin(HandlebarsApp) { // #endregion // #region Lifecycle + /** + * @override + * Making it so that if the app is already open, it's brought to + * top after being re-rendered as normal + */ + async render(options = {}, _options = {}) { + super.render(options, _options); + const instance = foundry.applications.instances.get(this.id); + if (instance !== undefined && !options.noBringToFront) { + instance.bringToFront(); + }; + }; + async _preparePartContext(partId, ctx, opts) { ctx = await super._preparePartContext(partId, ctx, opts); delete ctx.document; @@ -41,7 +57,7 @@ export function GenericAppMixin(HandlebarsApp) { // #region Actions /** @this {GenericRipCryptApp} */ - static async rollDice(_$e, el) { + static async _rollDice(_$e, el) { const data = el.dataset; const diceCount = parseInt(data.diceCount); const flavor = data.flavor; diff --git a/module/Apps/utils.mjs b/module/Apps/utils.mjs new file mode 100644 index 0000000..ebc1d22 --- /dev/null +++ b/module/Apps/utils.mjs @@ -0,0 +1,27 @@ +/* +This file contains utilities used by Applications in order to be DRYer +*/ + +/** + * @param {HTMLElement} target The element that gets + */ +export async function editItemFromElement(target) { + const itemEl = target.closest(`[data-item-id]`); + if (!itemEl) { return }; + const itemId = itemEl.dataset.itemId; + if (!itemId) { return }; + const item = await fromUuid(itemId); + item.sheet.render({ force: true }); +}; + +/** + * @param {HTMLElement} target The element that gets + */ +export async function deleteItemFromElement(target) { + const itemEl = target.closest(`[data-item-id]`); + if (!itemEl) { return }; + const itemId = itemEl.dataset.itemId; + if (!itemId) { return }; + const item = await fromUuid(itemId); + item.delete(); +}; diff --git a/module/data/Item/Protector.mjs b/module/data/Item/Protector.mjs index 67bb4e5..aa07c59 100644 --- a/module/data/Item/Protector.mjs +++ b/module/data/Item/Protector.mjs @@ -46,7 +46,7 @@ export class ProtectorData extends foundry.abstract.TypeDataModel { // #endregion // #region Sheet Data - getFormFields(ctx) { + getFormFields(_ctx) { const fields = [ { id: `location`, diff --git a/module/gameTerms.mjs b/module/gameTerms.mjs index e65ca26..b893c3b 100644 --- a/module/gameTerms.mjs +++ b/module/gameTerms.mjs @@ -23,4 +23,10 @@ export const gameTerms = Object.preventExtensions({ ARMS: `arms`, LEGS: `legs`, }), + /** The types of items that contribute to the gear limit */ + gearItemTypes: new Set([ + `armour`, + `weapon`, + `shield`, + ]), }); diff --git a/templates/Apps/HeroSkillsCardV1/content.hbs b/templates/Apps/HeroSkillsCardV1/content.hbs index 3f6db8d..b2752e1 100644 --- a/templates/Apps/HeroSkillsCardV1/content.hbs +++ b/templates/Apps/HeroSkillsCardV1/content.hbs @@ -48,18 +48,16 @@ {{ rc-i18n "RipCrypt.common.slot" }}
    -
  1. -
  2. -
  3. -
  4. -
  5. -
  6. -
  7. -
  8. -
  9. -
  10. -
  11. -
  12. + {{#each gear as | itemInSlot |}} +
  13. + {{itemInSlot.name}} +
  14. + {{/each}}
diff --git a/templates/Apps/HeroSkillsCardV1/style.css b/templates/Apps/HeroSkillsCardV1/style.css index e4c935d..858e2ee 100644 --- a/templates/Apps/HeroSkillsCardV1/style.css +++ b/templates/Apps/HeroSkillsCardV1/style.css @@ -85,6 +85,10 @@ grid-template-rows: subgrid; list-style-type: none; + > li { + padding: 0 4px; + } + > :nth-child(even) { background: var(--alt-row-background); color: var(--alt-row-text);