Files
fvtt-yggdrasill/modules/applications/sheets/base-actor-sheet.mjs

450 lines
13 KiB
JavaScript

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 })
}
}