Get the base favourite mechanism working so the items are visible on the skills card

This commit is contained in:
Oliver-Akins 2025-03-22 21:20:02 -06:00
parent 7d39c487dc
commit c495f45015
14 changed files with 165 additions and 27 deletions

View file

@ -1,6 +1,8 @@
Oliver Akins: Oliver Akins:
- geist-silhouette.v2.svg : All rights reserved. - geist-silhouette.v2.svg : All rights reserved.
- caster-silhouette.v1.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): Kýnan Antos (Gritsilk Games):
- hero-silhouette.svg : Licensed to Distribute and Modify within the bounds of the "Foundry-RipCrypt" system. - hero-silhouette.svg : Licensed to Distribute and Modify within the bounds of the "Foundry-RipCrypt" system.

View file

@ -0,0 +1 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path d="M93.824 44.383 80.058 61.379a6.3 6.3 0 0 0-1.398 4.3l1.144 21.84c.2 3.802-3.578 6.548-7.133 5.18l-20.418-7.84a6.3 6.3 0 0 0-4.52 0L27.317 92.7c-3.551 1.364-7.332-1.382-7.133-5.18l1.145-21.84a6.3 6.3 0 0 0-1.399-4.3L6.163 44.383C3.77 41.426 5.21 36.985 8.886 36l21.125-5.66a6.3 6.3 0 0 0 3.656-2.656L45.577 9.34c2.07-3.192 6.742-3.192 8.816 0l11.91 18.344a6.3 6.3 0 0 0 3.657 2.656L91.085 36c3.675.985 5.128 5.43 2.734 8.387z" style="stroke-width: 8px; stroke: black; fill: transparent;"/></svg>

After

Width:  |  Height:  |  Size: 565 B

1
assets/icons/star.svg Normal file
View file

@ -0,0 +1 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path d="M93.824 44.383 80.058 61.379a6.3 6.3 0 0 0-1.398 4.3l1.144 21.84c.2 3.802-3.578 6.548-7.133 5.18l-20.418-7.84a6.3 6.3 0 0 0-4.52 0L27.317 92.7c-3.551 1.364-7.332-1.382-7.133-5.18l1.145-21.84a6.3 6.3 0 0 0-1.399-4.3L6.163 44.383C3.77 41.426 5.21 36.985 8.886 36l21.125-5.66a6.3 6.3 0 0 0 3.656-2.656L45.577 9.34c2.07-3.192 6.742-3.192 8.816 0l11.91 18.344a6.3 6.3 0 0 0 3.657 2.656L91.085 36c3.675.985 5.128 5.43 2.734 8.387z"/></svg>

After

Width:  |  Height:  |  Size: 504 B

View file

