Throw some initial version of code at the wall for the tabbed character sheet

This commit is contained in:
Oliver-Akins 2025-02-11 23:40:35 -07:00
parent eb6d7fee94
commit b72f22380f
10 changed files with 213 additions and 2 deletions

View file

@ -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
};

View file

@ -1,12 +1,16 @@
import { handlebarsLocalizer, localizer } from "../utils/Localizer.mjs"; import { handlebarsLocalizer, localizer } from "../utils/Localizer.mjs";
import { formFields } from "./inputs/formFields.mjs"; import { formFields } from "./inputs/formFields.mjs";
import { options } from "./options.mjs"; import { options } from "./options.mjs";
import { toAttributes } from "./toAttributes.mjs";
import { toClasses } from "./toClasses.mjs";
export default { export default {
// #region Complex // #region Complex
"rc-formFields": formFields, "rc-formFields": formFields,
"rc-i18n": handlebarsLocalizer, "rc-i18n": handlebarsLocalizer,
"rc-options": options, "rc-options": options,
"rc-toAttributes": toAttributes,
"rc-toClasses": toClasses,
// #region Simple // #region Simple
"rc-empty-state": (v) => v ?? localizer(`RipCrypt.common.empty`), "rc-empty-state": (v) => v ?? localizer(`RipCrypt.common.empty`),

View file

@ -0,0 +1,13 @@
/**
* Allows converting an object of <attribute names, value> into HTML
* attribute-value pairs that can be inserted into the DOM
*
* @param {Record<string, any>} 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(` `));
};

View file

@ -0,0 +1,14 @@
/**
* Allows converting an object of <class names, boolean-likes> into an HTML-compatible class list.
*
* @param {Record<string, any>} 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(` `));
};

View file

@ -4,6 +4,7 @@ import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs";
import { DelveTourApp } from "../Apps/DelveTourApp.mjs"; import { DelveTourApp } from "../Apps/DelveTourApp.mjs";
import { HeroSkillsCardV1 } from "../Apps/ActorSheets/HeroSkillsCardV1.mjs"; import { HeroSkillsCardV1 } from "../Apps/ActorSheets/HeroSkillsCardV1.mjs";
import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs"; import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs";
import { TabbedHeroSheet } from "../Apps/ActorSheets/TabbedHeroSheet.mjs";
// Data Models // Data Models
import { AmmoData } from "../data/Item/Ammo.mjs"; import { AmmoData } from "../data/Item/Ammo.mjs";
@ -69,6 +70,12 @@ Hooks.once(`init`, () => {
label: `RipCrypt.sheet-names.CombinedHeroSheet`, label: `RipCrypt.sheet-names.CombinedHeroSheet`,
themes: CombinedHeroSheet.themes, 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, { Actors.registerSheet(game.system.id, HeroSummaryCardV1, {
types: [`hero`], types: [`hero`],
label: `RipCrypt.sheet-names.HeroSummaryCardV1`, label: `RipCrypt.sheet-names.HeroSummaryCardV1`,

View file

@ -1,4 +1,4 @@
<div class="HeroSkillsCardV1"> <div class="HeroSkillsCardV1 {{ rc-toClasses classes }}" {{ rc-toAttributes attrs }}>
<div class="label col-header list-header gait-skills-header"> <div class="label col-header list-header gait-skills-header">
<span>{{ rc-i18n "RipCrypt.Apps.grit-skills" }}</span> <span>{{ rc-i18n "RipCrypt.Apps.grit-skills" }}</span>
</div> </div>

View file

@ -1,4 +1,4 @@
<div class="HeroSummaryCardV1"> <div class="HeroSummaryCardV1 {{ rc-toClasses classes }}" {{ rc-toAttributes attrs }}>
{{!-- * Header --}} {{!-- * Header --}}
<div class="header"> <div class="header">
<div class="image">Logo Image</div> <div class="image">Logo Image</div>

View file

@ -0,0 +1,9 @@
.ripcrypt.ripcrypt--TabbedHeroSheet {
> .window-content {
gap: 4px;
background: var(--base-background);
> .tab:not(.visible) {
display: none;
}
}
}

View file

@ -0,0 +1,26 @@
<nav>
<button
type="button"
data-action="tab"
data-group="root"
data-tab="HeroSummaryCardV1"
>
Stats
</button>
<button
type="button"
data-action="tab"
data-group="root"
data-tab="HeroSkillsCardV1"
>
Skills
</button>
<button
type="button"
data-action="tab"
data-group="root"
data-tab="HeroCraftCardV1"
>
Craft
</button>
</nav>

View file

@ -5,3 +5,4 @@
@import url("./HeroSummaryCardV1/style.css"); @import url("./HeroSummaryCardV1/style.css");
@import url("./HeroSkillsCardV1/style.css"); @import url("./HeroSkillsCardV1/style.css");
@import url("./RichEditor/style.css"); @import url("./RichEditor/style.css");
@import url("./TabbedHeroSheet/style.css");