const { HandlebarsApplicationMixin } = foundry.applications.api import { MaleficesUtility } from "../../malefices-utility.js" export default class MaleficesActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) { constructor(options = {}) { super(options) this.#dragDrop = this.#createDragDropHandlers() this._editScore = true } #dragDrop /** @override */ static DEFAULT_OPTIONS = { classes: ["fvtt-malefices", "actor"], position: { width: 640, height: 680, }, form: { submitOnChange: true, }, window: { resizable: true, }, dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }], actions: { editImage: MaleficesActorSheet.#onEditImage, toggleSheet: MaleficesActorSheet.#onToggleSheet, editItem: MaleficesActorSheet.#onEditItem, deleteItem: MaleficesActorSheet.#onDeleteItem, createItem: MaleficesActorSheet.#onCreateItem, equipItem: MaleficesActorSheet.#onEquipItem, modifyQuantity: MaleficesActorSheet.#onModifyQuantity, modifyAmmo: MaleficesActorSheet.#onModifyAmmo, rollAttribut: MaleficesActorSheet.#onRollAttribut, rollArme: MaleficesActorSheet.#onRollArme, editSubActor: MaleficesActorSheet.#onEditSubActor, deleteSubActor: MaleficesActorSheet.#onDeleteSubActor, }, } /** @type {object} */ tabGroups = { primary: "main" } /** @override */ async _prepareContext() { const actor = this.document return { actor, system: actor.system, source: actor.toObject(), fields: actor.schema.fields, systemFields: actor.system.schema.fields, isEditable: this.isEditable, cssClass: this.isEditable ? "editable" : "locked", isGM: game.user.isGM, config: game.system.malefices.config, editScore: this._editScore, } } /** @override */ _onRender(context, options) { super._onRender(context, options) this.#dragDrop.forEach((d) => d.bind(this.element)) // Ignore Enter key in text inputs (not textarea) this.element.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA') e.preventDefault() }) // Manual tab navigation const nav = this.element.querySelector('nav.tabs[data-group]') if (nav) { const group = nav.dataset.group const activeTab = this.tabGroups[group] || "main" nav.querySelectorAll('[data-tab]').forEach(link => { link.classList.toggle('active', link.dataset.tab === activeTab) link.addEventListener('click', (event) => { event.preventDefault() this.tabGroups[group] = link.dataset.tab this.render() }) }) this.element.querySelectorAll(`[data-group="${group}"][data-tab]`).forEach(content => { content.classList.toggle('active', content.dataset.tab === activeTab) }) } // Handle .update-field change events (legacy support) this.element.querySelectorAll('.update-field').forEach(el => { el.addEventListener('change', (ev) => { const fieldName = ev.currentTarget.dataset.fieldName const value = Number(ev.currentTarget.value) this.actor.update({ [fieldName]: value }) }) }) } // #region Drag-and-Drop #createDragDropHandlers() { return this.options.dragDrop.map((d) => { d.permissions = { dragstart: this._canDragStart.bind(this), drop: this._canDragDrop.bind(this), } d.callbacks = { dragstart: this._onDragStart.bind(this), dragover: this._onDragOver.bind(this), drop: this._onDrop.bind(this), } return new foundry.applications.ux.DragDrop.implementation(d) }) } _canDragStart(selector) { return this.isEditable } _canDragDrop(selector) { return this.isEditable } _onDragStart(event) { const li = event.currentTarget.closest('.item') if (!li) return const itemId = li.dataset.itemId const item = this.actor.items.get(itemId) if (item) { event.dataTransfer.setData("text/plain", JSON.stringify({ type: "Item", uuid: item.uuid })) } } _onDragOver(event) {} async _onDrop(event) { const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event) if (data?.type === "Actor") { const actor = await fromUuid(data.uuid) if (actor) this.actor.addSubActor(actor.id) } else { super._onDrop(event) } } // #endregion // #region Actions static async #onEditImage(event, target) { const fp = new FilePicker({ type: "image", current: this.document.img, callback: (path) => { this.document.update({ img: path }) }, }) return fp.browse() } static async #onToggleSheet(event, target) { this._editScore = !this._editScore this.render() } static async #onEditItem(event, target) { const li = target.closest(".item") const itemId = li?.dataset.itemId if (!itemId) return this.actor.items.get(itemId)?.sheet.render(true) } static async #onDeleteItem(event, target) { const li = target.closest(".item") MaleficesUtility.confirmDelete(this, li) } static async #onCreateItem(event, target) { const dataType = target.dataset.type this.actor.createEmbeddedDocuments('Item', [{ name: "NewItem", type: dataType }], { renderSheet: true }) } static async #onEquipItem(event, target) { const li = target.closest(".item") const itemId = li?.dataset.itemId if (!itemId) return await this.actor.equipItem(itemId) this.render() } static async #onModifyQuantity(event, target) { const li = target.closest(".item") const itemId = li?.dataset.itemId if (!itemId) return const delta = parseInt(target.dataset.delta) || 0 this.actor.incDecQuantity(itemId, delta) } static async #onModifyAmmo(event, target) { const li = target.closest(".item") const itemId = li?.dataset.itemId if (!itemId) return const delta = parseInt(target.dataset.delta) || 0 this.actor.incDecAmmo(itemId, delta) } static async #onRollAttribut(event, target) { const attrKey = target.dataset.attrKey this.actor.rollAttribut(attrKey) } static async #onRollArme(event, target) { const armeId = target.dataset.armeId this.actor.rollArme(armeId) } static async #onEditSubActor(event, target) { const li = target.closest(".item") const actorId = li?.dataset.actorId if (!actorId) return game.actors.get(actorId)?.sheet.render(true) } static async #onDeleteSubActor(event, target) { const li = target.closest(".item") const actorId = li?.dataset.actorId if (!actorId) return this.actor.delSubActor(actorId) } // #endregion }