zischenstand
This commit is contained in:
226
src/macro_enable-history-tab.js
Normal file
226
src/macro_enable-history-tab.js
Normal file
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* Add a History tab to PF1 actor sheets with HP/XP/Currency subtabs.
|
||||
* Requires the corresponding flag-tracking macros to populate data.
|
||||
*/
|
||||
|
||||
const TAB_ID = "pf1-history";
|
||||
const TAB_LABEL = "History";
|
||||
|
||||
if (!game.system.id.includes("pf1")) {
|
||||
ui.notifications.warn("This macro is intended for the PF1 system.");
|
||||
return;
|
||||
}
|
||||
|
||||
game.pf1 ??= {};
|
||||
game.pf1.historyTab ??= {};
|
||||
|
||||
const PF1_SHEET_HOOKS = [
|
||||
"renderActorSheetPFCharacter",
|
||||
"renderActorSheetPFNPC",
|
||||
"renderActorSheetPFNPCLoot",
|
||||
"renderActorSheetPFNPCLite",
|
||||
"renderActorSheetPFTrap",
|
||||
"renderActorSheetPFVehicle",
|
||||
"renderActorSheetPFHaunt",
|
||||
"renderActorSheetPFBasic",
|
||||
];
|
||||
|
||||
game.pf1.historyTab.renderHooks ??= [];
|
||||
if (game.pf1.historyTab.renderHooks.length) {
|
||||
return ui.notifications.info("History tab hooks already active.");
|
||||
}
|
||||
|
||||
for (const hook of PF1_SHEET_HOOKS) {
|
||||
const id = Hooks.on(hook, (sheet, html) => {
|
||||
const delays = [0, 50, 200];
|
||||
delays.forEach((delay) =>
|
||||
setTimeout(() => {
|
||||
try {
|
||||
injectHistoryTab(sheet);
|
||||
} catch (err) {
|
||||
console.error("PF1 History Tab | Failed to render history tab", err);
|
||||
}
|
||||
}, delay)
|
||||
);
|
||||
});
|
||||
game.pf1.historyTab.renderHooks.push({ event: hook, id });
|
||||
}
|
||||
|
||||
ui.notifications.info("PF1 History tab enabled. Reopen actor sheets to see the new tab.");
|
||||
|
||||
function injectHistoryTab(sheet) {
|
||||
if (!sheet?.element?.length) return;
|
||||
console.log("PF1 History Tab | Injecting for", sheet.actor?.name, sheet.id);
|
||||
const actor = sheet.actor;
|
||||
const nav = sheet.element.find('.sheet-navigation.tabs[data-group="primary"]').first();
|
||||
const body = sheet.element.find(".sheet-body").first();
|
||||
if (!nav.length || !body.length) return;
|
||||
|
||||
ensureHistoryTab(nav);
|
||||
|
||||
const existing = body.find(`.tab[data-tab="${TAB_ID}"]`).first();
|
||||
const content = buildHistoryContent(actor);
|
||||
if (existing.length) existing.html(content);
|
||||
else body.append(`<div class="tab history-tab" data-tab="${TAB_ID}" data-group="primary">${content}</div>`);
|
||||
|
||||
sheet._tabs?.forEach((tabs) => tabs.bind(sheet.element[0]));
|
||||
|
||||
setupObserver(sheet, nav);
|
||||
}
|
||||
|
||||
function ensureHistoryTab(nav) {
|
||||
if (nav.find(`[data-tab="${TAB_ID}"]`).length) return;
|
||||
nav.append(`<a class="item" data-group="primary" data-tab="${TAB_ID}">${TAB_LABEL}</a>`);
|
||||
}
|
||||
|
||||
function setupObserver(sheet, nav) {
|
||||
game.pf1.historyTab.observers ??= new Map();
|
||||
const key = sheet.id;
|
||||
if (game.pf1.historyTab.observers.has(key)) return;
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
const currentNav = sheet.element.find('.sheet-navigation.tabs[data-group="primary"]').first();
|
||||
if (!currentNav.length) return;
|
||||
ensureHistoryTab(currentNav);
|
||||
});
|
||||
|
||||
observer.observe(sheet.element[0], { childList: true, subtree: true });
|
||||
game.pf1.historyTab.observers.set(key, observer);
|
||||
|
||||
const closeHandler = () => {
|
||||
observer.disconnect();
|
||||
game.pf1.historyTab.observers.delete(key);
|
||||
sheet.element.off("remove", closeHandler);
|
||||
};
|
||||
sheet.element.on("remove", closeHandler);
|
||||
}
|
||||
|
||||
function buildHistoryContent(actor) {
|
||||
const configs = [
|
||||
{
|
||||
id: "hp",
|
||||
label: "HP",
|
||||
flag: "pf1HpHistory",
|
||||
columns: [
|
||||
{ label: "Timestamp", render: (e) => formatDate(e.timestamp) },
|
||||
{ label: "HP", render: (e) => e.hp },
|
||||
{ label: "Δ", render: (e) => e.diff },
|
||||
{ label: "User", render: (e) => e.user ?? "" },
|
||||
{ label: "Source", render: (e) => e.source ?? "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "xp",
|
||||
label: "XP",
|
||||
flag: "pf1XpHistory",
|
||||
columns: [
|
||||
{ label: "Timestamp", render: (e) => formatDate(e.timestamp) },
|
||||
{ label: "XP", render: (e) => e.value },
|
||||
{ label: "Δ", render: (e) => e.diff },
|
||||
{ label: "User", render: (e) => e.user ?? "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "currency",
|
||||
label: "Currency",
|
||||
flag: "pf1CurrencyHistory",
|
||||
columns: [
|
||||
{ label: "Timestamp", render: (e) => formatDate(e.timestamp) },
|
||||
{ label: "Totals", render: (e) => e.value },
|
||||
{ label: "Δ", render: (e) => e.diff },
|
||||
{ label: "User", render: (e) => e.user ?? "" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const nav = configs
|
||||
.map((cfg, idx) => `<a class="history-subtab ${idx === 0 ? "active" : ""}" data-history-subtab="${cfg.id}">${cfg.label}</a>`)
|
||||
.join("");
|
||||
|
||||
const panels = configs
|
||||
.map((cfg, idx) => {
|
||||
const entries = actor.getFlag("world", cfg.flag) ?? [];
|
||||
const table = renderHistoryTable(entries, cfg.columns, cfg.id);
|
||||
return `<section class="history-panel" data-history-panel="${cfg.id}" style="display:${idx === 0 ? "block" : "none"}">${table}</section>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
return `
|
||||
<section class="pf1-history-root" data-history-block="history-tab">
|
||||
<nav class="history-subnav">
|
||||
${nav}
|
||||
</nav>
|
||||
<div class="history-panels">
|
||||
${panels}
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
(function(){
|
||||
const root = document.currentScript.closest('[data-history-block="history-tab"]');
|
||||
if (!root) return;
|
||||
const tabs = Array.from(root.querySelectorAll('[data-history-subtab]'));
|
||||
const panels = Array.from(root.querySelectorAll('[data-history-panel]'));
|
||||
tabs.forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
const target = btn.dataset.historySubtab;
|
||||
tabs.forEach((n) => n.classList.toggle('active', n === btn));
|
||||
panels.forEach((panel) => (panel.style.display = panel.dataset.historyPanel === target ? 'block' : 'none'));
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderHistoryTable(entries, columns, id) {
|
||||
if (!entries.length) return `<p class="history-empty">No ${id.toUpperCase()} history recorded.</p>`;
|
||||
|
||||
const rows = entries
|
||||
.map(
|
||||
(entry) => `
|
||||
<tr data-ts="${entry.timestamp}">
|
||||
${columns.map((col) => `<td>${col.render(entry) ?? ""}</td>`).join("")}
|
||||
</tr>`
|
||||
)
|
||||
.join("");
|
||||
|
||||
return `
|
||||
<div class="history-filters">
|
||||
<label>From <input type="date" data-filter="from-${id}"></label>
|
||||
<label>To <input type="date" data-filter="to-${id}"></label>
|
||||
</div>
|
||||
<table class="history-table history-${id}">
|
||||
<thead>
|
||||
<tr>${columns.map((col) => `<th>${col.label}</th>`).join("")}</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${rows}
|
||||
</tbody>
|
||||
</table>
|
||||
<script type="text/javascript">
|
||||
(function(){
|
||||
const root = document.currentScript.closest('.history-panel[data-history-panel="${id}"]');
|
||||
if (!root) return;
|
||||
const rows = Array.from(root.querySelectorAll("tbody tr"));
|
||||
const fromInput = root.querySelector('input[data-filter="from-${id}"]');
|
||||
const toInput = root.querySelector('input[data-filter="to-${id}"]');
|
||||
function applyFilters(){
|
||||
const from = fromInput?.value ? Date.parse(fromInput.value) : null;
|
||||
const to = toInput?.value ? Date.parse(toInput.value) + 86399999 : null;
|
||||
rows.forEach((row) => {
|
||||
const ts = Number(row.dataset.ts);
|
||||
const visible = (!from || ts >= from) && (!to || ts <= to);
|
||||
row.style.display = visible ? "" : "none";
|
||||
});
|
||||
}
|
||||
fromInput?.addEventListener("input", applyFilters);
|
||||
toInput?.addEventListener("input", applyFilters);
|
||||
applyFilters();
|
||||
})();
|
||||
</script>
|
||||
`;
|
||||
}
|
||||
|
||||
function formatDate(ts) {
|
||||
return new Date(ts).toLocaleString();
|
||||
}
|
||||
Reference in New Issue
Block a user