Improve ref pull comparison UX and fight filtering

- Replace ghost icons (missing buffs in current pull) with green-bordered
  icons on the REF row, indicating buffs unique to the reference pull
- Add shield tooltip to REF row absorbed values showing which shields were
  active and which are missing in the current pull
- Filter same-report and external ref fight dropdowns to only show fights
  matching the current fight name; current fight excluded from same-report list
- Extract FFLogs report code from pasted URLs in the ext-report input field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
xziino 2026-05-21 14:45:37 +02:00
parent 182f24ee93
commit e5b70c5589
2 changed files with 44 additions and 15 deletions

View File

@ -252,6 +252,12 @@
border-radius: 2px;
}
.aoe-buff-ref-unique {
border: 2px solid #3dff6e;
border-radius: 3px;
box-shadow: 0 0 4px #3dff6e;
}
/* ── Target buffs ────────────────────────────────────────────────────────── */
.aoe-target-buffs {
display: flex;

View File

@ -201,16 +201,27 @@
renderTimeline(lastEvents, lastFightStart);
});
function onFightsLoaded(fights) {
let allSameReportFights = [];
function populateRefFightSelect() {
const currentName = (window.App.fights ?? []).find(f => f.id === window.App.fightId)?.name;
const visible = allSameReportFights.filter(f =>
f.id !== window.App.fightId && (!currentName || f.name === currentName)
);
refFightSelect.innerHTML = '<option value="">Kein Vergleich</option>';
fights.forEach(f => {
visible.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}`;
refFightSelect.appendChild(opt);
});
refFightSelect.style.display = '';
refFightSelect.style.display = visible.length ? '' : 'none';
}
function onFightsLoaded(fights) {
allSameReportFights = fights;
populateRefFightSelect();
}
// ── External report comparison ────────────────────────────────────────────
@ -221,6 +232,11 @@
const refReportLoad = document.getElementById('ref-report-load');
const refExtFightSelect = document.getElementById('ref-ext-fight-select');
refReportInput.addEventListener('input', () => {
const match = refReportInput.value.match(/fflogs\.com\/reports\/([A-Za-z0-9]+)/);
if (match) refReportInput.value = match[1];
});
refExtToggle.addEventListener('click', () => {
const hidden = refExtPanel.style.display === 'none';
refExtPanel.style.display = hidden ? '' : 'none';
@ -246,15 +262,17 @@
extFights = fights;
extReportCode = code;
const currentName = (window.App.fights ?? []).find(f => f.id === window.App.fightId)?.name;
const visibleExt = currentName ? fights.filter(f => f.name === currentName) : fights;
refExtFightSelect.innerHTML = '<option value="">— Fight auswählen —</option>';
fights.forEach(f => {
visibleExt.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';
refExtFightSelect.style.display = visibleExt.length ? '' : 'none';
} catch { }
refReportLoad.disabled = false;
@ -389,12 +407,6 @@
return `<img class="aoe-target-buff-icon" src="${iconSrc}" alt="${m.name}" title="${m.name}${dr}">`;
}).join('');
const missingIcons = missingMitigs.map(m => {
const iconSrc = MITIG_ICONS[m.name];
if (!iconSrc) return '';
return `<img class="aoe-target-buff-icon aoe-buff-missing" src="${iconSrc}" alt="${m.name}" title="${m.name} fehlt (war im Referenz-Pull aktiv)">`;
}).join('');
// Shield tooltip on absorbed value
const activeShields = (t.mitigations ?? []).filter(m => m.buffType === 'shield');
const missingShields = refTarget
@ -423,7 +435,7 @@
${hpBar}
</div>
</div>
${(mitigIcons || missingIcons) ? `<div class="aoe-target-buffs">${mitigIcons}${missingIcons}</div>` : ''}
${mitigIcons ? `<div class="aoe-target-buffs">${mitigIcons}</div>` : ''}
</div>`;
}).join('');
@ -457,13 +469,23 @@
? `<span class="${diff > 0 ? 'aoe-delta-worse' : 'aoe-delta-better'}">${diff > 0 ? '+' : '-'}${fmtDmg(Math.abs(diff))}</span>`
: '';
const currMitigNames = new Set((curr?.mitigations ?? []).map(m => m.name));
const refMitigIcons = (t.mitigations ?? []).filter(m => m.buffType === 'buff').map(m => {
const iconSrc = MITIG_ICONS[m.name];
if (!iconSrc) return '';
const dr = m.dr > 0 ? ` ${m.dr}%` : '';
return `<img class="aoe-target-buff-icon" src="${iconSrc}" alt="${m.name}" title="${m.name}${dr}">`;
const dr = m.dr > 0 ? ` ${m.dr}%` : '';
const missing = !currMitigNames.has(m.name);
const cls = missing ? ' aoe-buff-ref-unique' : '';
const titleSufx = missing ? ' (fehlt im aktuellen Pull)' : '';
return `<img class="aoe-target-buff-icon${cls}" src="${iconSrc}" alt="${m.name}" title="${m.name}${dr}${titleSufx}">`;
}).join('');
const refShields = (t.mitigations ?? []).filter(m => m.buffType === 'shield');
const refShieldTitle = refShields.length
? refShields.map(s => currMitigNames.has(s.name) ? s.name : `${s.name} [fehlt im aktuellen Pull]`).join('\n')
: null;
return `
<div class="aoe-target-wrap">
<div class="aoe-ref-target${dead ? ' aoe-target--dead' : ''}">
@ -472,7 +494,7 @@
${deltaHtml}
</div>
<span class="aoe-target-name">${t.name}</span>
<span class="aoe-target-dmg">${fmtDmg(t.amount)}${t.absorbed > 0 ? ` <span class="aoe-target-absorbed">+${fmtDmg(t.absorbed)}</span>` : ''}</span>
<span class="aoe-target-dmg">${fmtDmg(t.amount)}${t.absorbed > 0 ? ` <span class="aoe-target-absorbed" title="${refShieldTitle ?? 'Keine erkannten Schilde'}">+${fmtDmg(t.absorbed)}</span>` : ''}</span>
</div>
${refMitigIcons ? `<div class="aoe-target-buffs">${refMitigIcons}</div>` : ''}
</div>`;
@ -545,6 +567,7 @@
if (json.error) { setEmpty('Fehler: ' + json.error); return; }
lastFightId = fightId;
populateRefFightSelect();
setupPhases(window.App?.phases ?? []);
renderPlayers(json.players ?? []);
renderTimeline(json.aoe_events ?? [], json.fight_start ?? fightStart);