File Structure Rework (#262)

* Restructured all the files

* Moved build/daggerheart.js to ./daggerheart.js. Changed rollup to use the css file instead of the less

* Restored build/ folder

* Mvoed config out form under application

* Moved roll.mjs to module/dice and renamed to dhRolls.mjs

* Update module/canvas/placeables/_module.mjs

Co-authored-by: joaquinpereyra98 <24190917+joaquinpereyra98@users.noreply.github.com>

* Le massive export update

* Removed unncessary import

---------

Co-authored-by: joaquinpereyra98 <24190917+joaquinpereyra98@users.noreply.github.com>
This commit is contained in:
WBHarry 2025-07-05 00:26:33 +02:00 committed by GitHub
parent 099a4576da
commit 9d76405221
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
203 changed files with 1556 additions and 2565 deletions

View file

@ -0,0 +1,5 @@
export { default as ActionConfig } from './action-config.mjs';
export { default as AdversarySettings } from './adversary-settings.mjs';
export { default as CompanionSettings } from './companion-settings.mjs';
export { default as EnvironmentSettings } from './environment-settings.mjs';
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';

View file

@ -0,0 +1,199 @@
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
const { ApplicationV2 } = foundry.applications.api;
export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
constructor(action) {
super({});
this.action = action;
this.openSection = null;
}
static DEFAULT_OPTIONS = {
tag: 'form',
id: 'daggerheart-action',
classes: ['daggerheart', 'views', 'action'],
position: { width: 600, height: 'auto' },
actions: {
toggleSection: this.toggleSection,
addEffect: this.addEffect,
removeEffect: this.removeEffect,
addElement: this.addElement,
removeElement: this.removeElement,
editEffect: this.editEffect,
addDamage: this.addDamage,
removeDamage: this.removeDamage
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
};
static PARTS = {
form: {
id: 'action',
template: 'systems/daggerheart/templates/config/action.hbs'
}
};
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects'];
_getTabs() {
const tabs = {
base: { active: true, cssClass: '', group: 'primary', id: 'base', icon: null, label: 'Base' },
config: { active: false, cssClass: '', group: 'primary', id: 'config', icon: null, label: 'Configuration' },
effect: { active: false, cssClass: '', group: 'primary', id: 'effect', icon: null, label: 'Effect' }
};
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
v.cssClass = v.active ? 'active' : '';
}
return tabs;
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options, 'action');
context.source = this.action.toObject(false);
context.openSection = this.openSection;
context.tabs = this._getTabs();
context.config = CONFIG.DH;
if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id));
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
context.hasBaseDamage = !!this.action.parent.damage;
context.getRealIndex = this.getRealIndex.bind(this);
context.getEffectDetails = this.getEffectDetails.bind(this);
context.disableOption = this.disableOption.bind(this);
context.isNPC = this.action.actor && this.action.actor.type !== 'character';
context.hasRoll = this.action.hasRoll;
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
context.tierOptions = [
{ key: 1, label: game.i18n.localize('DAGGERHEART.Tiers.tier1') },
...Object.values(settingsTiers).map(x => ({ key: x.tier, label: x.name }))
];
return context;
}
static toggleSection(_, button) {
this.openSection = button.dataset.section === this.openSection ? null : button.dataset.section;
this.render(true);
}
disableOption(index, options, choices) {
const filtered = foundry.utils.deepClone(options);
Object.keys(filtered).forEach(o => {
if (choices.find((c, idx) => c.type === o && index !== idx)) delete filtered[o];
});
return filtered;
}
getRealIndex(index) {
const data = this.action.toObject(false);
return data.damage.parts.find(d => d.base) ? index - 1 : index;
}
getEffectDetails(id) {
return this.action.item.effects.get(id);
}
_prepareSubmitData(event, formData) {
const submitData = foundry.utils.expandObject(formData.object);
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
const data = foundry.utils.getProperty(submitData, keyPath);
if (data) foundry.utils.setProperty(submitData, keyPath, Object.values(data));
}
return submitData;
}
static async updateForm(event, _, formData) {
const submitData = this._prepareSubmitData(event, formData),
data = foundry.utils.mergeObject(this.action.toObject(), submitData),
container = foundry.utils.getProperty(this.action.parent, this.action.systemPath);
let newActions;
if (Array.isArray(container)) {
newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject()); // Find better way
if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data);
} else newActions = data;
const updates = await this.action.parent.parent.update({ [`system.${this.action.systemPath}`]: newActions });
if (!updates) return;
this.action = Array.isArray(container)
? foundry.utils.getProperty(updates.system, this.action.systemPath)[this.action.index]
: foundry.utils.getProperty(updates.system, this.action.systemPath);
this.render();
}
static addElement(event) {
const data = this.action.toObject(),
key = event.target.closest('.action-category-data').dataset.key;
if (!this.action[key]) return;
data[key].push({});
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static removeElement(event) {
event.stopPropagation();
const data = this.action.toObject(),
key = event.target.closest('.action-category-data').dataset.key,
index = event.target.dataset.index;
data[key].splice(index, 1);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static addDamage(event) {
if (!this.action.damage.parts) return;
const data = this.action.toObject();
data.damage.parts.push({});
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static removeDamage(event) {
if (!this.action.damage.parts) return;
const data = this.action.toObject(),
index = event.target.dataset.index;
data.damage.parts.splice(index, 1);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static async addEffect(event) {
if (!this.action.effects) return;
const effectData = this._addEffectData.bind(this)(),
[created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], { render: false }),
data = this.action.toObject();
data.effects.push({ _id: created._id });
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
this.action.item.effects.get(created._id).sheet.render(true);
}
/**
* The data for a newly created applied effect.
* @returns {object}
* @protected
*/
_addEffectData() {
return {
name: this.action.item.name,
img: this.action.item.img,
origin: this.action.item.uuid,
transfer: false
};
}
static removeEffect(event) {
if (!this.action.effects) return;
const index = event.target.dataset.index,
effectId = this.action.effects[index]._id;
this.constructor.removeElement.bind(this)(event);
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
}
static editEffect(event) {
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
this.action.item.effects.get(id).sheet.render(true);
}
}

View file

@ -0,0 +1,62 @@
export default class DhActiveEffectConfig extends foundry.applications.sheets.ActiveEffectConfig {
static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'sheet', 'dh-style']
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' },
tabs: { template: 'templates/generic/tab-navigation.hbs' },
details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] },
duration: { template: 'systems/daggerheart/templates/sheets/activeEffect/duration.hbs' },
changes: {
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
scrollable: ['ol[data-changes]']
},
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
};
static TABS = {
sheet: {
tabs: [
{ id: 'details', icon: 'fa-solid fa-book' },
{ id: 'duration', icon: 'fa-solid fa-clock' },
{ id: 'changes', icon: 'fa-solid fa-gears' }
],
initial: 'details',
labelPrefix: 'EFFECT.TABS'
}
};
async _preparePartContext(partId, context) {
const partContext = await super._preparePartContext(partId, context);
switch (partId) {
case 'changes':
const fieldPaths = [];
const validFieldPath = fieldPath => this.validFieldPath(fieldPath, this.#unapplicablePaths);
context.document.parent.system.schema.apply(function () {
if (!(this instanceof foundry.data.fields.SchemaField)) {
if (validFieldPath(this.fieldPath)) {
fieldPaths.push(this.fieldPath);
}
}
});
context.fieldPaths = fieldPaths;
break;
}
return partContext;
}
#unapplicablePaths = ['story', 'pronouns', 'description'];
validFieldPath(fieldPath, unapplicablePaths) {
const splitPath = fieldPath.split('.');
if (splitPath.length > 1 && unapplicablePaths.includes(splitPath[1])) return false;
/* The current value of a resource should not be modified */
if (new RegExp(/resources.*\.value/).exec(fieldPath)) return false;
return true;
}
}

