450 lines
13 KiB
JavaScript
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 })
|
|
}
|
|
}
|