small fix, stay on playner page on refresh and translate planner
This commit is contained in:
parent
14674d2842
commit
c67b08737e
@ -212,11 +212,12 @@ foreach (MITIGATION_ABILITIES as $name => $meta) {
|
||||
}
|
||||
}
|
||||
|
||||
// statusId set for shield abilities — used to filter the buff timeline query
|
||||
$shieldStatusIds = [];
|
||||
// statusId set for tracked mitigations — used to resolve localized buff names
|
||||
// from Buffs events and to build the shield fallback timeline.
|
||||
$trackedStatusIds = [];
|
||||
foreach (MITIGATION_ABILITIES as $meta) {
|
||||
if ($meta['buffType'] === 'shield' && isset($meta['statusId'])) {
|
||||
$shieldStatusIds[$meta['statusId']] = true;
|
||||
if (isset($meta['statusId'])) {
|
||||
$trackedStatusIds[$meta['statusId']] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,8 +277,9 @@ for ($page = 0; $page < 10; $page++) {
|
||||
// Builds applybuff/removebuff intervals per target so we can detect shields
|
||||
// that were consumed by a hit (absent from the damage event's buffs snapshot).
|
||||
$shieldTimeline = []; // targetId → statusId → [[apply, remove|null], ...]
|
||||
$statusNames = []; // statusId → localized display name from Buffs events
|
||||
|
||||
if (!empty($shieldStatusIds)) {
|
||||
if (!empty($trackedStatusIds)) {
|
||||
$nextPage = $startTime;
|
||||
for ($page = 0; $page < 10; $page++) {
|
||||
$bfResult = fflogs_gql(<<<GQL
|
||||
@ -304,11 +306,19 @@ if (!empty($shieldStatusIds)) {
|
||||
$bfEv = $bfResult['data']['reportData']['report']['events'] ?? [];
|
||||
foreach ($bfEv['data'] ?? [] as $ev) {
|
||||
$abId = (int)($ev['abilityGameID'] ?? 0);
|
||||
if (!isset($shieldStatusIds[$abId])) continue;
|
||||
if (!isset($trackedStatusIds[$abId])) continue;
|
||||
|
||||
$evName = $ev['ability']['name'] ?? null;
|
||||
if (is_string($evName) && $evName !== '') {
|
||||
$statusNames[$abId] = $evName;
|
||||
}
|
||||
|
||||
$tgtId = (int)($ev['targetID'] ?? 0);
|
||||
$ts = (float)($ev['timestamp'] ?? 0);
|
||||
$type = $ev['type'] ?? '';
|
||||
$meta = $mitigIdMap[$abId] ?? null;
|
||||
|
||||
if (($meta['buffType'] ?? null) !== 'shield') continue;
|
||||
|
||||
if ($type === 'applybuff') {
|
||||
$shieldTimeline[$tgtId][$abId][] = ['apply' => $ts, 'remove' => null];
|
||||
@ -329,6 +339,20 @@ if (!empty($shieldStatusIds)) {
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($statusNames as $statusId => $displayName) {
|
||||
if (isset($mitigIdMap[$statusId])) {
|
||||
$mitigIdMap[$statusId]['name'] = $displayName;
|
||||
}
|
||||
}
|
||||
|
||||
$mitigationNames = [];
|
||||
foreach ($mitigIdMap as $meta) {
|
||||
$key = $meta['key'] ?? null;
|
||||
if ($key) {
|
||||
$mitigationNames[$key] = $meta['name'] ?? $key;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. AoE detection — proximity clustering ────────────────────────────────
|
||||
// Group events by abilityId, then cluster by time proximity (≤ 1000ms from
|
||||
// the first event in the cluster) to avoid fixed-window boundary splits.
|
||||
@ -477,4 +501,5 @@ echo json_encode([
|
||||
'players' => array_values($players),
|
||||
'aoe_events' => $aoeEvents,
|
||||
'fight_start' => (int)$startTime,
|
||||
'mitigation_names' => $mitigationNames,
|
||||
]);
|
||||
|
||||
@ -103,6 +103,7 @@
|
||||
let currentPlayers = [];
|
||||
let extFights = [];
|
||||
let extReportCode = '';
|
||||
let mitigationNames = {};
|
||||
|
||||
// ── Player grid ──────────────────────────────────────────────────────────
|
||||
|
||||
@ -683,6 +684,7 @@
|
||||
populateRefFightSelect();
|
||||
setupPhases(window.App?.phases ?? []);
|
||||
renderPlayers(json.players ?? []);
|
||||
mitigationNames = json.mitigation_names ?? {};
|
||||
renderTimeline(json.aoe_events ?? [], json.fight_start ?? fightStart);
|
||||
|
||||
document.getElementById('analysis-loading').style.display = 'none';
|
||||
@ -717,6 +719,9 @@
|
||||
players: currentPlayers,
|
||||
fightName: fight?.name ?? `Fight ${window.App?.fightId ?? '?'}`,
|
||||
reportCode: window.App?.reportCode ?? '',
|
||||
fightId: window.App?.fightId ?? 0,
|
||||
fightEnd: window.App?.fightEnd ?? 0,
|
||||
mitigationNames,
|
||||
};
|
||||
},
|
||||
reset() {
|
||||
@ -726,6 +731,7 @@
|
||||
refPlayers = [];
|
||||
extFights = [];
|
||||
extReportCode = '';
|
||||
mitigationNames = {};
|
||||
document.getElementById('ref-player-section').style.display = 'none';
|
||||
refFightSelect.value = '';
|
||||
refFightSelect.style.display = 'none';
|
||||
|
||||
@ -137,6 +137,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelector('.tabs .tab[data-tab="analysis"]')?.click();
|
||||
}
|
||||
|
||||
function shouldAutoOpenAnalysis() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const requestedTab = params.get('tab') || localStorage.getItem('ff14-mitigator-active-tab');
|
||||
return requestedTab !== 'planner';
|
||||
}
|
||||
|
||||
function selectFight(id, updateUrl = true) {
|
||||
const fight = allFights.find(f => f.id === id);
|
||||
if (!fight) return false;
|
||||
@ -341,7 +347,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (initialUrlState.reportCode) {
|
||||
form.elements['report_code'].value = initialUrlState.reportCode;
|
||||
loadReport(initialUrlState.reportCode, initialUrlState.fightId).then(() => {
|
||||
if (initialUrlState.fightId) {
|
||||
if (initialUrlState.fightId && shouldAutoOpenAnalysis()) {
|
||||
openAnalysisTab();
|
||||
}
|
||||
if (initialUrlState.compareFightId) {
|
||||
|
||||
133
js/planner.js
133
js/planner.js
@ -1,6 +1,7 @@
|
||||
// ── Storage ───────────────────────────────────────────────────────────────────
|
||||
|
||||
const PLANNER_KEY = 'ff14-planner-plans';
|
||||
const PLANNER_ACTIVE_KEY = 'ff14-planner-active-plan';
|
||||
|
||||
function loadPlans() {
|
||||
try { return JSON.parse(localStorage.getItem(PLANNER_KEY) || '[]'); }
|
||||
@ -20,6 +21,7 @@ function createPlan(name) {
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
source: null,
|
||||
mitigationNames: {},
|
||||
jobComposition: Array(8).fill(''),
|
||||
mechanics: []
|
||||
};
|
||||
@ -89,8 +91,22 @@ function fmtNumber(n) {
|
||||
return Number(n).toLocaleString('de-DE');
|
||||
}
|
||||
|
||||
function assignmentAbilityName(assignment) {
|
||||
return assignment?.abilityName ?? assignment?.ability ?? '';
|
||||
function assignmentAbilityName(assignment, plan = null) {
|
||||
const key = assignment?.ability ?? '';
|
||||
return assignment?.abilityName ?? plan?.mitigationNames?.[key] ?? key;
|
||||
}
|
||||
|
||||
function plannerLanguage() {
|
||||
return window.App?.language || localStorage.getItem('ff14-mitigator-language') || 'en';
|
||||
}
|
||||
|
||||
function sameMechanic(existing, incoming, source) {
|
||||
const fightStart = source?.fightStart ?? 0;
|
||||
const incomingRel = incoming.timestamp - fightStart;
|
||||
if (existing.abilityId && incoming.abilityId && existing.abilityId === incoming.abilityId) {
|
||||
return Math.abs(existing.timestamp - incomingRel) < 1500;
|
||||
}
|
||||
return Math.abs(existing.timestamp - incomingRel) < 1500;
|
||||
}
|
||||
|
||||
// ── Rendering: Plan List ──────────────────────────────────────────────────────
|
||||
@ -226,7 +242,7 @@ function renderMechanicListHtml(plan) {
|
||||
: 'badge-assign-buff';
|
||||
const isMissing = !!a.job && !activeJobSet.has(a.job);
|
||||
const icon = MITIG_ICONS[a.ability] ?? '';
|
||||
const ability = assignmentAbilityName(a);
|
||||
const ability = assignmentAbilityName(a, plan);
|
||||
const label = a.job ? `${escHtml(a.job)} · ${escHtml(ability)}` : escHtml(ability);
|
||||
const title = isMissing ? `${escHtml(a.job)} nicht in Jobaufstellung` : '';
|
||||
return `<span class="badge badge-assign ${cls}${isMissing ? ' badge-assign--missing-job' : ''}"${title ? ` title="${title}"` : ''}>
|
||||
@ -384,8 +400,10 @@ function startRename(id, currentName) {
|
||||
|
||||
function openPlan(id) {
|
||||
activePlanId = id;
|
||||
localStorage.setItem(PLANNER_ACTIVE_KEY, id);
|
||||
renderPlanList();
|
||||
renderPlanDetail(getPlan(id));
|
||||
refreshPlanLanguage(id);
|
||||
}
|
||||
|
||||
// ── New plan form ─────────────────────────────────────────────────────────────
|
||||
@ -633,7 +651,7 @@ function mitigationKey(mitigation) {
|
||||
|
||||
// ── AoE Events → Plan Mechanics ───────────────────────────────────────────────
|
||||
|
||||
function aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitigations) {
|
||||
function aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitigations, mitigationNames = {}) {
|
||||
return aoeEvents.map(ev => {
|
||||
const relTs = ev.timestamp - fightStart;
|
||||
const phase = (phases ?? []).filter(p => p.id !== 0).find(p =>
|
||||
@ -657,7 +675,7 @@ function aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitiga
|
||||
seen.add(key);
|
||||
assignments.push({
|
||||
ability: key,
|
||||
abilityName: mitigationDisplayName(m),
|
||||
abilityName: mitigationDisplayName(m) || mitigationNames[key],
|
||||
job: guessJob(key, players),
|
||||
buffType: m.buffType ?? '',
|
||||
});
|
||||
@ -669,6 +687,7 @@ function aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitiga
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
name: ev.abilityName,
|
||||
abilityId: ev.abilityId,
|
||||
timestamp: relTs,
|
||||
phase: phase?.name ?? '',
|
||||
unmitigatedDamage: avgUnmit,
|
||||
@ -694,14 +713,16 @@ function extractJobComp(players) {
|
||||
}
|
||||
|
||||
function doImport(data, withMitigations, whereMode, mergeId, newName) {
|
||||
const { aoeEvents, fightStart, phases, players, fightName, reportCode } = data;
|
||||
const mechanics = aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitigations);
|
||||
const { aoeEvents, fightStart, fightEnd, phases, players, fightName, reportCode, fightId, mitigationNames = {} } = data;
|
||||
const mechanics = aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitigations, mitigationNames);
|
||||
const source = { reportCode, fightId, fightName, fightStart, fightEnd, language: plannerLanguage() };
|
||||
|
||||
if (whereMode === 'new') {
|
||||
const plan = createPlan(newName || fightName || 'Importierter Plan');
|
||||
return updatePlan(plan.id, {
|
||||
mechanics,
|
||||
source: { reportCode, fightName },
|
||||
source,
|
||||
mitigationNames,
|
||||
jobComposition: extractJobComp(players),
|
||||
});
|
||||
}
|
||||
@ -719,7 +740,87 @@ function doImport(data, withMitigations, whereMode, mergeId, newName) {
|
||||
}
|
||||
merged.sort((a, b) => a.timestamp - b.timestamp);
|
||||
|
||||
return updatePlan(mergeId, { mechanics: merged });
|
||||
return updatePlan(mergeId, {
|
||||
mechanics: merged,
|
||||
source: { ...(plan.source ?? {}), ...source },
|
||||
mitigationNames: { ...(plan.mitigationNames ?? {}), ...mitigationNames },
|
||||
});
|
||||
}
|
||||
|
||||
const refreshingPlans = new Set();
|
||||
|
||||
async function refreshPlanLanguage(planId) {
|
||||
const plan = getPlan(planId);
|
||||
const source = plan?.source ?? {};
|
||||
const language = plannerLanguage();
|
||||
if (!plan || refreshingPlans.has(planId)) return;
|
||||
if (!source.reportCode || !source.fightId || !source.fightStart || !source.fightEnd) return;
|
||||
if (source.language === language && plan.mitigationNames && Object.keys(plan.mitigationNames).length) return;
|
||||
|
||||
refreshingPlans.add(planId);
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
report_code: source.reportCode,
|
||||
fight_id: source.fightId,
|
||||
start_time: source.fightStart,
|
||||
end_time: source.fightEnd,
|
||||
language,
|
||||
});
|
||||
|
||||
const res = await fetch('api/analysis.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: params,
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.reauth) { window.location.href = window.App?.authStartUrl?.() ?? 'auth/start.php'; return; }
|
||||
if (json.error) return;
|
||||
|
||||
const refreshed = (json.aoe_events ?? []);
|
||||
const mechanics = plan.mechanics.map(mechanic => {
|
||||
const match = refreshed.find(ev => sameMechanic(mechanic, ev, source));
|
||||
if (!match) return mechanic;
|
||||
const assignments = (mechanic.assignments ?? []).map(a => ({
|
||||
...a,
|
||||
abilityName: json.mitigation_names?.[a.ability] ?? a.abilityName,
|
||||
}));
|
||||
return {
|
||||
...mechanic,
|
||||
name: match.abilityName ?? mechanic.name,
|
||||
abilityId: match.abilityId ?? mechanic.abilityId,
|
||||
assignments,
|
||||
};
|
||||
});
|
||||
|
||||
let fightName = source.fightName;
|
||||
try {
|
||||
const fightRes = await fetch('api/fight.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({ report_code: source.reportCode, language }),
|
||||
});
|
||||
const fightJson = await fightRes.json();
|
||||
const fight = (fightJson?.data?.reportData?.report?.fights ?? []).find(f => f.id === source.fightId);
|
||||
if (fight?.name) fightName = fight.name;
|
||||
} catch { }
|
||||
|
||||
const nextSource = { ...source, fightName, language };
|
||||
const nextName = plan.name === source.fightName && fightName ? fightName : plan.name;
|
||||
const updated = updatePlan(planId, {
|
||||
name: nextName,
|
||||
mechanics,
|
||||
source: nextSource,
|
||||
mitigationNames: json.mitigation_names ?? plan.mitigationNames ?? {},
|
||||
});
|
||||
|
||||
if (updated && activePlanId === planId) {
|
||||
renderPlanList();
|
||||
renderPlanDetail(updated);
|
||||
}
|
||||
} catch { }
|
||||
finally {
|
||||
refreshingPlans.delete(planId);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Ability Assignment Modal ──────────────────────────────────────────────────
|
||||
@ -775,8 +876,8 @@ function renderAbilityModalContent() {
|
||||
const otherClass = byOtherJob ? ' ability-chip--other-job' : '';
|
||||
const title = byOtherJob ? `Bereits von ${escHtml(assigned.job)} zugewiesen` : '';
|
||||
const icon = MITIG_ICONS[ab.name] ?? '';
|
||||
const assignedName = assigned ? assignmentAbilityName(assigned) : '';
|
||||
const label = assignedName || ab.name;
|
||||
const assignedName = assigned ? assignmentAbilityName(assigned, plan) : '';
|
||||
const label = assignedName || plan.mitigationNames?.[ab.name] || ab.name;
|
||||
return `<button class="ability-chip ${cls}${activeClass}${otherClass}"
|
||||
data-ability="${escHtml(ab.name)}"
|
||||
data-job="${escHtml(job)}"
|
||||
@ -809,7 +910,7 @@ function toggleAbilityAssignment(abilityName, job, buffType) {
|
||||
mechanic.assignments[idx].job = job;
|
||||
}
|
||||
} else {
|
||||
mechanic.assignments.push({ ability: abilityName, job, buffType });
|
||||
mechanic.assignments.push({ ability: abilityName, abilityName: plan.mitigationNames?.[abilityName], job, buffType });
|
||||
}
|
||||
|
||||
updatePlan(abilityModalPlanId, { mechanics: plan.mechanics });
|
||||
@ -950,11 +1051,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
initNewPlanForm();
|
||||
initImportModal();
|
||||
initAbilityModal();
|
||||
activePlanId = localStorage.getItem(PLANNER_ACTIVE_KEY);
|
||||
renderPlanList();
|
||||
if (activePlanId && getPlan(activePlanId)) {
|
||||
renderPlanDetail(getPlan(activePlanId));
|
||||
} else {
|
||||
activePlanId = null;
|
||||
renderPlanDetail(null);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('ff14-language-change', () => {
|
||||
if (!activePlanId) return;
|
||||
renderPlanDetail(getPlan(activePlanId));
|
||||
refreshPlanLanguage(activePlanId);
|
||||
});
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tabs = document.querySelectorAll('.tabs .tab');
|
||||
const contents = document.querySelectorAll('.tab-content');
|
||||
const validTabs = new Set([...tabs].map(btn => btn.dataset.tab));
|
||||
|
||||
function showTab(name) {
|
||||
if (!validTabs.has(name)) name = 'report';
|
||||
contents.forEach(el => el.style.display = 'none');
|
||||
tabs.forEach(btn => btn.classList.remove('active'));
|
||||
|
||||
@ -14,8 +16,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
if (name === 'analysis') window.analysisTab?.onTabOpen?.();
|
||||
if (name === 'planner') window.plannerTab?.onTabOpen?.();
|
||||
|
||||
localStorage.setItem('ff14-mitigator-active-tab', name);
|
||||
}
|
||||
|
||||
tabs.forEach(btn => btn.addEventListener('click', () => showTab(btn.dataset.tab)));
|
||||
window.showTab = showTab;
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
showTab(params.get('tab') || localStorage.getItem('ff14-mitigator-active-tab') || 'report');
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user