View file

@ -0,0 +1,180 @@
import DHActionConfig from './action-config.mjs';
import DHBaseItemSheet from '../sheets/api/base-item.mjs';
import { actionsTypes } from '../../data/action/_module.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DHAdversarySettings extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(actor) {
super({});
this.actor = actor;
}
get title() {
return `${game.i18n.localize('DAGGERHEART.Sheets.TABS.settings')}`;
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dh-style', 'dialog', 'adversary-settings'],
window: {
icon: 'fa-solid fa-wrench',
resizable: false
},
position: { width: 455, height: 'auto' },
actions: {
addExperience: this.#addExperience,
removeExperience: this.#removeExperience,
addAction: this.#addAction,
editAction: this.#editAction,
removeAction: this.#removeAction
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
};
static PARTS = {
header: {
id: 'header',
template: 'systems/daggerheart/templates/sheets-settings/adversary-settings/header.hbs'
},
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
details: {
id: 'details',
template: 'systems/daggerheart/templates/sheets-settings/adversary-settings/details.hbs'
},
attack: {
id: 'attack',
template: 'systems/daggerheart/templates/sheets-settings/adversary-settings/attack.hbs'
},
experiences: {
id: 'experiences',
template: 'systems/daggerheart/templates/sheets-settings/adversary-settings/experiences.hbs'
},
actions: {
id: 'actions',
template: 'systems/daggerheart/templates/sheets-settings/adversary-settings/actions.hbs'
}
};
static TABS = {
details: {
active: true,
cssClass: '',
group: 'primary',
id: 'details',
icon: null,
label: 'DAGGERHEART.General.tabs.details'
},
attack: {
active: false,
cssClass: '',
group: 'primary',
id: 'attack',
icon: null,
label: 'DAGGERHEART.General.tabs.attack'
},
experiences: {
active: false,
cssClass: '',
group: 'primary',
id: 'experiences',
icon: null,
label: 'DAGGERHEART.General.tabs.experiences'
},
actions: {
active: false,
cssClass: '',
group: 'primary',
id: 'actions',
icon: null,
label: 'DAGGERHEART.General.tabs.actions'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.actor;
context.tabs = this._getTabs(this.constructor.TABS);
context.systemFields = this.actor.system.schema.fields;
context.systemFields.attack.fields = this.actor.system.attack.schema.fields;
context.isNPC = true;
return context;
}
_getTabs(tabs) {
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
v.cssClass = v.active ? 'active' : '';
}
return tabs;
}
static async #addExperience() {
const newExperience = {
name: 'Experience',
modifier: 0
};
await this.actor.update({ [`system.experiences.${foundry.utils.randomID()}`]: newExperience });
this.render();
}
static async #removeExperience(_, target) {
await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null });
this.render();
}
static async #addAction(_event, _button) {
const actionType = await DHBaseItemSheet.selectActionType();
if (!actionType) return;
try {
const cls = actionsTypes[actionType] ?? actionsTypes.attack,
action = new cls(
{
_id: foundry.utils.randomID(),
type: actionType,
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType].name),
...cls.getSourceConfig(this.actor)
},
{
parent: this.actor
}
);
await this.actor.update({ 'system.actions': [...this.actor.system.actions, action] });
await new DHActionConfig(this.actor.system.actions[this.actor.system.actions.length - 1]).render({
force: true
});
this.render();
} catch (error) {
console.log(error);
}
}
static async #editAction(event, target) {
event.stopPropagation();
const actionIndex = target.dataset.index;
await new DHActionConfig(this.actor.system.actions[actionIndex]).render({
force: true
});
}
static async #removeAction(event, target) {
event.stopPropagation();
const actionIndex = target.dataset.index;
await this.actor.update({
'system.actions': this.actor.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex))
});
this.render();
}
static async updateForm(event, _, formData) {
await this.actor.update(formData.object);
this.render();
}
}

