From 845d72e20c62f9c176de6484f77a5d99d7f555bf Mon Sep 17 00:00:00 2001 From: Dapoolp Date: Tue, 5 Aug 2025 01:15:16 +0200 Subject: [PATCH] Item Browser v0.5 --- .../applications/sheets/actors/character.mjs | 11 +- module/applications/ui/itemBrowser.mjs | 341 +++++++++++++++++- module/config/_module.mjs | 1 + module/config/itemBrowserConfig.mjs | 231 ++++++++++++ module/config/system.mjs | 4 +- module/data/fields/action/rangeField.mjs | 3 +- module/data/fields/action/rollField.mjs | 2 +- module/data/item/weapon.mjs | 6 +- module/helpers/handlebarsHelper.mjs | 1 + styles/less/ui/index.less | 1 + styles/less/ui/item-browser/item-browser.less | 204 +++++++++++ .../sheets/actors/character/inventory.hbs | 1 + templates/sheets/items/weapon/settings.hbs | 6 +- templates/ui/itemBrowser.hbs | 0 templates/ui/itemBrowser/itemBrowser.hbs | 80 ++++ templates/ui/itemBrowser/sidebar.hbs | 18 + 16 files changed, 880 insertions(+), 30 deletions(-) create mode 100644 module/config/itemBrowserConfig.mjs create mode 100644 styles/less/ui/item-browser/item-browser.less delete mode 100644 templates/ui/itemBrowser.hbs create mode 100644 templates/ui/itemBrowser/itemBrowser.hbs create mode 100644 templates/ui/itemBrowser/sidebar.hbs diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 76ca7562..35b3860b 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -5,6 +5,7 @@ import DhCharacterlevelUp from '../../levelup/characterLevelup.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs'; +import { ItemBrowser } from '../../ui/itemBrowser.mjs'; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ @@ -25,7 +26,8 @@ export default class CharacterSheet extends DHBaseActorSheet { toggleEquipItem: CharacterSheet.#toggleEquipItem, toggleResourceDice: CharacterSheet.#toggleResourceDice, handleResourceDice: CharacterSheet.#handleResourceDice, - useDowntime: this.useDowntime + useDowntime: this.useDowntime, + tempBrowser: CharacterSheet.#tempBrowser, }, window: { resizable: true @@ -707,6 +709,13 @@ export default class CharacterSheet extends DHBaseActorSheet { }); } + /** + * Temp + */ + static async #tempBrowser(_, target) { + new ItemBrowser().render({ force: true }); + } + /** * Handle the roll values of resource dice. * @type {ApplicationClickAction} diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index ae33c924..82ef5dba 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -9,49 +9,350 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; * @mixes HandlebarsApplication */ -export default class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { +export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { constructor(options = {}) { super(options); + this.items = []; + this.fieldFilter = []; + this.selectedMenu = { path: [], data: null }; + this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig; } /** @inheritDoc */ static DEFAULT_OPTIONS = { id: 'itemBrowser', - classes: [], + classes: ['dh-style'], tag: 'div', - // window: { - // frame: true, - // title: 'Item Browser', - // positioned: true, - // resizable: true - // }, - actions: { - // setFear: FearTracker.setFear, - // increaseFear: FearTracker.increaseFear + // title: 'Item Browser', + window: { + frame: true, + title: 'Item Browser', + positioned: true, + resizable: true }, - /* position: { - width: 222, - height: 222 + actions: { + selectFolder: this.selectFolder, + expandContent: this.expandContent, + resetFilters: this.resetFilters + }, + position: { + width: 1000, + height: 800 // top: "200px", // left: "120px" - } */ + } }; /** @override */ static PARTS = { - resources: { - root: true, - template: 'systems/daggerheart/templates/ui/itemBrowser.hbs' + sidebar: { + template: 'systems/daggerheart/templates/ui/itemBrowser/sidebar.hbs' + }, + list: { + template: 'systems/daggerheart/templates/ui/itemBrowser/itemBrowser.hbs' } }; + /* -------------------------------------------- */ + /* Filter Tracking */ + /* -------------------------------------------- */ + + /** + * The currently active search filter. + * @type {foundry.applications.ux.SearchFilter} + */ + #search = {}; + + #input = {}; + + /** + * Tracks which item IDs are currently displayed, organized by filter type and section. + * @type {{ + * inventory: { + * search: Set, + * input: Set + * } + * }} + */ + #filteredItems = { + browser: { + search: new Set(), + input: new Set() + } + }; + + /** @inheritDoc */ + async _onRender(context, options) { + await super._onRender(context, options); + + this._createSearchFilter(); + this._createFilterInputs(); + this._createDragProcess(); + } + /* -------------------------------------------- */ /* Rendering */ /* -------------------------------------------- */ /** @override */ async _prepareContext(options) { - const data = await super._prepareContext(options); - return data; + const context = await super._prepareContext(options); + context.compendiums = this.getCompendiumFolders(foundry.utils.deepClone(this.config)); + // context.pathTitle = this.pathTile; + context.menu = this.selectedMenu; + context.formatLabel = this.formatLabel; + context.formatChoices = this.formatChoices; + context.fieldFilter = this.fieldFilter = this.selectedMenu.data?.filters ? this._createFieldFilter() : []; + context.items = this.items; + console.log(this.items) + return context; + } + + getCompendiumFolders(config, parent = null, depth = 0) { + let folders = []; + Object.values(config).forEach(c => { + const folder = { + id: c.id, + label: c.label, + selected: (!parent || parent.selected) && this.selectedMenu.path[depth] === c.id + } + folder.folders = c.folders ? ItemBrowser.sortBy(this.getCompendiumFolders(c.folders, folder, depth + 2), 'label') : []; + // sortBy(Object.values(c.folders), 'label') + folders.push(folder) + }) + + // console.log(folders) + return folders; + } + + static async selectFolder(_, target) { + const config = foundry.utils.deepClone(this.config), + compendium = target.closest('[data-compendium-id]').dataset.compendiumId, + folderId = target.dataset.folderId, + folderPath = `${compendium}.folders.${folderId}`, + folderData = foundry.utils.getProperty(config, folderPath); + + this.selectedMenu = { + path: folderPath.split('.'), + data: folderData + } + + let items = []; + for(const key of folderData.keys) { + const comp = game.packs.get(`${compendium}.${key}`); + if(!comp) return; + items = items.concat(await comp.getDocuments({ type__in: folderData.type })); + } + + this.items = ItemBrowser.sortBy(items, 'name'); + this.render({ force: true }); + } + + static expandContent(_, target) { + const parent = target.parentElement; + parent.classList.toggle("expanded"); + } + + static sortBy(data, property) { + return data.sort((a, b) => a[property] > b[property] ? 1 : -1) + } + + formatLabel(item, field) { + const property = foundry.utils.getProperty(item, field.key); + if(typeof field.format !== 'function') return property; + return field.format(property); + } + + formatChoices(data) { + if(!data.field.choices) return null; + const config = { + choices: data.field.choices + }; + foundry.data.fields.StringField._prepareChoiceConfig(config); + return config.options.filter(c => data.filtered.includes(c.value) || data.filtered.includes(c.label.toLowerCase())); + } + + _createFieldFilter() { + const filters = []; + this.selectedMenu.data.filters.forEach(f => { + if(typeof f.field === 'string') + f.field = foundry.utils.getProperty(game, f.field); + else if(typeof f.choices === 'function') + f.choices = f.choices(); + filters.push(f) + }) + return filters; + } + + /* -------------------------------------------- */ + /* Search Inputs */ + /* -------------------------------------------- */ + + /** + * 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: 'browser', + input: 'input[type="search"].search-browser', + content: '[data-application-part="list"] .item-list', + callback: this._onSearchFilterBrowser.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; + } + } + + /* -------------------------------------------- */ + /* Filter Inputs */ + /* -------------------------------------------- */ + + _createFilterInputs() { + const inputs = [ + { + key: 'browser', + container: '[data-application-part="list"] .filter-content .wrapper', + content: '[data-application-part="list"] .item-list', + callback: this._onInputFilterBrowser.bind(this), + // target: '.filter-button', + // filters: FilterMenu.invetoryFilters + } + ]; + + inputs.forEach(m => { + const container = this.element.querySelector(m.container); + if(!container) return this.#input[m.key] = {}; + const inputs = container.querySelectorAll('input, select'); + inputs.forEach(input => { + input.addEventListener('change', this._onInputFilterBrowser.bind(this)) + }); + this.#filteredItems[m.key].input = new Set(this.items.map(i => i.id)); + this.#input[m.key] = inputs; + }); + } + + /** + * 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 + */ + async _onSearchFilterBrowser(event, query, rgx, html) { + this.#filteredItems.browser.search.clear(); + + for (const li of html.querySelectorAll('.item-container')) { + const itemUUID = li.dataset.itemUuid, + item = this.items.find(i => i.uuid === itemUUID); + const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name); + if (matchesSearch) this.#filteredItems.browser.search.add(item.id); + const { input } = this.#filteredItems.browser; + li.hidden = !(input.has(item.id) && matchesSearch); + // li.hidden = !(matchesSearch); + } + } + + /** + * Callback when filters change + * @param {PointerEvent} event + * @param {HTMLElement} html + */ + async _onInputFilterBrowser(event) { + this.#filteredItems.browser.input.clear(); + + console.log(event.target.name) + + this.fieldFilter.find(f => f.key === event.target.name).value = event.target.value; + + // console.log(_event, html, filters) + + for (const li of event.target.closest('[data-application-part="list"]').querySelectorAll('.item-container')) { + const itemUUID = li.dataset.itemUuid, + item = this.items.find(i => i.uuid === itemUUID); + + const matchesMenu = + this.fieldFilter.length === 0 || this.fieldFilter.every(f => { + return (!f.value && f.value !== false) || foundry.applications.ux.SearchFilter.evaluateFilter(item, this.createFilterData(f)) + }); + if (matchesMenu) this.#filteredItems.browser.input.add(item.id); + + const { search } = this.#filteredItems.browser; + li.hidden = !(search.has(item.id) && matchesMenu); + // li.hidden = !(matchesMenu); + } + } + + createFilterData(filter) { + return { + field: filter.key, + value: isNaN(filter.value) ? (["true", "false"].includes(filter.value) ? filter.value === "true" : filter.value) : Number(filter.value), + operator: filter.operator, + negate: filter.negate + } + } + + static resetFilters() { + this.render({ force: true }); + } + + /** + * Serialize salient information about this Document when dragging it. + * @returns {object} An object of drag data. + */ + // toDragData() { + // const dragData = {type: this.documentName}; + // if ( this.id ) dragData.uuid = this.uuid; + // else dragData.data = this.toObject(); + // return dragData; + // } + + _createDragProcess() { + new foundry.applications.ux.DragDrop.implementation({ + dragSelector: ".item-container", + // dropSelector: ".directory-list", + permissions: { + dragstart: this._canDragStart.bind(this), + // drop: this._canDragDrop.bind(this) + }, + callbacks: { + // dragover: this._onDragOver.bind(this), + dragstart: this._onDragStart.bind(this), + // drop: this._onDrop.bind(this) + } + }).bind(this.element); + // this.element.querySelectorAll(".directory-item.folder").forEach(folder => { + // folder.addEventListener("dragenter", this._onDragHighlight.bind(this)); + // folder.addEventListener("dragleave", this._onDragHighlight.bind(this)); + // }); + } + + async _onDragStart(event) { + // console.log(event) + // ui.context?.close({ animate: false }); + const { itemUuid } = event.target.closest('[data-item-uuid]').dataset; + const dragData = foundry.utils.fromUuidSync(itemUuid).toDragData(); + // console.log(dragData) + // const dragData = { UUID: itemUuid }; + event.dataTransfer.setData("text/plain", JSON.stringify(dragData)); + } + + _canDragStart() { + return true; } } \ No newline at end of file diff --git a/module/config/_module.mjs b/module/config/_module.mjs index 99069dda..63797607 100644 --- a/module/config/_module.mjs +++ b/module/config/_module.mjs @@ -7,3 +7,4 @@ export * as generalConfig from './generalConfig.mjs'; export * as itemConfig from './itemConfig.mjs'; export * as settingsConfig from './settingsConfig.mjs'; export * as systemConfig from './system.mjs'; +export * as itemBrowserConfig from './itemBrowserConfig.mjs'; diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs new file mode 100644 index 00000000..232aa413 --- /dev/null +++ b/module/config/itemBrowserConfig.mjs @@ -0,0 +1,231 @@ +export const compendiumConfig = { + "daggerheart": { + id: "daggerheart", + label: "DAGGERHEART", + folders: { + "adversaries": { + id: "adversaries", + keys: ["adversaries"], + label: "Adversaries", + type: ["adversary"], + columns: [ + { + key: "system.tier", + label: "Tier" + }, + { + key: "system.type", + label: "Type" + } + ], + filters: [ + { + key: "system.tier", + label: "Tier", + field: 'system.api.models.actors.DhAdversary.schema.fields.tier' + }, + { + key: "system.type", + label: "Type", + field: 'system.api.models.actors.DhAdversary.schema.fields.type' + }, + { + key: "system.difficulty", + label: "Difficulty (Min)", + field: 'system.api.models.actors.DhAdversary.schema.fields.difficulty', + operator: "gte" + }, + { + key: "system.difficulty", + label: "Difficulty (Max)", + field: 'system.api.models.actors.DhAdversary.schema.fields.difficulty', + operator: "lte" + }, + { + key: "system.resources.hitPoints.max", + label: "Hit Points (Min)", + field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.hitPoints.fields.max', + operator: "gte" + }, + { + key: "system.resources.hitPoints.max", + label: "Hit Points (Max)", + field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.hitPoints.fields.max', + operator: "lte" + }, + { + key: "system.resources.stress.max", + label: "Stress (Min)", + field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.stress.fields.max', + operator: "gte" + }, + { + key: "system.resources.stress.max", + label: "Stress (Max)", + field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.stress.fields.max', + operator: "lte" + }, + ] + }, + "ancestries": { + id: "ancestries", + keys: ["ancestries"], + label: "Ancestries", + type: ["ancestry"], + folders: { + "features": { + id: "features", + keys: ["ancestries"], + label: "Features", + type: ["feature"] + } + } + }, + "equipments": { + id: "equipments", + keys: ["armors", "weapons", "consumables", "loot"], + label: "Equipments", + type: ["armor", "weapon", "consumable", "loot"], + columns: [ + { + key: "type", + label: "Type" + }, + { + key: "system.secondary", + label: "Subtype", + format: (isSecondary) => isSecondary ? "secondary" : (isSecondary === false ? "primary" : '-') + }, + { + key: "system.tier", + label: "Tier" + } + ], + filters: [ + { + key: "type", + label: "Type", + // filtered: ["armor", "weapon", "consumable", "loot"], + // field: 'system.api.documents.DHItem.schema.fields.type', + // valueAttr: 'label' + choices: () => CONFIG.Item.documentClass.TYPES.filter(t => ["armor", "weapon", "consumable", "loot"].includes(t)).map(t => ({ value: t, label: t })) + }, + { + key: "system.secondary", + label: "Subtype", + choices: [ + { value: false, label: "Primary Weapon"}, + { value: true, label: "Secondary Weapon"} + ] + }, + { + key: "system.tier", + label: "Tier", + choices: [{ value: "1", label: "1"}, { value: "2", label: "2"}, { value: "3", label: "3"}, { value: "4", label: "4"}] + }, + { + key: "system.burden", + label: "Burden", + field: 'system.api.models.items.DHWeapon.schema.fields.burden' + }, + { + key: "system.attack.roll.trait", + label: "Trait", + field: 'system.api.models.actions.actionsTypes.attack.schema.fields.roll.fields.trait' + }, + { + key: "system.attack.range", + label: "Range", + field: 'system.api.models.actions.actionsTypes.attack.schema.fields.range' + }, + { + key: "system.baseScore", + label: "Armor Score (Min)", + field: 'system.api.models.items.DHArmor.schema.fields.baseScore', + operator: "gte" + }, + { + key: "system.baseScore", + label: "Armor Score (Max)", + field: 'system.api.models.items.DHArmor.schema.fields.baseScore', + operator: "lte" + } + ] + }, + "classes": { + id: "classes", + keys: ["classes"], + label: "Classes", + type: ["class"], + folders: { + "features": { + id: "features", + keys: ["classes"], + label: "Features", + type: ["feature"] + }, + "items": { + id: "items", + keys: ["classes"], + label: "Items", + type: ["armor", "weapon", "consumable", "loot"] + } + } + }, + "subclasses": { + id: "subclasses", + keys: ["subclasses"], + label: "Subclasses", + type: ["subclass"], + folders: { + "features": { + id: "features", + keys: ["subclasses"], + label: "Features", + type: ["feature"] + } + } + }, + "domains": { + id: "domains", + keys: ["domains"], + label: "Domain Cards", + type: ["domainCard"] + }, + "communities": { + id: "communities", + keys: ["communities"], + label: "Communities", + type: ["community"], + folders: { + "features": { + id: "features", + keys: ["communities"], + label: "Features", + type: ["feature"] + } + } + }, + "environments": { + id: "environments", + keys: ["environments"], + label: "Environments", + type: ["environment"] + }, + "beastforms": { + id: "beastforms", + keys: ["beastforms"], + label: "Beastforms", + type: ["beastform"], + folders: { + "features": { + id: "features", + keys: ["beastforms"], + label: "Features", + type: ["feature"] + } + } + } + } + } +} \ No newline at end of file diff --git a/module/config/system.mjs b/module/config/system.mjs index e72667b1..374fd58c 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -6,6 +6,7 @@ import * as SETTINGS from './settingsConfig.mjs'; import * as EFFECTS from './effectConfig.mjs'; import * as ACTIONS from './actionConfig.mjs'; import * as FLAGS from './flagsConfig.mjs'; +import * as ITEMBROWSER from './itemBrowserConfig.mjs' export const SYSTEM_ID = 'daggerheart'; @@ -18,5 +19,6 @@ export const SYSTEM = { SETTINGS, EFFECTS, ACTIONS, - FLAGS + FLAGS, + ITEMBROWSER }; diff --git a/module/data/fields/action/rangeField.mjs b/module/data/fields/action/rangeField.mjs index 2c906edb..221f00af 100644 --- a/module/data/fields/action/rangeField.mjs +++ b/module/data/fields/action/rangeField.mjs @@ -5,7 +5,8 @@ export default class RangeField extends fields.StringField { const options = { choices: CONFIG.DH.GENERAL.range, required: false, - blank: true + blank: true, + label: "DAGGERHEART.GENERAL.range" }; super(options, context); } diff --git a/module/data/fields/action/rollField.mjs b/module/data/fields/action/rollField.mjs index 86681265..a4df2a9e 100644 --- a/module/data/fields/action/rollField.mjs +++ b/module/data/fields/action/rollField.mjs @@ -5,7 +5,7 @@ export class DHActionRollData extends foundry.abstract.DataModel { static defineSchema() { return { type: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.GENERAL.rollTypes }), - trait: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.ACTOR.abilities }), + trait: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.ACTOR.abilities, label: "DAGGERHEART.GENERAL.Trait.single" }), difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }), bonus: new fields.NumberField({ nullable: true, initial: null, integer: true }), advState: new fields.StringField({ diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index aab5a895..eca5f288 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -18,12 +18,12 @@ export default class DHWeapon extends AttachableItem { const fields = foundry.data.fields; return { ...super.defineSchema(), - tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }), + tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1, label: "DAGGERHEART.GENERAL.Tiers.singular" }), equipped: new fields.BooleanField({ initial: false }), //SETTINGS - secondary: new fields.BooleanField({ initial: false }), - burden: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.burden, initial: 'oneHanded' }), + secondary: new fields.BooleanField({ initial: false, label: "DAGGERHEART.ITEMS.Weapon.secondaryWeapon" }), + burden: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.burden, initial: 'oneHanded', label: "DAGGERHEART.GENERAL.burden" }), weaponFeatures: new fields.ArrayField( new fields.SchemaField({ value: new fields.StringField({ diff --git a/module/helpers/handlebarsHelper.mjs b/module/helpers/handlebarsHelper.mjs index 6d6c2bbc..deb62659 100644 --- a/module/helpers/handlebarsHelper.mjs +++ b/module/helpers/handlebarsHelper.mjs @@ -11,6 +11,7 @@ export default class RegisterHandlebarsHelpers { damageSymbols: this.damageSymbols, rollParsed: this.rollParsed, hasProperty: foundry.utils.hasProperty, + getProperty: foundry.utils.getProperty, setVar: this.setVar, empty: this.empty }); diff --git a/styles/less/ui/index.less b/styles/less/ui/index.less index c2ad2b3b..e9865a2d 100644 --- a/styles/less/ui/index.less +++ b/styles/less/ui/index.less @@ -9,6 +9,7 @@ @import './combat-sidebar/encounter-controls.less'; @import './combat-sidebar/spotlight-control.less'; @import './combat-sidebar/token-actions.less'; +@import './item-browser/item-browser.less'; @import './countdown/countdown.less'; @import './countdown/sheet.less'; diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less new file mode 100644 index 00000000..eb9b761e --- /dev/null +++ b/styles/less/ui/item-browser/item-browser.less @@ -0,0 +1,204 @@ +@scrollbar-width: 20px; +#itemBrowser { + border: initial; + .window-content { + display: flex; + flex-direction: row; + gap: 50px; + + > div { + overflow: hidden; + display: flex; + flex-direction: column; + gap: 20px; + } + + div[data-application-part="list"] { + flex: 1; + gap: 10px; + } + + .compendium-container { + summary { + font-family: 'Montserrat', sans-serif; + font-weight: bold; + padding: 2px 12px; + border-radius: 3px; + background-color: light-dark(#18162e, #f3c267); + color: light-dark(#efe6d8, #18162e); + list-style: none; + + &::marker, /* Latest Chrome, Edge, Firefox */ + &::-webkit-details-marker /* Safari */ { + display: none; + } + } + + > .folder-list { + padding: 10px; + gap: 0; + + > div { + &.folder-list { + > div { + margin-top: 5px; + } + } + + &:not(.folder-list) { + margin-top: 10px; + } + } + } + } + + .menu-path, option, select { + text-transform: capitalize; + } + + .menu-path > :first-child { + text-transform: uppercase; + } + + .menu-path { + display: flex; + align-items: center; + > span { + font-family: 'Montserrat', sans-serif; + font-weight: bold; + padding: 2px 12px; + border-radius: 3px; + background-color: light-dark(#18162e, #f3c267); + color: light-dark(#efe6d8, #18162e); + + &.path-link { + padding: 0 5px; + background-color: light-dark(#f3c267, #18162e); + color: light-dark(#18162e, #efe6d8); + } + } + } + + .folder-list, .item-list-header, .item-header > div { + gap: 10px; + cursor: pointer; + } + + .item-filter { + + .wrapper, .form-group { + display: flex; + align-items: center; + gap: 10px; + } + + .form-group { + white-space: nowrap; + } + + .filter-header { + display: flex; + align-items: center; + gap: 10px; + } + } + + .folder-list { + display: flex; + flex-direction: column; + [data-folder-id] { + padding: 2px 12px; + border: 1px solid transparent; + } + + .is-selected, > [data-folder-id]:hover { + font-weight: bold; + border-radius: 3px; + background-color: light-dark(transparent, #18162e); + color: light-dark(#18162e, #f3c267); + border-color: light-dark(#18162e, #f3c267); + } + + .subfolder-list { + margin: 0 10px; + gap: 0; + } + } + + .item-list-header, .item-header > div { + display: grid; + grid-template-columns: 40px 400px repeat(auto-fit, minmax(100px, 1fr)); + align-items: center; + text-transform: capitalize; + // gap: 10px; + } + + .item-list-header, .item-list { + overflow-y: auto; + scrollbar-gutter: stable; + } + + .item-list-header { + background-color: light-dark(transparent, #18162e); + color: light-dark(#18162e, #f3c267); + border: 1px solid light-dark(#18162e, #f3c267); + border-radius: 6px; + min-height: 30px; + } + + .item-list { + display: flex; + flex-direction: column; + gap: 5px; + + .item-container { + &:hover { + background: rgba(255, 255, 255, .1); + } + } + + .item-desc .wrapper { + padding: 0 10px; + } + + img { + border-radius: 5px; + } + } + + .filter-content { + padding: 0 10px; + } + + .filter-content, .item-desc { + display: grid; + grid-template-rows: 0fr; + transition: all 0.3s ease-in-out; + .wrapper { + overflow: hidden; + display: grid; + grid-template-columns: repeat(4, 1fr); + + .form-group { + label { + flex: 1; + } + .form-fields { + flex: 2; + } + } + } + } + + .expanded + .extensible { + grid-template-rows: 1fr; + padding-top: 10px; + } + + .welcome-message { + display: flex; + align-items: center; + justify-content: center; + } + } +} \ No newline at end of file diff --git a/templates/sheets/actors/character/inventory.hbs b/templates/sheets/actors/character/inventory.hbs index 10433483..33efe829 100644 --- a/templates/sheets/actors/character/inventory.hbs +++ b/templates/sheets/actors/character/inventory.hbs @@ -68,4 +68,5 @@ canCreate=true }} + \ No newline at end of file diff --git a/templates/sheets/items/weapon/settings.hbs b/templates/sheets/items/weapon/settings.hbs index fd8f16d9..021fc627 100644 --- a/templates/sheets/items/weapon/settings.hbs +++ b/templates/sheets/items/weapon/settings.hbs @@ -6,15 +6,15 @@
{{localize tabs.settings.label}} {{localize "DAGGERHEART.GENERAL.Tiers.singular"}} - {{formField systemFields.tier value=source.system.tier}} + {{formInput systemFields.tier value=source.system.tier}} {{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon"}} - {{formField systemFields.secondary value=source.system.secondary}} + {{formInput systemFields.secondary value=source.system.secondary}} {{localize "DAGGERHEART.GENERAL.Trait.single"}} {{formInput systemFields.attack.fields.roll.fields.trait value=document.system.attack.roll.trait name="system.attack.roll.trait" label="DAGGERHEART.GENERAL.Trait.single" localize=true}} {{localize "DAGGERHEART.GENERAL.range"}} {{formInput systemFields.attack.fields.range value=document.system.attack.range label="Range" name="system.attack.range" localize=true}} {{localize "DAGGERHEART.GENERAL.burden"}} - {{formField systemFields.burden value=source.system.burden localize=true}} + {{formInput systemFields.burden value=source.system.burden localize=true}}
diff --git a/templates/ui/itemBrowser.hbs b/templates/ui/itemBrowser.hbs deleted file mode 100644 index e69de29b..00000000 diff --git a/templates/ui/itemBrowser/itemBrowser.hbs b/templates/ui/itemBrowser/itemBrowser.hbs new file mode 100644 index 00000000..ab7ebd79 --- /dev/null +++ b/templates/ui/itemBrowser/itemBrowser.hbs @@ -0,0 +1,80 @@ +
+ {{#if menu.data }} + +
+
+ + {{#if fieldFilter}} + + {{/if}} + +
+
+
+ {{#each fieldFilter}} + {{#if choices }} +
+ +
+ +
+
+ {{else}} + {{#if filtered }} + {{formField field localize=true blank="" choices=(@root.formatChoices this) valueAttr="value" name=key}} + {{else}} + {{#if field.label}} + {{formField field localize=true blank="" name=key}} + {{else}} + {{formField field localize=true blank="" name=key label=label}} + {{/if}} + {{/if}} + {{/if}} + {{/each}} +
+
+
+ {{!--
--}} + {{#if menu.data.columns.length}} +
+
+
Name
+ {{#each menu.data.columns}} +
{{label}}
+ {{/each}} +
+ {{/if}} +
+ {{#each items}} +
+
+
+ +
{{name}}
+ {{#each ../menu.data.columns}} +
{{#with (@root.formatLabel ../this this) as | label |}}{{label}}{{/with}}
+ {{/each}} +
+
+
+
{{{system.description}}}
+
+
+ {{/each}} +
+ {{!--
--}} + {{else}} +

Welcome to DAGGERHEART Items Browser

+ {{/if}} +
\ No newline at end of file diff --git a/templates/ui/itemBrowser/sidebar.hbs b/templates/ui/itemBrowser/sidebar.hbs new file mode 100644 index 00000000..c7d3dddc --- /dev/null +++ b/templates/ui/itemBrowser/sidebar.hbs @@ -0,0 +1,18 @@ +
+ {{#each compendiums}} +
+ {{label}} +
+ {{#each folders}} +
{{label}}
+ {{!--
{{label}}
--}} +
+ {{#each folders}} +
- {{label}}
+ {{/each}} +
+ {{/each}} +
+
+ {{/each}} +
\ No newline at end of file