dh-path-browser/scripts/app.mjs

182 lines
6.1 KiB
JavaScript

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 getTranslations = (model, path) => {
if (path === 'resources.hope.max')
return {
label: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.FIELDS.maxHope.label'),
hint: ''
};
// Armor overrides
if (path.endsWith('armor.max')) return { label: game.i18n.localize('DAGGERHEART.ArmorScoreMax'), hint: '' };
if (path.endsWith('armor.current')) return { label: game.i18n.localize('DAGGERHEART.ArmorScoreCurrent'), hint: '' };
const field = model.schema.getField(path);
return {
label: field ? game.i18n.localize(field.label) : path,
hint: field ? game.i18n.localize(field.hint) : ''
};
};
const getAllLeaves = (model, root, group, parentPath = '') => {
if (!root) return [];
const leaves = [];
const rootKey = `${parentPath ? `${parentPath}.` : ''}${root.name}`;
const fields = root.fields || root;
for (const [name, field] of Object.entries(fields)) {
const currentPath = `${rootKey}.${name}`;
if (field instanceof foundry.data.fields.SchemaField) {
leaves.push(...getAllLeaves(model, field, group, rootKey));
} else {
const trans = getTranslations(model, currentPath);
leaves.push({
value: currentPath,
label: trans.label || name,
hint: trans.hint || '',
group
});
}
}
return leaves;
};
const choices = [];
// Process Actors
for (const [key, model] of Object.entries(game.system.api.models.actors)) {
if (ignoredActorKeys.includes(key)) continue;
const group = game.i18n.localize(model.metadata.label);
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model.metadata.type);
const bars = attributes.bar.flatMap(x => {
const baseJoined = x.join('.');
return [
{ value: `${baseJoined}.max`, ...getTranslations(model, `${baseJoined}.max`), group },
{ value: `${baseJoined}.value`, ...getTranslations(model, `${baseJoined}.value`), group }
];
});
const values = attributes.value.flatMap(x => {
const joined = x.join('.');
return { value: joined, ...getTranslations(model, joined), group };
});
const bonuses = getAllLeaves(model, model.schema.fields.bonuses, group);
const rules = getAllLeaves(model, model.schema.fields.rules, group);
const armor = getAllLeaves(model, model.schema.fields.armor, group);
choices.push(...bars, ...values, ...rules, ...bonuses, ...armor);
}
// Process Items
for (const [key, model] of Object.entries(game.system.api.models.items)) {
const group = `${game.i18n.localize('DOCUMENT.Item')} (${game.i18n.localize(model.metadata.label)})`;
const bonuses = getAllLeaves(model, model.schema.fields.bonuses, group);
const armor = getAllLeaves(model, model.schema.fields.armor, group);
choices.push(...bonuses, ...armor);
}
return choices;
}
}