import { DS4ItemDataType } from "../item/item-data"; import { DS4Actor } from "./actor"; import { DS4ActorDataType } from "./actor-data"; /** * Extend the basic ActorSheet with some very simple modifications * @extends {ActorSheet} */ export class DS4ActorSheet extends ActorSheet { /** @override */ static get defaultOptions(): FormApplicationOptions { return mergeObject(super.defaultOptions, { classes: ["ds4", "sheet", "actor"], width: 745, height: 600, tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "inventory" }], }); } /** @override */ get template(): string { const path = "systems/ds4/templates/actor"; return `${path}/${this.actor.data.type}-sheet.hbs`; } /* -------------------------------------------- */ /** * This method returns the data for the template of the actor sheet. * It explicitly adds the items of the object sorted by type in the * object itemsByType. * @returns the data fed to the template of the actor sheet */ getData(): ActorSheetData { const data = { ...super.getData(), // Add the localization config to the data: config: CONFIG.DS4, // Add the items explicitly sorted by type to the data: itemsByType: this.actor.itemTypes, }; console.log("Data:", data); return data; } /* -------------------------------------------- */ /** @override */ activateListeners(html: JQuery): void { super.activateListeners(html); // Everything below here is only needed if the sheet is editable if (!this.options.editable) return; // Add Inventory Item html.find(".item-create").on("click", this._onItemCreate.bind(this)); // Update Inventory Item html.find(".item-edit").on("click", (ev) => { const li = $(ev.currentTarget).parents(".item"); const item = this.actor.getOwnedItem(li.data("itemId")); item.sheet.render(true); }); // Delete Inventory Item html.find(".item-delete").on("click", (ev) => { const li = $(ev.currentTarget).parents(".item"); this.actor.deleteOwnedItem(li.data("itemId")); li.slideUp(200, () => this.render(false)); }); html.find(".item-change").on("change", this._onItemChange.bind(this)); // Rollable abilities. html.find(".rollable").click(this._onRoll.bind(this)); } /* -------------------------------------------- */ /** * Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset * @param {JQuery.ClickEvent} event The originating click event * @private */ private _onItemCreate(event: JQuery.ClickEvent): Promise { event.preventDefault(); const header = event.currentTarget; // Get the type of item to create. const type = header.dataset.type; // Grab any data associated with this control. const data = duplicate(header.dataset); // Initialize a default name. const name = `New ${type.capitalize()}`; // Prepare the item object. const itemData = { name: name, type: type, data: data, }; // Remove the type from the dataset since it's in the itemData.type prop. delete itemData.data.type; // Finally, create the item! return this.actor.createOwnedItem(itemData); } /** * Handle changes to properties of an Owned Item from within character sheet. * Can currently properly bind: see getValue(). * Assumes the item property is given as the value of the HTML element property 'data-property'. * @param {JQuery.ChangeEvent} ev The originating change event * @private */ private _onItemChange(ev: JQuery.ChangeEvent): void { ev.preventDefault(); console.log("Current target:", $(ev.currentTarget).get(0)["name"]); const el: HTMLFormElement = $(ev.currentTarget).get(0); const id = $(ev.currentTarget).parents(".item").data("itemId"); const item = duplicate(this.actor.getOwnedItem(id)); // getOwnedItem is typed incorrectly, it actually returns a ItemData, not an Item const property: string | undefined = $(ev.currentTarget).data("property"); // Early return: // Disabled => do nothing if (el.disabled || el.getAttribute("disabled")) return; // name not given => raise if (property === undefined) { throw TypeError("HTML element does not provide 'data-property' attribute"); } // Set new value const newValue = this.getValue(el); setProperty(item, property, newValue); this.actor.updateOwnedItem(item); } /** * Collect the value of a form element depending on the element's type * The value is parsed to: * - Checkbox: boolean * - Text input: string * - Number: number * @param el the input element to collect the value of */ private getValue(el: HTMLFormElement): boolean | string | number { // One needs to differentiate between e.g. checkboxes (value="on") and select boxes etc. // Checkbox: if (el.type === "checkbox") { const value: boolean = el.checked; return value; } // Text input: else if (el.type === "text") { const value: string = el.value; return value; } // Numbers: else if (el.type === "number") { const value = Number(el.value.trim()); return value; } // // Ranges: // else if (el.type === "range") { // const value: string = el.value.trim(); // return value; // } // // Radio Checkboxes (untested, cf. FormDataExtended.process) // else if (el.type === "radio") { // const chosen: HTMLFormElement = el.find((r: HTMLFormElement) => r["checked"]); // const value: string = chosen ? chosen.value : null; // return value; // } // // Multi-Select (untested, cf. FormDataExtended.process) // else if (el.type === "select-multiple") { // const value: Array = []; // el.options.array.forEach((opt: HTMLOptionElement) => { // if (opt.selected) value.push(opt.value); // }); // return value; // unsupported: else { throw TypeError("Binding of item property to this type of HTML element not supported; given: " + el); } } /** * Handle clickable rolls. * @param {JQuery.ClickEvent} event The originating click event * @private */ private _onRoll(event: JQuery.ClickEvent): void { event.preventDefault(); const element = event.currentTarget; const dataset = element.dataset; if (dataset.roll) { const roll = new Roll(dataset.roll, this.actor.data.data); const label = dataset.label ? `Rolling ${dataset.label}` : ""; roll.roll().toMessage({ speaker: ChatMessage.getSpeaker({ actor: this.actor }), flavor: label, }); } } }