diff --git a/langs/en-ca.json b/langs/en-ca.json index a15ef76..4610f0a 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -9,6 +9,11 @@ }, "taf": { "settings": { + "actorDefaultAttributes": { + "name": "Remove Default Attributes", + "hint": "This removes the default attributes that are applied when a new actor is created, making it so that no attributes get created alongside the actor.", + "label": "Remove Attributes" + }, "canPlayersManageAttributes": { "name": "Players Can Manage Attributes", "hint": "This allows players who have edit access to a document to be able to edit what attributes those characters have via the attribute editor" @@ -34,10 +39,9 @@ "true": "Resizable" } }, - "actorDefaultAttributes": { - "name": "Remove Default Attributes", - "hint": "This removes the default attributes that are applied when a new actor is created, making it so that no attributes get created alongside the actor.", - "label": "Remove Attributes" + "weightUnit": { + "name": "Weight Unit", + "hint": "This unit is used to display the units for the weights of items and carrying capacity of actors. This does NOTHING beyond adding the unit into the displays, it will not automatically convert between any units." } }, "sheet-names": { diff --git a/module/apps/PlayerSheet.mjs b/module/apps/PlayerSheet.mjs index da03055..5d65dd2 100644 --- a/module/apps/PlayerSheet.mjs +++ b/module/apps/PlayerSheet.mjs @@ -2,10 +2,12 @@ import { __ID__, filePath } from "../consts.mjs"; import { AttributeManager } from "./AttributeManager.mjs"; import { attributeSorter } from "../utils/attributeSort.mjs"; import { TAFDocumentSheetConfig } from "./TAFDocumentSheetConfig.mjs"; +import { toPrecision } from "../utils/roundToPrecision.mjs"; const { HandlebarsApplicationMixin } = foundry.applications.api; const { ActorSheetV2 } = foundry.applications.sheets; const { getProperty, hasProperty } = foundry.utils; +const { TextEditor } = foundry.applications.ux; const propertyToParts = { "name": [`header`], @@ -39,6 +41,7 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) { actions: { manageAttributes: this.#manageAttributes, configureSheet: this.#configureSheet, + toggleExpand: this.#toggleExpand, }, }; @@ -68,6 +71,15 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) { }; // #endregion Options + // #region Instance Data + /** + * This Set is used to keep track of which items have had their full + * details expanded so that it can be persisted across rerenders as + * they occur. + */ + #expandedItems = new Set(); + // #endregion Instance Data + // #region Lifecycle _initializeApplicationOptions(options) { const sizing = getProperty(options.document, `flags.${__ID__}.PlayerSheet.size`) ?? {}; @@ -203,24 +215,73 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) { ctx.toggled = true; ctx.tabActive = this.tabGroups.primary === `content` || this.actor.items.size === 0; - const TextEditor = foundry.applications.ux.TextEditor.implementation; ctx.enriched = { system: { - content: await TextEditor.enrichHTML(this.actor.system.content), + content: await TextEditor.implementation.enrichHTML(this.actor.system.content), }, }; }; async _prepareItems(ctx) { ctx.tabActive = this.tabGroups.primary === `items`; + + const weightUnit = game.settings.get(__ID__, `weightUnit`); + let totalWeight = 0; + + ctx.itemGroups = []; + for (const [groupName, items] of Object.entries(this.actor.itemTypes)) { + const preparedItems = []; + + let summedWeight = 0; + for (const item of items) { + summedWeight += item.system.quantifiedWeight; + preparedItems.push(await this._prepareItem(item)); + }; + totalWeight += summedWeight; + + ctx.itemGroups.push({ + name: groupName.titleCase(), + items: preparedItems, + weight: toPrecision(summedWeight, 2) + weightUnit, + }); + }; + + ctx.totalWeight = toPrecision(totalWeight, 2) + weightUnit; }; - async _prepareItem(item) {}; + async _prepareItem(item) { + const weightUnit = game.settings.get(__ID__, `weightUnit`) + const ctx = { + uuid: item.uuid, + img: item.img, + name: item.name, + equipped: item.system.equipped, + quantity: item.system.quantity, + weight: item.system.quantifiedWeight + weightUnit, + isExpanded: this.#expandedItems.has(item.uuid), + canExpand: item.system.description.length > 0, + }; + + ctx.description = ``; + if (item.system.description.length > 0) { + ctx.description = await TextEditor.implementation.enrichHTML(item.system.description); + }; + + return ctx; + }; // #endregion Data Prep // #region Actions #attributeManager = null; - /** @this {PlayerSheet} */ + + /** + * This action opens an instance of the AttributeManager application + * so that the user can edit and update all of the attributes for the + * actor. This persists the application instance for the duration of + * the ActorSheet's lifespan. + * + * @this {PlayerSheet} + */ static async #manageAttributes() { this.#attributeManager ??= new AttributeManager({ document: this.actor }); if (this.#attributeManager.rendered) { @@ -233,6 +294,13 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) { }; }; + /** + * This action overrides the default Foundry action in order to tell + * it to open my custom DocumentSheetConfig application instead of + * opening the non-customized sheet config app. + * + * @this {PlayerSheet} + */ static async #configureSheet(event) { event.stopPropagation(); if ( event.detail > 1 ) { return } @@ -248,5 +316,27 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) { window: { windowId: this.window.windowId }, }); }; + + /** + * This action is used by the item lists in order to expand/collapse + * the descriptions while maintaining that state across renders. + * + * @this {PlayerSheet} + */ + static async #toggleExpand(event, target) { + if (event.srcElement instanceof HTMLInputElement) { return }; + + const { itemUuid } = target.closest(`[data-item-uuid]`)?.dataset ?? {}; + if (!itemUuid) { return }; + + const expanded = this.#expandedItems.has(itemUuid); + if (expanded) { + this.#expandedItems.delete(itemUuid); + target.nextElementSibling.dataset.expanded = false; + } else { + this.#expandedItems.add(itemUuid); + target.nextElementSibling.dataset.expanded = true; + } + }; // #endregion Actions }; diff --git a/module/data/Item/generic.mjs b/module/data/Item/generic.mjs index e082582..77021a7 100644 --- a/module/data/Item/generic.mjs +++ b/module/data/Item/generic.mjs @@ -1,3 +1,5 @@ +import { toPrecision } from "../../utils/roundToPrecision.mjs"; + export class GenericItemData extends foundry.abstract.TypeDataModel { static defineSchema() { const fields = foundry.data.fields; @@ -28,4 +30,12 @@ export class GenericItemData extends foundry.abstract.TypeDataModel { }), }; }; + + /** + * Calculates the total weight of the item based on the quantity of it, this + * rounds the number to the nearest 2 decimal places. + */ + get quantifiedWeight() { + return toPrecision(this.weight * this.quantity, 2); + }; }; diff --git a/module/settings/world.mjs b/module/settings/world.mjs index 330f433..bb0cd06 100644 --- a/module/settings/world.mjs +++ b/module/settings/world.mjs @@ -13,6 +13,15 @@ export function registerWorldSettings() { scope: `world`, }); + game.settings.register(__ID__, `weightUnit`, { + name: `taf.settings.weightUnit.name`, + hint: `taf.settings.weightUnit.hint`, + config: true, + type: String, + default: ``, + scope: `world`, + }); + game.settings.register(__ID__, `canPlayersManageAttributes`, { name: `taf.settings.canPlayersManageAttributes.name`, hint: `taf.settings.canPlayersManageAttributes.hint`, diff --git a/module/utils/roundToPrecision.mjs b/module/utils/roundToPrecision.mjs new file mode 100644 index 0000000..5365690 --- /dev/null +++ b/module/utils/roundToPrecision.mjs @@ -0,0 +1,20 @@ +/** + * Takes a possibly-decimal value and rounds after a certain precision, keeping + * only the specified amount of decimals. + * + * @param {number} value The value that is to be rounded. + * @param {number} precision The number of decimal places to round to. Must be a + * positive integer. + * @returns The rounded number + */ +export function toPrecision(value, precision = 1) { + if (!Number.isInteger(precision)) { + throw `Precision must be an integer`; + }; + if (precision < 0) { + throw `Precision must be greater than or equal to 0`; + }; + + const m = 10 ** precision; + return Math.round(value * m) / m; +}; diff --git a/styles/Apps/PlayerSheet.css b/styles/Apps/PlayerSheet.css index 525f01b..cb2e44c 100644 --- a/styles/Apps/PlayerSheet.css +++ b/styles/Apps/PlayerSheet.css @@ -37,6 +37,64 @@ } } + .item-list { + list-style: none; + margin: 0; + padding: 0; + } + + .item { + background: var(--item-card-background); + color: var(--item-card-color); + border-radius: 4px; + overflow: hidden; + + .summary { + display: grid; + grid-template-columns: auto 1fr 50px auto; + align-items: center; + gap: 8px; + background: var(--item-card-header-background); + color: var(--item-card-header-color); + padding: 4px; + + img { + --size: 35px; + width: var(--size); + height: var(--size); + border-radius: 6px; + } + + .title { + display: flex; + flex-direction: column; + gap: 4px; + } + + .name { + font-size: 1.1rem; + } + .subtitle { + font-size: 0.7rem; + opacity: 90%; + } + + input { + background: var(--item-card-header-input-background); + color: var(--item-card-header-input-color); + text-align: center; + } + } + + .full-details { + padding: 4px; + + &[data-expanded="false"] { + display: none; + } + } + } + .content { flex-grow: 1; overflow: hidden; diff --git a/styles/themes/dark.css b/styles/themes/dark.css index 5d833cf..780ecb1 100644 --- a/styles/themes/dark.css +++ b/styles/themes/dark.css @@ -7,6 +7,13 @@ --tab-button-active-border: rebeccapurple; --tab-button-hover-bg: var(--color-cool-3); + --item-card-background: #1d262f; + --item-card-color: white; + --item-card-header-background: #242d38; + --item-card-header-color: white; + --item-card-header-input-background: #2b3642; + --item-card-header-input-color: white; + /* Chip Variables */ --chip-color: #fff7ed; --chip-background: #2b3642; diff --git a/templates/PlayerSheet/item-lists.hbs b/templates/PlayerSheet/item-lists.hbs index 517b1be..56585e4 100644 --- a/templates/PlayerSheet/item-lists.hbs +++ b/templates/PlayerSheet/item-lists.hbs @@ -3,5 +3,23 @@ data-group="primary" data-tab="items" > - Item Tab Content Here + Total Weight: {{totalWeight}} +
+ {{#each itemGroups as | group |}} +
+
+ + {{ group.name }} + + + {{ group.weight }} + +
+ +
+ {{/each}} diff --git a/templates/PlayerSheet/item.hbs b/templates/PlayerSheet/item.hbs index 7c89b54..e18c57e 100644 --- a/templates/PlayerSheet/item.hbs +++ b/templates/PlayerSheet/item.hbs @@ -1 +1,29 @@ -
+
  • +
    + +
    + {{ name }} + {{ weight }} +
    + +
    + {{#if canExpand}} +
    + {{{ description }}} +
    + {{/if}} +