feat: Implement Daggerheart Path Browser module to display and copy Active Effect data paths.
This commit is contained in:
commit
922814242d
7 changed files with 387 additions and 0 deletions
157
scripts/app.mjs
Normal file
157
scripts/app.mjs
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
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;
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
21
scripts/main.mjs
Normal file
21
scripts/main.mjs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { DhPathBrowserApp } from "./app.mjs";
|
||||
|
||||
Hooks.on("init", () => {
|
||||
console.log("Daggerheart Path Browser | Initializing module");
|
||||
});
|
||||
|
||||
Hooks.on("renderDaggerheartMenu", (app, html, data) => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.innerHTML = `<i class="fa-solid fa-code"></i> ${game.i18n.localize("Browse Data Paths")}`;
|
||||
button.classList.add("dh-path-browser-btn");
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
new DhPathBrowserApp().render(true);
|
||||
});
|
||||
|
||||
const container = html.querySelector("div");
|
||||
if (container) {
|
||||
container.appendChild(button);
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue