const { HandlebarsApplicationMixin } = foundry.applications.api import { YggdrasillUtility } from "../../yggdrasill-utility.js" export default class YggdrasillActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) { /** * Different sheet modes. * @enum {number} */ static SHEET_MODES = { EDIT: 0, PLAY: 1 } constructor(options = {}) { super(options) this.#dragDrop = this.#createDragDropHandlers() this._sheetMode = this.constructor.SHEET_MODES.PLAY } #dragDrop /** @override */ static DEFAULT_OPTIONS = { classes: ["fvtt-yggdrasill", "sheet", "actor"], position: { width: 750, height: 720, }, form: { submitOnChange: true, closeOnSubmit: false, }, window: { resizable: true, }, tabs: [ { navSelector: 'nav[data-group="primary"]', contentSelector: "section.sheet-body", initial: "principal", }, ], dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }], actions: { editImage: YggdrasillActorSheet.#onEditImage, toggleSheet: YggdrasillActorSheet.#onToggleSheet, editItem: YggdrasillActorSheet.#onEditItem, deleteItem: YggdrasillActorSheet.#onDeleteItem, createItem: YggdrasillActorSheet.#onCreateItem, equipItem: YggdrasillActorSheet.#onEquipItem, rollCarac: YggdrasillActorSheet.#onRollCarac, rollCompetence: YggdrasillActorSheet.#onRollCompetence, rollArme: YggdrasillActorSheet.#onRollArme, rollSort: YggdrasillActorSheet.#onRollSort, rollProuesse: YggdrasillActorSheet.#onRollProuesse, rollDamage: YggdrasillActorSheet.#onRollDamage, lockUnlock: YggdrasillActorSheet.#onLockUnlock, incrementPV: YggdrasillActorSheet.#onIncrementPV, decrementPV: YggdrasillActorSheet.#onDecrementPV, updateCompetence: YggdrasillActorSheet.#onUpdateCompetence, }, } /** * Is the sheet currently in 'Play' mode? * @type {boolean} */ get isPlayMode() { if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY return this._sheetMode === this.constructor.SHEET_MODES.PLAY } /** * Is the sheet currently in 'Edit' mode? * @type {boolean} */ get isEditMode() { if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY return this._sheetMode === this.constructor.SHEET_MODES.EDIT } /** * Tab groups state * @type {object} */ tabGroups = { primary: "principal", } /** @override */ async _prepareContext() { const context = { fields: this.document.schema.fields, systemFields: this.document.system.schema.fields, actor: this.document, system: this.document.system, source: this.document.toObject(), isEditMode: this.isEditMode, isPlayMode: this.isPlayMode, isEditable: this.isEditable, isGM: game.user.isGM, config: game.system.yggdrasill.config, editScore: this.isEditMode, } return context } /** @override */ _onRender(context, options) { super._onRender(context, options) // Activate tab navigation manually const nav = this.element.querySelector('nav.tabs[data-group], nav.sheet-tabs[data-group]') if (nav) { const group = nav.dataset.group // Activate the current tab const activeTab = this.tabGroups[group] || "principal" nav.querySelectorAll('[data-tab]').forEach(link => { const tab = link.dataset.tab link.classList.toggle('active', tab === activeTab) link.addEventListener('click', (event) => { event.preventDefault() this.tabGroups[group] = tab this.render() }) }) // Show/hide tab content this.element.querySelectorAll('[data-group="' + group + '"][data-tab]').forEach(content => { content.classList.toggle('active', content.dataset.tab === activeTab) }) } // Add change listener for competence niveau selects this.element.querySelectorAll('select.competence-niveau').forEach(select => { select.addEventListener('change', async (event) => { const itemId = event.target.dataset.itemId const item = this.document.items.get(itemId) if (item) { const newNiveau = parseInt(event.target.value) await item.update({ "system.niveau": newNiveau }) } }) }) } /** * Creates drag-and-drop handlers for this application * @returns {DragDrop[]} An array of DragDrop handlers * @private */ #createDragDropHandlers() { return [] } /** * Handle changing a Document's image * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static async #onEditImage(event, target) { const attr = target.dataset.edit const current = foundry.utils.getProperty(this.document, attr) const fp = new FilePicker({ current, type: "image", callback: (path) => { this.document.update({ [attr]: path }) }, }) return fp.browse() } /** * Toggle sheet mode between Edit and Play * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static #onToggleSheet(event, target) { this._sheetMode = this.isEditMode ? this.constructor.SHEET_MODES.PLAY : this.constructor.SHEET_MODES.EDIT this.render() } /** * Handle item editing * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static #onEditItem(event, target) { const itemId = target.closest("[data-item-id]").dataset.itemId const item = this.document.items.get(itemId) if (item) item.sheet.render(true) } /** * Handle item deletion * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static async #onDeleteItem(event, target) { const itemId = target.closest("[data-item-id]").dataset.itemId const item = this.document.items.get(itemId) if (item) { await item.delete() } } /** * Handle item creation * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static async #onCreateItem(event, target) { const itemType = target.dataset.itemType const itemData = { name: `Nouveau ${itemType}`, type: itemType, } await this.document.createEmbeddedDocuments("Item", [itemData]) } /** * Handle item equip toggle * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static async #onEquipItem(event, target) { const itemId = target.closest("[data-item-id]").dataset.itemId const item = this.document.items.get(itemId) if (item && item.system.equipe !== undefined) { await item.update({ "system.equipe": !item.system.equipe }) } } /** * Handle characteristic roll * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static #onRollCarac(event, target) { const caracCateg = target.dataset.caracCateg const caracKey = target.dataset.caracKey this.document.rollCarac(caracCateg, caracKey) } /** * Handle competence roll * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static #onRollCompetence(event, target) { const itemId = target.closest("[data-item-id]").dataset.itemId this.document.rollCompetence(itemId) } /** * Handle weapon roll * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static #onRollArme(event, target) { const itemId = target.closest("[data-item-id]").dataset.itemId this.document.rollArme(itemId) } /** * Handle lock/unlock toggle * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static #onLockUnlock(event, target) { this._sheetMode = this.isEditMode ? this.constructor.SHEET_MODES.PLAY : this.constructor.SHEET_MODES.EDIT this.render() } /** * Handle incrementing PV * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static async #onIncrementPV(event, target) { const currentPV = this.document.system.caracsecondaire.pv.value || 0 const maxPV = this.document.system.caracsecondaire.pv.max || 0 const newPV = Math.min(currentPV + 1, maxPV) await this.document.update({ "system.caracsecondaire.pv.value": newPV }) } /** * Handle decrementing PV * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static async #onDecrementPV(event, target) { const currentPV = this.document.system.caracsecondaire.pv.value || 0 const newPV = Math.max(currentPV - 1, 0) await this.document.update({ "system.caracsecondaire.pv.value": newPV }) } /** * Handle competence niveau update * @this {YggdrasillActorSheet} * @param {Event} event - The triggering event * @param {HTMLElement} target - The select element */ static async #onUpdateCompetence(event, target) { const itemId = target.dataset.itemId const item = this.document.items.get(itemId) if (!item) return const newNiveau = parseInt(target.value) await item.update({ "system.niveau": newNiveau }) } /** * Handle sort roll * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static #onRollSort(event, target) { const itemId = target.closest("[data-item-id]").dataset.itemId const sortType = target.dataset.sortType || "sejdr" this.document.rollSort(itemId, sortType) } /** * Handle prouesse roll * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static #onRollProuesse(event, target) { const itemId = target.closest("[data-item-id]").dataset.itemId this.document.rollProuesse(itemId) } /** * Handle damage roll * @this {YggdrasillActorSheet} * @param {PointerEvent} event - The triggering event * @param {HTMLElement} target - The button element */ static #onRollDamage(event, target) { const itemId = target.closest("[data-item-id]").dataset.itemId const weapon = this.document.items.get(itemId) if (weapon) { this.document.rollDamage(weapon, 'damage') } } /** * Handle beginning of a drag operation * @param {DragEvent} event - The originating drag event * @protected */ _onDragStart(event) { const li = event.currentTarget const itemId = li.dataset.itemId const item = this.document.items.get(itemId) if (!item) return const dragData = item.toDragData() event.dataTransfer.setData("text/plain", JSON.stringify(dragData)) } /** * Handle a drop event * @param {DragEvent} event - The originating drop event * @protected */ async _onDrop(event) { const data = TextEditor.getDragEventData(event) const actor = this.document // Handle different data types switch (data.type) { case "Item": return this._onDropItem(event, data) case "ActiveEffect": return this._onDropActiveEffect(event, data) } } /** * Handle dropping an Item on the sheet * @param {DragEvent} event - The originating drop event * @param {object} data - The dropped data * @protected */ async _onDropItem(event, data) { if (!this.isEditable) return false const item = await Item.implementation.fromDropData(data) const itemData = item.toObject() // Handle item from same actor if (this.document.uuid === item.parent?.uuid) return this._onSortItem(event, itemData) // Create the item return this._onDropItemCreate(itemData) } /** * Handle creating an owned item from drop data * @param {object} itemData - The item data to create * @protected */ async _onDropItemCreate(itemData) { itemData = itemData instanceof Array ? itemData : [itemData] return this.document.createEmbeddedDocuments("Item", itemData) } /** * Handle sorting items * @param {DragEvent} event - The originating drop event * @param {object} itemData - The item data being sorted * @protected */ _onSortItem(event, itemData) { // Implement sorting logic if needed return Promise.resolve() } /** * Handle dropping an ActiveEffect on the sheet * @param {DragEvent} event - The originating drop event * @param {object} data - The dropped data * @protected */ async _onDropActiveEffect(event, data) { const effect = await ActiveEffect.implementation.fromDropData(data) if (!this.isEditable || !effect) return false if (this.document.uuid === effect.parent?.uuid) return false return ActiveEffect.create(effect.toObject(), { parent: this.document }) } }