diff --git a/.editorconfig b/.editorconfig index 12b53f83..ba4931db 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,6 @@ root = true end_of_line = lf insert_final_newline = true indent_style = space -indent_size = 2 +indent_size = 4 charset = utf-8 trim_trailing_whitespace = true diff --git a/lang/de.json b/lang/de.json index c5176813..3f7208c1 100644 --- a/lang/de.json +++ b/lang/de.json @@ -184,9 +184,7 @@ "DS4.EffectFactor": "Faktor (wie oft der Effekt angewendet wird)", "DS4.EffectFactorAbbr": "F", "DS4.ActorName": "Name", - "DS4.ActorSheet": "Aktorbogen", "DS4.ActorImageAltText": "Bild des Aktors", - "DS4.ItemSheet": "Itembogen", "DS4.ActorTypeCharacter": "Charakter", "DS4.ActorTypeCreature": "Kreatur", "DS4.Attribute": "Attribut", diff --git a/lang/en.json b/lang/en.json index 8b032343..e58b6caf 100644 --- a/lang/en.json +++ b/lang/en.json @@ -184,9 +184,7 @@ "DS4.EffectFactor": "Factor (the number of times the effect is being applied)", "DS4.EffectFactorAbbr": "F", "DS4.ActorName": "Name", - "DS4.ActorSheet": "Actor Sheet", "DS4.ActorImageAltText": "Image of the Actor", - "DS4.ItemSheet": "Item Sheet", "DS4.ActorTypeCharacter": "Character", "DS4.ActorTypeCreature": "Creature", "DS4.Attribute": "Attribute", diff --git a/scss/components/shared/_ruler.scss b/scss/components/shared/_ruler.scss index ebbb188d..3fdb0065 100644 --- a/scss/components/shared/_ruler.scss +++ b/scss/components/shared/_ruler.scss @@ -53,8 +53,7 @@ /* Icon animations */ @keyframes pulse { - 0%, - 100% { + 0%, 100% { opacity: 1; } 50% { diff --git a/scss/utils/_variables.scss b/scss/utils/_variables.scss index 9909c4ed..f01f73be 100644 --- a/scss/utils/_variables.scss +++ b/scss/utils/_variables.scss @@ -6,6 +6,7 @@ @use "./colors"; + $padding-sm: 5px; $padding-md: 10px; $padding-lg: 20px; diff --git a/src/apps/actor/base-sheet.js b/src/apps/actor/base-sheet.js index 285a023a..55e2163a 100644 --- a/src/apps/actor/base-sheet.js +++ b/src/apps/actor/base-sheet.js @@ -6,12 +6,12 @@ // // SPDX-License-Identifier: MIT + + /** * The base sheet class for DS4 Actor Sheets */ -export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicationMixin( - foundry.applications.sheets.ActorSheetV2, -) { +export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) { static DEFAULT_OPTIONS = { classes: ["sheet", "ds4-actor-sheet", "themed"], tag: "form", @@ -41,7 +41,14 @@ export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicatio changeEffect: DS4ActorSheet.prototype._onChangeEffect, sortItems: DS4ActorSheet.prototype._onSortItems, changeTab: DS4ActorSheet.prototype._onChangeTab, - editImage: DS4ActorSheet.prototype._onEditImage, + edititem: DS4ActorSheet.prototype._onEditItem, + deleteitem: DS4ActorSheet.prototype._onDeleteItem, + createitem: DS4ActorSheet.prototype._onCreateItem, + editeffect: DS4ActorSheet.prototype._onEditEffect, + deleteeffect: DS4ActorSheet.prototype._onDeleteEffect, + createeffect: DS4ActorSheet.prototype._onCreateEffect, + editImage: DS4ActorSheet._onEditImage, + }, }; @@ -58,14 +65,14 @@ export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicatio /** @override */ get template() { - const templatePath = - !game.user?.isGM && this.document.limited - ? "systems/ds4/templates/sheets/actor/limited-sheet.hbs" - : `systems/ds4/templates/sheets/actor/${this.document.type}-sheet.hbs`; + const templatePath = !game.user?.isGM && this.document.limited + ? "systems/ds4/templates/sheets/actor/limited-sheet.hbs" + : `systems/ds4/templates/sheets/actor/${this.document.type}-sheet.hbs`; return templatePath; } + /** @override */ async _renderHTML(context) { return await foundry.applications.handlebars.renderTemplate(this.template, context); @@ -76,6 +83,8 @@ export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicatio content.innerHTML = result; } + + /** @override */ async _prepareContext(options) { const context = await super._prepareContext(options); @@ -85,6 +94,8 @@ export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicatio throw new Error("Document not available for sheet rendering"); } + + // Add document data context.data = this.document; context.system = this.document.system; @@ -100,6 +111,8 @@ export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicatio showSlayerPoints: game.settings.get("ds4", "showSlayerPoints") || false, }; + + // Add items organized by type context.itemsByType = {}; if (this.document.items && this.document.items.size > 0) { @@ -339,6 +352,7 @@ export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicatio } } + /** * Handle changing an item property * @param {Event} event - The triggering event @@ -470,6 +484,8 @@ export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicatio } } + + /** * Handle sorting items * @param {Event} event - The triggering event @@ -481,7 +497,7 @@ export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicatio if (!dataPath || !itemType) return; - const items = this.document.items.filter((item) => item.type === itemType); + const items = this.document.items.filter(item => item.type === itemType); const sortedItems = this.sortItems(items, dataPath); const updates = sortedItems.map((item, index) => ({ @@ -533,13 +549,13 @@ export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicatio if (!nav || !sheet) return; // Update navigation active state - nav.querySelectorAll(".ds4-sheet-tab-nav__item").forEach((item) => { + 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) => { + sheet.querySelectorAll(".ds4-sheet-tab").forEach(tabContent => { tabContent.classList.remove("active"); }); @@ -573,8 +589,8 @@ export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicatio const tabContents = sheet.querySelectorAll(".ds4-sheet-tab"); // Remove active class from all items first - navItems.forEach((item) => item.classList.remove("active")); - tabContents.forEach((content) => content.classList.remove("active")); + navItems.forEach(item => item.classList.remove("active")); + tabContents.forEach(content => content.classList.remove("active")); // Find the currently active tab or default to first let targetTab = this.activeTab; @@ -610,7 +626,7 @@ export class DS4ActorSheet extends foundry.applications.api.HandlebarsApplicatio const fp = new foundry.applications.apps.FilePicker({ type: "image", current: current, - callback: (path) => this.document.update({ [field]: path }), + callback: (path) => this.document.update({ [field]: path }) }); return fp.browse(); diff --git a/src/apps/actor/character-sheet.js b/src/apps/actor/character-sheet.js index 715b345d..1e25b487 100644 --- a/src/apps/actor/character-sheet.js +++ b/src/apps/actor/character-sheet.js @@ -25,7 +25,7 @@ export class DS4CharacterActorSheet extends DS4ActorSheet { { async: true, relativeTo: this.document, - }, + } ); } diff --git a/src/apps/actor/creature-sheet.js b/src/apps/actor/creature-sheet.js index 3c68de8e..9bd8cd1f 100644 --- a/src/apps/actor/creature-sheet.js +++ b/src/apps/actor/creature-sheet.js @@ -25,7 +25,7 @@ export class DS4CreatureActorSheet extends DS4ActorSheet { { async: true, relativeTo: this.document, - }, + } ); } diff --git a/src/apps/dialog-with-listeners.js b/src/apps/dialog-with-listeners.js index b495b867..5eba545d 100644 --- a/src/apps/dialog-with-listeners.js +++ b/src/apps/dialog-with-listeners.js @@ -25,7 +25,7 @@ export class DialogWithListeners extends foundry.applications.api.DialogV2 { await super._onRender(context, options); // Attach additional listeners if provided - if (this.activateAdditionalListeners && typeof this.activateAdditionalListeners === "function") { + if (this.activateAdditionalListeners && typeof this.activateAdditionalListeners === 'function') { this.activateAdditionalListeners(this.element, this); } } diff --git a/src/apps/item-sheet.js b/src/apps/item-sheet.js index 8ff85ee7..004b3ab9 100644 --- a/src/apps/item-sheet.js +++ b/src/apps/item-sheet.js @@ -8,9 +8,7 @@ /** * The Sheet class for DS4 Items */ -export class DS4ItemSheet extends foundry.applications.api.HandlebarsApplicationMixin( - foundry.applications.sheets.ItemSheetV2, -) { +export class DS4ItemSheet extends foundry.applications.api.HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) { static DEFAULT_OPTIONS = { classes: ["sheet", "ds4-item-sheet", "themed"], tag: "form", @@ -28,13 +26,18 @@ export class DS4ItemSheet extends foundry.applications.api.HandlebarsApplication actions: { controlEffect: DS4ItemSheet.prototype._onControlEffect, createEffect: DS4ItemSheet.prototype._onCreateEffect, + createeffect: DS4ItemSheet.prototype._onCreateEffect, editEffect: DS4ItemSheet.prototype._onEditEffect, + editeffect: DS4ItemSheet.prototype._onEditEffect, deleteEffect: DS4ItemSheet.prototype._onDeleteEffect, + deleteeffect: DS4ItemSheet.prototype._onDeleteEffect, changeTab: DS4ItemSheet.prototype._onChangeTab, editImage: DS4ItemSheet.prototype._onEditImage, }, }; + + static TABS = {}; constructor(options = {}) { @@ -102,13 +105,14 @@ export class DS4ItemSheet extends foundry.applications.api.HandlebarsApplication this.item.system.description, { secrets: this.item.isOwner, - relativeTo: this.item, - }, + relativeTo: this.item + } ); } else { context.enrichedDescription = ""; } + return context; } @@ -179,12 +183,10 @@ export class DS4ItemSheet extends foundry.applications.api.HandlebarsApplication const effect = this.item.effects.get(effectId); if (!effect) { - throw new Error( - game.i18n.format("DS4.ErrorItemDoesNotHaveEffect", { - id: effectId, - item: this.item.name, - }), - ); + throw new Error(game.i18n.format("DS4.ErrorItemDoesNotHaveEffect", { + id: effectId, + item: this.item.name + })); } await effect.sheet.render(true); @@ -233,13 +235,13 @@ export class DS4ItemSheet extends foundry.applications.api.HandlebarsApplication if (!nav || !sheet) return; // Update navigation active state - nav.querySelectorAll(".ds4-sheet-tab-nav__item").forEach((item) => { + 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) => { + sheet.querySelectorAll(".ds4-sheet-tab").forEach(tabContent => { tabContent.classList.remove("active"); }); @@ -261,7 +263,7 @@ export class DS4ItemSheet extends foundry.applications.api.HandlebarsApplication const fp = new foundry.applications.apps.FilePicker({ type: "image", current: current, - callback: (path) => this.item.update({ [field]: path }), + callback: (path) => this.item.update({ [field]: path }) }); return fp.browse(); } @@ -274,6 +276,8 @@ export class DS4ItemSheet extends foundry.applications.api.HandlebarsApplication this._initializeTabs(); } + + /** @override */ async _onClose(options) { await super._onClose(options); @@ -295,8 +299,8 @@ export class DS4ItemSheet extends foundry.applications.api.HandlebarsApplication const tabContents = sheet.querySelectorAll(".ds4-sheet-tab"); // Remove active class from all items first - navItems.forEach((item) => item.classList.remove("active")); - tabContents.forEach((content) => content.classList.remove("active")); + navItems.forEach(item => item.classList.remove("active")); + tabContents.forEach(content => content.classList.remove("active")); // Find the currently active tab or default to first let targetTab = this.activeTab; @@ -308,6 +312,8 @@ export class DS4ItemSheet extends foundry.applications.api.HandlebarsApplication targetTab = targetNavItem?.dataset.tab; } + + // Set target tab navigation as active if (targetNavItem && targetTab) { targetNavItem.classList.add("active"); diff --git a/src/apps/ruler/token-ruler.js b/src/apps/ruler/token-ruler.js index 467ffa0d..3d48362a 100644 --- a/src/apps/ruler/token-ruler.js +++ b/src/apps/ruler/token-ruler.js @@ -7,38 +7,41 @@ * Based on actor movement combat value */ export class DS4TokenRuler extends foundry.canvas.placeables.tokens.TokenRuler { - static WAYPOINT_LABEL_TEMPLATE = "systems/ds4/templates/partials/waypoint-label.hbs"; - /** - * Enhance waypoint label context with movement range information - * @param {object} waypoint - The waypoint data - * @param {object} state - The current ruler state - * @returns {object} Enhanced context with range class - */ - _getWaypointLabelContext(waypoint, state) { - const context = super._getWaypointLabelContext(waypoint, state); + static WAYPOINT_LABEL_TEMPLATE = "systems/ds4/templates/partials/waypoint-label.hbs"; - // Only apply movement coloring for distance measurements in meters - if (context?.cost?.units === "m" || context?.distance?.units === "m") { - const movement = this.token?.actor?.system?.combatValues?.movement?.total; - if (movement) { - const total = Number(context.cost?.total || context.distance?.total || 0); - // DS4 movement rules: - // - Normal movement: up to movement value - // - Dash: up to 2x movement value (requires action) - // - Beyond 2x: impossible in single turn - if (total > 2 * movement) { - context.rangeClass = "out-of-range"; - } else if (total <= movement) { - context.rangeClass = "move-range"; - } else { - context.rangeClass = "dash-range"; + /** + * Enhance waypoint label context with movement range information + * @param {object} waypoint - The waypoint data + * @param {object} state - The current ruler state + * @returns {object} Enhanced context with range class + */ + _getWaypointLabelContext(waypoint, state) { + const context = super._getWaypointLabelContext(waypoint, state); + + // Only apply movement coloring for distance measurements in meters + if (context?.cost?.units === "m" || context?.distance?.units === "m") { + const movement = this.token?.actor?.system?.combatValues?.movement?.total; + + if (movement) { + const total = Number(context.cost?.total || context.distance?.total || 0); + + // DS4 movement rules: + // - Normal movement: up to movement value + // - Dash: up to 2x movement value (requires action) + // - Beyond 2x: impossible in single turn + if (total > 2 * movement) { + context.rangeClass = "out-of-range"; + } else if (total <= movement) { + context.rangeClass = "move-range"; + } else { + context.rangeClass = "dash-range"; + } + } } - } - } - return context; - } + return context; + } } diff --git a/src/dice/check-factory.js b/src/dice/check-factory.js index 250dcd83..d5a64c10 100644 --- a/src/dice/check-factory.js +++ b/src/dice/check-factory.js @@ -212,10 +212,7 @@ async function askForInteractiveRollData(checkTargetNumber, options = {}, { temp if (checkModifierSelect) { checkModifierSelect.addEventListener("change", (event) => { - if ( - event.currentTarget.value === "custom" && - checkModifierCustomFormGroup?.classList.contains("ds4-hidden") - ) { + if (event.currentTarget.value === "custom" && checkModifierCustomFormGroup?.classList.contains("ds4-hidden")) { checkModifierCustomFormGroup.classList.remove("ds4-hidden"); app.setPosition({ height: "auto" }); } else if (checkModifierCustomFormGroup && !checkModifierCustomFormGroup.classList.contains("ds4-hidden")) { diff --git a/src/documents/actor/actor.js b/src/documents/actor/actor.js index 0725a444..a7d0b2c3 100644 --- a/src/documents/actor/actor.js +++ b/src/documents/actor/actor.js @@ -466,33 +466,30 @@ export class DS4Actor extends Actor { const traitIdentifier = "attribute-trait-selection-trait"; return foundry.applications.api.DialogV2.prompt({ window: { title: getGame().i18n.localize("DS4.DialogAttributeTraitSelection") }, - content: await foundry.applications.handlebars.renderTemplate( - "systems/ds4/templates/dialogs/simple-select-form.hbs", - { - selects: [ - { - label: getGame().i18n.localize("DS4.Attribute"), - identifier: attributeIdentifier, - options: Object.fromEntries( - Object.entries(DS4.i18n.attributes).map(([attribute, translation]) => [ - attribute, - `${translation} (${this.system.attributes[attribute].total})`, - ]), - ), - }, - { - label: getGame().i18n.localize("DS4.Trait"), - identifier: traitIdentifier, - options: Object.fromEntries( - Object.entries(DS4.i18n.traits).map(([trait, translation]) => [ - trait, - `${translation} (${this.system.traits[trait].total})`, - ]), - ), - }, - ], - }, - ), + content: await foundry.applications.handlebars.renderTemplate("systems/ds4/templates/dialogs/simple-select-form.hbs", { + selects: [ + { + label: getGame().i18n.localize("DS4.Attribute"), + identifier: attributeIdentifier, + options: Object.fromEntries( + Object.entries(DS4.i18n.attributes).map(([attribute, translation]) => [ + attribute, + `${translation} (${this.system.attributes[attribute].total})`, + ]), + ), + }, + { + label: getGame().i18n.localize("DS4.Trait"), + identifier: traitIdentifier, + options: Object.fromEntries( + Object.entries(DS4.i18n.traits).map(([trait, translation]) => [ + trait, + `${translation} (${this.system.traits[trait].total})`, + ]), + ), + }, + ], + }), ok: { label: getGame().i18n.localize("DS4.GenericOkButton"), callback: (_event, button) => { diff --git a/src/documents/item/weapon/weapon.js b/src/documents/item/weapon/weapon.js index 3a96ddac..b125966f 100644 --- a/src/documents/item/weapon/weapon.js +++ b/src/documents/item/weapon/weapon.js @@ -86,21 +86,18 @@ export class DS4Weapon extends DS4Item { const identifier = `attack-type-selection-${foundry.utils.randomID()}`; return foundry.applications.api.DialogV2.prompt({ window: { title: getGame().i18n.localize("DS4.DialogAttackTypeSelection") }, - content: await foundry.applications.handlebars.renderTemplate( - "systems/ds4/templates/dialogs/simple-select-form.hbs", - { - selects: [ - { - label: getGame().i18n.localize("DS4.AttackType"), - identifier, - options: { melee, ranged }, - }, - ], - }, - ), + content: await foundry.applications.handlebars.renderTemplate("systems/ds4/templates/dialogs/simple-select-form.hbs", { + selects: [ + { + label: getGame().i18n.localize("DS4.AttackType"), + identifier, + options: { melee, ranged }, + }, + ], + }), ok: { label: getGame().i18n.localize("DS4.GenericOkButton"), - callback: (_event, button) => { + callback: (_event, button, _dialog) => { const selectedAttackType = button.form.elements[identifier].value; if (selectedAttackType !== "melee" && selectedAttackType !== "ranged") { throw new Error( diff --git a/src/handlebars/handlebars-helpers.ts b/src/handlebars/handlebars-helpers.ts index 5f9dcbc9..015064a4 100644 --- a/src/handlebars/handlebars-helpers.ts +++ b/src/handlebars/handlebars-helpers.ts @@ -14,11 +14,6 @@ const helpers = { isEmpty: (input: Array | null | undefined): boolean => (input?.length ?? 0) === 0, - capitalize: (str: string): string => { - if (typeof str !== "string") return ""; - return str.charAt(0).toUpperCase() + str.slice(1); - }, - toRomanNumerals, }; diff --git a/src/hooks/hooks.ts b/src/hooks/hooks.ts index b69fbb1f..684a04db 100644 --- a/src/hooks/hooks.ts +++ b/src/hooks/hooks.ts @@ -9,6 +9,8 @@ import { registerForReadyHook } from "./ready"; import { registerForRenderHooks } from "./render"; import { registerForSetupHook } from "./setup"; + + export function registerForHooks(): void { registerForHotbarDropHook(); registerForPreCreateItemHook(); diff --git a/src/hooks/render.js b/src/hooks/render.js index ad5224c3..1c8c580a 100644 --- a/src/hooks/render.js +++ b/src/hooks/render.js @@ -21,12 +21,12 @@ export function registerForRenderHooks() { */ function selectTargetInputOnFocus(app, element) { // V13: element is always a plain DOM element - if (!element || typeof element.querySelectorAll !== "function") { + if (!element || typeof element.querySelectorAll !== 'function') { return; } const inputs = element.querySelectorAll("input"); - inputs.forEach((input) => { + inputs.forEach(input => { input.addEventListener("focus", (ev) => { ev.currentTarget.select(); }); diff --git a/src/utils/utils.js b/src/utils/utils.js index b7979b64..1849eb6e 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -30,10 +30,7 @@ export function enforce(value, message) { * @returns {Canvas} */ export function getCanvas() { - enforce( - canvas instanceof foundry.canvas.Canvas && canvas.ready, - getGame().i18n.localize("DS4.ErrorCanvasIsNotInitialized"), - ); + enforce(canvas instanceof foundry.canvas.Canvas && canvas.ready, getGame().i18n.localize("DS4.ErrorCanvasIsNotInitialized")); return canvas; } diff --git a/templates/sheets/actor/creature-sheet.hbs b/templates/sheets/actor/creature-sheet.hbs index cfa20a7e..afa35ba0 100644 --- a/templates/sheets/actor/creature-sheet.hbs +++ b/templates/sheets/actor/creature-sheet.hbs @@ -12,6 +12,7 @@ SPDX-License-Identifier: MIT {{> systems/ds4/templates/sheets/actor/components/creature-properties.hbs}} {{/systems/ds4/templates/sheets/actor/components/actor-header.hbs}} + {{!-- Sheet Tab Navigation --}}