Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions discworld.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as Rolls from "./modules/rolls/index.mjs";

import DiscworldCharacter from "./modules/documents/character.mjs";

import DiscworldSheetMixin from "./modules/applications/sheets/base-document-sheet.mjs";
import TraitSheet from "./modules/applications/sheets/trait-sheet.mjs";
import DiscworldJournalEntrySheet from "./modules/applications/sheets/journal-entry-sheet.mjs";

Expand All @@ -29,6 +30,7 @@ globalThis.discworld = {
TraitDataModel,
},
sheets: {
DiscworldSheetMixin,
DiscworldActorSheet,
CharacterSheet,
TraitSheet,
Expand Down
8 changes: 7 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,19 @@
"background": "Background",
"consequences": "Consequences",
"core": "Core",
"features": "Features",
"mannerism": "Mannerism",
"niche": "Niche",
"organization": "Organization",
"other": "Other",
"quirks": "Quirks",
"other": "Other"
"species": "Species"
},

"FIELDS": {
"actorType": {
"label": "Actor Type"
},
"notes": {
"label": "Notes"
},
Expand Down
31 changes: 21 additions & 10 deletions modules/applications/sheets/actors/base-actor-sheet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ export default class DiscworldActorSheet extends DiscworldSheetMixin(ActorSheetV
return this.actor.helpMode.enabled;
}

/* ------------------------------------------------- */

/**
* Helper to get the base actor type, e.g., removing any prefixes from modules.
* @example "character" -> "character"; "discworld-core.npc" -> "npc"
* @type {string}
*/
get actorBaseType() {
return this.actor.type.replace(/.*\./, "");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem necessary to me. Can you explain the logic behind this and the link between it and DISCWORLD.traitTypes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actorType filters the available options for traitTypes, so that npcs and characters each get their own set of traits types.

This particular method just gets the base actor type without the discworld-core, so that storage/retrieval is simpler (i.e., not having to store/retrieve a cumbersome id like this, discworld-core.npc).

}

/* -------------------------------------------------- */

/** @inheritdoc */
Expand All @@ -76,11 +87,12 @@ export default class DiscworldActorSheet extends DiscworldSheetMixin(ActorSheetV
{ rollData: this.document.getRollData(), relativeTo: this.document },
);

const traitActorGroup = DISCWORLD.traitTypes[this.actorBaseType]; // Get just the base actor type.
// Construct arrays of traits, filtered by category.
context.traitGroups = this._getTraitGroups();
context.traitGroups = this._getTraitGroups(traitActorGroup);

// Translation of trait types.
context.traitTypeTranslationMap = DISCWORLD.traitTypes;
context.traitTypeTranslationMap = traitActorGroup;

return context;
}
Expand Down Expand Up @@ -116,10 +128,10 @@ export default class DiscworldActorSheet extends DiscworldSheetMixin(ActorSheetV

/**
* Get trait groups by type.
* @param {Object} traitTypes
* @returns {Record<keyof DISCWORLD.traitTypes, Item[]>}
* @param {Object} traitTypes Trait types for a particular sheet.
* @returns {Record<keyof DISCWORLD.traitTypes[keyof DISCWORLD.actorTypes], Item[]>}
*/
_getTraitGroups(traitTypes = DISCWORLD.traitTypes) {
_getTraitGroups(traitTypes) {
const items = Object.groupBy(this.actor.items, item => item.system.type);
return Object.keys(traitTypes).reduce((acc, traitType) => {
acc[traitType] = items[traitType] ?? [];
Expand Down Expand Up @@ -150,7 +162,7 @@ export default class DiscworldActorSheet extends DiscworldSheetMixin(ActorSheetV
const { traitType } = event.target.closest(".trait-category").dataset;
if (!traitType) return;

this.constructor._addTrait.call(this, traitType);
DiscworldActorSheet.#addTrait.call(this, traitType);
});

// Make each description direct child element rollable.
Expand Down Expand Up @@ -227,7 +239,7 @@ export default class DiscworldActorSheet extends DiscworldSheetMixin(ActorSheetV
switch (actionType) {
case "add": {
const { traitType } = target.closest(".trait-category").dataset;
this.constructor._addTrait.call(this, traitType);
DiscworldActorSheet.#addTrait.call(this, traitType);
break;
}
case "edit":
Expand All @@ -248,14 +260,13 @@ export default class DiscworldActorSheet extends DiscworldSheetMixin(ActorSheetV
* Add a new trait of the given type to the character.
* @this DiscworldActorSheet
* @param {string} traitType The type of trait to add. Must be one of the {@link DISCWORLD.traitTypes} constants.
* @param {object} extraData Additional creation data that a module may provide. E.g., for new datapoints.
*/
static async _addTrait(traitType, extraData = {}) {
static async #addTrait(traitType, extraData = {}) {
const createData = foundry.utils.mergeObject(
{
name: _loc("DOCUMENT.New", { type: _loc(`DISCWORLD.trait.type.${traitType}`) }),
type: "trait",
system: { type: traitType },
system: { type: traitType, actorType: this.actorBaseType },
},
extraData,
);
Expand Down
74 changes: 62 additions & 12 deletions modules/applications/sheets/trait-sheet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,34 +31,43 @@ export default class TraitSheet extends DiscworldSheetMixin(ItemSheetV2) {
/** @inheritdoc */
async _prepareContext(options) {
const context = await super._prepareContext(options);
const { document } = this;
const { system } = this.document;

context.fields = {
name: {
field: this.document.schema.getField("name"),
field: document.schema.getField("name"),
value: this.isEditMode
? this.document._source.name
: this.document.name,
? document._source.name
: document.name,
},
actorType: {
field: system.schema.getField("actorType"),
value: this.isEditMode
? system._source.actorType
: system.actorType,
},
type: {
field: this.document.system.schema.getField("type"),
field: system.schema.getField("type"),
value: this.isEditMode
? this.document.system._source.type
: this.document.system.type,
? system._source.type
: system.type,
},
severity: {
field: this.document.system.schema.getField("severity"),
field: system.schema.getField("severity"),
value: this.isEditMode
? this.document.system._source.severity
: this.document.system.severity,
? system._source.severity
: system.severity,
},
notes: {
field: this.document.system.schema.getField("notes"),
field: system.schema.getField("notes"),
value: this.isEditMode
? this.document.system._source.notes
: this.document.system.notes,
? system._source.notes
: system.notes,
},
};

context.fields.actorType.show = !document.isOwned && (Object.keys(DISCWORLD.actorTypes).length > 1);
context.fields.severity.show = context.fields.type.value === "consequences";
context.fields.notes.enriched = await CONFIG.ux.TextEditor.enrichHTML(
context.fields.notes.value,
Expand All @@ -70,6 +79,30 @@ export default class TraitSheet extends DiscworldSheetMixin(ItemSheetV2) {

/* -------------------------------------------------- */

/** @inheritdoc */
async _onRender(context, options) {
await super._onRender(context, options);

const { system } = this.document;

// Sort and filter <option> elements.
const validTraits = Object.keys(discworld.config.traitTypes[system.actorType]);
const select = this.element.querySelector("select[name=\"system.type\"]");
const optionElements = Array.from(select.querySelectorAll("option"));

optionElements
.sort((a, b) => validTraits.indexOf(a.value) - validTraits.indexOf(b.value))
.forEach((option) => {
if (!validTraits.includes(option.value)) {
option.remove();
} else {
select.appendChild(option);
}
});
}
Comment on lines +84 to +102

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should all just be in context prep.


/* -------------------------------------------------- */

/** @inheritdoc */
async render(options) {
const renderedApp = await super.render(options);
Expand All @@ -87,4 +120,21 @@ export default class TraitSheet extends DiscworldSheetMixin(ItemSheetV2) {

return renderedApp;
}

/* ------------------------------------------------- */

/** @inheritdoc */
async _onChangeForm(formConfig, event) {
const { target } = event;

// Handle changes to actorType, assigning a valid trait type.
if (target?.name === "system.actorType") {
const [defaultType] = Object.keys(discworld.config.traitTypes[target.value]);
this.document.update(
{ "system.type": defaultType },
{ render: false },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand what's going on here. This seems like a workaround for something?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When actorType is changed, the set of available trait types change. This sets the trait type to an allowed default for a particular actorType when it is changed

);
}
return super._onChangeForm(formConfig, event);
}
}
21 changes: 13 additions & 8 deletions modules/config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* Centralized config constants for the DISCWORLD system.
*/

const DISCWORLD = {
id: "discworld-modiphius",
consequenceSeverity: {
Expand All @@ -12,13 +11,19 @@ const DISCWORLD = {
},

traitTypes: {
organization: "DISCWORLD.trait.type.organization",
background: "DISCWORLD.trait.type.background",
niche: "DISCWORLD.trait.type.niche",
core: "DISCWORLD.trait.type.core",
quirks: "DISCWORLD.trait.type.quirks",
consequences: "DISCWORLD.trait.type.consequences",
other: "DISCWORLD.trait.type.other",
character: {
organization: "DISCWORLD.trait.type.organization",
background: "DISCWORLD.trait.type.background",
niche: "DISCWORLD.trait.type.niche",
core: "DISCWORLD.trait.type.core",
quirks: "DISCWORLD.trait.type.quirks",
consequences: "DISCWORLD.trait.type.consequences",
other: "DISCWORLD.trait.type.other",
},
},

actorTypes: {
character: "TYPES.Actor.character",
},
};

Expand Down
12 changes: 11 additions & 1 deletion modules/datamodels/trait-schema.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,20 @@ const { HTMLField, StringField } = foundry.data.fields;
export default class TraitDataModel extends foundry.abstract.TypeDataModel {
/** @inheritdoc */
static defineSchema() {
const traitTypeChoices = () => Object.values(DISCWORLD.traitTypes).reduce((acc, val) => {
return foundry.utils.mergeObject(acc, val, { inplace: false });
});
const actorTypeChoices = () => DISCWORLD.actorTypes;

return {
notes: new HTMLField(),
severity: new StringField({ required: true, initial: "minor", choices: DISCWORLD.consequenceSeverity }),
type: new StringField({ required: true, choices: () => DISCWORLD.traitTypes, initial: "consequences" }),
type: new StringField({
required: true, choices: traitTypeChoices, initial: "consequences",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't have choices here at all if a module can extend it. It would lead to the actor/item becoming invalid when the module is disabled.

Instead, remove choices here. Prepare the choices in the sheet's context preparation. If the current value is truthy and not one of the options, add it as an additional option with value === label.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand your instruction here. Is something like this what you mean?

prepareContext() {
  ...
  const { traitTypes } = discworld.config;
  let typeChoices = traitTypes[system.actorType] ?? traitTypes.character;
  if (!Object.keys(typeChoices).includes(system.type) && system.type) {
    typeChoices = foundry.utils.mergeObject(
      typeChoices,
      { [system.type]: _loc(`DISCWORLD.trait.type.${system.type}`) },
      { inplace: false },
    );
  }
  context.fields.type.choices = typeChoices;
 ...
}

// Got rid of this override: 
// _onRender() {
// }

And then in the form group:

{{formGroup fields.type.field value=fields.type.value choices=fields.type.choices rootId=rootId}}

}),
actorType: new StringField({
required: true, choices: actorTypeChoices, initial: "character",
}),
};
}

Expand Down
4 changes: 2 additions & 2 deletions templates/character-sheet/header.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<header class="sheet-header flexrow">
<div class="portrait-container">
<img class="portrait" src="{{document.img}}" data-edit="img" data-action="editImage" title="{{document.name}}"
alt="">
<img class="portrait" src="{{document.img}}" data-edit="img" data-action="editImage" data-tooltip="{{document.name}}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<img class="portrait" src="{{document.img}}" data-edit="img" data-action="editImage" data-tooltip="{{document.name}}"
<img class="portrait" src="{{document.img}}" data-edit="img" data-action="editImage" data-tooltip-text="{{document.name}}"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in recent commit

alt="{{document.name}}">
</div>

<div class="header-data-container flexcol">
Expand Down
14 changes: 10 additions & 4 deletions templates/trait-sheet/header.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@
src="{{document.img}}"
data-edit="img"
data-action="editImage"
title="{{document.name}}">
data-tooltip="{{document.name}}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
data-tooltip="{{document.name}}"
data-tooltip-text="{{document.name}}"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in recent commit

alt="{{document.name}}">
</div>

<div class="header-data-container flexcol">
<h1 class="document-name">{{formInput fields.name.field value=fields.name.value}}</h1>
<h1 class="document-name">{{formInput fields.name.field value=fields.name.value rootId=document.id}}</h1>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rootId is not unique. Should use the application's id, but it may be that rootId is already in the context if it's DocumentSheet.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in recent commit

<div class="document-details flexrow">
<div class="detail-left trait-type">{{formGroup fields.type.field value=fields.type.value}}</div>
{{#if fields.actorType.show }}
<div class="detail-left actor-type">
{{formGroup fields.actorType.field value=fields.actorType.value localize=true rootId=document.id}}
</div>
{{/if}}
<div class="detail-left trait-type">{{formGroup fields.type.field value=fields.type.value rootId=document.id}}</div>
{{#if fields.severity.show }}
<div class="detail-right severity">
{{formGroup fields.severity.field value=fields.severity.value}}
{{formGroup fields.severity.field value=fields.severity.value rootId=document.id}}
</div>
{{/if}}
</div>
Expand Down