View file

@ -0,0 +1,160 @@
import { GMUpdateEvent, socketEvent } from '../../systemRegistration/socket.mjs';
import DhCompanionlevelUp from '../levelup/companionLevelup.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DHCompanionSettings extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(actor) {
super({});
this.actor = actor;
}
get title() {
return `${game.i18n.localize('DAGGERHEART.Sheets.TABS.settings')}`;
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dh-style', 'dialog', 'companion-settings'],
window: {
icon: 'fa-solid fa-wrench',
resizable: false
},
position: { width: 455, height: 'auto' },
actions: {
levelUp: this.levelUp
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
};
static PARTS = {
header: {
id: 'header',
template: 'systems/daggerheart/templates/sheets-settings/companion-settings/header.hbs'
},
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
details: {
id: 'details',
template: 'systems/daggerheart/templates/sheets-settings/companion-settings/details.hbs'
},
experiences: {
id: 'experiences',
template: 'systems/daggerheart/templates/sheets-settings/companion-settings/experiences.hbs'
},
attack: {
id: 'attack',
template: 'systems/daggerheart/templates/sheets-settings/companion-settings/attack.hbs'
}
};
static TABS = {
details: {
active: true,
cssClass: '',
group: 'primary',
id: 'details',
icon: null,
label: 'DAGGERHEART.General.tabs.details'
},
experiences: {
active: false,
cssClass: '',
group: 'primary',
id: 'experiences',
icon: null,
label: 'DAGGERHEART.General.tabs.experiences'
},
attack: {
active: false,
cssClass: '',
group: 'primary',
id: 'attack',
icon: null,
label: 'DAGGERHEART.General.tabs.attack'
}
};
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelector('.partner-value')?.addEventListener('change', this.onPartnerChange.bind(this));
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.actor;
context.tabs = this._getTabs(this.constructor.TABS);
context.systemFields = this.actor.system.schema.fields;
context.systemFields.attack.fields = this.actor.system.attack.schema.fields;
context.isNPC = true;
context.playerCharacters = game.actors
.filter(
x =>
x.type === 'character' &&
(x.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) ||
this.document.system.partner?.uuid === x.uuid)
)
.map(x => ({ key: x.uuid, name: x.name }));
return context;
}
_getTabs(tabs) {
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
v.cssClass = v.active ? 'active' : '';
}
return tabs;
}
async onPartnerChange(event) {
const partnerDocument = event.target.value
? await foundry.utils.fromUuid(event.target.value)
: this.actor.system.partner;
const partnerUpdate = { 'system.companion': event.target.value ? this.actor.uuid : null };
if (!partnerDocument.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) {
await game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.GMUpdate,
data: {
action: GMUpdateEvent.UpdateDocument,
uuid: partnerDocument.uuid,
update: update
}
});
} else {
await partnerDocument.update(partnerUpdate);
}
await this.actor.update({ 'system.partner': event.target.value });
if (!event.target.value) {
await this.actor.updateLevel(1);
}
this.render();
}
async viewActor(_, button) {
const target = button.closest('[data-item-uuid]');
const actor = await foundry.utils.fromUuid(target.dataset.itemUuid);
if (!actor) return;
actor.sheet.render(true);
}
static async levelUp() {
new DhCompanionlevelUp(this.actor).render(true);
}
static async updateForm(event, _, formData) {
await this.actor.update(formData.object);
this.render();
}
}

