From c495f45015250549551c8c6dea9a4f4bf6d96b10 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 22 Mar 2025 21:20:02 -0600 Subject: [PATCH] Get the base favourite mechanism working so the items are visible on the skills card --- assets/_credit.txt | 2 + assets/icons/star-empty.svg | 1 + assets/icons/star.svg | 1 + langs/en-ca.json | 9 ++- module/Apps/ActorSheets/HeroSkillsCardV1.mjs | 18 +++++- module/Apps/GenericApp.mjs | 5 ++ module/Apps/popovers/AmmoTracker.mjs | 57 ++++++++++++++++++- module/Apps/popovers/GenericPopoverMixin.mjs | 2 +- templates/Apps/HeroSkillsCardV1/content.hbs | 18 ++++++ templates/Apps/HeroSkillsCardV1/style.css | 8 ++- .../Apps/popovers/AmmoTracker/ammoList.hbs | 45 ++++++++++----- templates/Apps/popovers/AmmoTracker/style.css | 22 +++++-- templates/css/common.css | 1 + templates/css/elements/button.css | 3 + 14 files changed, 165 insertions(+), 27 deletions(-) create mode 100644 assets/icons/star-empty.svg create mode 100644 assets/icons/star.svg diff --git a/assets/_credit.txt b/assets/_credit.txt index 43cf3fb..bac6dc2 100644 --- a/assets/_credit.txt +++ b/assets/_credit.txt @@ -1,6 +1,8 @@ Oliver Akins: - geist-silhouette.v2.svg : All rights reserved. - caster-silhouette.v1.svg : All rights reserved. + - icons/star-empty.svg : Modified from https://thenounproject.com/icon/star-7711815/ by Llisole + - icons/star.svg : Modified from https://thenounproject.com/icon/star-7711815/ by Llisole Kýnan Antos (Gritsilk Games): - hero-silhouette.svg : Licensed to Distribute and Modify within the bounds of the "Foundry-RipCrypt" system. diff --git a/assets/icons/star-empty.svg b/assets/icons/star-empty.svg new file mode 100644 index 0000000..8760cc9 --- /dev/null +++ b/assets/icons/star-empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/star.svg b/assets/icons/star.svg new file mode 100644 index 0000000..829431b --- /dev/null +++ b/assets/icons/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/langs/en-ca.json b/langs/en-ca.json index badf3f2..de9e051 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -173,12 +173,17 @@ "rollTarget": "Target", "difficulty": "(DC: {dc})", "RichEditor-no-collaborative": "Warning: This editor is not collaborative, that means that if you and someone else are editing it at the same time, you won't see that someone else is making changes until they save, and then your changes will be lost.", - "no-ammo": "You don't have any ammo!" + "AmmoTracker": { + "no-ammo": "You don't have any ammo!", + "pin-button": "Pin {name} to your character sheet", + "pin-button-tooltip": "Pin {name}" + } }, "notifs": { "error": { "cannot-equip": "Cannot equip the {itemType}, see console for more details.", - "invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action." + "invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action.", + "at-favourite-limit": "Cannot favourite more than three items, unfavourite one to make space." }, "warn": { "cannot-go-negative": "\"{name}\" is unable to be a negative number." diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index ccffbef..be8fb26 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -139,7 +139,23 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin }; static async prepareAmmo(ctx) { - ctx.ammo = 0; + let total = 0; + ctx.favouriteAmmo = []; + + for (const ammo of ctx.actor.itemTypes.ammo) { + total += ammo.system.quantity; + + if (ctx.favouriteAmmo.length < 3 && ammo.getFlag(game.system.id, `favourited`)) { + ctx.favouriteAmmo.push({ + uuid: ammo.uuid, + name: ammo.name, + quantity: ammo.system.quantity, + }); + }; + }; + ctx.favouriteAmmo.length = 3; // assert array length + + ctx.ammo = total; return ctx; }; diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index 7c1077e..2e9c1c2 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -52,6 +52,11 @@ export function GenericAppMixin(HandlebarsApp) { }; }; + /** + * @override + * This override makes it so that if the application has any framable popovers + * within it that are currently open, they will rerender as well. + */ async _onRender() { await super._onRender(); for (const manager of this._popoverManagers.values()) { diff --git a/module/Apps/popovers/AmmoTracker.mjs b/module/Apps/popovers/AmmoTracker.mjs index e1203c0..8c4c798 100644 --- a/module/Apps/popovers/AmmoTracker.mjs +++ b/module/Apps/popovers/AmmoTracker.mjs @@ -1,5 +1,7 @@ import { filePath } from "../../consts.mjs"; import { GenericPopoverMixin } from "./GenericPopoverMixin.mjs"; +import { localizer } from "../../utils/Localizer.mjs"; +import { Logger } from "../../utils/Logger.mjs"; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -15,7 +17,10 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( `ripcrypt--AmmoTracker`, ], }, - actions: {}, + actions: { + favourite: this.#favourite, + unfavourite: this.#unfavourite, + }, }; static PARTS = { @@ -26,17 +31,63 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( // #endregion // #region Instance Data + _favouriteCount = 0; // #endregion // #region Lifecycle async _preparePartContext(partId, data) { const ctx = { partId }; - ctx.canPin = false; - ctx.ammos = data.ammos; + + let favouriteCount = 0; + ctx.ammos = data.ammos.map(ammo => { + const favourite = ammo.getFlag(game.system.id, `favourited`) ?? false; + if (favourite) { favouriteCount++ }; + + return { + ammo, + favourite, + }; + }); + + this._favouriteCount = favouriteCount; + ctx.atFavouriteLimit = favouriteCount >= 3; return ctx; }; // #endregion // #region Actions + static async #favourite(_, el) { + const targetEl = el.closest(`[data-item-id]`); + if (!targetEl) { + Logger.warn(`Cannot find a parent element with data-item-id`); + return; + }; + + // get count of favourites + if (this._favouriteCount > 3) { + ui.notifications.error(localizer(`RipCrypt.notifs.error.at-favourite-limit`)); + return; + }; + + const data = targetEl.dataset; + const item = await fromUuid(data.itemId); + if (!item) { return }; + + item.setFlag(game.system.id, `favourited`, true); + }; + + static async #unfavourite(_, el) { + const targetEl = el.closest(`[data-item-id]`); + if (!targetEl) { + Logger.warn(`Cannot find a parent element with data-item-id`); + return; + }; + + const data = targetEl.dataset; + const item = await fromUuid(data.itemId); + if (!item) { return }; + + item.unsetFlag(game.system.id, `favourited`); + }; // #endregion }; diff --git a/module/Apps/popovers/GenericPopoverMixin.mjs b/module/Apps/popovers/GenericPopoverMixin.mjs index 017c2b0..a64b067 100644 --- a/module/Apps/popovers/GenericPopoverMixin.mjs +++ b/module/Apps/popovers/GenericPopoverMixin.mjs @@ -81,7 +81,7 @@ export function GenericPopoverMixin(HandlebarsApp) { * want it to when being created. * * Most of this implementation is identical to the ApplicationV2 - * implementation, the biggest difference is how targetLeft and targetRight + * implementation, the biggest difference is how targetLeft and targetTop * are calculated. */ _updatePosition(position) { diff --git a/templates/Apps/HeroSkillsCardV1/content.hbs b/templates/Apps/HeroSkillsCardV1/content.hbs index 776f673..5259737 100644 --- a/templates/Apps/HeroSkillsCardV1/content.hbs +++ b/templates/Apps/HeroSkillsCardV1/content.hbs @@ -118,6 +118,24 @@ {{ ammo }} + {{#each favouriteAmmo as | data |}} + {{#if data}} +
+ + +
+ {{else}} + {{/if}} + {{/each}} {{!-- * Currencies --}}
diff --git a/templates/Apps/HeroSkillsCardV1/style.css b/templates/Apps/HeroSkillsCardV1/style.css index c19aaff..bf35321 100644 --- a/templates/Apps/HeroSkillsCardV1/style.css +++ b/templates/Apps/HeroSkillsCardV1/style.css @@ -8,6 +8,7 @@ grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(13, minmax(0, 1fr)); column-gap: var(--col-gap); + row-gap: var(--row-gap); background: var(--base-background); color: var(--base-text); @@ -125,8 +126,13 @@ } } + label, .label { + white-space: nowrap; + text-overflow: ellipsis; + } - .input { + + input, .input { margin: 2px; border-radius: 999px; text-align: center; diff --git a/templates/Apps/popovers/AmmoTracker/ammoList.hbs b/templates/Apps/popovers/AmmoTracker/ammoList.hbs index 87118d7..a7dc8b5 100644 --- a/templates/Apps/popovers/AmmoTracker/ammoList.hbs +++ b/templates/Apps/popovers/AmmoTracker/ammoList.hbs @@ -1,18 +1,37 @@
{{#if ammos}}
    - {{#each ammos as | ammo |}} -
    - {{ ammo.name }} - {{ ammo.system.quantity }} - -
    + {{#each ammos as | data |}} +
  • + {{ data.ammo.name }} + {{ data.ammo.system.quantity }} + {{#if data.favourite}} + + {{else}} + + {{/if}} +
  • {{/each}}
{{else}} @@ -20,4 +39,4 @@ {{ rc-i18n "RipCrypt.Apps.no-ammo" }} {{/if}} -
\ No newline at end of file +
diff --git a/templates/Apps/popovers/AmmoTracker/style.css b/templates/Apps/popovers/AmmoTracker/style.css index 1f1cf1d..d81ef2d 100644 --- a/templates/Apps/popovers/AmmoTracker/style.css +++ b/templates/Apps/popovers/AmmoTracker/style.css @@ -1,8 +1,9 @@ .ripcrypt--AmmoTracker.ripcrypt--AmmoTracker { color: var(--popover-text); background: var(--popover-background); - padding: 4px 8px; + padding: 4px; + --row-gap: 4px; --button-text: var(--header-text); --button-background: var(--header-background); @@ -11,11 +12,20 @@ } ul { - &:nth-child(even) { - color: var(--popover-alt-row-text); - background: var(--popover-alt-row-background); - --button-text: unset; - --button-background: unset; + display: flex; + flex-direction: column; + row-gap: var(--row-gap); + + > li { + padding: 4px 8px; + border-radius: 999px; + + &:nth-child(even) { + color: var(--popover-alt-row-text); + background: var(--popover-alt-row-background); + --button-text: unset; + --button-background: unset; + } } } diff --git a/templates/css/common.css b/templates/css/common.css index 8d025bb..1b2758e 100644 --- a/templates/css/common.css +++ b/templates/css/common.css @@ -29,6 +29,7 @@ /* height: 270px; */ width: 680px; --col-gap: 2px; + --row-gap: 4px; } label, input, select { diff --git a/templates/css/elements/button.css b/templates/css/elements/button.css index e912a42..62431e7 100644 --- a/templates/css/elements/button.css +++ b/templates/css/elements/button.css @@ -1,6 +1,9 @@ .ripcrypt:where(.popover.frameless, .hud) button, .ripcrypt > .window-content button { all: revert; + display: flex; + justify-content: center; + align-items: center; outline: none; border: none; padding: 2px 4px;