From b23b6c75fb5651883b72db178992ed7d03e77e64 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Mon, 9 Feb 2026 12:42:00 +0100 Subject: [PATCH] [Feature] Browser Compendium Handling (#1648) * Initial version * . * Fixed so that CompendiumSetting saving refreshes the CompendiumBrowser for all users * . * Improved design * Fixed max height * Fixed local reload * Added GM restriction * Raised version * Fixed tooltip * Raised verison to 1.7.0 --- lang/en.json | 12 +- .../dialogs/CompendiumBrowserSettings.mjs | 136 ++++++++++++++++++ module/applications/dialogs/_module.mjs | 1 + module/applications/ui/itemBrowser.mjs | 43 +++++- module/config/settingsConfig.mjs | 1 + module/data/_module.mjs | 1 + module/data/compendiumBrowserSettings.mjs | 35 +++++ module/systemRegistration/settings.mjs | 8 +- module/systemRegistration/socket.mjs | 3 +- .../compendiumBrowserPackDialog/sheet.less | 105 ++++++++++++++ styles/less/dialog/index.less | 2 + styles/less/global/elements.less | 8 ++ styles/less/ui/chat/chat.less | 2 +- system.json | 2 +- .../footer.hbs | 3 + .../compendiumBrowserSettingsDialog/packs.hbs | 36 +++++ templates/ui/itemBrowser/sidebar.hbs | 3 +- 17 files changed, 392 insertions(+), 9 deletions(-) create mode 100644 module/applications/dialogs/CompendiumBrowserSettings.mjs create mode 100644 module/data/compendiumBrowserSettings.mjs create mode 100644 styles/less/dialog/compendiumBrowserPackDialog/sheet.less create mode 100644 templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs create mode 100644 templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs diff --git a/lang/en.json b/lang/en.json index 9b2b4872..b1883221 100755 --- a/lang/en.json +++ b/lang/en.json @@ -352,6 +352,11 @@ "requestSpotlight": "Request The Spotlight", "openCountdowns": "Countdowns" }, + "CompendiumBrowserSettings": { + "title": "Enable Compendiums", + "enableSource": "Enable Source", + "disableSource": "Disable Source" + }, "ContextMenu": { "disableEffect": "Disable Effect", "enableEffect": "Enable Effect", @@ -2177,7 +2182,9 @@ "configuration": "Configuration", "base": "Base", "triggers": "Triggers", - "deathMoves": "Deathmoves" + "deathMoves": "Deathmoves", + "sources": "Sources", + "packs": "Packs" }, "Tiers": { "singular": "Tier", @@ -2846,6 +2853,7 @@ "ItemBrowser": { "title": "Daggerheart Compendium Browser", "hint": "Select a Folder in sidebar to start browsing through the compendium", + "browserSettings": "Browser Settings", "searchPlaceholder": "Search...", "columnName": "Name", "tooltipFilters": "Filters", @@ -3002,7 +3010,7 @@ "rulesOn": "Rules On", "rulesOff": "Rules Off", "remainingUses": "Uses refresh on {type}", - "rightClickExtand": "Right-Click to extand", + "rightClickExtend": "Right-Click to extend", "companionPartnerLevelBlock": "The companion needs an assigned partner to level up.", "configureAttribution": "Configure Attribution", "deleteItem": "Delete Item", diff --git a/module/applications/dialogs/CompendiumBrowserSettings.mjs b/module/applications/dialogs/CompendiumBrowserSettings.mjs new file mode 100644 index 00000000..42d0e256 --- /dev/null +++ b/module/applications/dialogs/CompendiumBrowserSettings.mjs @@ -0,0 +1,136 @@ +const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; + +export default class CompendiumBrowserSettings extends HandlebarsApplicationMixin(ApplicationV2) { + constructor() { + super(); + + this.browserSettings = game.settings + .get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings) + .toObject(); + } + + static DEFAULT_OPTIONS = { + tag: 'div', + classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'compendium-brower-settings'], + window: { + icon: 'fa-solid fa-book', + title: 'DAGGERHEART.APPLICATIONS.CompendiumBrowserSettings.title' + }, + position: { + width: 500 + }, + actions: { + toggleSource: CompendiumBrowserSettings.#toggleSource, + finish: CompendiumBrowserSettings.#finish + } + }; + + /** @override */ + static PARTS = { + packs: { + id: 'packs', + template: 'systems/daggerheart/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs' + }, + footer: { template: 'systems/daggerheart/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs' } + }; + + static #browserPackTypes = ['Actor', 'Item']; + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + for (const element of htmlElement.querySelectorAll('.pack-checkbox')) + element.addEventListener('change', this.toggleTypedPack.bind(this)); + } + + /**@inheritdoc */ + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + + const excludedSourceData = this.browserSettings.excludedSources; + const excludedPackData = this.browserSettings.excludedPacks; + context.typePackCollections = game.packs.reduce((acc, pack) => { + const { type, label, packageType, packageName, id } = pack.metadata; + if (packageType === 'world' || !CompendiumBrowserSettings.#browserPackTypes.includes(type)) return acc; + + const sourceChecked = + !excludedSourceData[packageName] || + !excludedSourceData[packageName].excludedDocumentTypes.includes(type); + const sourceLabel = game.modules.get(packageName)?.title ?? game.system.title; + if (!acc[type]) acc[type] = { label: game.i18n.localize(`DOCUMENT.${type}s`), sources: {} }; + if (!acc[type].sources[packageName]) + acc[type].sources[packageName] = { label: sourceLabel, checked: sourceChecked, packs: [] }; + + const checked = !excludedPackData[id] || !excludedPackData[id].excludedDocumentTypes.includes(type); + + acc[type].sources[packageName].packs.push({ + pack: id, + type, + label: id === game.system.id ? game.system.title : game.i18n.localize(label), + checked: checked + }); + + return acc; + }, {}); + + return context; + } + + static #toggleSource(event, button) { + event.stopPropagation(); + + const { type, source } = button.dataset; + const currentlyExcluded = this.browserSettings.excludedSources[source] + ? this.browserSettings.excludedSources[source].excludedDocumentTypes.includes(type) + : false; + + if (!this.browserSettings.excludedSources[source]) + this.browserSettings.excludedSources[source] = { excludedDocumentTypes: [] }; + this.browserSettings.excludedSources[source].excludedDocumentTypes = currentlyExcluded + ? this.browserSettings.excludedSources[source].excludedDocumentTypes.filter(x => x !== type) + : [...(this.browserSettings.excludedSources[source]?.excludedDocumentTypes ?? []), type]; + + const toggleIcon = button.querySelector('a > i'); + toggleIcon.classList.toggle('fa-toggle-off'); + toggleIcon.classList.toggle('fa-toggle-on'); + button.closest('.source-container').querySelector('.checks-container').classList.toggle('collapsed'); + } + + toggleTypedPack(event) { + event.stopPropagation(); + + const { type, pack } = event.target.dataset; + const currentlyExcluded = this.browserSettings.excludedPacks[pack] + ? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.includes(type) + : false; + + if (!this.browserSettings.excludedPacks[pack]) + this.browserSettings.excludedPacks[pack] = { excludedDocumentTypes: [] }; + this.browserSettings.excludedPacks[pack].excludedDocumentTypes = currentlyExcluded + ? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.filter(x => x !== type) + : [...(this.browserSettings.excludedPacks[pack]?.excludedDocumentTypes ?? []), type]; + + this.render(); + } + + static async #finish() { + const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings); + await settings.updateSource(this.browserSettings); + await game.settings.set( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings, + settings.toObject() + ); + + this.updated = true; + this.close(); + } + + static async configure() { + return new Promise(resolve => { + const app = new this(); + app.addEventListener('close', () => resolve(app.updated), { once: true }); + app.render({ force: true }); + }); + } +} diff --git a/module/applications/dialogs/_module.mjs b/module/applications/dialogs/_module.mjs index 4eda8579..a479100a 100644 --- a/module/applications/dialogs/_module.mjs +++ b/module/applications/dialogs/_module.mjs @@ -16,3 +16,4 @@ export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs'; export { default as GroupRollDialog } from './group-roll-dialog.mjs'; export { default as TagTeamDialog } from './tagTeamDialog.mjs'; export { default as RiskItAllDialog } from './riskItAllDialog.mjs'; +export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs'; diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index b35573f7..2d882eba 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -1,3 +1,5 @@ +import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; + const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; /** @@ -17,6 +19,15 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig; this.presets = {}; this.compendiumBrowserTypeKey = 'compendiumBrowserDefault'; + + this.setupHooks = Hooks.on(socketEvent.Refresh, ({ refreshType }) => { + if (refreshType === RefreshType.CompendiumBrowser) { + if (this.rendered) { + this.render(); + this.loadItems(); + } + } + }); } /** @inheritDoc */ @@ -35,7 +46,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { selectFolder: this.selectFolder, expandContent: this.expandContent, resetFilters: this.resetFilters, - sortList: this.sortList + sortList: this.sortList, + openSettings: this.openSettings }, position: { left: 100, @@ -157,6 +169,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { context.formatChoices = this.formatChoices; context.items = this.items; context.presets = this.presets; + context.isGM = game.user.isGM; + return context; } @@ -214,6 +228,10 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { loadItems() { let loadTimeout = this.toggleLoader(true); + const browserSettings = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings + ); const promises = []; game.packs.forEach(pack => { @@ -227,7 +245,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { Promise.all(promises).then(async result => { this.items = ItemBrowser.sortBy( - result.flatMap(r => r), + result.flatMap(r => r).filter(r => !browserSettings.isEntryExcluded.bind(browserSettings)(r)), 'name' ); @@ -512,6 +530,22 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { itemListContainer.replaceChildren(...newOrder); } + static async openSettings() { + const settingsUpdated = await game.system.api.applications.dialogs.CompendiumBrowserSettingsDialog.configure(); + if (settingsUpdated) { + if (this.rendered) { + this.render(); + this.loadItems(); + } + await game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.Refresh, + data: { + refreshType: RefreshType.CompendiumBrowser + } + }); + } + } + _createDragProcess() { new foundry.applications.ux.DragDrop.implementation({ dragSelector: '.item-container', @@ -571,4 +605,9 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { headerActions.append(button); } } + + async close(options = {}) { + Hooks.off(socketEvent.Refresh, this.setupHooks); + await super.close(options); + } } diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index 3d993949..d3f752bb 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -30,6 +30,7 @@ export const gameSettings = { LastMigrationVersion: 'LastMigrationVersion', TagTeamRoll: 'TagTeamRoll', SpotlightRequestQueue: 'SpotlightRequestQueue', + CompendiumBrowserSettings: 'CompendiumBrowserSettings' }; export const actionAutomationChoices = { diff --git a/module/data/_module.mjs b/module/data/_module.mjs index f7e25a4e..52fa689e 100644 --- a/module/data/_module.mjs +++ b/module/data/_module.mjs @@ -3,6 +3,7 @@ export { default as DhCombatant } from './combatant.mjs'; export { default as DhTagTeamRoll } from './tagTeamRoll.mjs'; export { default as DhRollTable } from './rollTable.mjs'; export { default as RegisteredTriggers } from './registeredTriggers.mjs'; +export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs'; export * as countdowns from './countdowns.mjs'; export * as actions from './action/_module.mjs'; diff --git a/module/data/compendiumBrowserSettings.mjs b/module/data/compendiumBrowserSettings.mjs new file mode 100644 index 00000000..9e8025dd --- /dev/null +++ b/module/data/compendiumBrowserSettings.mjs @@ -0,0 +1,35 @@ +export default class CompendiumBrowserSettings extends foundry.abstract.DataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + excludedSources: new fields.TypedObjectField( + new fields.SchemaField({ + excludedDocumentTypes: new fields.ArrayField( + new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES }) + ) + }) + ), + excludedPacks: new fields.TypedObjectField( + new fields.SchemaField({ + excludedDocumentTypes: new fields.ArrayField( + new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES }) + ) + }) + ) + }; + } + + isEntryExcluded(item) { + const pack = game.packs.get(item.pack); + if (!pack) return false; + + const excludedSourceData = this.excludedSources[pack.metadata.packageName]; + if (excludedSourceData && excludedSourceData.excludedDocumentTypes.includes(pack.metadata.type)) return true; + + const excludedPackData = this.excludedPacks[item.pack]; + if (excludedPackData && excludedPackData.excludedDocumentTypes.includes(pack.metadata.type)) return true; + + return false; + } +} diff --git a/module/systemRegistration/settings.mjs b/module/systemRegistration/settings.mjs index 053325a8..49361877 100644 --- a/module/systemRegistration/settings.mjs +++ b/module/systemRegistration/settings.mjs @@ -7,7 +7,7 @@ import { DhHomebrewSettings, DhVariantRuleSettings } from '../applications/settings/_module.mjs'; -import { DhTagTeamRoll } from '../data/_module.mjs'; +import { CompendiumBrowserSettings, DhTagTeamRoll } from '../data/_module.mjs'; export const registerDHSettings = () => { registerMenuSettings(); @@ -142,6 +142,12 @@ const registerNonConfigSettings = () => { config: false, type: DhTagTeamRoll }); + + game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings, { + scope: 'client', + config: false, + type: CompendiumBrowserSettings + }); }; /** diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs index a9e86917..173ef02b 100644 --- a/module/systemRegistration/socket.mjs +++ b/module/systemRegistration/socket.mjs @@ -38,7 +38,8 @@ export const RefreshType = { Countdown: 'DhCoundownRefresh', TagTeamRoll: 'DhTagTeamRollRefresh', EffectsDisplay: 'DhEffectsDisplayRefresh', - Scene: 'DhSceneRefresh' + Scene: 'DhSceneRefresh', + CompendiumBrowser: 'DhCompendiumBrowserRefresh' }; export const registerSocketHooks = () => { diff --git a/styles/less/dialog/compendiumBrowserPackDialog/sheet.less b/styles/less/dialog/compendiumBrowserPackDialog/sheet.less new file mode 100644 index 00000000..dfe375b5 --- /dev/null +++ b/styles/less/dialog/compendiumBrowserPackDialog/sheet.less @@ -0,0 +1,105 @@ +.daggerheart.dialog.dh-style.views.compendium-brower-settings { + --text-color: light-dark(@dark-blue, @beige); + color: var(--text-color); + + .window-content { + justify-content: space-between; + + > div { + overflow: auto; + display: flex; + flex-direction: column; + max-height: 440px; + } + } + + .types-container { + display: flex; + flex-direction: column; + gap: 8px; + + .type-container { + display: flex; + flex-direction: column; + gap: 8px; + + > label { + display: flex; + align-items: center; + font-size: var(--font-size-16); + font-family: @font-subtitle; + font-weight: bold; + + &::before { + content: ''; + flex: 1; + height: 2px; + background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, light-dark(@dark-blue, @golden) 100%); + margin-right: 8px; + } + &::after { + content: ''; + flex: 1; + height: 2px; + background: linear-gradient(90deg, light-dark(@dark-blue, @golden) 0%, rgba(0, 0, 0, 0) 100%); + margin-left: 8px; + } + } + + .sources-container { + display: flex; + flex-direction: column; + gap: 8px; + + .source-container { + display: flex; + flex-direction: column; + gap: 2px; + + .source-inner-container { + display: flex; + justify-content: space-between; + + .source-inner-label-container { + width: 100%; + display: flex; + gap: 8px; + + i { + font-size: 18px; + // color: light-dark(@dark-blue, @golden); + } + } + } + } + } + } + } + + .checks-container { + padding-left: 24px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4px; + transition: height 0.4s ease-in-out; + overflow: hidden; + + &.collapsed { + height: 0px; + } + + .check-container { + display: flex; + align-items: center; + } + } + + footer { + margin-top: 8px; + display: flex; + + button { + flex: 1; + } + } +} diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less index 733cdd1c..0c70df9f 100644 --- a/styles/less/dialog/index.less +++ b/styles/less/dialog/index.less @@ -43,3 +43,5 @@ @import './risk-it-all/sheet.less'; @import './character-reset/sheet.less'; + +@import './compendiumBrowserPackDialog/sheet.less'; diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 713a4481..98c05348 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -52,6 +52,14 @@ } } + input[type='checkbox'] { + &:indeterminate { + &::before { + content: '\f0fe'; + } + } + } + input[type='checkbox'], input[type='radio'] { height: 20px; diff --git a/styles/less/ui/chat/chat.less b/styles/less/ui/chat/chat.less index 8728fbda..1e723ed7 100644 --- a/styles/less/ui/chat/chat.less +++ b/styles/less/ui/chat/chat.less @@ -233,7 +233,7 @@ font-family: @font-subtitle; font-size: var(--font-size-18); font-weight: bold; - color: var(--text-color); + color: light-dark(@dark-blue, var(--text-color)); margin-bottom: -2px; } diff --git a/system.json b/system.json index fd0d7e1c..b753b540 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "1.6.4", + "version": "1.7.0", "compatibility": { "minimum": "13.346", "verified": "13.351", diff --git a/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs b/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs new file mode 100644 index 00000000..9dc61cbe --- /dev/null +++ b/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs b/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs new file mode 100644 index 00000000..dcda8108 --- /dev/null +++ b/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs @@ -0,0 +1,36 @@ +