Add a helper application to manage attributes on characters
This commit is contained in:
parent
91ccd93814
commit
6b81d8d83b
10 changed files with 298 additions and 12 deletions
171
module/apps/AttributeManager.mjs
Normal file
171
module/apps/AttributeManager.mjs
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
import { __ID__, filePath } from "../consts.mjs";
|
||||
import { Logger } from "../utils/Logger.mjs";
|
||||
import { toID } from "../utils/toID.mjs";
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
const { deepClone, diffObject, randomID, setProperty } = foundry.utils;
|
||||
|
||||
export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
||||
// #region Options
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: `form`,
|
||||
classes: [
|
||||
__ID__,
|
||||
`AttributeManager`,
|
||||
],
|
||||
position: {
|
||||
width: 400,
|
||||
height: 350,
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
},
|
||||
form: {
|
||||
submitOnChange: false,
|
||||
closeOnSubmit: true,
|
||||
handler: this.#onSubmit,
|
||||
},
|
||||
actions: {
|
||||
addNew: this.#addNew,
|
||||
removeAttribute: this.#remove,
|
||||
},
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
attributes: {
|
||||
template: filePath(`templates/AttributeManager/attribute-list.hbs`),
|
||||
},
|
||||
controls: {
|
||||
template: filePath(`templates/AttributeManager/controls.hbs`),
|
||||
},
|
||||
};
|
||||
// #endregion Options
|
||||
|
||||
// #region Instance Data
|
||||
/** @type {string | null} */
|
||||
#doc = null;
|
||||
|
||||
#attributes;
|
||||
|
||||
constructor({ document , ...options } = {}) {
|
||||
super(options);
|
||||
this.#doc = document;
|
||||
this.#attributes = deepClone(document.system.attr);
|
||||
};
|
||||
|
||||
get title() {
|
||||
return `Attributes: ${this.#doc.name}`;
|
||||
};
|
||||
// #endregion Instance Data
|
||||
|
||||
// #region Lifecycle
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
|
||||
const elements = this.element
|
||||
.querySelectorAll(`[data-bind]`);
|
||||
for (const input of elements) {
|
||||
input.addEventListener(`change`, this.#bindListener.bind(this));
|
||||
};
|
||||
};
|
||||
// #endregion Lifecycle
|
||||
|
||||
// #region Data Prep
|
||||
async _preparePartContext(partId) {
|
||||
const ctx = {};
|
||||
|
||||
ctx.actor = this.#doc;
|
||||
|
||||
switch (partId) {
|
||||
case `attributes`: {
|
||||
await this._prepareAttributeContext(ctx);
|
||||
};
|
||||
};
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
||||
async _prepareAttributeContext(ctx) {
|
||||
const attrs = [];
|
||||
for (const [id, data] of Object.entries(this.#attributes)) {
|
||||
if (data == null) { continue };
|
||||
attrs.push({
|
||||
id,
|
||||
name: data.name,
|
||||
isRange: data.isRange,
|
||||
isNew: data.isNew ?? false,
|
||||
});
|
||||
};
|
||||
ctx.attrs = attrs;
|
||||
};
|
||||
// #endregion Data Prep
|
||||
|
||||
// #region Actions
|
||||
/**
|
||||
* @param {Event} event
|
||||
*/
|
||||
async #bindListener(event) {
|
||||
const target = event.target;
|
||||
const data = target.dataset;
|
||||
const binding = data.bind;
|
||||
|
||||
let value = target.value;
|
||||
switch (target.type) {
|
||||
case `checkbox`: {
|
||||
value = target.checked;
|
||||
};
|
||||
};
|
||||
|
||||
Logger.debug(`Updating ${binding} value to ${value}`);
|
||||
setProperty(this.#attributes, binding, value);
|
||||
await this.render();
|
||||
};
|
||||
|
||||
/** @this {AttributeManager} */
|
||||
static async #addNew() {
|
||||
const id = randomID();
|
||||
this.#attributes[id] = {
|
||||
name: ``,
|
||||
isRange: false,
|
||||
isNew: true,
|
||||
};
|
||||
await this.render({ parts: [ `attributes` ]});
|
||||
};
|
||||
|
||||
/** @this {AttributeManager} */
|
||||
static async #remove($e, element) {
|
||||
const attribute = element.closest(`[data-attribute]`)?.dataset.attribute;
|
||||
if (!attribute) { return };
|
||||
delete this.#attributes[attribute];
|
||||
this.#attributes[`-=${attribute}`] = null;
|
||||
await this.render({ parts: [ `attributes` ] });
|
||||
};
|
||||
|
||||
/** @this {AttributeManager} */
|
||||
static async #onSubmit() {
|
||||
const entries = Object.entries(this.#attributes)
|
||||
.map(([id, attr]) => {
|
||||
if (attr == null) {
|
||||
return [ id, attr ];
|
||||
};
|
||||
|
||||
if (attr.isNew) {
|
||||
delete attr.isNew;
|
||||
return [ toID(attr.name), attr ];
|
||||
};
|
||||
|
||||
return [ id, attr ];
|
||||
});
|
||||
const data = Object.fromEntries(entries);
|
||||
|
||||
const diff = diffObject(
|
||||
this.#doc.system.attr,
|
||||
data,
|
||||
{ inner: false, deletionKeys: true },
|
||||
);
|
||||
|
||||
await this.#doc.update({ "system.attr": diff });
|
||||
};
|
||||
// #endregion Actions
|
||||
};
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { __ID__, filePath } from "../consts.mjs";
|
||||
import { AttributeManager } from "./AttributeManager.mjs";
|
||||
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
const { ActorSheetV2 } = foundry.applications.sheets;
|
||||
|
|
@ -12,17 +13,29 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
`PlayerSheet`,
|
||||
],
|
||||
position: {
|
||||
width: 400,
|
||||
height: 500,
|
||||
width: 575,
|
||||
height: 740,
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
controls: [
|
||||
{
|
||||
icon: `fa-solid fa-at`,
|
||||
label: `Manage Attributes`,
|
||||
action: `manageAttributes`,
|
||||
visible: () => {
|
||||
return game.user.isGM;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
closeOnSubmit: false,
|
||||
},
|
||||
actions: {},
|
||||
actions: {
|
||||
manageAttributes: this.#manageAttributes,
|
||||
},
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
|
|
@ -82,5 +95,10 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
// #endregion Data Prep
|
||||
|
||||
// #region Actions
|
||||
/** @this {PlayerSheet} */
|
||||
static async #manageAttributes() {
|
||||
const app = new AttributeManager({ document: this.actor });
|
||||
await app.render({ force: true });
|
||||
};
|
||||
// #endregion Actions
|
||||
};
|
||||
|
|
|
|||
13
module/utils/toID.mjs
Normal file
13
module/utils/toID.mjs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* A helper method that converts an arbitrary string into a format that can be
|
||||
* used as an object key easily.
|
||||
*
|
||||
* @param {string} text The text to convert
|
||||
* @returns The converted ID
|
||||
*/
|
||||
export function toID(text) {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/\s/g, `_`)
|
||||
.replace(/\W/g, ``);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue