From 98fee6096dfc9e67fe57c3222f62f699b902e090 Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Sun, 6 Jul 2025 12:49:08 -0300 Subject: [PATCH] FEAT: create isNPC geeter and add the prop on metada on actors FEAT: create common method for documents sheets FEAT: create BaseActorSheet and implementation --- .../applications/sheets/actors/adversary.mjs | 60 +-- .../applications/sheets/actors/character.mjs | 350 ++++++------------ .../applications/sheets/actors/companion.mjs | 48 +-- .../sheets/actors/environment.mjs | 53 +-- module/applications/sheets/api/_modules.mjs | 1 + .../sheets/api/application-mixin.mjs | 126 +++++-- module/applications/sheets/api/base-actor.mjs | 30 ++ module/data/actor/base.mjs | 4 +- module/data/actor/character.mjs | 3 +- module/documents/actor.mjs | 38 +- templates/sheets/global/tabs/tab-effects.hbs | 7 +- 11 files changed, 302 insertions(+), 418 deletions(-) create mode 100644 module/applications/sheets/api/base-actor.mjs diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index 17f56c02..3aeaffc6 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -1,12 +1,10 @@ +import DHBaseActorSheet from '../api/base-actor.mjs'; import DHActionConfig from '../../sheets-configs/action-config.mjs'; -import DaggerheartSheet from '../daggerheart-sheet.mjs'; import DHAdversarySettings from '../../sheets-configs/adversary-settings.mjs'; -const { ActorSheetV2 } = foundry.applications.sheets; -export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { +export default class AdversarySheet extends DHBaseActorSheet { static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'adversary'], + classes: ['adversary'], position: { width: 660, height: 766 }, actions: { reactionRoll: this.reactionRoll, @@ -19,11 +17,6 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { toggleStress: this.toggleStress, openSettings: this.openSettings }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false - } }; static PARTS = { @@ -34,40 +27,20 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { effects: { template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs' } }; + + /** @inheritdoc */ static TABS = { - features: { - active: true, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: 'DAGGERHEART.General.tabs.features' - }, - notes: { - active: false, - cssClass: '', - group: 'primary', - id: 'notes', - icon: null, - label: 'DAGGERHEART.Sheets.Adversary.Tabs.notes' - }, - effects: { - active: false, - cssClass: '', - group: 'primary', - id: 'effects', - icon: null, - label: 'DAGGERHEART.Sheets.Adversary.Tabs.effects' + primary: { + tabs: [{ id: 'features' }, { id: 'notes'}, { id: 'effects'}], + initial: 'features', + labelPrefix: 'DAGGERHEART.Sheets.Adversary.Tabs' } }; - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); + /**@inheritdoc */ + async _prepareContext(options) { + const context = await super._prepareContext(options); context.systemFields.attack.fields = this.document.system.attack.schema.fields; - context.getEffectDetails = this.getEffectDetails.bind(this); - context.isNPC = true; return context; } @@ -77,11 +50,6 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { return item; } - static async updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } - static async reactionRoll(event) { const config = { event: event, @@ -100,10 +68,6 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { this.actor.diceRoll(config); } - getEffectDetails(id) { - return {}; - } - static async openSettings() { await new DHAdversarySettings(this.document).render(true); } diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 9122122c..bc9ab557 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -1,25 +1,19 @@ -import { capitalize } from '../../../helpers/utils.mjs'; +import DHBaseActorSheet from '../api/base-actor.mjs'; import DhpDeathMove from '../../dialogs/deathMove.mjs'; import DhpDowntime from '../../dialogs/downtime.mjs'; -import DaggerheartSheet from '.././daggerheart-sheet.mjs'; import { abilities } from '../../../config/actorConfig.mjs'; import DhCharacterlevelUp from '../../levelup/characterLevelup.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; import DHActionConfig from '../../sheets-configs/action-config.mjs'; -const { ActorSheetV2 } = foundry.applications.sheets; const { TextEditor } = foundry.applications.ux; -export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { - constructor(options = {}) { - super(options); - } - +export default class CharacterSheet extends DHBaseActorSheet { static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'daggerheart', 'character'], + classes: ['character'], position: { width: 850, height: 800 }, actions: { + triggerContextMenu: CharacterSheet.#triggerContextMenu, attributeRoll: this.rollAttribute, toggleMarks: this.toggleMarks, toggleHP: this.toggleHP, @@ -37,7 +31,6 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { useFeature: this.useFeature, takeShortRest: this.takeShortRest, takeLongRest: this.takeLongRest, - deleteItem: this.deleteItem, addScar: this.addScar, deleteScar: this.deleteScar, makeDeathMove: this.makeDeathMove, @@ -50,16 +43,22 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { toggleVault: this.toggleVault, levelManagement: this.levelManagement, editImage: this._onEditImage, - triggerContextMenu: this.triggerContextMenu }, window: { resizable: true }, - form: { - submitOnChange: true, - closeOnSubmit: false - }, - dragDrop: [] + dragDrop: [], + contextMenus: [ + { + handler: CharacterSheet._getContextMenuOptions, + selector: '[data-item-id]', + options: { + parentClassHooks: false, + fixed: true + } + } + ] + }; static PARTS = { @@ -93,217 +92,42 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } }; + + /** @inheritdoc */ static TABS = { - features: { - active: true, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: 'DAGGERHEART.Sheets.PC.Tabs.Features' - }, - loadout: { - active: false, - cssClass: '', - group: 'primary', - id: 'loadout', - icon: null, - label: 'DAGGERHEART.Sheets.PC.Tabs.Loadout' - }, - inventory: { - active: false, - cssClass: '', - group: 'primary', - id: 'inventory', - icon: null, - label: 'DAGGERHEART.Sheets.PC.Tabs.Inventory' - }, - biography: { - active: false, - cssClass: '', - group: 'primary', - id: 'biography', - icon: null, - label: 'DAGGERHEART.Sheets.PC.Tabs.biography' - }, - effects: { - active: false, - cssClass: '', - group: 'primary', - id: 'effects', - icon: null, - label: 'DAGGERHEART.Sheets.PC.Tabs.effects' + primary: { + tabs: [{ + id: 'features', + label: "DAGGERHEART.Sheets.PC.Tabs.Features" + }, { + id: 'loadout', + label: "DAGGERHEART.Sheets.PC.Tabs.Loadout" + }, { + id: 'inventory', + label: "DAGGERHEART.Sheets.PC.Tabs.Inventory" + }, { + id: 'biography', + label: "DAGGERHEART.Sheets.PC.Tabs.biography" + }, { + id: 'effects', + label: "DAGGERHEART.Sheets.PC.Tabs.effects" + }], + initial: 'features', } }; - _getTabs() { - const setActive = 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' : ''; - } - }; - - const primaryTabs = { - features: { - active: true, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Features') - }, - loadout: { - active: false, - cssClass: '', - group: 'primary', - id: 'loadout', - icon: null, - label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Loadout') - }, - inventory: { - active: false, - cssClass: '', - group: 'primary', - id: 'inventory', - icon: null, - label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Inventory') - }, - story: { - active: false, - cssClass: '', - group: 'primary', - id: 'story', - icon: null, - label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Story') - } - }; - const secondaryTabs = { - foundation: { - active: true, - cssClass: '', - group: 'secondary', - id: 'foundation', - icon: null, - label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Foundation') - }, - loadout: { - active: false, - cssClass: '', - group: 'secondary', - id: 'loadout', - icon: null, - label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Loadout') - }, - vault: { - active: false, - cssClass: '', - group: 'secondary', - id: 'vault', - icon: null, - label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Vault') - } - }; - - setActive(primaryTabs); - setActive(secondaryTabs); - - return { primary: primaryTabs, secondary: secondaryTabs }; - } - - /**@inheritdoc */ - async _onFirstRender(context, options) { - await super._onFirstRender(context, options); - - this._createContextMenues(); - } - /** @inheritDoc */ async _onRender(context, options) { await super._onRender(context, options); + this.element.querySelector('.level-value')?.addEventListener('change', this.onLevelChange.bind(this)); + this._createFilterMenus(); this._createSearchFilter(); } /* -------------------------------------------- */ - _createContextMenues() { - const allOptions = { - useItem: { - name: 'DAGGERHEART.Sheets.PC.ContextMenu.UseItem', - icon: '', - condition: el => { - const item = this.getItem(el); - return !['class', 'subclass'].includes(item.type); - }, - callback: (button, event) => this.constructor.useItem.bind(this)(event, button) - }, - equip: { - name: 'DAGGERHEART.Sheets.PC.ContextMenu.Equip', - icon: '', - condition: el => { - const item = this.getItem(el); - return ['weapon', 'armor'].includes(item.type) && !item.system.equipped; - }, - callback: this.constructor.toggleEquipItem.bind(this) - }, - unequip: { - name: 'DAGGERHEART.Sheets.PC.ContextMenu.Unequip', - icon: '', - condition: el => { - const item = this.getItem(el); - return ['weapon', 'armor'].includes(item.type) && item.system.equipped; - }, - callback: this.constructor.toggleEquipItem.bind(this) - }, - sendToLoadout: { - name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToLoadout', - icon: '', - condition: el => { - const item = this.getItem(el); - return ['domainCard'].includes(item.type) && item.system.inVault; - }, - callback: this.constructor.toggleVault.bind(this) - }, - sendToVault: { - name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToVault', - icon: '', - condition: el => { - const item = this.getItem(el); - return ['domainCard'].includes(item.type) && !item.system.inVault; - }, - callback: this.constructor.toggleVault.bind(this) - }, - sendToChat: { - name: 'DAGGERHEART.Sheets.PC.ContextMenu.SendToChat', - icon: '', - callback: this.constructor.toChat.bind(this) - }, - edit: { - name: 'DAGGERHEART.Sheets.PC.ContextMenu.Edit', - icon: '', - callback: this.constructor.viewObject.bind(this) - }, - delete: { - name: 'DAGGERHEART.Sheets.PC.ContextMenu.Delete', - icon: '', - callback: this.constructor.deleteItem.bind(this) - } - }; - - this._createContextMenu(() => Object.values(allOptions), `[data-item-id]`, { - parentClassHooks: false, - fixed: true - }); - } - - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - htmlElement.querySelector('.level-value')?.addEventListener('change', this.onLevelChange.bind(this)); - } - getItem(element) { const listElement = (element.target ?? element).closest('[data-item-id]'); const itemId = listElement.dataset.itemId; @@ -316,10 +140,6 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } } - static triggerContextMenu(event, button) { - return CONFIG.ux.ContextMenu.triggerContextMenu(event); - } - static _onEditImage() { const fp = new foundry.applications.apps.FilePicker.implementation({ current: this.document.img, @@ -334,9 +154,6 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); - context.config = CONFIG.DH; context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => { acc[key] = { @@ -370,6 +187,78 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { return context; } + /* -------------------------------------------- */ + /* Context Menu */ + /* -------------------------------------------- */ + + /** + * Get the set of ContextMenu options. + * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} The Array of context options passed to the ContextMenu instance + * @protected + */ + static _getContextMenuOptions() { + + /** + * Get the item from the element. + * @param {HTMLElement} el + * @returns {foundry.documents.Item?} + */ + const getItem = (el) => this.actor.items.get(el.closest('[data-item-id]')?.dataset.itemId); + + return [{ + name: 'DAGGERHEART.Sheets.PC.ContextMenu.UseItem', + icon: '', + condition: el => { + const item = getItem(el); + return !['class', 'subclass'].includes(item.type); + }, + callback: (button, event) => CharacterSheet.useItem.call(this, event, button) + }, { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.Equip', + icon: '', + condition: el => { + const item = getItem(el); + return ['weapon', 'armor'].includes(item.type) && !item.system.equipped; + }, + callback: CharacterSheet.toggleEquipItem.bind(this) + }, { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.Unequip', + icon: '', + condition: el => { + const item = getItem(el); + return ['weapon', 'armor'].includes(item.type) && item.system.equipped; + }, + callback: CharacterSheet.toggleEquipItem.bind(this) + }, { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToLoadout', + icon: '', + condition: (el) => { + const item = getItem(el); + return ['domainCard'].includes(item.type) && item.system.inVault; + }, + callback: CharacterSheet.toggleVault.bind(this) + }, { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToVault', + icon: '', + condition: el => { + const item = getItem(el); + return ['domainCard'].includes(item.type) && !item.system.inVault; + }, + callback: CharacterSheet.toggleVault.bind(this) + }, { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.SendToChat', + icon: '', + callback: CharacterSheet.toChat.bind(this) + }, { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.Edit', + icon: '', + callback: CharacterSheet.viewObject.bind(this) + }, { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.Delete', + icon: '', + callback: (el) => getItem(el).delete() + }]; + } /* -------------------------------------------- */ /* Filter Tracking */ /* -------------------------------------------- */ @@ -804,13 +693,6 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { this.render(); } - static async deleteItem(event) { - const item = this.getItem(event); - if (!item) return; - - await item.delete(); - } - static async setItemQuantity(button, value) { const item = this.getItem(button); if (!item) return; @@ -876,7 +758,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { : this.document.system.class.subclass; const ability = item.system[`${button.dataset.key}Feature`]; const title = `${item.name} - ${game.i18n.localize( - `DAGGERHEART.Sheets.PC.DomainCard.${capitalize(button.dataset.key)}Title` + `DAGGERHEART.Sheets.PC.DomainCard.${button.dataset.key.capitalize()}Title` )}`; const cls = getDocumentClass('ChatMessage'); @@ -982,4 +864,14 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { itemData = itemData instanceof Array ? itemData : [itemData]; return this.document.createEmbeddedDocuments('Item', itemData); } + + /** + * Trigger the context menu. + * @param {PointerEvent} event - + * @param {HTMLElement} _ - + * @returns + */ + static #triggerContextMenu(event, _) { + return CONFIG.ux.ContextMenu.triggerContextMenu(event); + } } diff --git a/module/applications/sheets/actors/companion.mjs b/module/applications/sheets/actors/companion.mjs index bf8f2ec6..0d6f4e19 100644 --- a/module/applications/sheets/actors/companion.mjs +++ b/module/applications/sheets/actors/companion.mjs @@ -1,11 +1,9 @@ -import DaggerheartSheet from '../daggerheart-sheet.mjs'; +import DHBaseActorSheet from '../api/base-actor.mjs'; import DHCompanionSettings from '../../sheets-configs/companion-settings.mjs'; -const { ActorSheetV2 } = foundry.applications.sheets; -export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) { +export default class DhCompanionSheet extends DHBaseActorSheet { static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'companion'], + classes: ['actor', 'companion'], position: { width: 300 }, actions: { viewActor: this.viewActor, @@ -13,11 +11,6 @@ export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) { useItem: this.useItem, toChat: this.toChat }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false - } }; static PARTS = { @@ -26,38 +19,17 @@ export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) { effects: { template: 'systems/daggerheart/templates/sheets/actors/companion/effects.hbs' } }; + /* -------------------------------------------- */ + + /** @inheritdoc */ static TABS = { - details: { - active: true, - cssClass: '', - group: 'primary', - id: 'details', - icon: null, - label: 'DAGGERHEART.General.tabs.details' - }, - effects: { - active: false, - cssClass: '', - group: 'primary', - id: 'effects', - icon: null, - label: 'DAGGERHEART.Sheets.PC.Tabs.effects' + primary: { + tabs: [{ id: 'details' }, { id: 'effects', label: 'DAGGERHEART.Sheets.TABS.effects' }], + initial: 'details', + labelPrefix: 'DAGGERHEART.General.tabs' } }; - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); - - return context; - } - - static async updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } - static async viewActor(_, button) { const target = button.closest('[data-item-uuid]'); const actor = await foundry.utils.fromUuid(target.dataset.itemUuid); diff --git a/module/applications/sheets/actors/environment.mjs b/module/applications/sheets/actors/environment.mjs index 1182a764..d9d66a33 100644 --- a/module/applications/sheets/actors/environment.mjs +++ b/module/applications/sheets/actors/environment.mjs @@ -1,11 +1,9 @@ -import DaggerheartSheet from '../daggerheart-sheet.mjs'; +import DHBaseActorSheet from '../api/base-actor.mjs'; import DHEnvironmentSettings from '../../sheets-configs/environment-settings.mjs'; -const { ActorSheetV2 } = foundry.applications.sheets; -export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) { +export default class DhpEnvironment extends DHBaseActorSheet { static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'environment'], + classes: ['environment'], position: { width: 500 }, @@ -16,11 +14,6 @@ export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) { useItem: this.useItem, toChat: this.toChat }, - form: { - handler: this._updateForm, - submitOnChange: true, - closeOnSubmit: false - }, dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }] }; @@ -33,38 +26,17 @@ export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) { notes: { template: 'systems/daggerheart/templates/sheets/actors/environment/notes.hbs' } }; + /** @inheritdoc */ static TABS = { - features: { - active: true, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: 'DAGGERHEART.General.tabs.features' - }, - potentialAdversaries: { - active: false, - cssClass: '', - group: 'primary', - id: 'potentialAdversaries', - icon: null, - label: 'DAGGERHEART.General.tabs.potentialAdversaries' - }, - notes: { - active: false, - cssClass: '', - group: 'primary', - id: 'notes', - icon: null, - label: 'DAGGERHEART.Sheets.Adversary.Tabs.notes' + primary: { + tabs: [{ id: 'features' }, { id: 'potentialAdversaries' }, { id: 'notes', label: "DAGGERHEART.Sheets.Adversary.Tabs.notes" }], + initial: 'features', + labelPrefix: 'DAGGERHEART.General.tabs' } }; async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); - context.getEffectDetails = this.getEffectDetails.bind(this); return context; } @@ -79,15 +51,6 @@ export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) { await new DHEnvironmentSettings(this.document).render(true); } - static async _updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } - - getEffectDetails(id) { - return {}; - } - static async addAdversary() { await this.document.update({ [`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize( diff --git a/module/applications/sheets/api/_modules.mjs b/module/applications/sheets/api/_modules.mjs index 4316da2a..4c80133d 100644 --- a/module/applications/sheets/api/_modules.mjs +++ b/module/applications/sheets/api/_modules.mjs @@ -1,3 +1,4 @@ export { default as DHApplicationMixin } from './application-mixin.mjs'; export { default as DHBaseItemSheet } from './base-item.mjs'; export { default as DHHeritageSheet } from './heritage-sheet.mjs'; +export { default as DHBaseActorSheet } from "./base-actor.mjs"; diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 3ba471db..929a72c8 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -5,6 +5,14 @@ import { tagifyElement } from '../../../helpers/utils.mjs'; * @typedef {object} DragDropConfig * @property {string} [dragSelector] - A CSS selector that identifies draggable elements. * @property {string} [dropSelector] - A CSS selector that identifies drop targets. + * + * @typedef {object} ContextMenuConfig + * @property {() => ContextMenuEntry[]} handler - A handler function that provides initial context options + * @property {string} selector - A CSS selector to which the ContextMenu will be bound + * @property {object} [options] - Additional options which affect ContextMenu construction + * @property {HTMLElement} [options.container] - A parent HTMLElement which contains the selector target + * @property {string} [options.hookName] - The hook name + * @property {boolean} [options.parentClassHooks=true] - Whether to call hooks for the parent classes in the inheritance chain. * * @typedef {Object} TagOption * @property {string} label @@ -24,11 +32,21 @@ import { tagifyElement } from '../../../helpers/utils.mjs'; * * @typedef {Object} TagifyOptions * @property {number} [maxTags] - Maximum number of allowed tags - * - * @typedef {import("@client/applications/api/handlebars-application.mjs").HandlebarsRenderOptions} HandlebarsRenderOptions - * @typedef {foundry.applications.types.ApplicationConfiguration & HandlebarsRenderOptions & { dragDrop?: DragDropConfig[], tagifyConfigs?: TagifyConfig[] }} DHSheetV2Configuration */ +/** + * @typedef {import("@client/applications/api/handlebars-application.mjs").HandlebarsRenderOptions} HandlebarsRenderOptions + * @typedef {foundry.applications.types.ApplicationConfiguration} FoundryAppConfig + * + * @typedef {FoundryAppConfig & HandlebarsRenderOptions & { + * dragDrop?: DragDropConfig[], + * tagifyConfigs?: TagifyConfig[], + * contextMenus?: ContextMenuConfig[], + * }} DHSheetV2Configuration + */ + + + /** * @template {Constructor} BaseDocumentSheet * @param {BaseDocumentSheet} Base - The base class to extend. @@ -54,15 +72,12 @@ export default function DHApplicationMixin(Base) { */ static DEFAULT_OPTIONS = { classes: ['daggerheart', 'sheet', 'dh-style'], - position: { - width: 480, - height: 'auto' - }, actions: { - addEffect: DHSheetV2.#addEffect, - editEffect: DHSheetV2.#editEffect, - removeEffect: DHSheetV2.#removeEffect + createDoc: DHSheetV2.#createDoc, + editDoc: DHSheetV2.#editDoc, + deleteDoc: DHSheetV2.#deleteDoc }, + contextMenus: [], dragDrop: [], tagifyConfigs: [] }; @@ -74,6 +89,11 @@ export default function DHApplicationMixin(Base) { super._attachPartListeners(partId, htmlElement, options); this._dragDrop.forEach(d => d.bind(htmlElement)); } + /**@inheritdoc */ + async _onFirstRender(context, options) { + await super._onFirstRender(context, options); + if (!!this.options.contextMenus.length) this._createContextMenus(); + } /**@inheritdoc */ async _onRender(context, options) { @@ -81,6 +101,10 @@ export default function DHApplicationMixin(Base) { this._createTagifyElements(this.options.tagifyConfigs); } + /* -------------------------------------------- */ + /* Tags */ + /* -------------------------------------------- */ + /** * Creates Tagify elements from configuration objects * @param {TagifyConfig[]} tagConfigs - Array of Tagify configuration objects @@ -140,32 +164,50 @@ export default function DHApplicationMixin(Base) { * @param {DragEvent} event * @protected */ - _onDragStart(event) {} + _onDragStart(event) { } /** * Handle drop event. * @param {DragEvent} event * @protected */ - _onDrop(event) {} + _onDrop(event) { } + + + /* -------------------------------------------- */ + /* Context Menu */ + /* -------------------------------------------- */ + + _createContextMenus() { + for (const config of this.options.contextMenus) { + const { handler, selector, options } = config; + this._createContextMenu(handler.bind(this), selector, options); + } + } + + /* -------------------------------------------- */ + + + /** + * Get the set of ContextMenu options which should be used for journal entry pages in the sidebar. + * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} + * @protected + */ + _getEntryContextOptions() { + return []; + } /* -------------------------------------------- */ /* Prepare Context */ /* -------------------------------------------- */ - /** - * Prepare the template context. - * @param {object} options - * @param {string} [objectPath='document'] - * @returns {Promise} - * @inheritdoc - */ - async _prepareContext(options, objectPath = 'document') { + /**@inheritdoc*/ + async _prepareContext(options) { const context = await super._prepareContext(options); context.config = CONFIG.DH; - context.source = this[objectPath]; - context.fields = this[objectPath].schema.fields; - context.systemFields = this[objectPath].system ? this[objectPath].system.schema.fields : {}; + context.source = this.document; + context.fields = this.document.schema.fields; + context.systemFields = this.document.system.schema.fields; return context; } @@ -174,38 +216,44 @@ export default function DHApplicationMixin(Base) { /* -------------------------------------------- */ /** - * Renders an ActiveEffect's sheet sheet. + * Create an embedded document. * @param {PointerEvent} event - The originating click event * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] */ - static async #addEffect() { - const cls = foundry.documents.ActiveEffect; - await cls.create( - { - name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize(cls.metadata.label) }) - }, - { parent: this.document } - ); + static async #createDoc(event, button) { + const { type } = button.dataset; + const operation = { + parent: this.document, + pack: this.document.pack, + renderSheet: !event.shiftKey, + } + + const cls = getDocumentClass(type); + return await cls.createDocuments([{ + name: cls.defaultName(operation) + }], operation); } /** - * Renders an ActiveEffect's sheet sheet. + * Renders an embedded document. * @param {PointerEvent} event - The originating click event * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] */ - static async #editEffect(_event, button) { - const effect = this.document.effects.get(button.dataset.effect); - effect.sheet.render({ force: true }); + static #editDoc(_event, button) { + const { type, docId } = button.dataset; + this.document.getEmbeddedDocument(type, docId, { strict: true }).sheet.render({ force: true }); } /** - * Delete an ActiveEffect from the item. + * Delete an embedded document. * @param {PointerEvent} _event - The originating click event * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] */ - static async #removeEffect(_event, button) { - await this.document.effects.get(button.dataset.effect).delete(); + static async #deleteDoc(_event, button) { + const { type, docId } = button.dataset; + await this.document.getEmbeddedDocument(type, docId, { strict: true }).delete(); } + } return DHSheetV2; diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs new file mode 100644 index 00000000..3a2187d7 --- /dev/null +++ b/module/applications/sheets/api/base-actor.mjs @@ -0,0 +1,30 @@ +import DHApplicationMixin from './application-mixin.mjs'; + +const { ActorSheetV2 } = foundry.applications.sheets; + +/** + * A base actor sheet extending {@link ActorSheetV2} via {@link DHApplicationMixin} + * @extends ActorSheetV2 + * @mixes DHSheetV2 + */ +export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { + /** @inheritDoc */ + static DEFAULT_OPTIONS = { + classes: ['actor'], + position: { + width: 480, + }, + form: { + submitOnChange: true + }, + actions: {}, + dragDrop: [] + }; + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.isNPC = this.document.isNPC; + + return context; + } +} \ No newline at end of file diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs index 442c6e93..a04138ff 100644 --- a/module/data/actor/base.mjs +++ b/module/data/actor/base.mjs @@ -3,13 +3,15 @@ * @typedef {Object} ActorDataModelMetadata * @property {string} label - A localizable label used on application. * @property {string} type - The system type that this data model represents. + * @property {Boolean} isNPC - This data model represents a NPC? */ export default class BaseDataActor extends foundry.abstract.TypeDataModel { /** @returns {ActorDataModelMetadata}*/ static get metadata() { return { label: 'Base Actor', - type: 'base' + type: 'base', + isNPC: true, }; } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index f94ce7d1..1b8e6e77 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -28,7 +28,8 @@ export default class DhCharacter extends BaseDataActor { static get metadata() { return foundry.utils.mergeObject(super.metadata, { label: 'TYPES.Actor.character', - type: 'character' + type: 'character', + isNPC: false, }); } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index a12d4e6a..b619d4ff 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -5,6 +5,16 @@ import { LevelOptionType } from '../data/levelTier.mjs'; import DHFeature from '../data/item/feature.mjs'; export default class DhpActor extends Actor { + + /** + * Whether this actor is an NPC. + * @returns {boolean} + */ + get isNPC() { + return this.system.constructor.metadata.isNPC; + } + + async _preCreate(data, options, user) { if ((await super._preCreate(data, options, user)) === false) return false; @@ -351,16 +361,16 @@ export default class DhpActor extends Actor { const modifier = roll.modifier !== null ? Number.parseInt(roll.modifier) : null; return modifier !== null ? [ - { - value: modifier, - label: roll.label - ? modifier >= 0 - ? `${roll.label} +${modifier}` - : `${roll.label} ${modifier}` - : null, - title: roll.label - } - ] + { + value: modifier, + label: roll.label + ? modifier >= 0 + ? `${roll.label} +${modifier}` + : `${roll.label} ${modifier}` + : null, + title: roll.label + } + ] : []; } @@ -440,10 +450,10 @@ export default class DhpActor extends Actor { damage >= this.system.damageThresholds.severe ? 3 : damage >= this.system.damageThresholds.major - ? 2 - : damage >= this.system.damageThresholds.minor - ? 1 - : 0; + ? 2 + : damage >= this.system.damageThresholds.minor + ? 1 + : 0; if ( this.type === 'character' && diff --git a/templates/sheets/global/tabs/tab-effects.hbs b/templates/sheets/global/tabs/tab-effects.hbs index e6191c80..8e625e09 100644 --- a/templates/sheets/global/tabs/tab-effects.hbs +++ b/templates/sheets/global/tabs/tab-effects.hbs @@ -4,15 +4,16 @@ data-group='{{tabs.effects.group}}' >
- {{localize "DAGGERHEART.Sheets.Global.Effects"}} + {{localize "DAGGERHEART.Sheets.Global.Effects"}} +
{{#each document.effects as |effect|}}
{{effect.name}}
- - + +
{{/each}}