Akurosia Kamo 19bd79c056 if tank is selected track also tankbuster
on login keep url params
2026-05-22 08:43:45 +02:00

352 lines
14 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
window.App = { reportCode: null, fightId: null, fightStart: 0, fightEnd: 0, language: 'en', phases: [], fights: [] };
const form = document.getElementById('report-form');
const output = document.getElementById('output');
const outputCard = document.getElementById('output-card');
const initialHint = document.getElementById('initial-hint');
const fightSelectCard = document.getElementById('fight-select-card');
const fightSelect = document.getElementById('fight-select');
const fflogsReportLink = document.getElementById('fflogs-report-link');
const languageSelect = document.getElementById('language-select');
const explorerCard = document.getElementById('event-explorer-card');
const exLoadBtn = document.getElementById('ex-load-btn');
const exAbilitySelect = document.getElementById('ex-ability');
const exPlayerSelect = document.getElementById('ex-player-name');
let allFights = [];
function getUrlState() {
const params = new URLSearchParams(window.location.search);
const pick = (...names) => {
for (const name of names) {
const value = params.get(name);
if (value !== null && value !== '') return value;
}
return '';
};
return {
reportCode: pick('report_code', 'reportCode', 'report'),
fightId: parseInt(pick('fightid', 'fight_id', 'fightId'), 10) || 0,
compareReportCode: pick('compare_report_code', 'compareReportCode', 'compare_report', 'ref_report'),
compareFightId: parseInt(pick('comparefightid', 'compare_fight_id', 'compareFightId', 'ref_fight_id'), 10) || 0,
language: pick('language', 'lang'),
translate: pick('translate'),
};
}
function normalizeLanguage(value, fallback = 'en') {
const lang = String(value || '').toLowerCase();
return ['en', 'de', 'fr', 'jp'].includes(lang) ? lang : fallback;
}
function setUrlState(updates) {
const url = new URL(window.location.href);
const setOrDelete = (name, value) => {
if (value === null || value === undefined || value === '') url.searchParams.delete(name);
else url.searchParams.set(name, value);
};
if ('reportCode' in updates) setOrDelete('report_code', updates.reportCode);
if ('fightId' in updates) setOrDelete('fightid', updates.fightId);
if ('compareReportCode' in updates) setOrDelete('compare_report_code', updates.compareReportCode);
if ('compareFightId' in updates) setOrDelete('comparefightid', updates.compareFightId);
if ('language' in updates) {
setOrDelete('language', normalizeLanguage(updates.language));
url.searchParams.delete('translate');
}
window.history.replaceState(null, '', url);
}
window.App.setUrlState = setUrlState;
function authStartUrl() {
return 'auth/start.php?return=' + encodeURIComponent(window.location.pathname + window.location.search);
}
window.App.authStartUrl = authStartUrl;
function fflogsReportUrl(reportCode, fightId = 0) {
const code = String(reportCode || '').trim();
if (!code) return '#';
const host = {
de: 'de.fflogs.com',
fr: 'fr.fflogs.com',
jp: 'ja.fflogs.com',
}[window.App.language] ?? 'www.fflogs.com';
const fight = parseInt(fightId, 10) || 0;
return `https://${host}/reports/${encodeURIComponent(code)}${fight ? `#fight=${fight}` : ''}`;
}
window.App.fflogsReportUrl = fflogsReportUrl;
function updateFflogsReportLink() {
if (!window.App.reportCode) {
fflogsReportLink.style.display = 'none';
fflogsReportLink.href = '#';
return;
}
fflogsReportLink.href = fflogsReportUrl(window.App.reportCode, window.App.fightId);
fflogsReportLink.style.display = '';
}
const initialUrlState = getUrlState();
const storedLanguage = localStorage.getItem('ff14-mitigator-language');
const legacyTranslateLanguage = initialUrlState.translate === '1' ? 'de' : 'en';
languageSelect.value = normalizeLanguage(
initialUrlState.language || storedLanguage,
initialUrlState.translate !== '' ? legacyTranslateLanguage : 'en'
);
window.App.language = languageSelect.value;
languageSelect.addEventListener('change', () => {
window.App.language = normalizeLanguage(languageSelect.value);
localStorage.setItem('ff14-mitigator-language', window.App.language);
setUrlState({ language: window.App.language });
if (window.App.reportCode) {
loadReport(window.App.reportCode, window.App.fightId);
}
});
const codeInput = form.elements['report_code'];
codeInput.addEventListener('input', () => {
const match = codeInput.value.match(/fflogs\.com\/reports\/([A-Za-z0-9]+)/);
if (match) codeInput.value = match[1];
});
function formatDuration(ms) {
const min = Math.floor(ms / 60000);
const sec = String(Math.floor((ms % 60000) / 1000)).padStart(2, '0');
return `${min}:${sec}`;
}
function formatBossHp(fight) {
if (fight.kill) return 'Kill';
const pct = fight.fightPercentage;
if (pct == null) return '?';
return pct.toFixed(2) + '%';
}
function displayFight(fight) {
output.textContent = JSON.stringify(fight, null, 2);
outputCard.style.display = 'block';
}
function openAnalysisTab() {
document.querySelector('.tabs .tab[data-tab="analysis"]')?.click();
}
function selectFight(id, updateUrl = true) {
const fight = allFights.find(f => f.id === id);
if (!fight) return false;
fightSelect.value = String(id);
window.App.fightId = id;
window.App.fightStart = fight.startTime;
window.App.fightEnd = fight.endTime;
window.App.phases = buildPhases(fight);
updateFflogsReportLink();
displayFight(fight);
explorerCard.style.display = 'block';
if (updateUrl) {
setUrlState({
reportCode: window.App.reportCode,
fightId: id,
language: window.App.language,
});
}
window.analysisTab?.onFightSelected?.();
loadAbilities(id, fight.startTime, fight.endTime);
return true;
}
fightSelect.addEventListener('change', () => {
if (!fightSelect.value) return;
selectFight(parseInt(fightSelect.value, 10));
});
function buildPhases(fight) {
const transitions = fight.phaseTransitions ?? [];
if (transitions.length === 0) return [];
const phases = transitions.map((t, i) => ({
id: t.id,
name: `Phase ${t.id}`,
startTime: t.startTime,
endTime: transitions[i + 1]?.startTime ?? fight.endTime,
}));
return [{ id: 0, name: 'Ganzer Fight', startTime: fight.startTime, endTime: fight.endTime }, ...phases];
}
async function loadAbilities(fightId, startTime, endTime) {
exAbilitySelect.innerHTML = '<option value="">Lädt…</option>';
try {
const res = await fetch('api/abilities.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
report_code: window.App.reportCode,
fight_id: fightId,
start_time: startTime,
end_time: endTime,
language: window.App.language,
}),
});
const json = await res.json();
exAbilitySelect.innerHTML = '<option value="">Alle</option>';
(json.abilities ?? []).forEach(ab => {
const opt = document.createElement('option');
opt.value = ab.id;
opt.textContent = ab.name;
exAbilitySelect.appendChild(opt);
});
exPlayerSelect.innerHTML = '<option value="">Alle</option>';
(json.players ?? []).forEach(p => {
const opt = document.createElement('option');
opt.value = p.name;
opt.textContent = p.name;
exPlayerSelect.appendChild(opt);
});
} catch {
exAbilitySelect.innerHTML = '<option value="">Fehler beim Laden</option>';
}
}
exLoadBtn.addEventListener('click', async () => {
if (!window.App.fightId) return;
output.textContent = '// loading events...';
outputCard.style.display = 'block';
const params = {
report_code: window.App.reportCode,
fight_id: window.App.fightId,
start_time: window.App.fightStart,
end_time: window.App.fightEnd,
language: window.App.language,
data_type: document.getElementById('ex-data-type').value,
ability_id: exAbilitySelect.value,
event_type: document.getElementById('ex-event-type').value.trim(),
player_name: document.getElementById('ex-player-name').value.trim(),
limit: document.getElementById('ex-limit').value,
start_offset: document.getElementById('ex-start-offset').value,
end_offset: document.getElementById('ex-end-offset').value,
};
try {
const res = await fetch('api/debug-events.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(params),
});
const json = await res.json();
if (json.reauth) { window.location.href = authStartUrl(); return; }
output.textContent = JSON.stringify(json, null, 2);
} catch (err) {
output.textContent = '// Fehler: ' + err.message;
}
});
async function loadReport(reportCode, preferredFightId = 0) {
initialHint.style.display = 'none';
outputCard.style.display = 'block';
output.textContent = '// fetching...';
fightSelectCard.style.display = 'none';
explorerCard.style.display = 'none';
fightSelect.innerHTML = '<option value="">— Fight auswählen —</option>';
fflogsReportLink.style.display = 'none';
fflogsReportLink.href = '#';
allFights = [];
window.App.reportCode = reportCode;
window.App.fightId = null;
window.App.fightStart = 0;
window.App.fightEnd = 0;
window.App.language = normalizeLanguage(languageSelect.value);
window.App.phases = [];
window.App.fights = [];
window.analysisTab?.reset?.();
localStorage.setItem('ff14-mitigator-language', window.App.language);
setUrlState({
reportCode,
fightId: '',
compareReportCode: '',
compareFightId: '',
language: window.App.language,
});
let response, json;
try {
response = await fetch('api/fight.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ report_code: reportCode, language: window.App.language }),
});
json = await response.json();
} catch (err) {
output.textContent = '// network error: ' + err.message;
return;
}
if (json.reauth) {
output.textContent = '// session expired — redirecting...';
setTimeout(() => { window.location.href = authStartUrl(); }, 1500);
return;
}
if (json.errors) {
output.textContent = '// GraphQL error:\n' + JSON.stringify(json.errors, null, 2);
return;
}
const report = json?.data?.reportData?.report;
if (!report) {
output.textContent = JSON.stringify(json, null, 2);
return;
}
allFights = report.fights ?? [];
if (allFights.length === 0) {
output.textContent = '// Keine Fights in diesem Report gefunden.';
return;
}
allFights.forEach(fight => {
const duration = formatDuration(fight.endTime - fight.startTime);
const hp = formatBossHp(fight);
const opt = document.createElement('option');
opt.value = fight.id;
opt.textContent = `${fight.name}${duration}${hp}`;
fightSelect.appendChild(opt);
});
window.App.fights = allFights;
fightSelectCard.style.display = 'block';
updateFflogsReportLink();
window.analysisTab?.onFightsLoaded?.(allFights);
if (preferredFightId && selectFight(preferredFightId, true)) return;
output.textContent = '// Fight auswählen ↑';
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
await loadReport(form.elements['report_code'].value.trim());
});
if (initialUrlState.reportCode) {
form.elements['report_code'].value = initialUrlState.reportCode;
loadReport(initialUrlState.reportCode, initialUrlState.fightId).then(() => {
if (initialUrlState.fightId) {
openAnalysisTab();
}
if (initialUrlState.compareFightId) {
window.analysisTab?.selectSharedCompare?.(initialUrlState.compareFightId, initialUrlState.compareReportCode);
}
});
}
});