@ -173,12 +173,17 @@
"rollTarget": "Target", "rollTarget": "Target",
"difficulty": "(DC: {dc})", "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.", "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": { "notifs": {
"error": { "error": {
"cannot-equip": "Cannot equip the {itemType}, see console for more details.", "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": { "warn": {
"cannot-go-negative": "\"{name}\" is unable to be a negative number." "cannot-go-negative": "\"{name}\" is unable to be a negative number."

View file

@ -139,7 +139,23 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin
}; };
static async prepareAmmo(ctx) { 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; return ctx;
}; };

View file

@ -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() { async _onRender() {
await super._onRender(); await super._onRender();
for (const manager of this._popoverManagers.values()) { for (const manager of this._popoverManagers.values()) {

View file

@ -1,5 +1,7 @@
import { filePath } from "../../consts.mjs"; import { filePath } from "../../consts.mjs";
import { GenericPopoverMixin } from "./GenericPopoverMixin.mjs"; import { GenericPopoverMixin } from "./GenericPopoverMixin.mjs";
import { localizer } from "../../utils/Localizer.mjs";
import { Logger } from "../../utils/Logger.mjs";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -15,7 +17,10 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin(
`ripcrypt--AmmoTracker`, `ripcrypt--AmmoTracker`,
], ],
}, },
actions: {}, actions: {
favourite: this.#favourite,
unfavourite: this.#unfavourite,
},
}; };
static PARTS = { static PARTS = {
@ -26,17 +31,63 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin(
// #endregion // #endregion
// #region Instance Data // #region Instance Data
_favouriteCount = 0;
// #endregion // #endregion
// #region Lifecycle // #region Lifecycle
async _preparePartContext(partId, data) { async _preparePartContext(partId, data) {
const ctx = { partId }; 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; return ctx;
}; };
// #endregion // #endregion
// #region Actions // #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 // #endregion
}; };

View file

@ -81,7 +81,7 @@ export function GenericPopoverMixin(HandlebarsApp) {
* want it to when being created. * want it to when being created.
* *
* Most of this implementation is identical to the ApplicationV2 * 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. * are calculated.
*/ */
_updatePosition(position) { _updatePosition(position) {

View file

@ -118,6 +118,24 @@
{{ ammo }} {{ ammo }}
</div> </div>
</div> </div>
{{#each favouriteAmmo as | data |}}
{{#if data}}
<div
class="half-pill fav-ammo"
data-item-id="{{data.uuid}}"
>
<label for="{{@root.meta.idp}}-{{data.uuid}}-quantity">
{{data.name}}
</label>
<input
type="number"
value="{{data.quantity}}"
id="{{@root.meta.idp}}-{{data.uuid}}-quantity"
>
</div>
{{else}}
{{/if}}
{{/each}}
{{!-- * Currencies --}} {{!-- * Currencies --}}
<div class="currencies"> <div class="currencies">

View file

@ -8,6 +8,7 @@
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-rows: repeat(13, minmax(0, 1fr)); grid-template-rows: repeat(13, minmax(0, 1fr));
column-gap: var(--col-gap); column-gap: var(--col-gap);
row-gap: var(--row-gap);
background: var(--base-background); background: var(--base-background);
color: var(--base-text); color: var(--base-text);
@ -125,8 +126,13 @@
} }
} }
label, .label {
white-space: nowrap;
text-overflow: ellipsis;
}
.input {
input, .input {
margin: 2px; margin: 2px;
border-radius: 999px; border-radius: 999px;
text-align: center; text-align: center;

View file

@ -1,18 +1,37 @@
<div> <div>
{{#if ammos}} {{#if ammos}}
<ul> <ul>
{{#each ammos as | ammo |}} {{#each ammos as | data |}}
<div class="ammo" data-item-id="{{ammo.uuid}}"> <li class="ammo" data-item-id="{{data.ammo.uuid}}">
<span class="name">{{ ammo.name }}</span> <span class="name">{{ data.ammo.name }}</span>
<span class="value">{{ ammo.system.quantity }}</span> <span class="value">{{ data.ammo.system.quantity }}</span>
<button {{#if data.favourite}}
type="button" <button
{{ disabled @root.canPin }} type="button"
data-action="pinAmmo" class="icon"
> data-action="unfavourite"
Pin aria-label="Unpin ammo"
</button> >
</div> <rc-icon
name="icons/star"
var:size="1rem"
></rc-icon>
</button>
{{else}}
<button
type="button"
class="icon"
{{ disabled @root.atFavouriteLimit }}
data-action="favourite"
aria-label="Pin ammo"
>
<rc-icon
name="icons/star-empty"
var:size="1rem"
></rc-icon>
</button>
{{/if}}
</li>
{{/each}} {{/each}}
</ul> </ul>
{{else}} {{else}}

View file

@ -1,8 +1,9 @@
.ripcrypt--AmmoTracker.ripcrypt--AmmoTracker { .ripcrypt--AmmoTracker.ripcrypt--AmmoTracker {
color: var(--popover-text); color: var(--popover-text);
background: var(--popover-background); background: var(--popover-background);
padding: 4px 8px; padding: 4px;
--row-gap: 4px;
--button-text: var(--header-text); --button-text: var(--header-text);
--button-background: var(--header-background); --button-background: var(--header-background);
@ -11,11 +12,20 @@
} }
ul { ul {
&:nth-child(even) { display: flex;
color: var(--popover-alt-row-text); flex-direction: column;
background: var(--popover-alt-row-background); row-gap: var(--row-gap);
--button-text: unset;
--button-background: unset; > 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;
}
} }
} }

View file

@ -29,6 +29,7 @@
/* height: 270px; */ /* height: 270px; */
width: 680px; width: 680px;
--col-gap: 2px; --col-gap: 2px;
--row-gap: 4px;
} }
label, input, select { label, input, select {

View file

@ -1,6 +1,9 @@
.ripcrypt:where(.popover.frameless, .hud) button, .ripcrypt:where(.popover.frameless, .hud) button,
.ripcrypt > .window-content button { .ripcrypt > .window-content button {
all: revert; all: revert;
display: flex;
justify-content: center;
align-items: center;
outline: none; outline: none;
border: none; border: none;
padding: 2px 4px; padding: 2px 4px;