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

548 lines
15 KiB
JavaScript

const { HandlebarsApplicationMixin } = foundry.applications.api
import { HawkmoonUtility } from "../../hawkmoon-utility.js"
import { HawkmoonAutomation } from "../../hawkmoon-automation.js"
export default class HawkmoonActorSheet 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 // Commencer en mode visualisation
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-hawkmoon-cyd", "sheet", "actor"],
position: {
width: 640,
height: 720,
},
window: {
resizable: true,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: "form" }],
actions: {
editImage: HawkmoonActorSheet.#onEditImage,
toggleSheet: HawkmoonActorSheet.#onToggleSheet,
editItem: HawkmoonActorSheet.#onEditItem,
deleteItem: HawkmoonActorSheet.#onDeleteItem,
createItem: HawkmoonActorSheet.#onCreateItem,
equipItem: HawkmoonActorSheet.#onEquipItem,
modifyQuantity: HawkmoonActorSheet.#onModifyQuantity,
modifyAdversite: HawkmoonActorSheet.#onModifyAdversite,
rollInitiative: HawkmoonActorSheet.#onRollInitiative,
rollAttribut: HawkmoonActorSheet.#onRollAttribut,
rollCompetence: HawkmoonActorSheet.#onRollCompetence,
rollArmeOffensif: HawkmoonActorSheet.#onRollArmeOffensif,
rollArmeDegats: HawkmoonActorSheet.#onRollArmeDegats,
rollAssommer: HawkmoonActorSheet.#onRollAssommer,
rollCoupBas: HawkmoonActorSheet.#onRollCoupBas,
rollImmobiliser: HawkmoonActorSheet.#onRollImmobiliser,
rollRepousser: HawkmoonActorSheet.#onRollRepousser,
rollDesengager: HawkmoonActorSheet.#onRollDesengager,
},
}
/**
* Is the sheet currently in 'Play' mode?
* @type {boolean}
*/
get isPlayMode() {
// Initialize if not set
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() {
// Initialize if not set
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 actor = this.document
const context = {
actor: actor,
system: actor.system,
source: actor.toObject(),
fields: actor.schema.fields,
systemFields: actor.system.schema.fields,
isEditable: this.isEditable,
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isGM: game.user.isGM,
config: CONFIG.HAWKMOON,
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.description || "", { async: true }),
enrichedHabitat: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.habitat || "", { async: true }),
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
// Activate drag & drop handlers
this.#dragDrop.forEach(d => d.bind(this.element))
// Manual tab navigation
const html = this.element
const tabLinks = html.querySelectorAll('a.item[data-tab]')
const tabContents = html.querySelectorAll('.tab[data-tab]')
// Hide all tabs initially
tabContents.forEach(tab => {
tab.classList.remove('active')
tab.style.display = 'none'
})
// Show active tab
const activeTab = this.tabGroups.primary
const activeTabContent = html.querySelector(`.tab[data-tab="${activeTab}"]`)
if (activeTabContent) {
activeTabContent.classList.add('active')
activeTabContent.style.display = 'block'
}
// Activate the corresponding nav link
tabLinks.forEach(link => {
if (link.dataset.tab === activeTab) {
link.classList.add('active')
} else {
link.classList.remove('active')
}
})
// Tab click handler
tabLinks.forEach(link => {
link.addEventListener('click', (event) => {
event.preventDefault()
const tab = link.dataset.tab
// Update state
this.tabGroups.primary = tab
// Hide all tabs
tabContents.forEach(t => {
t.classList.remove('active')
t.style.display = 'none'
})
// Show selected tab
const selectedTab = html.querySelector(`.tab[data-tab="${tab}"]`)
if (selectedTab) {
selectedTab.classList.add('active')
selectedTab.style.display = 'block'
}
// Update nav links
tabLinks.forEach(l => {
if (l.dataset.tab === tab) {
l.classList.add('active')
} else {
l.classList.remove('active')
}
})
})
})
// Inline item editing
html.querySelectorAll('.edit-item-data').forEach(input => {
input.addEventListener('change', (event) => {
const li = event.target.closest('.item')
const itemId = li.dataset.itemId
const itemType = li.dataset.itemType
const itemField = event.target.dataset.itemField
const dataType = event.target.dataset.dtype
const value = event.target.value
this.actor.editItemField(itemId, itemType, itemField, dataType, value)
})
})
}
// #region Drag & Drop
/**
* Create drag-and-drop workflow handlers for this Application
* @returns {DragDrop[]} An array of DragDrop handlers
* @private
*/
#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),
drop: this._onDrop.bind(this),
}
return new foundry.applications.ux.DragDrop(d)
})
}
/**
* Define whether a user is able to begin a dragstart workflow for a given drag selector
* @param {string} selector The candidate HTML selector for dragging
* @returns {boolean} Can the current user drag this selector?
* @protected
*/
_canDragStart(selector) {
return this.isEditable
}
/**
* Define whether a user is able to conclude a drag-and-drop workflow for a given drop selector
* @param {string} selector The candidate HTML selector for the drop target
* @returns {boolean} Can the current user drop on this selector?
* @protected
*/
_canDragDrop(selector) {
return this.isEditable
}
/**
* Callback actions which occur at the beginning of a drag start workflow.
* @param {DragEvent} event The originating DragEvent
* @protected
*/
_onDragStart(event) {
const li = event.currentTarget.closest(".item")
if (!li?.dataset.itemId) return
const item = this.actor.items.get(li.dataset.itemId)
if (!item) return
const dragData = item.toDragData()
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Callback actions which occur when a dragged element is dropped on a target.
* @param {DragEvent} event The originating DragEvent
* @protected
*/
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
const actor = this.actor
// Handle different data types
switch (data.type) {
case "Item":
return this._onDropItem(event, data)
case "Actor":
return this._onDropActor(event, data)
case "ActiveEffect":
return this._onDropActiveEffect(event, data)
}
}
/**
* Handle dropping an Item on the actor sheet
* @param {DragEvent} event
* @param {object} data
* @private
*/
async _onDropItem(event, data) {
if (!this.actor.isOwner) return false
let item = await fromUuid(data.uuid)
if (item.pack) {
item = await HawkmoonUtility.searchItem(item)
}
const autoresult = HawkmoonAutomation.processAutomations("on-drop", item, this.actor)
if (autoresult.isValid) {
// In AppV2, we need to get the item data differently
const itemData = item.toObject ? item.toObject() : item
return this.actor.createEmbeddedDocuments("Item", [itemData])
} else {
ui.notifications.warn(autoresult.warningMessage)
return false
}
}
/**
* Handle dropping an Actor on the sheet
* @param {DragEvent} event
* @param {object} data
* @private
*/
async _onDropActor(event, data) {
// To be implemented by subclasses if needed
return false
}
/**
* Handle dropping an ActiveEffect on the sheet
* @param {DragEvent} event
* @param {object} data
* @private
*/
async _onDropActiveEffect(event, data) {
// To be implemented by subclasses if needed
return false
}
// #endregion
// #region Action Handlers
/**
* Toggle between edit and play mode
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static #onToggleSheet(event, target) {
console.log("Toggle sheet clicked", this)
const wasEditMode = this.isEditMode
console.log("Current mode:", this._sheetMode, "isEditMode:", wasEditMode, "isPlayMode:", this.isPlayMode)
this._sheetMode = wasEditMode ? this.constructor.SHEET_MODES.PLAY : this.constructor.SHEET_MODES.EDIT
console.log("New mode set to:", this._sheetMode, "(", wasEditMode ? "PLAY" : "EDIT", ")")
console.log("After change - isEditMode:", this.isEditMode, "isPlayMode:", this.isPlayMode)
this.render({ force: true })
}
/**
* Edit the actor image
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onEditImage(event, target) {
const fp = new FilePicker({
type: "image",
current: this.actor.img,
callback: (path) => {
this.actor.update({ img: path })
},
})
return fp.browse()
}
/**
* Edit an item
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onEditItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
const item = this.actor.items.get(itemId)
if (item) item.sheet.render(true)
}
/**
* Delete an item
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onDeleteItem(event, target) {
const li = target.closest(".item")
await HawkmoonUtility.confirmDelete(this, li)
}
/**
* Create a new item
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onCreateItem(event, target) {
const itemType = target.dataset.type
await this.actor.createEmbeddedDocuments("Item", [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
}
/**
* Equip/unequip an item
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onEquipItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (itemId) {
await this.actor.equipItem(itemId)
this.render()
}
}
/**
* Modify item quantity
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onModifyQuantity(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
const value = Number(target.dataset.quantiteValue)
if (itemId) {
await this.actor.incDecQuantity(itemId, value)
}
}
/**
* Modify adversité
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onModifyAdversite(event, target) {
const li = target.closest(".item")
const adv = li?.dataset.adversite
const value = Number(target.dataset.adversiteValue)
if (adv) {
await this.actor.incDecAdversite(adv, value)
}
}
/**
* Roll initiative
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollInitiative(event, target) {
await this.actor.rollAttribut("adr", true)
}
/**
* Roll attribut
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollAttribut(event, target) {
const li = target.closest(".item")
const attrKey = li?.dataset.attrKey
if (attrKey) {
await this.actor.rollAttribut(attrKey, false)
}
}
/**
* Roll competence
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollCompetence(event, target) {
const li = target.closest(".item")
const attrKey = target.dataset.attrKey
const compId = li?.dataset.itemId
if (attrKey && compId) {
await this.actor.rollCompetence(attrKey, compId)
}
}
/**
* Roll arme offensif
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollArmeOffensif(event, target) {
const li = target.closest(".item")
const armeId = li?.dataset.itemId
if (armeId) {
await this.actor.rollArmeOffensif(armeId)
}
}
/**
* Roll arme degats
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollArmeDegats(event, target) {
const li = target.closest(".item")
const armeId = li?.dataset.itemId
if (armeId) {
await this.actor.rollArmeDegats(armeId)
}
}
/**
* Roll assommer
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollAssommer(event, target) {
await this.actor.rollAssommer()
}
/**
* Roll coup bas
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollCoupBas(event, target) {
await this.actor.rollCoupBas()
}
/**
* Roll immobiliser
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollImmobiliser(event, target) {
await this.actor.rollImmobiliser()
}
/**
* Roll repousser
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollRepousser(event, target) {
await this.actor.rollRepousser()
}
/**
* Roll désengager
* @param {Event} event
* @param {HTMLElement} target
* @private
*/
static async #onRollDesengager(event, target) {
await this.actor.rollDesengager()
}
// #endregion
}