Files
bol/module/applications/sheets/base-item-sheet.mjs

244 lines
6.3 KiB
JavaScript

const { HandlebarsApplicationMixin } = foundry.applications.api
/**
* Base Item Sheet for BoL system using AppV2
* @extends {ItemSheetV2}
*/
export default class BoLBaseItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["bol", "sheet", "item"],
position: {
width: 650,
height: 780,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
actions: {
editImage: BoLBaseItemSheet.#onEditImage,
postItem: BoLBaseItemSheet.#onPostItem,
},
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" }
/** @override */
async _prepareContext() {
const context = {
// Document & system
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
source: this.document.toObject(),
// Enriched content
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
this.document.system.description,
{ async: true }
),
// Properties
category: this.document.system.category,
itemProperties: this.document.itemProperties,
// Config & permissions
config: game.bol.config,
isGM: game.user.isGM,
isEditable: this.isEditable,
// CSS classes for template
cssClass: this.options.classes.join(" "),
// Tab state
tabs: this._getTabs(),
activeTab: this.tabGroups.primary || "description"
}
// Add careers if item is on an actor
if (this.document.actor) {
context.careers = this.document.actor.careers
}
// Apply dynamic defaults based on item type
this._applyDynamicDefaults(context)
return context
}
/**
* Get tabs configuration
* @returns {object[]}
* @private
*/
_getTabs() {
return [
{ id: "description", label: "BOL.ui.tab.description", icon: "fa-solid fa-book" },
{ id: "properties", label: "BOL.ui.tab.details", icon: "fa-solid fa-cog" }
]
}
/**
* Apply dynamic defaults to context based on item type and category
* @param {object} context
* @private
*/
_applyDynamicDefaults(context) {
const itemData = context.item
if (itemData.type === "item") {
// Set default category
if (!itemData.system.category) {
itemData.system.category = "equipment"
}
// Handle equipment slot
if (itemData.system.category === "equipment" && itemData.system.properties.equipable) {
if (!itemData.system.properties.slot) {
itemData.system.properties.slot = "-"
}
}
// Handle spell conditions
if (itemData.system.category === 'spell') {
if (!itemData.system.properties.mandatoryconditions) {
itemData.system.properties.mandatoryconditions = []
}
if (!itemData.system.properties.optionnalconditions) {
itemData.system.properties.optionnalconditions = []
}
for (let i = 0; i < 4; i++) {
itemData.system.properties.mandatoryconditions[i] = itemData.system.properties.mandatoryconditions[i] ?? ""
}
for (let i = 0; i < 8; i++) {
itemData.system.properties.optionnalconditions[i] = itemData.system.properties.optionnalconditions[i] ?? ""
}
}
} else if (itemData.type === "feature") {
// Set default subtype/category
if (!itemData.system.subtype) {
itemData.system.category = "origin"
}
}
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
this._activateTabs()
this._activateListeners()
}
/**
* Activate tab navigation
* @private
*/
_activateTabs() {
const nav = this.element.querySelector('nav.tabs[data-group="primary"]')
if (!nav) return
const activeTab = this.tabGroups.primary || "description"
// Activate tab links
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.primary = tab
this.render()
})
})
// Show/hide tab content
this.element.querySelectorAll('.tab[data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
}
/**
* Activate custom listeners
* @private
*/
_activateListeners() {
if (!this.isEditable) return
// Armor quality change handler
const armorQuality = this.element.querySelector('.armorQuality')
if (armorQuality) {
armorQuality.addEventListener('change', (ev) => {
const value = ev.currentTarget.value
const soakFormula = this.element.querySelector('.soakFormula')
if (soakFormula && game.bol.config.soakFormulas[value]) {
soakFormula.value = game.bol.config.soakFormulas[value]
}
})
}
}
// #region Drag-and-Drop Workflow
/**
* Create drag-and-drop workflow handlers for this Application
* @returns {DragDrop[]}
* @private
*/
#createDragDropHandlers() {
return []
}
// #endregion
// #region Actions
/**
* Handle editing the item image
* @param {PointerEvent} event
* @param {HTMLElement} target
* @private
*/
static async #onEditImage(event, target) {
const fp = new FilePicker({
current: this.document.img,
type: "image",
callback: (path) => {
this.document.update({ img: path })
},
})
return fp.browse()
}
/**
* Handle posting the item to chat
* @param {PointerEvent} event
* @param {HTMLElement} target
* @private
*/
static async #onPostItem(event, target) {
const BoLUtility = (await import("../../system/bol-utility.js")).BoLUtility
let chatData = foundry.utils.duplicate(this.document)
if (this.document.actor) {
chatData.actor = { id: this.document.actor.id }
}
BoLUtility.postItem(chatData)
}
// #endregion
}