From f59f14f6db7eff398e77ee77a0678bf15a280f22 Mon Sep 17 00:00:00 2001 From: "centron\\schwoerer" Date: Fri, 21 Nov 2025 12:55:34 +0100 Subject: [PATCH] working fine --- .../scripts/gowlers-tracking-ledger.js | 178 ++++++++++++++---- 1 file changed, 142 insertions(+), 36 deletions(-) diff --git a/src/macros_new/gowlers-tracking-ledger/foundry.gowlershome.dyndns.org/modules/gowlers-tracking-ledger/scripts/gowlers-tracking-ledger.js b/src/macros_new/gowlers-tracking-ledger/foundry.gowlershome.dyndns.org/modules/gowlers-tracking-ledger/scripts/gowlers-tracking-ledger.js index 8d1d30c8..b84bb121 100644 --- a/src/macros_new/gowlers-tracking-ledger/foundry.gowlershome.dyndns.org/modules/gowlers-tracking-ledger/scripts/gowlers-tracking-ledger.js +++ b/src/macros_new/gowlers-tracking-ledger/foundry.gowlershome.dyndns.org/modules/gowlers-tracking-ledger/scripts/gowlers-tracking-ledger.js @@ -1,6 +1,6 @@ const MODULE_ID = "gowlers-tracking-ledger"; -const MODULE_VERSION = "0.1.23"; +const MODULE_VERSION = "0.1.25"; const TRACK_SETTING = "actorSettings"; const FLAG_SCOPE = "world"; const MAX_HISTORY_ROWS = 100; @@ -80,14 +80,42 @@ const ledgerState = { lastCombatEndTime: 0, // Timestamp when combat ended openDialogs: new Map(), // actorId -> dialog instance (for live updates) recentMessages: [], // Array of recent message metadata: { message, timestamp, source } + historyPageState: new Map(), // actorId -> { [tabId]: { page, pageSize } } + historyTabState: new Map(), // actorId -> active tab id }; +function getHistoryPageState(actorId, tabId) { + if (!actorId || !tabId) return { page: 1, pageSize: 10 }; + const perActor = ledgerState.historyPageState.get(actorId) ?? {}; + return perActor[tabId] ?? { page: 1, pageSize: 10 }; +} + +function setHistoryPageState(actorId, tabId, page, pageSize) { + if (!actorId || !tabId) return; + const perActor = ledgerState.historyPageState.get(actorId) ?? {}; + perActor[tabId] = { + page: Math.max(1, Number.isFinite(page) ? page : 1), + pageSize: pageSize === "all" ? "all" : (Number(pageSize) > 0 ? Number(pageSize) : 10), + }; + ledgerState.historyPageState.set(actorId, perActor); +} + +function getActiveHistoryTab(actorId, fallback = "hp") { + if (!actorId) return fallback; + return ledgerState.historyTabState.get(actorId) ?? fallback; +} + +function setActiveHistoryTab(actorId, tabId) { + if (!actorId || !tabId) return; + ledgerState.historyTabState.set(actorId, tabId); +} + // Global tab switching function for history dialog -window.switchHistoryTab = function(tabId) { +window.switchHistoryTab = function(tabId, el = null) { console.log("[GowlersTracking] Tab switched to:", tabId); // Find the root element using the data-history-root attribute - const root = document.querySelector('[data-history-root]'); + const root = el?.closest?.('[data-history-root]') ?? document.querySelector('[data-history-root]'); if (!root) { console.warn("[GowlersTracking] History root element not found"); return; @@ -118,6 +146,11 @@ window.switchHistoryTab = function(tabId) { } else { console.warn("[GowlersTracking] Tab panel not found for:", tabId); } + + const actorId = root?.getAttribute('data-history-root'); + if (actorId) { + setActiveHistoryTab(actorId, tabId); + } }; // Global function for history dialog pagination navigation @@ -125,14 +158,14 @@ window.historyPageNav = function(button, direction) { const panel = button.closest('[data-history-panel]'); if (!panel) return; - const currentPage = parseInt(panel.getAttribute('data-page') || '1'); const pageInfo = panel.querySelector('[data-page-info]'); if (!pageInfo) return; const pageMatch = pageInfo.textContent.match(/Page (\d+) \/ (\d+)/); if (!pageMatch) return; - const totalPages = parseInt(pageMatch[2]); + const totalPages = parseInt(pageMatch[2]) || 1; + const currentPage = parseInt(panel.getAttribute('data-page') || pageMatch[1] || '1'); let newPage = currentPage; if (direction === 'next' && currentPage < totalPages) { @@ -145,8 +178,18 @@ window.historyPageNav = function(button, direction) { panel.setAttribute('data-page', newPage); console.log("[GowlersTracking] History pagination - moving to page:", newPage); - // Rebuild the table with new page const root = panel.closest('[data-history-root]'); + const actorId = root?.getAttribute('data-history-root'); + const tabId = panel.getAttribute('data-history-panel'); + + const pageSizeAttr = panel.getAttribute('data-page-size') || '10'; + const pageSize = pageSizeAttr === 'all' ? 'all' : Number(pageSizeAttr) || 10; + if (actorId && tabId) { + setHistoryPageState(actorId, tabId, newPage, pageSize); + refreshOpenDialogs(actorId); + } + + // Rebuild the table with new page if (root) { const app = root.closest('.app, .window-app, .dialog, [role="dialog"]'); if (app && app.__vue__?.constructor?.name === 'Dialog') { @@ -167,10 +210,18 @@ window.historyChangePageSize = function(select) { panel.setAttribute('data-page', '1'); panel.setAttribute('data-page-size', pageSize); + const root = panel.closest('[data-history-root]'); + const actorId = root?.getAttribute('data-history-root'); + const tabId = panel.getAttribute('data-history-panel'); + const normalizedSize = pageSize === 'all' ? 'all' : (Number(pageSize) > 0 ? Number(pageSize) : 10); + if (actorId && tabId) { + setHistoryPageState(actorId, tabId, 1, normalizedSize); + refreshOpenDialogs(actorId); + } + console.log("[GowlersTracking] History page size changed to:", pageSize); // Re-render dialog - const root = panel.closest('[data-history-root]'); if (root) { const app = root.closest('.app, .window-app, .dialog, [role="dialog"]'); if (app && app.__vue__?.constructor?.name === 'Dialog') { @@ -193,8 +244,9 @@ function refreshOpenDialogs(actorId) { const actor = game.actors.get(actorId); if (!actor) return; - // Rebuild content - const content = buildHistoryContent(actor, "hp"); + // Rebuild content using the last active tab for this actor + const activeTab = getActiveHistoryTab(actorId, "hp"); + const content = buildHistoryContent(actor, activeTab); dialog.data.content = content; // Re-render the dialog @@ -814,18 +866,7 @@ function buildXpBreakdownTooltip(actor, xpEntry) { let breakdown = []; if (encounter.participants && encounter.participants.length > 0) { // Resolve participant UUIDs/IDs to actor names - const participantNames = encounter.participants.slice(0, 2).map(p => { - // Try to resolve UUID or actor ID to name - if (p.startsWith("Actor.")) { - const participantActor = fromUuidSync(p); - return participantActor?.name || p.slice(0, 8); - } else if (typeof p === "string" && p.length > 20) { - // Likely a UUID - try to resolve - const participantActor = game.actors.get(p); - return participantActor?.name || p.slice(0, 8); - } - return p; // Already a name - }); + const participantNames = encounter.participants.slice(0, 2).map(resolveParticipantName); const participantsStr = participantNames.join(", "); const more = encounter.participants.length > 2 ? ` +${encounter.participants.length - 2} more` : ""; @@ -847,8 +888,9 @@ function buildXpBreakdownTooltip(actor, xpEntry) { } function buildHistoryContent(actor, tabArg) { - const initialTab = tabArg ?? "hp"; // Explicitly capture the tab parameter + const initialTab = tabArg ?? getActiveHistoryTab(actor.id, "hp"); // Explicitly capture the tab parameter console.log("[GowlersTracking] buildHistoryContent called with initialTab:", initialTab); + setActiveHistoryTab(actor.id, initialTab); const canConfigure = game.user?.isGM; const configs = [ { @@ -914,7 +956,7 @@ function buildHistoryContent(actor, tabArg) { .map( (cfg) => { const isActive = cfg.id === initialTab ? "active" : ""; - return ``; + return ``; } ) .join(""); @@ -924,19 +966,25 @@ function buildHistoryContent(actor, tabArg) { const entries = actor.getFlag(FLAG_SCOPE, cfg.flag) ?? []; const display = cfg.id === initialTab ? "block" : "none"; const isActive = cfg.id === initialTab ? "active" : ""; - const rowsPerPage = 10; // Default rows per page for history dialog - const totalPages = Math.ceil(entries.length / rowsPerPage); - return `
- ${renderHistoryTable(entries, cfg.columns, cfg.id, rowsPerPage, 1)} + const state = getHistoryPageState(actor.id, cfg.id); + let rowsPerPage = state.pageSize === "all" ? "all" : Math.max(1, Number(state.pageSize) || 10); + let currentPage = Math.max(1, Number(state.page) || 1); + const totalPages = rowsPerPage === "all" ? 1 : Math.max(1, Math.ceil(entries.length / rowsPerPage)); + if (currentPage > totalPages) { + currentPage = totalPages; + setHistoryPageState(actor.id, cfg.id, currentPage, rowsPerPage); + } + const selectOptions = [10, 20, 50, "all"] + .map((value) => ``) + .join(""); + return `
+ ${renderHistoryTable(entries, cfg.columns, cfg.id, rowsPerPage, currentPage)}
- Page 1 / ${Math.max(1, totalPages)} + Page ${currentPage} / ${Math.max(1, totalPages)}
`; @@ -1030,8 +1078,21 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op let encounterId = game.combat?.id ?? null; console.log(`[GowlersTracking] Recording ${statId} change - Active combat: ${encounterId ? "Yes (" + encounterId + ")" : "No"}`); - // If no active combat but XP is being recorded shortly after combat ended, link to last encounter - if (!encounterId && statId === "xp" && ledgerState.lastCombatId) { + // If no active combat but XP is being recorded shortly after combat ended, link to last encounter (unless explicitly manual) + const manualXp = + statId === "xp" && + (options?.manualXp === true || options?.type === "ManualXP" || options?.skipEncounterLink === true); + + const hasExplicitEncounter = + statId === "xp" && + (options?.encounterId || change?.encounterId || options?.encounter?.id || options?.encounter?._id); + + const canLinkXpToLastCombat = + statId === "xp" && + !manualXp && + (options?.type === "EncounterXP" || options?.type === "Encounter" || hasExplicitEncounter || game.combat?.id || ledgerState.lastCombatId); + + if (!encounterId && statId === "xp" && ledgerState.lastCombatId && canLinkXpToLastCombat) { const timeSinceEnd = Date.now() - ledgerState.lastCombatEndTime; // Link to last encounter if within 30 seconds (allows time for XP award after combat ends) if (timeSinceEnd < 30000) { @@ -1042,6 +1103,14 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op } } + // Allow explicit encounter ID/name payloads to override when provided (e.g., encounter XP awards) + if (statId === "xp") { + if (options?.encounterId) encounterId = options.encounterId; + else if (change?.encounterId) encounterId = change.encounterId; + else if (options?.encounter?.id) encounterId = options.encounter.id; + else if (options?.encounter?._id) encounterId = options.encounter._id; + } + // Detect source of the change with actor and item details let source = "Manual"; let sourceDetails = ""; @@ -1091,8 +1160,10 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op damageBreakdown = `${hpDiff} healing`; } } else if (statId === "xp" && diffValue > 0) { - // XP gains - could be from encounter end or manual award - source = "XP Award"; + // XP gains - encounter or manual award + source = options?.source ?? (encounterId ? "Encounter XP Award" : "XP Award"); + if (options?.encounterName) sourceDetails = options.encounterName; + if (!sourceDetails && encounterId) sourceDetails = encounterId.slice?.(0, 8) ?? encounterId; damageBreakdown = `${diffValue} XP`; } else if (statId === "currency" && diffValue !== 0) { // Currency changes @@ -1286,6 +1357,41 @@ function isNonlethalType(type) { return String(type ?? "").toLowerCase() === "nonlethal"; } +function resolveParticipantName(participant) { + if (!participant) return "Unknown"; + + // Objects that already include an id or name + if (typeof participant === "object") { + if (participant.name) return participant.name; + if (participant.id) { + const actor = game.actors.get(participant.id); + if (actor) return actor.name; + } + } + + if (typeof participant === "string") { + // Try UUID first (token or actor) + try { + if (typeof fromUuidSync === "function") { + const doc = fromUuidSync(participant); + if (doc?.name) return doc.name; + if (doc?.actor?.name) return doc.actor.name; + } + } catch (err) { + // Ignore UUID errors, fall through + } + + // Try direct actor lookup by ID + const actor = game.actors.get(participant); + if (actor) return actor.name; + + // Fallback to trimmed ID preview + return participant.slice(0, 8); + } + + return "Unknown"; +} + function checkboxValue(value, fallback = false) { if (value === undefined || value === null) return fallback; if (Array.isArray(value)) value = value[value.length - 1];