diff --git a/daggerheart.mjs b/daggerheart.mjs
index c08fed77..909324b4 100644
--- a/daggerheart.mjs
+++ b/daggerheart.mjs
@@ -62,7 +62,7 @@ Hooks.once('init', () => {
CONFIG.Dice.rolls = [...CONFIG.Dice.rolls, ...[DHRoll, DualityRoll, D20Roll, DamageRoll]];
CONFIG.MeasuredTemplate.objectClass = DhMeasuredTemplate;
- CONFIG.Item.documentClass = documents.DhpItem;
+ CONFIG.Item.documentClass = documents.DHItem;
//Registering the Item DataModel
CONFIG.Item.dataModels = models.items.config;
diff --git a/module/applications/daggerheart-sheet.mjs b/module/applications/daggerheart-sheet.mjs
deleted file mode 100644
index 32d5212e..00000000
--- a/module/applications/daggerheart-sheet.mjs
+++ /dev/null
@@ -1,48 +0,0 @@
-export default function DhpApplicationMixin(Base) {
- return class DhpSheet extends Base {
- static applicationType = 'sheets';
- static documentType = '';
-
- static get defaultOptions() {
- return Object.assign(super.defaultOptions, {
- classes: ['daggerheart', 'sheet', this.documentType],
- template: `systems/${SYSTEM.id}/templates/${this.applicationType}/${this.documentType}.hbs`,
- height: 'auto',
- submitOnChange: true,
- submitOnClose: false,
- width: 450
- });
- }
-
- /** @override */
- get title() {
- const { documentName, type, name } = this.object;
- // const typeLabel = game.i18n.localize(CONFIG[documentName].typeLabels[type]);
- const typeLabel = documentName;
- return `[${typeLabel}] ${name}`;
- }
-
- // async _renderOuter() {
- // const html = await super._renderOuter();
- // // const overlaySrc = "systems/amia/assets/ThePrimordial.png";
- // const overlay = `
`
- // $(html).find('.window-header').prepend(overlay);
- // return html;
- // }
-
- activateListeners(html) {
- super.activateListeners(html);
- html.on('click', '[data-action]', this.#onClickAction.bind(this));
- }
-
- async #onClickAction(event) {
- event.preventDefault();
- const button = event.currentTarget;
- const action = button.dataset.action;
-
- return this._handleAction(action, event, button);
- }
-
- async _handleAction(action, event, button) {}
- };
-}
diff --git a/module/applications/sheets/character.mjs b/module/applications/sheets/character.mjs
index b3e68609..ad294aff 100644
--- a/module/applications/sheets/character.mjs
+++ b/module/applications/sheets/character.mjs
@@ -56,7 +56,6 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
resizable: true
},
form: {
- handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
@@ -218,6 +217,15 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
this._createContextMenues();
}
+ /** @inheritDoc */
+ async _onRender(context, options) {
+ await super._onRender(context, options);
+
+ this._createSearchFilter();
+ }
+
+ /* -------------------------------------------- */
+
_createContextMenues() {
const allOptions = {
useItem: {
@@ -431,11 +439,105 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
return context;
}
- static async updateForm(event, _, formData) {
- await this.document.update(formData.object);
- this.render();
+ /* -------------------------------------------- */
+ /* Search Filter */
+ /* -------------------------------------------- */
+
+ /**
+ * The currently active search filter.
+ * @type {foundry.applications.ux.SearchFilter}
+ */
+ #search = {};
+
+ /**
+ * Track which item IDs are currently displayed due to a search filter.
+ * @type {{ inventory: Set, loadout: Set }}
+ */
+ #filteredItems = {
+ inventory: new Set(),
+ loadout: new Set()
+ };
+
+ /**
+ * Create and initialize search filter instances for the inventory and loadout sections.
+ *
+ * Sets up two {@link foundry.applications.ux.SearchFilter} instances:
+ * - One for the inventory, which filters items in the inventory grid.
+ * - One for the loadout, which filters items in the loadout/card grid.
+ * @private
+ */
+ _createSearchFilter() {
+ //Filters could be a application option if needed
+ const filters = [
+ {
+ key: 'inventory',
+ input: 'input[type="search"].search-inventory',
+ content: '[data-application-part="inventory"] .items-section',
+ callback: this._onSearchFilterInventory.bind(this)
+ },
+ {
+ key: 'loadout',
+ input: 'input[type="search"].search-loadout',
+ content: '[data-application-part="loadout"] .items-section',
+ callback: this._onSearchFilterCard.bind(this)
+ }
+ ];
+
+ for (const { key, input, content, callback } of filters) {
+ const filter = new foundry.applications.ux.SearchFilter({
+ inputSelector: input,
+ contentSelector: content,
+ callback
+ });
+ filter.bind(this.element);
+ this.#search[key] = filter;
+ }
}
+ /**
+ * Handle invetory items search and filtering.
+ * @param {KeyboardEvent} event The keyboard input event.
+ * @param {string} query The input search string.
+ * @param {RegExp} rgx The regular expression query that should be matched against.
+ * @param {HTMLElement} html The container to filter items from.
+ * @protected
+ */
+ _onSearchFilterInventory(event, query, rgx, html) {
+ this.#filteredItems.inventory.clear();
+
+ for (const ul of html.querySelectorAll('.items-list')) {
+ for (const li of ul.querySelectorAll('.inventory-item')) {
+ const item = this.document.items.get(li.dataset.itemId);
+ const match = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
+ if (match) this.#filteredItems.inventory.add(item.id);
+ li.hidden = !match;
+ }
+ }
+ }
+
+ /**
+ * Handle card items search and filtering.
+ * @param {KeyboardEvent} event The keyboard input event.
+ * @param {string} query The input search string.
+ * @param {RegExp} rgx The regular expression query that should be matched against.
+ * @param {HTMLElement} html The container to filter items from.
+ * @protected
+ */
+ _onSearchFilterCard(event, query, rgx, html) {
+ this.#filteredItems.loadout.clear();
+
+ const elements = html.querySelectorAll('.items-list .inventory-item, .card-list .card-item');
+
+ for (const li of elements) {
+ const item = this.document.items.get(li.dataset.itemId);
+ const match = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
+ if (match) this.#filteredItems.loadout.add(item.id);
+ li.hidden = !match;
+ }
+ }
+
+ /* -------------------------------------------- */
+
async mapFeatureType(data, configType) {
return await Promise.all(
data.map(async x => {
diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs
index c7f5af0b..ffd00a23 100644
--- a/module/data/item/armor.mjs
+++ b/module/data/item/armor.mjs
@@ -9,7 +9,8 @@ export default class DHArmor extends BaseDataItem {
label: 'TYPES.Item.armor',
type: 'armor',
hasDescription: true,
- isQuantifiable: true
+ isQuantifiable: true,
+ isInventoryItem: true,
});
}
diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs
index 219b43aa..0df64a3e 100644
--- a/module/data/item/base.mjs
+++ b/module/data/item/base.mjs
@@ -7,6 +7,7 @@ import { actionsTypes } from '../action/_module.mjs';
* @property {string} type - The system type that this data model represents.
* @property {boolean} hasDescription - Indicates whether items of this type have description field
* @property {boolean} isQuantifiable - Indicates whether items of this type have quantity field
+ * @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item
*/
const fields = foundry.data.fields;
@@ -18,7 +19,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
label: 'Base Item',
type: 'base',
hasDescription: false,
- isQuantifiable: false
+ isQuantifiable: false,
+ isInventoryItem: false,
};
}
diff --git a/module/data/item/consumable.mjs b/module/data/item/consumable.mjs
index 6c8df798..cb8a13b5 100644
--- a/module/data/item/consumable.mjs
+++ b/module/data/item/consumable.mjs
@@ -8,7 +8,8 @@ export default class DHConsumable extends BaseDataItem {
label: 'TYPES.Item.consumable',
type: 'consumable',
hasDescription: true,
- isQuantifiable: true
+ isQuantifiable: true,
+ isInventoryItem: true,
});
}
diff --git a/module/data/item/miscellaneous.mjs b/module/data/item/miscellaneous.mjs
index d7687dc7..529cf9a9 100644
--- a/module/data/item/miscellaneous.mjs
+++ b/module/data/item/miscellaneous.mjs
@@ -8,7 +8,8 @@ export default class DHMiscellaneous extends BaseDataItem {
label: 'TYPES.Item.miscellaneous',
type: 'miscellaneous',
hasDescription: true,
- isQuantifiable: true
+ isQuantifiable: true,
+ isInventoryItem: true,
});
}
diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs
index e7551a21..005f08af 100644
--- a/module/data/item/weapon.mjs
+++ b/module/data/item/weapon.mjs
@@ -12,10 +12,8 @@ export default class DHWeapon extends BaseDataItem {
type: 'weapon',
hasDescription: true,
isQuantifiable: true,
- embedded: {
- feature: 'featureTest'
- },
- hasInitialAction: true
+ isInventoryItem: true,
+ hasInitialAction: true,
});
}
diff --git a/module/documents/_module.mjs b/module/documents/_module.mjs
index 03237ee5..e6099009 100644
--- a/module/documents/_module.mjs
+++ b/module/documents/_module.mjs
@@ -1,4 +1,4 @@
export { default as DhpActor } from './actor.mjs';
-export { default as DhpItem } from './item.mjs';
+export { default as DHItem } from './item.mjs';
export { default as DhpCombat } from './combat.mjs';
export { default as DhActiveEffect } from './activeEffect.mjs';
diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs
index c7099b07..350c39a1 100644
--- a/module/documents/actor.mjs
+++ b/module/documents/actor.mjs
@@ -17,14 +17,6 @@ export default class DhpActor extends Actor {
this.updateSource({ prototypeToken });
}
- prepareData() {
- super.prepareData();
- }
-
- async _preUpdate(changed, options, user) {
- super._preUpdate(changed, options, user);
- }
-
async updateLevel(newLevel) {
if (this.type !== 'character' || newLevel === this.system.levelData.level.changed) return;
diff --git a/module/documents/item.mjs b/module/documents/item.mjs
index 195b9c27..45b9df8e 100644
--- a/module/documents/item.mjs
+++ b/module/documents/item.mjs
@@ -1,14 +1,8 @@
-export default class DhpItem extends Item {
- /** @inheritdoc */
- getEmbeddedDocument(embeddedName, id, { invalid = false, strict = false } = {}) {
- const systemEmbeds = this.system.constructor.metadata.embedded ?? {};
- if (embeddedName in systemEmbeds) {
- const path = `system.${systemEmbeds[embeddedName]}`;
- return foundry.utils.getProperty(this, path).get(id) ?? null;
- }
- return super.getEmbeddedDocument(embeddedName, id, { invalid, strict });
- }
-
+/**
+ * Override and extend the basic Item implementation.
+ * @extends {foundry.documents.Item}
+ */
+export default class DHItem extends foundry.documents.Item {
/** @inheritDoc */
prepareEmbeddedDocuments() {
super.prepareEmbeddedDocuments();
@@ -35,75 +29,59 @@ export default class DhpItem extends Item {
return data;
}
- isInventoryItem() {
- return ['weapon', 'armor', 'miscellaneous', 'consumable'].includes(this.type);
+ /**
+ * Determine if this item is classified as an inventory item based on its metadata.
+ * @returns {boolean} Returns `true` if the item is an inventory item.
+ */
+ get isInventoryItem() {
+ return this.system.constructor.metadata.isInventoryItem ?? false;
}
- static async createDialog(data = {}, { parent = null, pack = null, ...options } = {}) {
- const documentName = this.metadata.name;
- const types = game.documentTypes[documentName].filter(t => t !== CONST.BASE_DOCUMENT_TYPE);
- let collection;
- if (!parent) {
- if (pack) collection = game.packs.get(pack);
- else collection = game.collections.get(documentName);
- }
- const folders = collection?._formatFolderSelectOptions() ?? [];
- const label = game.i18n.localize(this.metadata.label);
- const title = game.i18n.format('DOCUMENT.Create', { type: label });
- const typeObjects = types.reduce((obj, t) => {
- const label = CONFIG[documentName]?.typeLabels?.[t] ?? t;
- obj[t] = { value: t, label: game.i18n.has(label) ? game.i18n.localize(label) : t };
- return obj;
- }, {});
+ /** @inheritdoc */
+ static async createDialog(data = {}, createOptions = {}, options = {}) {
+ const { folders, types, template, context = {}, ...dialogOptions } = options;
- // Render the document creation form
- const html = await foundry.applications.handlebars.renderTemplate(
- 'systems/daggerheart/templates/sidebar/documentCreate.hbs',
- {
- folders,
- name: data.name || game.i18n.format('DOCUMENT.New', { type: label }),
- folder: data.folder,
- hasFolders: folders.length >= 1,
- type: data.type || CONFIG[documentName]?.defaultType || typeObjects.armor,
- types: {
- Items: [typeObjects.armor, typeObjects.weapon, typeObjects.consumable, typeObjects.miscellaneous],
- Character: [
- typeObjects.class,
- typeObjects.subclass,
- typeObjects.ancestry,
- typeObjects.community,
- typeObjects.feature,
- typeObjects.domainCard
- ]
- },
- hasTypes: types.length > 1
+ if (types?.length === 0) {
+ throw new Error('The array of sub-types to restrict to must not be empty.');
+ }
+
+ const documentTypes = this.TYPES.filter(type => type !== 'base' && (!types || types.includes(type))).map(
+ type => {
+ const labelKey = CONFIG.Item?.typeLabels?.[type];
+ const label = labelKey && game.i18n.has(labelKey) ? game.i18n.localize(labelKey) : type;
+
+ const isInventoryItem = CONFIG.Item.dataModels[type]?.metadata?.isInventoryItem;
+ const group =
+ isInventoryItem === true
+ ? 'Inventory Items'
+ : isInventoryItem === false
+ ? 'Character Items'
+ : 'Other';
+
+ return { value: type, label, group };
}
);
- // Render the confirmation dialog window
- return Dialog.prompt({
- title: title,
- content: html,
- label: title,
- callback: html => {
- const form = html[0].querySelector('form');
- const fd = new FormDataExtended(form);
- foundry.utils.mergeObject(data, fd.object, { inplace: true });
- if (!data.folder) delete data.folder;
- if (types.length === 1) data.type = types[0];
- if (!data.name?.trim()) data.name = this.defaultName();
- return this.create(data, { parent, pack, renderSheet: true });
- },
- rejectClose: false,
- options
+ if (!documentTypes.length) {
+ throw new Error('No document types were permitted to be created.');
+ }
+
+ const sortedTypes = documentTypes.sort((a, b) => a.label.localeCompare(b.label, game.i18n.lang));
+
+ return await super.createDialog(data, createOptions, {
+ folders,
+ types,
+ template,
+ context: { types: sortedTypes, ...context },
+ ...dialogOptions
});
}
async selectActionDialog() {
const content = await foundry.applications.handlebars.renderTemplate(
- 'systems/daggerheart/templates/views/actionSelect.hbs',
- { actions: this.system.actions }
- ),
+ 'systems/daggerheart/templates/views/actionSelect.hbs',
+ { actions: this.system.actions }
+ ),
title = 'Select Action',
type = 'div',
data = {};
@@ -142,8 +120,8 @@ export default class DhpItem extends Item {
this.type === 'ancestry'
? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.AncestryTitle')
: this.type === 'community'
- ? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.CommunityTitle')
- : game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'),
+ ? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.CommunityTitle')
+ : game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'),
origin: origin,
img: this.img,
name: this.name,
diff --git a/styles/daggerheart.css b/styles/daggerheart.css
index cecf313c..4dbf6ac4 100755
--- a/styles/daggerheart.css
+++ b/styles/daggerheart.css
@@ -4080,6 +4080,10 @@ div.daggerheart.views.multiclass {
.application.sheet.daggerheart.actor.dh-style.character .tab.inventory .search-section .search-bar input:placeholder {
color: light-dark(#18162e50, #efe6d850);
}
+.application.sheet.daggerheart.actor.dh-style.character .tab.inventory .search-section .search-bar input::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+ display: none;
+}
.application.sheet.daggerheart.actor.dh-style.character .tab.inventory .search-section .search-bar .icon {
align-content: center;
height: 32px;
diff --git a/styles/less/actors/character/inventory.less b/styles/less/actors/character/inventory.less
index a6caf22b..c1583046 100644
--- a/styles/less/actors/character/inventory.less
+++ b/styles/less/actors/character/inventory.less
@@ -1,65 +1,70 @@
-@import '../../utils/colors.less';
-@import '../../utils/fonts.less';
-
-.application.sheet.daggerheart.actor.dh-style.character {
- .tab.inventory {
- .search-section {
- display: flex;
- gap: 10px;
- align-items: center;
-
- .search-bar {
- position: relative;
- color: light-dark(@dark-blue-50, @beige-50);
- width: 100%;
- padding-top: 5px;
-
- input {
- border-radius: 50px;
- font-family: @font-body;
- background: light-dark(@dark-blue-10, @golden-10);
- border: none;
- outline: 2px solid transparent;
- transition: all 0.3s ease;
- padding: 0 20px;
-
- &:hover {
- outline: 2px solid light-dark(@dark, @golden);
- }
-
- &:placeholder {
- color: light-dark(@dark-blue-50, @beige-50);
- }
- }
-
- .icon {
- align-content: center;
- height: 32px;
- position: absolute;
- right: 20px;
- font-size: 16px;
- z-index: 1;
- color: light-dark(@dark-blue-50, @beige-50);
- }
- }
- }
-
- .items-section {
- display: flex;
- flex-direction: column;
- gap: 10px;
- overflow-y: auto;
- mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%);
- padding: 20px 0;
- height: 80%;
-
- scrollbar-width: thin;
- scrollbar-color: light-dark(@dark-blue, @golden) transparent;
- }
-
- .currency-section {
- display: flex;
- gap: 10px;
- }
- }
-}
+@import '../../utils/colors.less';
+@import '../../utils/fonts.less';
+
+.application.sheet.daggerheart.actor.dh-style.character {
+ .tab.inventory {
+ .search-section {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+
+ .search-bar {
+ position: relative;
+ color: light-dark(@dark-blue-50, @beige-50);
+ width: 100%;
+ padding-top: 5px;
+
+ input {
+ border-radius: 50px;
+ font-family: @font-body;
+ background: light-dark(@dark-blue-10, @golden-10);
+ border: none;
+ outline: 2px solid transparent;
+ transition: all 0.3s ease;
+ padding: 0 20px;
+
+ &:hover {
+ outline: 2px solid light-dark(@dark, @golden);
+ }
+
+ &:placeholder {
+ color: light-dark(@dark-blue-50, @beige-50);
+ }
+
+ &::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+ display: none;
+ }
+ }
+
+ .icon {
+ align-content: center;
+ height: 32px;
+ position: absolute;
+ right: 20px;
+ font-size: 16px;
+ z-index: 1;
+ color: light-dark(@dark-blue-50, @beige-50);
+ }
+ }
+ }
+
+ .items-section {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ overflow-y: auto;
+ mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%);
+ padding: 20px 0;
+ height: 80%;
+
+ scrollbar-width: thin;
+ scrollbar-color: light-dark(@dark-blue, @golden) transparent;
+ }
+
+ .currency-section {
+ display: flex;
+ gap: 10px;
+ }
+ }
+}
diff --git a/templates/sheets/actors/character/inventory.hbs b/templates/sheets/actors/character/inventory.hbs
index 22b32d3f..3f4b98be 100644
--- a/templates/sheets/actors/character/inventory.hbs
+++ b/templates/sheets/actors/character/inventory.hbs
@@ -8,7 +8,7 @@
-
+
diff --git a/templates/sheets/actors/character/loadout.hbs b/templates/sheets/actors/character/loadout.hbs
index e80aaa80..de63323c 100644
--- a/templates/sheets/actors/character/loadout.hbs
+++ b/templates/sheets/actors/character/loadout.hbs
@@ -8,7 +8,7 @@
-
+