const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; export class DhPathBrowserApp extends HandlebarsApplicationMixin(ApplicationV2) { constructor(options) { super(options); this.paths = this.constructor.getChangeChoices(); } static DEFAULT_OPTIONS = { id: "dh-path-browser-app", classes: ["daggerheart", "dh-path-browser", "dh-style"], tag: "form", window: { title: "Data Path Browser", icon: "fa-solid fa-code", resizable: true, contentClasses: ["standard-form"] }, position: { width: 600, height: 700 }, actions: { copyPath: DhPathBrowserApp.#copyPath } }; static PARTS = { header: { template: "modules/dh-path-browser/templates/header.hbs" }, list: { template: "modules/dh-path-browser/templates/list.hbs", scrollable: [".paths-list"] } }; async _prepareContext(options) { const context = await super._prepareContext(options); const groups = {}; for (const p of this.paths) { if (!groups[p.group]) groups[p.group] = []; groups[p.group].push(p); } context.groups = Object.entries(groups).map(([group, paths]) => ({ group, paths: paths.sort((a, b) => a.label.localeCompare(b.label)) })).sort((a, b) => a.group.localeCompare(b.group)); return context; } _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); if (partId === "header") { const searchInput = htmlElement.querySelector('input[name="search"]'); if (searchInput) { searchInput.addEventListener("input", (e) => this.#filterPaths(e.target.value)); } } } #filterPaths(query) { query = query.toLowerCase(); const listElement = this.element.querySelector(".paths-list"); if (!listElement) return; const items = listElement.querySelectorAll(".path-item"); items.forEach(item => { const label = item.dataset.label.toLowerCase(); const value = item.dataset.value.toLowerCase(); if (label.includes(query) || value.includes(query)) { item.style.display = ""; } else { item.style.display = "none"; } }); const groups = listElement.querySelectorAll(".path-group"); groups.forEach(group => { const visibleItems = Array.from(group.querySelectorAll(".path-item")).filter(i => i.style.display !== "none"); group.style.display = visibleItems.length > 0 ? "" : "none"; }); } static async #copyPath(event, target) { const path = target.dataset.value; if (path) { const fullPath = `@system.${path}`; await navigator.clipboard.writeText(fullPath); ui.notifications.info(`Copied ${fullPath} to clipboard!`); } } static getChangeChoices() { const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty']; const getAllLeaves = (root, group, parentPath = '') => { const leaves = []; const rootKey = `${parentPath ? `${parentPath}.` : ''}${root.name}`; for (const field of Object.values(root.fields)) { if (field instanceof foundry.data.fields.SchemaField) leaves.push(...getAllLeaves(field, group, rootKey)); else leaves.push({ value: `${rootKey}.${field.name}`, label: game.i18n.localize(field.label), hint: game.i18n.localize(field.hint), group }); } return leaves; }; return Object.keys(game.system.api.models.actors).reduce((acc, key) => { if (ignoredActorKeys.includes(key)) return acc; const model = game.system.api.models.actors[key]; const group = game.i18n.localize(model.metadata.label); const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model.metadata.type); const getTranslations = path => { if (path === 'resources.hope.max') return { label: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.FIELDS.maxHope.label'), hint: '' }; const field = model.schema.getField(path); return { label: field ? game.i18n.localize(field.label) : path, hint: field ? game.i18n.localize(field.hint) : '' }; }; const bars = attributes.bar.flatMap(x => { const baseJoined = x.join('.'); return [ { value: `${baseJoined}.max`, ...getTranslations(`${baseJoined}.max`), group }, { value: `${baseJoined}.value`, ...getTranslations(`${baseJoined}.value`), group } ]; }); const values = attributes.value.flatMap(x => { const joined = x.join('.'); return { value: joined, ...getTranslations(joined), group }; }); const bonuses = getAllLeaves(model.schema.fields.bonuses, group); const rules = getAllLeaves(model.schema.fields.rules, group); acc.push(...bars, ...values, ...rules, ...bonuses); return acc; }, []); } }