View file

@ -0,0 +1,220 @@
import DHActionConfig from './action-config.mjs';
import DHBaseItemSheet from '../sheets/api/base-item.mjs';
import { actionsTypes } from '../../data/action/_module.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DHEnvironmentSettings extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(actor) {
super({});
this.actor = actor;
this._dragDrop = this._createDragDropHandlers();
}
get title() {
return `${game.i18n.localize('DAGGERHEART.Sheets.TABS.settings')}`;
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dh-style', 'dialog', 'environment-settings'],
window: {
icon: 'fa-solid fa-wrench',
resizable: false
},
position: { width: 455, height: 'auto' },
actions: {
addAction: this.#addAction,
editAction: this.#editAction,
removeAction: this.#removeAction,
addCategory: this.#addCategory,
deleteProperty: this.#deleteProperty,
viewAdversary: this.#viewAdversary,
deleteAdversary: this.#deleteAdversary
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
dragDrop: [{ dragSelector: null, dropSelector: '.category-container' }]
};
static PARTS = {
header: {
id: 'header',
template: 'systems/daggerheart/templates/sheets-settings/environment-settings/header.hbs'
},
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
details: {
id: 'details',
template: 'systems/daggerheart/templates/sheets-settings/environment-settings/details.hbs'
},
actions: {
id: 'actions',
template: 'systems/daggerheart/templates/sheets-settings/environment-settings/actions.hbs'
},
adversaries: {
id: 'adversaries',
template: 'systems/daggerheart/templates/sheets-settings/environment-settings/adversaries.hbs'
}
};
static TABS = {
details: {
active: true,
cssClass: '',
group: 'primary',
id: 'details',
icon: null,
label: 'DAGGERHEART.General.tabs.details'
},
actions: {
active: false,
cssClass: '',
group: 'primary',
id: 'actions',
icon: null,
label: 'DAGGERHEART.General.tabs.actions'
},
adversaries: {
active: false,
cssClass: '',
group: 'primary',
id: 'adversaries',
icon: null,
label: 'DAGGERHEART.General.tabs.adversaries'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.actor;
context.tabs = this._getTabs(this.constructor.TABS);
context.systemFields = this.actor.system.schema.fields;
context.isNPC = true;
return context;
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
this._dragDrop.forEach(d => d.bind(htmlElement));
}
_createDragDropHandlers() {
return this.options.dragDrop.map(d => {
d.callbacks = {
drop: this._onDrop.bind(this)
};
return new foundry.applications.ux.DragDrop.implementation(d);
});
}
_getTabs(tabs) {
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
v.cssClass = v.active ? 'active' : '';
}
return tabs;
}
static async #addAction(_event, _button) {
const actionType = await DHBaseItemSheet.selectActionType();
if (!actionType) return;
try {
const cls = actionsTypes[actionType] ?? actionsTypes.attack,
action = new cls(
{
_id: foundry.utils.randomID(),
type: actionType,
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType].name),
...cls.getSourceConfig(this.actor)
},
{
parent: this.actor
}
);
await this.actor.update({ 'system.actions': [...this.actor.system.actions, action] });
await new DHActionConfig(this.actor.system.actions[this.actor.system.actions.length - 1]).render({
force: true
});
this.render();
} catch (error) {
console.log(error);
}
}
static async #editAction(event, target) {
event.stopPropagation();
const actionIndex = target.dataset.index;
await new DHActionConfig(this.actor.system.actions[actionIndex]).render({
force: true
});
}
static async #removeAction(event, target) {
event.stopPropagation();
const actionIndex = target.dataset.index;
await this.actor.update({
'system.actions': this.actor.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex))
});
this.render();
}
static async #addCategory() {
await this.actor.update({
[`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize(
'DAGGERHEART.Sheets.Environment.newAdversary'
)
});
this.render();
}
static async #deleteProperty(_, target) {
await this.actor.update({ [`${target.dataset.path}.-=${target.id}`]: null });
this.render();
}
static async #viewAdversary(_, button) {
const adversary = await foundry.utils.fromUuid(button.dataset.adversary);
if (!adversary) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.adversaryMissing'));
return;
}
adversary.sheet.render(true);
}
static async #deleteAdversary(event, target) {
const adversaryKey = target.dataset.adversary;
const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`;
const newAdversaries = foundry.utils
.getProperty(this.actor, path)
.filter(x => x && (x?.uuid ?? x) !== adversaryKey);
await this.actor.update({ [path]: newAdversaries });
this.render();
}
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await fromUuid(data.uuid);
if (item.type === 'adversary') {
const target = event.target.closest('.category-container');
const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`;
const current = foundry.utils.getProperty(this.actor, path).map(x => x.uuid);
await this.actor.update({
[path]: [...current, item.uuid]
});
this.render();
}
}
static async updateForm(event, _, formData) {
await this.actor.update(formData.object);
this.render();
}
}