mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-14 20:51:07 +01:00
FEAT: add SearchFilter for character-sheet Inventory and DomainCards
FEAT: simplify the preparetion of inventory context
This commit is contained in:
parent
15b696398c
commit
11f6ee3a7f
7 changed files with 169 additions and 46 deletions
|
|
@ -56,7 +56,6 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
||||||
resizable: true
|
resizable: true
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
handler: this.updateForm,
|
|
||||||
submitOnChange: true,
|
submitOnChange: true,
|
||||||
closeOnSubmit: false
|
closeOnSubmit: false
|
||||||
},
|
},
|
||||||
|
|
@ -218,6 +217,15 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
||||||
this._createContextMenues();
|
this._createContextMenues();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _onRender(context, options) {
|
||||||
|
await super._onRender(context, options);
|
||||||
|
|
||||||
|
this._createSearchFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
_createContextMenues() {
|
_createContextMenues() {
|
||||||
const allOptions = {
|
const allOptions = {
|
||||||
useItem: {
|
useItem: {
|
||||||
|
|
@ -402,49 +410,156 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
|
|
||||||
context.inventory = {
|
return context;
|
||||||
consumable: {
|
}
|
||||||
titles: {
|
|
||||||
name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.ConsumableTitle'),
|
|
||||||
quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle')
|
|
||||||
},
|
|
||||||
items: this.document.items.filter(x => x.type === 'consumable')
|
|
||||||
},
|
|
||||||
miscellaneous: {
|
|
||||||
titles: {
|
|
||||||
name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.MiscellaneousTitle'),
|
|
||||||
quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle')
|
|
||||||
},
|
|
||||||
items: this.document.items.filter(x => x.type === 'miscellaneous')
|
|
||||||
},
|
|
||||||
weapons: {
|
|
||||||
titles: {
|
|
||||||
name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.WeaponsTitle'),
|
|
||||||
quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle')
|
|
||||||
},
|
|
||||||
items: this.document.items.filter(x => x.type === 'weapon')
|
|
||||||
},
|
|
||||||
armor: {
|
|
||||||
titles: {
|
|
||||||
name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.ArmorsTitle'),
|
|
||||||
quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle')
|
|
||||||
},
|
|
||||||
items: this.document.items.filter(x => x.type === 'armor')
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (context.inventory.length === 0) {
|
/**@inheritdoc */
|
||||||
context.inventory = Array(1).fill(Array(5).fill([]));
|
async _preparePartContext(partId, context, options) {
|
||||||
|
context = await super._preparePartContext(partId, context, options);
|
||||||
|
|
||||||
|
switch (partId) {
|
||||||
|
case "inventory":
|
||||||
|
context.inventory = this._prepareInventoryContext();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateForm(event, _, formData) {
|
/**
|
||||||
await this.document.update(formData.object);
|
* Prepare the inventory context, grouping items by type
|
||||||
this.render();
|
* and providing localized titles for display in the inventory UI.
|
||||||
|
*
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
_prepareInventoryContext() {
|
||||||
|
const items = this.document.itemTypes;
|
||||||
|
const quantityTitle = game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle');
|
||||||
|
|
||||||
|
const inventoryConfig = {
|
||||||
|
consumable: 'ConsumableTitle',
|
||||||
|
miscellaneous: 'MiscellaneousTitle',
|
||||||
|
weapons: 'WeaponsTitle',
|
||||||
|
armor: 'ArmorsTitle'
|
||||||
|
};
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(inventoryConfig).map(([key, nameKey]) => [
|
||||||
|
key,
|
||||||
|
{
|
||||||
|
titles: {
|
||||||
|
name: game.i18n.localize(`DAGGERHEART.Sheets.PC.InventoryTab.${nameKey}`),
|
||||||
|
quantity: quantityTitle
|
||||||
|
},
|
||||||
|
items: items[key]
|
||||||
|
}
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* 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<string>, loadout: Set<string> }}
|
||||||
|
*/
|
||||||
|
#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) {
|
async mapFeatureType(data, configType) {
|
||||||
return await Promise.all(
|
return await Promise.all(
|
||||||
data.map(async x => {
|
data.map(async x => {
|
||||||
|
|
@ -771,9 +886,8 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
const systemData = {
|
const systemData = {
|
||||||
name: game.i18n.localize('DAGGERHEART.General.Experience.Single'),
|
name: game.i18n.localize('DAGGERHEART.General.Experience.Single'),
|
||||||
description: `${experience.description} ${
|
description: `${experience.description} ${experience.total < 0 ? experience.total : `+${experience.total}`
|
||||||
experience.total < 0 ? experience.total : `+${experience.total}`
|
}`
|
||||||
}`
|
|
||||||
};
|
};
|
||||||
const msg = new cls({
|
const msg = new cls({
|
||||||
type: 'abilityUse',
|
type: 'abilityUse',
|
||||||
|
|
|
||||||
|
|
@ -3920,6 +3920,10 @@ div.daggerheart.views.multiclass {
|
||||||
.application.sheet.daggerheart.actor.dh-style.character .tab.inventory .search-section .search-bar input:placeholder {
|
.application.sheet.daggerheart.actor.dh-style.character .tab.inventory .search-section .search-bar input:placeholder {
|
||||||
color: light-dark(#18162e50, #efe6d850);
|
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 {
|
.application.sheet.daggerheart.actor.dh-style.character .tab.inventory .search-section .search-bar .icon {
|
||||||
align-content: center;
|
align-content: center;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,11 @@
|
||||||
&:placeholder {
|
&:placeholder {
|
||||||
color: light-dark(@dark-blue-50, @beige-50);
|
color: light-dark(@dark-blue-50, @beige-50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&::-webkit-search-cancel-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<i class="fa-solid fa-magnifying-glass"></i>
|
<i class="fa-solid fa-magnifying-glass"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" name="" id="" placeholder="Search...">
|
<input type="search" name="search" class="search-inventory" placeholder="Search...">
|
||||||
</div>
|
</div>
|
||||||
<a><i class="fa-solid fa-filter"></i></a>
|
<a><i class="fa-solid fa-filter"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<i class="fa-solid fa-magnifying-glass"></i>
|
<i class="fa-solid fa-magnifying-glass"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" name="" id="" placeholder="Search...">
|
<input type="search" name="search" class="search-loadout" placeholder="Search...">
|
||||||
</div>
|
</div>
|
||||||
<a><i class="fa-solid fa-filter"></i></a>
|
<a><i class="fa-solid fa-filter"></i></a>
|
||||||
<button class="btn-toggle-view" data-action="toggleLoadoutView" data-value="{{this.abilities.loadout.listView}}">
|
<button class="btn-toggle-view" data-action="toggleLoadoutView" data-value="{{this.abilities.loadout.listView}}">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<li class="card-item">
|
<li class="card-item" data-item-id="{{item.id}}">
|
||||||
<img src="{{item.img}}" data-action="viewObject" data-uuid="{{item.uuid}}" class="card-img" />
|
<img src="{{item.img}}" data-action="viewObject" data-uuid="{{item.uuid}}" class="card-img" />
|
||||||
<div class="card-label">
|
<div class="card-label">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<li class="inventory-item">
|
<li class="inventory-item" data-item-id="{{item.id}}">
|
||||||
<img src="{{item.img}}" data-action="viewObject" data-uuid="{{item.uuid}}" class="item-img" />
|
<img src="{{item.img}}" data-action="viewObject" data-uuid="{{item.uuid}}" class="item-img" />
|
||||||
<div class="item-label">
|
<div class="item-label">
|
||||||
<div class="item-name">{{item.name}}</div>
|
<div class="item-name">{{item.name}}</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue