diff --git a/src/apps/actor/base-sheet.js b/src/apps/actor/base-sheet.js
index bbe99672..ffc97282 100644
--- a/src/apps/actor/base-sheet.js
+++ b/src/apps/actor/base-sheet.js
@@ -67,12 +67,12 @@ export class DS4ActorSheet extends foundry.applications.api.DocumentSheetV2 {
}
/** @override */
- async _renderHTML(context, options) {
+ async _renderHTML(context) {
return await foundry.applications.handlebars.renderTemplate(this.template, context);
}
/** @override */
- _replaceHTML(result, content, options) {
+ _replaceHTML(result, content) {
content.innerHTML = result;
}
@@ -210,7 +210,7 @@ export class DS4ActorSheet extends foundry.applications.api.DocumentSheetV2 {
try {
const roll = new Roll(value);
return roll.evaluateSync().total;
- } catch (error) {
+ } catch {
return value;
}
}
diff --git a/src/apps/item-sheet.js b/src/apps/item-sheet.js
index e9b84533..6e4825f8 100644
--- a/src/apps/item-sheet.js
+++ b/src/apps/item-sheet.js
@@ -4,24 +4,42 @@
//
// SPDX-License-Identifier: MIT
-import { DS4 } from "../config";
-import { DS4ActiveEffect } from "../documents/active-effect";
-import { enforce, getGame } from "../utils/utils";
-import { disableOverriddenFields } from "./sheet-helpers";
-
/**
* The Sheet class for DS4 Items
*/
-export class DS4ItemSheet extends foundry.appv1.sheets.ItemSheet {
- /** @override */
- static get defaultOptions() {
- return foundry.utils.mergeObject(super.defaultOptions, {
- classes: ["sheet", "ds4-item-sheet"],
- height: 400,
- scrollY: [".ds4-sheet-body"],
- tabs: [{ navSelector: ".ds4-sheet-tab-nav", contentSelector: ".ds4-sheet-body", initial: "description" }],
+export class DS4ItemSheet extends foundry.applications.sheets.ItemSheetV2 {
+ static DEFAULT_OPTIONS = {
+ classes: ["sheet", "ds4-item-sheet"],
+ tag: "form",
+ form: {
+ submitOnChange: true,
+ closeOnSubmit: false,
+ },
+ position: {
width: 540,
- });
+ height: 400,
+ },
+ window: {
+ resizable: true,
+ },
+ actions: {
+ controlEffect: DS4ItemSheet.prototype._onControlEffect,
+ createEffect: DS4ItemSheet.prototype._onCreateEffect,
+ editEffect: DS4ItemSheet.prototype._onEditEffect,
+ deleteEffect: DS4ItemSheet.prototype._onDeleteEffect,
+ changeTab: DS4ItemSheet.prototype._onChangeTab,
+ },
+ };
+
+ static TABS = {};
+
+ constructor(options = {}) {
+ super(options);
+ this.activeTab = "description"; // Default active tab
+ }
+
+ get title() {
+ return `${this.item.name} [${game.i18n.localize("DS4.ItemSheet")}]`;
}
/** @override */
@@ -31,123 +49,242 @@ export class DS4ItemSheet extends foundry.appv1.sheets.ItemSheet {
}
/** @override */
- async getData(options = {}) {
- const superContext = await super.getData(options);
- superContext.data.system.description = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
- superContext.data.system.description,
- {
- async: true,
- },
- );
- const context = {
- ...superContext,
- config: DS4,
- isOwned: this.item.isOwned,
- actor: this.item.actor,
- };
+ async _renderHTML(context) {
+ return await foundry.applications.handlebars.renderTemplate(this.template, context);
+ }
+
+ /** @override */
+ _replaceHTML(result, content) {
+ content.innerHTML = result;
+ }
+
+ /** @override */
+ async _prepareContext(options) {
+ const context = await super._prepareContext(options);
+
+ // Validate document exists
+ if (!this.item) {
+ throw new Error("Item not available for sheet rendering");
+ }
+
+ // Add document data
+ context.data = this.item;
+ context.system = this.item.system;
+ context.source = this.item.toObject();
+ context.cssClass = this.item.constructor.name.toLowerCase();
+ context.editable = this.isEditable;
+ context.owner = this.item.isOwner;
+ context.isOwned = this.item.isOwned;
+ context.actor = this.actor;
+
+ // Add configuration
+ context.config = CONFIG.DS4;
+
+ // Add enriched effects
+ context.enrichedEffects = [];
+ if (this.item.effects && this.item.effects.size > 0) {
+ for (const effect of this.item.effects) {
+ const enrichedEffect = effect.toObject();
+ enrichedEffect.sourceName = effect.sourceName;
+ context.enrichedEffects.push(enrichedEffect);
+ }
+ }
+
+ // Enrich description HTML content
+ if (context.data.system.description) {
+ context.data.system.description = await TextEditor.enrichHTML(
+ context.data.system.description,
+ {
+ async: true,
+ relativeTo: this.item,
+ }
+ );
+ }
+
return context;
}
- /** @override */
- _getSubmitData(updateData = {}) {
- const data = super._getSubmitData(updateData);
+ /**
+ * Process form data for submission
+ * @param {Event} event - The form submission event
+ * @param {HTMLFormElement} form - The form element
+ * @param {FormDataExtended} formData - The form data
+ * @returns {object} The processed form data
+ */
+ _processFormData(event, form, formData) {
+ const submitData = foundry.utils.expandObject(formData.object);
+
// Prevent submitting overridden values
const overrides = foundry.utils.flattenObject(this.item.overrides);
for (const k of Object.keys(overrides)) {
- delete data[k];
+ foundry.utils.setProperty(submitData, k, undefined);
+ delete submitData[k];
+ }
+
+ return submitData;
+ }
+
+ /**
+ * Handle effect control actions
+ * @param {Event} event - The triggering event
+ * @param {HTMLElement} target - The target element
+ */
+ async _onControlEffect(event, target) {
+ const action = target.dataset.action;
+ const effectId = target.closest("[data-effect-id]")?.dataset.effectId;
+
+ if (!effectId) return;
+
+ const effect = this.item.effects.get(effectId);
+ if (!effect) return;
+
+ switch (action) {
+ case "edit":
+ await this._onEditEffect(event, target);
+ break;
+ case "delete":
+ await this._onDeleteEffect(event, target);
+ break;
+ }
+ }
+
+ /**
+ * Handle creating a new effect
+ */
+ async _onCreateEffect() {
+ const effectData = {
+ name: game.i18n.localize("DS4.EffectNew"),
+ icon: "icons/svg/aura.svg",
+ };
+
+ await this.item.createEmbeddedDocuments("ActiveEffect", [effectData]);
+ }
+
+ /**
+ * Handle editing an effect
+ * @param {Event} event - The triggering event
+ * @param {HTMLElement} target - The target element
+ */
+ async _onEditEffect(event, target) {
+ const effectId = target.closest("[data-effect-id]")?.dataset.effectId;
+ if (!effectId) return;
+
+ const effect = this.item.effects.get(effectId);
+ if (!effect) {
+ throw new Error(game.i18n.format("DS4.ErrorItemDoesNotHaveEffect", {
+ id: effectId,
+ item: this.item.name
+ }));
+ }
+
+ await effect.sheet.render(true);
+ }
+
+ /**
+ * Handle deleting an effect
+ * @param {Event} event - The triggering event
+ * @param {HTMLElement} target - The target element
+ */
+ async _onDeleteEffect(event, target) {
+ const effectId = target.closest("[data-effect-id]")?.dataset.effectId;
+ if (!effectId) return;
+
+ const effect = this.item.effects.get(effectId);
+ if (!effect) return;
+
+ const confirmed = await foundry.applications.api.DialogV2.confirm({
+ window: { title: game.i18n.localize("DS4.UserInteractionDeleteEffectTitle") },
+ content: game.i18n.format("DS4.UserInteractionDeleteEffectContent", { effect: effect.name }),
+ defaultYes: false,
+ });
+
+ if (confirmed) {
+ await effect.delete();
+ }
+ }
+
+ /**
+ * Handle tab changes manually for custom tab behavior
+ * @param {Event} event - The triggering event
+ * @param {HTMLElement} target - The target element
+ */
+ async _onChangeTab(event, target) {
+ event.preventDefault();
+ const tab = target.dataset.tab;
+ if (!tab) return;
+
+ // Store the active tab
+ this.activeTab = tab;
+
+ // Find tab navigation elements
+ const nav = target.closest(".ds4-sheet-tab-nav");
+ const sheet = this.element.querySelector(".ds4-sheet-body");
+
+ if (!nav || !sheet) return;
+
+ // Update navigation active state
+ nav.querySelectorAll(".ds4-sheet-tab-nav__item").forEach(item => {
+ item.classList.remove("active");
+ });
+ target.classList.add("active");
+
+ // Update tab content visibility
+ sheet.querySelectorAll(".ds4-sheet-tab").forEach(tabContent => {
+ tabContent.classList.remove("active");
+ });
+
+ const activeTab = sheet.querySelector(`.ds4-sheet-tab[data-tab="${tab}"]`);
+ if (activeTab) {
+ activeTab.classList.add("active");
}
- return data;
}
/** @override */
- setPosition(options = {}) {
- const position = super.setPosition(options);
- if (position) {
- const sheetBody = this.element.find(".sheet-body");
- const bodyHeight = position.height - 192;
- sheetBody.css("height", bodyHeight);
+ async _onRender(context, options) {
+ await super._onRender(context, options);
+
+ // Initialize first tab as active
+ this._initializeTabs();
+ }
+
+ /**
+ * Initialize tab state - show first tab, hide others
+ */
+ _initializeTabs() {
+ const nav = this.element.querySelector(".ds4-sheet-tab-nav");
+ const sheet = this.element.querySelector(".ds4-sheet-body");
+
+ if (!nav || !sheet) {
+ return;
}
- return position;
- }
+ // Get all tab navigation items and tab content
+ const navItems = nav.querySelectorAll(".ds4-sheet-tab-nav__item");
+ const tabContents = sheet.querySelectorAll(".ds4-sheet-tab");
- /**
- * @override
- * @param {JQuery} html
- */
- activateListeners(html) {
- super.activateListeners(html);
+ // Remove active class from all items first
+ navItems.forEach(item => item.classList.remove("active"));
+ tabContents.forEach(content => content.classList.remove("active"));
- if (!this.options.editable) return;
+ // Find the currently active tab or default to first
+ let targetTab = this.activeTab;
+ let targetNavItem = nav.querySelector(`[data-tab="${targetTab}"]`);
- html.find(".control-effect").on("click", this.onControlEffect.bind(this));
-
- disableOverriddenFields(this.form, this.item.overrides, (key) => `[name="${key}"]`);
- }
-
- /**
- * Handles a click on an element of this sheet to control an embedded effect of the item corresponding to this
- * sheet.
- *
- * @param {JQuery.ClickEvent} event The originating click event
- * @protected
- */
- onControlEffect(event) {
- event.preventDefault();
- const a = event.currentTarget;
- switch (a.dataset["action"]) {
- case "create":
- return this.onCreateEffect();
- case "edit":
- return this.onEditEffect(event);
- case "delete":
- return this.onDeleteEffect(event);
+ // If stored tab doesn't exist, fall back to first tab
+ if (!targetNavItem) {
+ targetNavItem = navItems[0];
+ targetTab = targetNavItem?.dataset.tab;
}
- }
- /**
- * Creates a new embedded effect.
- * @protected
- */
- onCreateEffect() {
- DS4ActiveEffect.createDefault(this.item);
- }
+ // Set target tab navigation as active
+ if (targetNavItem && targetTab) {
+ targetNavItem.classList.add("active");
- /**
- * Opens the sheet of the embedded effect corresponding to the clicked element.
- *
- * @param {JQuery.ClickEvent} event The originating click event
- * @porotected
- */
- onEditEffect(event) {
- const id = $(event.currentTarget)
- .parents(embeddedDocumentListEntryProperties.ActiveEffect.selector)
- .data(embeddedDocumentListEntryProperties.ActiveEffect.idDataAttribute);
- const effect = this.item.effects.get(id);
- enforce(effect, getGame().i18n.format("DS4.ErrorItemDoesNotHaveEffect", { id, item: this.item.name }));
- effect.sheet?.render(true);
- }
-
- /**
- * Deletes the embedded item corresponding to the clicked element.
- *
- * @param {JQuery.ClickEvent} event The originating click event
- * @protected
- */
- onDeleteEffect(event) {
- const li = $(event.currentTarget).parents(embeddedDocumentListEntryProperties.ActiveEffect.selector);
- const id = li.data(embeddedDocumentListEntryProperties.ActiveEffect.idDataAttribute);
- this.item.deleteEmbeddedDocuments("ActiveEffect", [id]);
- li.slideUp(200, () => this.render(false));
+ // Set corresponding tab content as active
+ const activeTabContent = sheet.querySelector(`.ds4-sheet-tab[data-tab="${targetTab}"]`);
+ if (activeTabContent) {
+ activeTabContent.classList.add("active");
+ }
+ }
}
}
-
-/**
- * This object contains information about specific properties embedded document list entries for each different type.
- */
-const embeddedDocumentListEntryProperties = Object.freeze({
- ActiveEffect: {
- selector: ".effect",
- idDataAttribute: "effectId",
- },
-});
diff --git a/templates/sheets/item/alphabet-sheet.hbs b/templates/sheets/item/alphabet-sheet.hbs
index f7089cf6..2f2d66de 100644
--- a/templates/sheets/item/alphabet-sheet.hbs
+++ b/templates/sheets/item/alphabet-sheet.hbs
@@ -10,8 +10,8 @@ SPDX-License-Identifier: MIT
{{!-- Sheet Tab Navigation --}}
{{!-- Sheet Body --}}
diff --git a/templates/sheets/item/armor-sheet.hbs b/templates/sheets/item/armor-sheet.hbs
index 78d8be64..548f7e68 100644
--- a/templates/sheets/item/armor-sheet.hbs
+++ b/templates/sheets/item/armor-sheet.hbs
@@ -10,9 +10,9 @@ SPDX-License-Identifier: MIT
{{!-- Sheet Tab Navigation --}}
{{!-- Sheet Body --}}
diff --git a/templates/sheets/item/equipment-sheet.hbs b/templates/sheets/item/equipment-sheet.hbs
index eeb322ff..05ae6f13 100644
--- a/templates/sheets/item/equipment-sheet.hbs
+++ b/templates/sheets/item/equipment-sheet.hbs
@@ -10,9 +10,9 @@ SPDX-License-Identifier: MIT
{{!-- Sheet Tab Navigation --}}
{{!-- Sheet Body --}}
diff --git a/templates/sheets/item/language-sheet.hbs b/templates/sheets/item/language-sheet.hbs
index f7089cf6..2f2d66de 100644
--- a/templates/sheets/item/language-sheet.hbs
+++ b/templates/sheets/item/language-sheet.hbs
@@ -10,8 +10,8 @@ SPDX-License-Identifier: MIT
{{!-- Sheet Tab Navigation --}}
{{!-- Sheet Body --}}
diff --git a/templates/sheets/item/loot-sheet.hbs b/templates/sheets/item/loot-sheet.hbs
index 0216ecc5..85524857 100644
--- a/templates/sheets/item/loot-sheet.hbs
+++ b/templates/sheets/item/loot-sheet.hbs
@@ -10,9 +10,9 @@ SPDX-License-Identifier: MIT
{{!-- Sheet Tab Navigation --}}
{{!-- Sheet Body --}}
diff --git a/templates/sheets/item/racialAbility-sheet.hbs b/templates/sheets/item/racialAbility-sheet.hbs
index f7089cf6..2f2d66de 100644
--- a/templates/sheets/item/racialAbility-sheet.hbs
+++ b/templates/sheets/item/racialAbility-sheet.hbs
@@ -10,8 +10,8 @@ SPDX-License-Identifier: MIT
{{!-- Sheet Tab Navigation --}}
{{!-- Sheet Body --}}
diff --git a/templates/sheets/item/shield-sheet.hbs b/templates/sheets/item/shield-sheet.hbs
index 6b9c8803..9a07cce9 100644
--- a/templates/sheets/item/shield-sheet.hbs
+++ b/templates/sheets/item/shield-sheet.hbs
@@ -10,9 +10,9 @@ SPDX-License-Identifier: MIT
{{!-- Sheet Tab Navigation --}}
{{!-- Sheet Body --}}
diff --git a/templates/sheets/item/specialCreatureAbility-sheet.hbs b/templates/sheets/item/specialCreatureAbility-sheet.hbs
index 83b3be7d..002852cc 100644
--- a/templates/sheets/item/specialCreatureAbility-sheet.hbs
+++ b/templates/sheets/item/specialCreatureAbility-sheet.hbs
@@ -10,9 +10,9 @@ SPDX-License-Identifier: MIT
{{!-- Sheet Tab Navigation --}}
{{!-- Sheet Body --}}
diff --git a/templates/sheets/item/spell-sheet.hbs b/templates/sheets/item/spell-sheet.hbs
index 1291a407..38874d00 100644
--- a/templates/sheets/item/spell-sheet.hbs
+++ b/templates/sheets/item/spell-sheet.hbs
@@ -10,9 +10,9 @@ SPDX-License-Identifier: MIT
{{!-- Sheet Tab Navigation --}}
{{!-- Sheet Body --}}
diff --git a/templates/sheets/item/talent-sheet.hbs b/templates/sheets/item/talent-sheet.hbs
index a6faf4f4..0e7d94d5 100644
--- a/templates/sheets/item/talent-sheet.hbs
+++ b/templates/sheets/item/talent-sheet.hbs
@@ -10,9 +10,9 @@ SPDX-License-Identifier: MIT
{{!-- Sheet Tab Navigation --}}
{{!-- Sheet Body --}}
diff --git a/templates/sheets/item/weapon-sheet.hbs b/templates/sheets/item/weapon-sheet.hbs
index c08ff3de..05319a7a 100644
--- a/templates/sheets/item/weapon-sheet.hbs
+++ b/templates/sheets/item/weapon-sheet.hbs
@@ -10,9 +10,9 @@ SPDX-License-Identifier: MIT
{{!-- Sheet Tab Navigation --}}
{{!-- Sheet Body --}}