From 76c5d80cc2640ff195022e8d32a44a8ba42b8a9e Mon Sep 17 00:00:00 2001 From: xziino Date: Wed, 20 May 2026 16:18:43 +0200 Subject: [PATCH] Add cross-report comparison and fix delta/filter bugs - Cross-report comparison: toggle panel to load fights from a second report code, select a fight to use as reference (mutually exclusive with same-report ref dropdown) - Fix ref-row player filter: use hiddenPlayerNames (name-based) so ext-report targets with different actor IDs are correctly hidden - Fix negative delta formatting: pass Math.abs(diff) to fmtDmg so negative values are abbreviated the same way as positive ones Co-Authored-By: Claude Sonnet 4.6 --- css/analysis.css | 21 +++++++ js/analysis.js | 114 ++++++++++++++++++++++++++++++++++--- templates/tab-analysis.php | 10 ++++ 3 files changed, 138 insertions(+), 7 deletions(-) diff --git a/css/analysis.css b/css/analysis.css index eeea9e6..27912f4 100644 --- a/css/analysis.css +++ b/css/analysis.css @@ -65,6 +65,27 @@ margin-left: auto; } +/* ── External report panel ───────────────────────────────────────────────── */ +.ref-ext-row { + margin-top: 10px; + display: flex; + flex-direction: column; + gap: 8px; +} + +#ref-ext-panel { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; +} + +.ref-report-input { + width: 200px; + padding: 4px 9px; + font-size: 12px; +} + /* ── AoE Timeline ────────────────────────────────────────────────────────── */ .aoe-event { display: grid; diff --git a/js/analysis.js b/js/analysis.js index dbcb53f..3e18091 100644 --- a/js/analysis.js +++ b/js/analysis.js @@ -55,13 +55,16 @@ return `${min}:${sec}`; } - let hiddenPlayers = new Set(); + let hiddenPlayers = new Set(); + let hiddenPlayerNames = new Set(); let lastEvents = []; let lastFightStart = 0; let playerFilter = ''; let phaseFilter = { startTime: 0, endTime: Infinity }; let refEvents = []; let refFightStart = 0; + let extFights = []; + let extReportCode = ''; // ── Player grid ────────────────────────────────────────────────────────── @@ -73,10 +76,11 @@ return roleCmp !== 0 ? roleCmp : a.name.localeCompare(b.name); }); - hiddenPlayers = new Set(players.filter(p => p.role === 'tank').map(p => p.id)); + hiddenPlayers = new Set(players.filter(p => p.role === 'tank').map(p => p.id)); + hiddenPlayerNames = new Set(players.filter(p => p.role === 'tank').map(p => p.name)); grid.innerHTML = players.map(p => ` -
+
${abbr(p.type)}
${p.name}
@@ -89,12 +93,15 @@ document.getElementById('player-grid').addEventListener('click', e => { const card = e.target.closest('.player-card'); if (!card) return; - const id = parseInt(card.dataset.playerId, 10); + const id = parseInt(card.dataset.playerId, 10); + const name = card.dataset.playerName; if (hiddenPlayers.has(id)) { hiddenPlayers.delete(id); + hiddenPlayerNames.delete(name); card.classList.remove('player-hidden'); } else { hiddenPlayers.add(id); + hiddenPlayerNames.add(name); card.classList.add('player-hidden'); } renderTimeline(lastEvents, lastFightStart); @@ -147,6 +154,9 @@ const fight = (window.App?.fights ?? []).find(f => f.id === refId); if (!fight) return; + // Clear ext-report selection + refExtFightSelect.value = ''; + refFightSelect.disabled = true; try { const res = await fetch('api/analysis.php', { @@ -181,6 +191,91 @@ refFightSelect.style.display = ''; } + // ── External report comparison ──────────────────────────────────────────── + + const refExtToggle = document.getElementById('ref-ext-toggle'); + const refExtPanel = document.getElementById('ref-ext-panel'); + const refReportInput = document.getElementById('ref-report-input'); + const refReportLoad = document.getElementById('ref-report-load'); + const refExtFightSelect = document.getElementById('ref-ext-fight-select'); + + refExtToggle.addEventListener('click', () => { + const hidden = refExtPanel.style.display === 'none'; + refExtPanel.style.display = hidden ? '' : 'none'; + }); + + refReportLoad.addEventListener('click', async () => { + const code = refReportInput.value.trim(); + if (!code) return; + + refReportLoad.disabled = true; + refReportLoad.textContent = 'Lädt…'; + + try { + const res = await fetch('api/fight.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ report_code: code }), + }); + const json = await res.json(); + if (json.reauth) { window.location.href = 'auth/start.php'; return; } + + const fights = json?.data?.reportData?.report?.fights ?? []; + extFights = fights; + extReportCode = code; + + refExtFightSelect.innerHTML = ''; + fights.forEach(f => { + const hp = f.kill ? 'Kill' : (f.fightPercentage != null ? f.fightPercentage.toFixed(2) + '%' : '?'); + const opt = document.createElement('option'); + opt.value = f.id; + opt.textContent = `${f.name} — ${fmtDur(f.endTime - f.startTime)} — ${hp}`; + refExtFightSelect.appendChild(opt); + }); + refExtFightSelect.style.display = fights.length ? '' : 'none'; + } catch { } + + refReportLoad.disabled = false; + refReportLoad.textContent = 'Laden'; + }); + + refExtFightSelect.addEventListener('change', async () => { + const refId = parseInt(refExtFightSelect.value, 10); + if (!refId) { + refEvents = []; + refFightStart = 0; + renderTimeline(lastEvents, lastFightStart); + return; + } + + const fight = extFights.find(f => f.id === refId); + if (!fight) return; + + // Clear same-report selection + refFightSelect.value = ''; + + refExtFightSelect.disabled = true; + try { + const res = await fetch('api/analysis.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + report_code: extReportCode, + fight_id: refId, + start_time: fight.startTime, + end_time: fight.endTime, + }), + }); + const json = await res.json(); + if (!json.error && !json.reauth) { + refEvents = json.aoe_events ?? []; + refFightStart = json.fight_start ?? fight.startTime; + } + } catch { } + refExtFightSelect.disabled = false; + renderTimeline(lastEvents, lastFightStart); + }); + // ── Timeline rendering ──────────────────────────────────────────────────── function renderTimeline(events, fightStart) { @@ -271,7 +366,7 @@ let refHtml = ''; if (refEv) { const refVisible = refEv.targets.filter(t => - !hiddenPlayers.has(t.id) && + !hiddenPlayerNames.has(t.name) && (!playerFilter || t.name.toLowerCase().includes(playerFilter)) ); if (refVisible.length) { @@ -284,7 +379,7 @@ const dead = t.hp === 0 && t.maxHp > 0; const deltaHtml = diff !== 0 - ? `${diff > 0 ? '+' : ''}${fmtDmg(diff)}` + ? `${diff > 0 ? '+' : '-'}${fmtDmg(Math.abs(diff))}` : ''; const refMitigIcons = (t.mitigations ?? []).map(m => { @@ -390,8 +485,13 @@ lastFightId = null; refEvents = []; refFightStart = 0; - refFightSelect.value = ''; + extFights = []; + extReportCode = ''; + refFightSelect.value = ''; refFightSelect.style.display = 'none'; + refExtFightSelect.value = ''; + refExtFightSelect.style.display = 'none'; + refExtPanel.style.display = 'none'; }, }; })(); diff --git a/templates/tab-analysis.php b/templates/tab-analysis.php index aa732da..f4f546b 100644 --- a/templates/tab-analysis.php +++ b/templates/tab-analysis.php @@ -18,6 +18,16 @@
+
+ + +