208 lines
7.1 KiB
JavaScript
208 lines
7.1 KiB
JavaScript
import { onManageActiveEffect } from "../../system/effects.mjs"
|
|
|
|
const { HandlebarsApplicationMixin } = foundry.applications.api
|
|
|
|
/**
|
|
* Fiche de base pour tous les acteurs Vermine 2047 (ApplicationV2).
|
|
* Remplace VermineActorSheet (AppV1).
|
|
*/
|
|
export class VermineBaseActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
|
|
|
// ── Mode édition / jeu ──────────────────────────────────────────────
|
|
|
|
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
|
|
|
|
_sheetMode = this.constructor.SHEET_MODES.PLAY
|
|
|
|
get isPlayMode() { return this._sheetMode === this.constructor.SHEET_MODES.PLAY }
|
|
get isEditMode() { return this._sheetMode === this.constructor.SHEET_MODES.EDIT }
|
|
|
|
// ── Options par défaut ──────────────────────────────────────────────
|
|
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["vermine2047", "actor"],
|
|
position: { width: 800, height: "auto" },
|
|
form: { submitOnChange: true },
|
|
window: { resizable: true },
|
|
dragDrop: [{ dragSelector: ".item", dropSelector: null }],
|
|
actions: {
|
|
editImage: VermineBaseActorSheet.#onEditImage,
|
|
toggleSheet: VermineBaseActorSheet.#onToggleSheet,
|
|
edit: VermineBaseActorSheet.#onItemEdit,
|
|
delete: VermineBaseActorSheet.#onItemDelete,
|
|
create: VermineBaseActorSheet.#onItemCreate,
|
|
roll: VermineBaseActorSheet.#onRollItem,
|
|
clickRadio: VermineBaseActorSheet.#onClickRadioHexa,
|
|
effectControl: VermineBaseActorSheet.#onEffectControl,
|
|
chooseTotem: VermineBaseActorSheet.#onChooseTotem
|
|
}
|
|
}
|
|
|
|
// ── Drag & Drop ─────────────────────────────────────────────────────
|
|
|
|
#dragDrop
|
|
|
|
constructor(options = {}) {
|
|
super(options)
|
|
this.#dragDrop = this.#createDragDropHandlers()
|
|
}
|
|
|
|
#createDragDropHandlers() {
|
|
return this.options.dragDrop.map(d => {
|
|
d.permissions = {
|
|
dragstart: this._canDragStart.bind(this),
|
|
drop: this._canDragDrop.bind(this)
|
|
}
|
|
d.callbacks = {
|
|
dragover: this._onDragOver.bind(this),
|
|
drop: this._onDrop.bind(this)
|
|
}
|
|
return new foundry.applications.ux.DragDrop.implementation(d)
|
|
})
|
|
}
|
|
|
|
_canDragStart() { return this.isEditable }
|
|
_canDragDrop() { return this.isEditable }
|
|
|
|
// ── Contexte commun ─────────────────────────────────────────────────
|
|
|
|
async _prepareContext() {
|
|
return {
|
|
fields: this.document.schema.fields,
|
|
systemFields: this.document.system.schema.fields,
|
|
actor: this.document,
|
|
system: this.document.system,
|
|
source: this.document.toObject(),
|
|
config: CONFIG.VERMINE,
|
|
rollData: this.document.getRollData(),
|
|
isGM: game.user.isGM,
|
|
isEditMode: this.isEditMode,
|
|
isPlayMode: this.isPlayMode,
|
|
isEditable: this.isEditable
|
|
}
|
|
}
|
|
|
|
// ── Rendu ───────────────────────────────────────────────────────────
|
|
|
|
async _onRoll(event) {
|
|
event.preventDefault()
|
|
const el = event.currentTarget
|
|
const type = el.dataset.type
|
|
const label = el.dataset.label
|
|
if (!type || !label) return
|
|
const { default: RollDialog } = await import("../../system/dialogs/rollDialog.mjs")
|
|
const dialog = await RollDialog.create({
|
|
actorId: this.document.id,
|
|
rolltype: type,
|
|
label
|
|
})
|
|
if (dialog) dialog.render(true)
|
|
}
|
|
|
|
// ── Actions ─────────────────────────────────────────────────────────
|
|
|
|
_onRender(context, options) {
|
|
super._onRender(context, options)
|
|
this.#dragDrop.forEach(d => d.bind(this.element))
|
|
this.element.querySelectorAll(".rollable").forEach(el => {
|
|
el.addEventListener("click", this._onRoll.bind(this))
|
|
})
|
|
// Auto-fill empty number inputs on change to prevent validation errors
|
|
this.element.addEventListener("change", e => {
|
|
const input = e.target
|
|
if (input?.type === "number" && !input.value && input.name && input !== document.activeElement) {
|
|
input.value = "0"
|
|
}
|
|
}, { capture: true })
|
|
}
|
|
|
|
/** @override */
|
|
async _onDropItem(item) {
|
|
const doc = item instanceof foundry.abstract.Document ? item : await fromUuid(item.uuid)
|
|
if (!doc) return
|
|
const itemData = doc.toObject()
|
|
await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
|
|
}
|
|
|
|
static #onToggleSheet() {
|
|
const modes = this.constructor.SHEET_MODES
|
|
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
|
|
this.render()
|
|
}
|
|
|
|
static async #onEditImage(event, target) {
|
|
const attr = target.dataset.edit ?? "img"
|
|
const current = foundry.utils.getProperty(this.document, attr)
|
|
const fp = new FilePicker({
|
|
current,
|
|
type: "image",
|
|
callback: (path) => this.document.update({ [attr]: path }),
|
|
top: this.position.top + 40,
|
|
left: this.position.left + 10
|
|
})
|
|
return fp.browse()
|
|
}
|
|
|
|
static async #onItemEdit(event, target) {
|
|
const id = target.closest("[data-item-id]")?.dataset?.itemId
|
|
const uuid = target.closest("[data-item-uuid]")?.dataset?.itemUuid
|
|
let item
|
|
if (uuid) item = await fromUuid(uuid)
|
|
if (!item) item = this.document.items.get(id)
|
|
item?.sheet.render(true)
|
|
}
|
|
|
|
static async #onItemDelete(event, target) {
|
|
const itemUuid = target.closest("[data-item-uuid]")?.dataset?.itemUuid
|
|
if (itemUuid) {
|
|
const item = await fromUuid(itemUuid)
|
|
await item?.deleteDialog()
|
|
return
|
|
}
|
|
const id = target.closest("[data-item-id]")?.dataset?.itemId
|
|
const item = this.document.items.get(id)
|
|
await item?.deleteDialog()
|
|
}
|
|
|
|
static async #onItemCreate(event, target) {
|
|
const type = target.dataset.type
|
|
if (!type) return
|
|
const name = game.i18n.localize("ITEMS.new_" + type)
|
|
await this.document.createEmbeddedDocuments("Item", [{ name, type }])
|
|
}
|
|
|
|
static async #onRollItem(event, target) {
|
|
const id = target.closest("[data-item-id]")?.dataset?.itemId
|
|
if (!id) return
|
|
const item = this.document.items.get(id)
|
|
item?.roll()
|
|
}
|
|
|
|
static #onClickRadioHexa(event, target) {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
const input = target
|
|
const update = {}
|
|
let current = this.document
|
|
const propTree = input.name.split(".")
|
|
for (const prop of propTree) {
|
|
current = current[prop]
|
|
}
|
|
if (current != input.value) {
|
|
update[input.name] = parseInt(input.value)
|
|
} else {
|
|
update[input.name] = parseInt(input.value) - 1
|
|
}
|
|
this.document.update(update)
|
|
}
|
|
|
|
static #onEffectControl(event, target) {
|
|
onManageActiveEffect(event, this.document)
|
|
}
|
|
|
|
static async #onChooseTotem(event, target) {
|
|
const { TotemPicker } = await import("../../system/applications.mjs")
|
|
new TotemPicker(target, this.document).render(true)
|
|
}
|
|
}
|