Migration FOundry v13/v14
This commit is contained in:
253
src/module/applications/sheets/item-sheet.mjs
Normal file
253
src/module/applications/sheets/item-sheet.mjs
Normal file
@@ -0,0 +1,253 @@
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
import { MGT2Helper } from "../../helper.js";
|
||||
|
||||
export default class TravellerItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["mgt2", "sheet", "item"],
|
||||
position: { width: 630 },
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
closeOnSubmit: false,
|
||||
},
|
||||
window: { resizable: true },
|
||||
actions: {
|
||||
careerEventCreate: TravellerItemSheet.#onCareerEventCreate,
|
||||
careerEventDelete: TravellerItemSheet.#onCareerEventDelete,
|
||||
optionCreate: TravellerItemSheet.#onOptionCreate,
|
||||
optionDelete: TravellerItemSheet.#onOptionDelete,
|
||||
modifierCreate: TravellerItemSheet.#onModifierCreate,
|
||||
modifierDelete: TravellerItemSheet.#onModifierDelete,
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
sheet: {
|
||||
// template is dynamic — resolved in _prepareContext / _renderHTML
|
||||
template: "",
|
||||
},
|
||||
}
|
||||
|
||||
/** Resolve template dynamically based on item type */
|
||||
get template() {
|
||||
return `systems/mgt2/templates/items/${this.document.type}-sheet.html`;
|
||||
}
|
||||
|
||||
tabGroups = { primary: "tab1" }
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const item = this.document;
|
||||
const source = item.toObject();
|
||||
|
||||
const settings = {
|
||||
usePronouns: game.settings.get("mgt2", "usePronouns"),
|
||||
};
|
||||
|
||||
let containers = null;
|
||||
let computers = null;
|
||||
let hadContainer = false;
|
||||
|
||||
if (item.actor !== null) {
|
||||
hadContainer = true;
|
||||
containers = [{ name: "", _id: "" }].concat(item.actor.getContainers());
|
||||
computers = [{ name: "", _id: "" }].concat(item.actor.getComputers());
|
||||
}
|
||||
|
||||
let weight = null;
|
||||
if (item.system.hasOwnProperty("weight")) {
|
||||
weight = MGT2Helper.convertWeightForDisplay(item.system.weight);
|
||||
}
|
||||
|
||||
let skills = [];
|
||||
if (this.actor !== null) {
|
||||
for (let actorItem of this.actor.items) {
|
||||
if (actorItem.type === "talent" && actorItem.system.subType === "skill")
|
||||
skills.push({ _id: actorItem._id, name: actorItem.getRollDisplay() });
|
||||
}
|
||||
}
|
||||
skills.sort(MGT2Helper.compareByName);
|
||||
skills = [{ _id: "NP", name: game.i18n.localize("MGT2.Items.NotProficient") }].concat(skills);
|
||||
|
||||
return {
|
||||
item: item,
|
||||
document: item,
|
||||
cssClass: this.isEditable ? "editable" : "locked",
|
||||
system: item.system,
|
||||
source: source.system,
|
||||
fields: item.schema.fields,
|
||||
systemFields: item.system.schema.fields,
|
||||
isEditable: this.isEditable,
|
||||
isGM: game.user.isGM,
|
||||
config: CONFIG,
|
||||
settings: settings,
|
||||
containers: containers,
|
||||
computers: computers,
|
||||
hadContainer: hadContainer,
|
||||
weight: weight,
|
||||
unitlabels: { weight: MGT2Helper.getWeightLabel() },
|
||||
skills: skills,
|
||||
};
|
||||
}
|
||||
|
||||
/** @override — resolve the per-type template before rendering */
|
||||
async _renderHTML(context, options) {
|
||||
const templatePath = `systems/mgt2/templates/items/${this.document.type}-sheet.html`;
|
||||
const html = await renderTemplate(templatePath, context);
|
||||
return { sheet: html };
|
||||
}
|
||||
|
||||
/** @override — put rendered HTML into the window content */
|
||||
_replaceHTML(result, content, options) {
|
||||
content.innerHTML = result.sheet;
|
||||
this._activateTabGroups();
|
||||
this._bindItemEvents();
|
||||
}
|
||||
|
||||
/** Bind CSS class-based events (templates not yet migrated to data-action) */
|
||||
_bindItemEvents() {
|
||||
const html = this.element;
|
||||
if (!this.isEditable) return;
|
||||
const bind = (sel, handler) => {
|
||||
for (const el of html.querySelectorAll(sel)) {
|
||||
el.addEventListener("click", (ev) => handler.call(this, ev, ev.currentTarget));
|
||||
}
|
||||
};
|
||||
bind(".event-create", TravellerItemSheet.#onCareerEventCreate);
|
||||
bind(".event-delete", TravellerItemSheet.#onCareerEventDelete);
|
||||
bind(".options-create", TravellerItemSheet.#onOptionCreate);
|
||||
bind(".options-delete", TravellerItemSheet.#onOptionDelete);
|
||||
bind(".modifiers-create", TravellerItemSheet.#onModifierCreate);
|
||||
bind(".modifiers-delete", TravellerItemSheet.#onModifierDelete);
|
||||
}
|
||||
|
||||
_activateTabGroups() {
|
||||
for (const [group, activeTab] of Object.entries(this.tabGroups)) {
|
||||
const nav = this.element.querySelector(`nav[data-group="${group}"], .horizontal-tabs`);
|
||||
if (!nav) continue;
|
||||
|
||||
nav.querySelectorAll('[data-tab]').forEach(link => {
|
||||
link.classList.toggle('active', link.dataset.tab === activeTab);
|
||||
link.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
this.tabGroups[group] = link.dataset.tab;
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
|
||||
this.element.querySelectorAll(`.itemsheet-panel [data-tab], [data-group="${group}"][data-tab]`).forEach(content => {
|
||||
content.classList.toggle('active', content.dataset.tab === activeTab);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** @override — process form data before submit (weight/qty/cost conversions + container logic) */
|
||||
_prepareSubmitData(event, form, formData) {
|
||||
const data = foundry.utils.expandObject(formData.object);
|
||||
|
||||
if (data.hasOwnProperty("weight")) {
|
||||
data.system = data.system || {};
|
||||
data.system.weight = MGT2Helper.convertWeightFromInput(data.weight);
|
||||
delete data.weight;
|
||||
}
|
||||
|
||||
if (data.system?.hasOwnProperty("quantity")) {
|
||||
data.system.quantity = MGT2Helper.getIntegerFromInput(data.system.quantity);
|
||||
}
|
||||
|
||||
if (data.system?.hasOwnProperty("cost")) {
|
||||
data.system.cost = MGT2Helper.getIntegerFromInput(data.system.cost);
|
||||
}
|
||||
|
||||
// Container/equipped logic
|
||||
if (data.system?.hasOwnProperty("container") && this.document.system.hasOwnProperty("equipped")) {
|
||||
const equippedChange = this.document.system.equipped !== data.system.equipped;
|
||||
const containerChange = this.document.system.container?.id !== data.system.container?.id;
|
||||
|
||||
if (equippedChange && data.system.equipped === true) {
|
||||
data.system.container = { id: "" };
|
||||
} else if (containerChange && data.system.container?.id !== "" && this.document.system.container?.id === "") {
|
||||
data.system.equipped = false;
|
||||
}
|
||||
}
|
||||
|
||||
return foundry.utils.flattenObject(data);
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Actions
|
||||
// =========================================================
|
||||
|
||||
static async #onCareerEventCreate(event) {
|
||||
event.preventDefault();
|
||||
const events = this.document.system.events;
|
||||
let newEvents;
|
||||
if (!events || events.length === 0) {
|
||||
newEvents = [{ age: "", description: "" }];
|
||||
} else {
|
||||
newEvents = [...events, { age: "", description: "" }];
|
||||
}
|
||||
return this.document.update({ system: { events: newEvents } });
|
||||
}
|
||||
|
||||
static async #onCareerEventDelete(event, target) {
|
||||
event.preventDefault();
|
||||
const element = target.closest("[data-events-part]");
|
||||
const index = Number(element.dataset.eventsPart);
|
||||
const events = foundry.utils.deepClone(this.document.system.events);
|
||||
const newEvents = Object.entries(events)
|
||||
.filter(([key]) => Number(key) !== index)
|
||||
.map(([, val]) => val);
|
||||
return this.document.update({ system: { events: newEvents } });
|
||||
}
|
||||
|
||||
static async #onOptionCreate(event, target) {
|
||||
event.preventDefault();
|
||||
const property = target.dataset.property;
|
||||
const options = this.document.system[property];
|
||||
let newOptions;
|
||||
if (!options || options.length === 0) {
|
||||
newOptions = [{ name: "", description: "" }];
|
||||
} else {
|
||||
newOptions = [...options, { name: "", description: "" }];
|
||||
}
|
||||
return this.document.update({ [`system.${property}`]: newOptions });
|
||||
}
|
||||
|
||||
static async #onOptionDelete(event, target) {
|
||||
event.preventDefault();
|
||||
const element = target.closest("[data-options-part]");
|
||||
const property = element.dataset.property;
|
||||
const index = Number(element.dataset.optionsPart);
|
||||
const options = foundry.utils.deepClone(this.document.system[property]);
|
||||
const newOptions = Object.entries(options)
|
||||
.filter(([key]) => Number(key) !== index)
|
||||
.map(([, val]) => val);
|
||||
return this.document.update({ [`system.${property}`]: newOptions });
|
||||
}
|
||||
|
||||
static async #onModifierCreate(event) {
|
||||
event.preventDefault();
|
||||
const modifiers = this.document.system.modifiers;
|
||||
let newModifiers;
|
||||
if (!modifiers || modifiers.length === 0) {
|
||||
newModifiers = [{ characteristic: "Endurance", value: null }];
|
||||
} else {
|
||||
newModifiers = [...modifiers, { characteristic: "Endurance", value: null }];
|
||||
}
|
||||
return this.document.update({ system: { modifiers: newModifiers } });
|
||||
}
|
||||
|
||||
static async #onModifierDelete(event, target) {
|
||||
event.preventDefault();
|
||||
const element = target.closest("[data-modifiers-part]");
|
||||
const index = Number(element.dataset.modifiersPart);
|
||||
const modifiers = foundry.utils.deepClone(this.document.system.modifiers);
|
||||
const newModifiers = Object.entries(modifiers)
|
||||
.filter(([key]) => Number(key) !== index)
|
||||
.map(([, val]) => val);
|
||||
return this.document.update({ system: { modifiers: newModifiers } });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user