From b72f22380f18abc8b832947099e873b9c10639fa Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 11 Feb 2025 23:40:35 -0700 Subject: [PATCH] Throw some initial version of code at the wall for the tabbed character sheet --- module/Apps/ActorSheets/TabbedHeroSheet.mjs | 137 +++++++++++++++++++ module/handlebarHelpers/_index.mjs | 4 + module/handlebarHelpers/toAttributes.mjs | 13 ++ module/handlebarHelpers/toClasses.mjs | 14 ++ module/hooks/init.mjs | 7 + templates/Apps/HeroSkillsCardV1/content.hbs | 2 +- templates/Apps/HeroSummaryCardV1/content.hbs | 2 +- templates/Apps/TabbedHeroSheet/style.css | 9 ++ templates/Apps/TabbedHeroSheet/tabs.hbs | 26 ++++ templates/Apps/apps.css | 1 + 10 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 module/Apps/ActorSheets/TabbedHeroSheet.mjs create mode 100644 module/handlebarHelpers/toAttributes.mjs create mode 100644 module/handlebarHelpers/toClasses.mjs create mode 100644 templates/Apps/TabbedHeroSheet/style.css create mode 100644 templates/Apps/TabbedHeroSheet/tabs.hbs diff --git a/module/Apps/ActorSheets/TabbedHeroSheet.mjs b/module/Apps/ActorSheets/TabbedHeroSheet.mjs new file mode 100644 index 0000000..8fb8657 --- /dev/null +++ b/module/Apps/ActorSheets/TabbedHeroSheet.mjs @@ -0,0 +1,137 @@ +import { filePath } from "../../consts.mjs"; +import { GenericAppMixin } from "../GenericApp.mjs"; +import { HeroCraftCardV1 } from "./HeroCraftCardV1.mjs"; +import { HeroSkillsCardV1 } from "./HeroSkillsCardV1.mjs"; +import { HeroSummaryCardV1 } from "./HeroSummaryCardV1.mjs"; +import { Logger } from "../../utils/Logger.mjs"; + +const { HandlebarsApplicationMixin } = foundry.applications.api; +const { ActorSheetV2 } = foundry.applications.sheets; + +export class TabbedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixin(ActorSheetV2)) { + + // #region Options + static DEFAULT_OPTIONS = { + classes: [ + `ripcrypt--actor`, + `ripcrypt--TabbedHeroSheet`, + ], + position: { + width: `auto`, + height: `auto`, + }, + window: { + resizable: false, + }, + actions: {}, + form: { + submitOnChange: true, + closeOnSubmit: false, + }, + }; + + static PARTS = { + nav: { + template: filePath(`templates/Apps/TabbedHeroSheet/tabs.hbs`), + }, + summary: { + template: filePath(`templates/Apps/HeroSummaryCardV1/content.hbs`), + }, + skills: { + template: filePath(`templates/Apps/HeroSkillsCardV1/content.hbs`), + }, + }; + // #endregion + + // #region Instance Data + #tabs = { + root: `HeroSummaryCardV1`, + }; + // #endregion + + // #region Lifecycle + async _onRender(context, options) { + await super._onRender(context, options); + + const summaryElement = this.element.querySelector(`.HeroSummaryCardV1`); + HeroSummaryCardV1._onRender( + context, + { + ...options, + element: summaryElement, + isEditable: this.isEditable, + }, + ); + + const skillsElement = this.element.querySelector(`.HeroSkillsCardV1`); + HeroSkillsCardV1._onRender.bind(this)( + context, + { + ...options, + element: skillsElement, + isEditable: this.isEditable, + }, + ); + + const craftsElement = this.element.querySelector(`.crafts-summary`); + HeroCraftCardV1._onRender.bind(this)( + context, + { + ...options, + element: craftsElement, + isEditable: this.isEditable, + }, + ); + }; + + async _preparePartContext(partId, ctx, opts) { + ctx = await super._preparePartContext(partId, ctx, opts); + ctx.actor = this.document; + + ctx.classes = { + tab: true, + visible: false, + }; + ctx.attrs = {}; + + + let tabName; + switch (partId) { + case `summary`: { + tabName = `HeroSummaryCardV1`; + ctx = await HeroSummaryCardV1.prepareGuts(ctx); + ctx = await HeroSummaryCardV1.prepareWeapons(ctx); + ctx = await HeroSummaryCardV1.prepareArmor(ctx); + ctx = await HeroSummaryCardV1.prepareFatePath(ctx); + ctx = await HeroSummaryCardV1.prepareAbilityRow(ctx); + ctx = await HeroSummaryCardV1.prepareSpeed(ctx); + ctx = await HeroSummaryCardV1.prepareLevelData(ctx); + break; + }; + case `skills`: { + tabName = `HeroSkillsCardV1`; + ctx = await HeroSkillsCardV1.prepareGear(ctx); + ctx = await HeroSkillsCardV1.prepareAmmo(ctx); + ctx = await HeroSkillsCardV1.prepareSkills(ctx); + break; + }; + case `craft`: { + tabName = `HeroCraftCardV1`; + ctx = await HeroCraftCardV1.prepareCraft(ctx); + break; + }; + }; + if (tabName) { + ctx.attrs[`data-tab`] = tabName; + ctx.attrs[`data-group`] = `root`; + ctx.classes.visible = this.#tabs.root === tabName; + }; + + Logger.debug(`Context keys:`, Object.keys(ctx)); + return ctx; + }; + // #endregion + + // #region Actions + // #endregion +}; diff --git a/module/handlebarHelpers/_index.mjs b/module/handlebarHelpers/_index.mjs index 9c8587d..23011eb 100644 --- a/module/handlebarHelpers/_index.mjs +++ b/module/handlebarHelpers/_index.mjs @@ -1,12 +1,16 @@ import { handlebarsLocalizer, localizer } from "../utils/Localizer.mjs"; import { formFields } from "./inputs/formFields.mjs"; import { options } from "./options.mjs"; +import { toAttributes } from "./toAttributes.mjs"; +import { toClasses } from "./toClasses.mjs"; export default { // #region Complex "rc-formFields": formFields, "rc-i18n": handlebarsLocalizer, "rc-options": options, + "rc-toAttributes": toAttributes, + "rc-toClasses": toClasses, // #region Simple "rc-empty-state": (v) => v ?? localizer(`RipCrypt.common.empty`), diff --git a/module/handlebarHelpers/toAttributes.mjs b/module/handlebarHelpers/toAttributes.mjs new file mode 100644 index 0000000..05a14bd --- /dev/null +++ b/module/handlebarHelpers/toAttributes.mjs @@ -0,0 +1,13 @@ +/** + * Allows converting an object of into HTML + * attribute-value pairs that can be inserted into the DOM + * + * @param {Record} obj The object of attributes to their value + */ +export function toAttributes(obj = {}) { + let attributes = []; + for (const [ attr, value] of Object.entries(obj)) { + attributes.push(`${attr}=${Handlebars.escapeExpression(value)}`); + }; + return new Handlebars.SafeString(attributes.join(` `)); +}; diff --git a/module/handlebarHelpers/toClasses.mjs b/module/handlebarHelpers/toClasses.mjs new file mode 100644 index 0000000..54b0eb4 --- /dev/null +++ b/module/handlebarHelpers/toClasses.mjs @@ -0,0 +1,14 @@ +/** + * Allows converting an object of into an HTML-compatible class list. + * + * @param {Record} obj The object of class names to boolean-like values for if that class should be included. + */ +export function toClasses(obj = {}) { + let classes = []; + for (const [ klass, include ] of Object.entries(obj)) { + if (include) { + classes.push(klass); + }; + }; + return new Handlebars.SafeString(classes.join(` `)); +}; diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 7ea36b7..0c0153e 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -4,6 +4,7 @@ import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs"; import { DelveTourApp } from "../Apps/DelveTourApp.mjs"; import { HeroSkillsCardV1 } from "../Apps/ActorSheets/HeroSkillsCardV1.mjs"; import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs"; +import { TabbedHeroSheet } from "../Apps/ActorSheets/TabbedHeroSheet.mjs"; // Data Models import { AmmoData } from "../data/Item/Ammo.mjs"; @@ -69,6 +70,12 @@ Hooks.once(`init`, () => { label: `RipCrypt.sheet-names.CombinedHeroSheet`, themes: CombinedHeroSheet.themes, }); + Actors.registerSheet(game.system.id, TabbedHeroSheet, { + makeDefault: false, + types: [`hero`], + label: `RipCrypt.sheet-names.TabbedHeroSheet`, + themes: TabbedHeroSheet.themes, + }); Actors.registerSheet(game.system.id, HeroSummaryCardV1, { types: [`hero`], label: `RipCrypt.sheet-names.HeroSummaryCardV1`, diff --git a/templates/Apps/HeroSkillsCardV1/content.hbs b/templates/Apps/HeroSkillsCardV1/content.hbs index eee8d4a..42a2a3d 100644 --- a/templates/Apps/HeroSkillsCardV1/content.hbs +++ b/templates/Apps/HeroSkillsCardV1/content.hbs @@ -1,4 +1,4 @@ -
+
{{ rc-i18n "RipCrypt.Apps.grit-skills" }}
diff --git a/templates/Apps/HeroSummaryCardV1/content.hbs b/templates/Apps/HeroSummaryCardV1/content.hbs index 23b84c8..c897264 100644 --- a/templates/Apps/HeroSummaryCardV1/content.hbs +++ b/templates/Apps/HeroSummaryCardV1/content.hbs @@ -1,4 +1,4 @@ -
+
{{!-- * Header --}}
Logo Image
diff --git a/templates/Apps/TabbedHeroSheet/style.css b/templates/Apps/TabbedHeroSheet/style.css new file mode 100644 index 0000000..f7116fe --- /dev/null +++ b/templates/Apps/TabbedHeroSheet/style.css @@ -0,0 +1,9 @@ +.ripcrypt.ripcrypt--TabbedHeroSheet { + > .window-content { + gap: 4px; + background: var(--base-background); + > .tab:not(.visible) { + display: none; + } + } +} diff --git a/templates/Apps/TabbedHeroSheet/tabs.hbs b/templates/Apps/TabbedHeroSheet/tabs.hbs new file mode 100644 index 0000000..e528839 --- /dev/null +++ b/templates/Apps/TabbedHeroSheet/tabs.hbs @@ -0,0 +1,26 @@ + diff --git a/templates/Apps/apps.css b/templates/Apps/apps.css index bcf722e..1fa53a5 100644 --- a/templates/Apps/apps.css +++ b/templates/Apps/apps.css @@ -5,3 +5,4 @@ @import url("./HeroSummaryCardV1/style.css"); @import url("./HeroSkillsCardV1/style.css"); @import url("./RichEditor/style.css"); +@import url("./TabbedHeroSheet/style.css");