317 lines
12 KiB
JavaScript
317 lines
12 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 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;
|
|
|
|
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);
|
|
|
|
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 = 'auth/start.php'; 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>';
|
|
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 = 'auth/start.php'; }, 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';
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
});
|