forked from xziino/ff14-mitigator
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
|
// statusId set for tracked mitigations — used to resolve localized buff names
|
||||||
$shieldStatusIds = [];
|
// from Buffs events and to build the shield fallback timeline.
|
||||||
|
$trackedStatusIds = [];
|
||||||
foreach (MITIGATION_ABILITIES as $meta) {
|
foreach (MITIGATION_ABILITIES as $meta) {
|
||||||
if ($meta['buffType'] === 'shield' && isset($meta['statusId'])) {
|
if (isset($meta['statusId'])) {
|
||||||
$shieldStatusIds[$meta['statusId']] = true;
|
$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
|
// 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).
|
// that were consumed by a hit (absent from the damage event's buffs snapshot).
|
||||||
$shieldTimeline = []; // targetId → statusId → [[apply, remove|null], ...]
|
$shieldTimeline = []; // targetId → statusId → [[apply, remove|null], ...]
|
||||||
|
$statusNames = []; // statusId → localized display name from Buffs events
|
||||||
|
|
||||||
if (!empty($shieldStatusIds)) {
|
if (!empty($trackedStatusIds)) {
|
||||||
$nextPage = $startTime;
|
$nextPage = $startTime;
|
||||||
for ($page = 0; $page < 10; $page++) {
|
for ($page = 0; $page < 10; $page++) {
|
||||||
$bfResult = fflogs_gql(<<<GQL
|
$bfResult = fflogs_gql(<<<GQL
|
||||||
@ -304,11 +306,19 @@ if (!empty($shieldStatusIds)) {
|
|||||||
$bfEv = $bfResult['data']['reportData']['report']['events'] ?? [];
|
$bfEv = $bfResult['data']['reportData']['report']['events'] ?? [];
|
||||||
foreach ($bfEv['data'] ?? [] as $ev) {
|
foreach ($bfEv['data'] ?? [] as $ev) {
|
||||||
$abId = (int)($ev['abilityGameID'] ?? 0);
|
$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);
|
$tgtId = (int)($ev['targetID'] ?? 0);
|
||||||
$ts = (float)($ev['timestamp'] ?? 0);
|
$ts = (float)($ev['timestamp'] ?? 0);
|
||||||
$type = $ev['type'] ?? '';
|
$type = $ev['type'] ?? '';
|
||||||
|
$meta = $mitigIdMap[$abId] ?? null;
|
||||||
|
|
||||||
|
if (($meta['buffType'] ?? null) !== 'shield') continue;
|
||||||
|
|
||||||
if ($type === 'applybuff') {
|
if ($type === 'applybuff') {
|
||||||
$shieldTimeline[$tgtId][$abId][] = ['apply' => $ts, 'remove' => null];
|
$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 ────────────────────────────────
|
// ── 3. AoE detection — proximity clustering ────────────────────────────────
|
||||||
// Group events by abilityId, then cluster by time proximity (≤ 1000ms from
|
// Group events by abilityId, then cluster by time proximity (≤ 1000ms from
|
||||||
// the first event in the cluster) to avoid fixed-window boundary splits.
|
// the first event in the cluster) to avoid fixed-window boundary splits.
|
||||||
@ -477,4 +501,5 @@ echo json_encode([
|
|||||||
'players' => array_values($players),
|
'players' => array_values($players),
|
||||||
'aoe_events' => $aoeEvents,
|
'aoe_events' => $aoeEvents,
|
||||||
'fight_start' => (int)$startTime,
|
'fight_start' => (int)$startTime,
|
||||||
|
'mitigation_names' => $mitigationNames,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -103,6 +103,7 @@
|
|||||||
let currentPlayers = [];
|
let currentPlayers = [];
|
||||||
let extFights = [];
|
let extFights = [];
|
||||||
let extReportCode = '';
|
let extReportCode = '';
|
||||||
|
let mitigationNames = {};
|
||||||
|
|
||||||
// ── Player grid ──────────────────────────────────────────────────────────
|
// ── Player grid ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@ -683,6 +684,7 @@
|
|||||||
populateRefFightSelect();
|
populateRefFightSelect();
|
||||||
setupPhases(window.App?.phases ?? []);
|
setupPhases(window.App?.phases ?? []);
|
||||||
renderPlayers(json.players ?? []);
|
renderPlayers(json.players ?? []);
|
||||||
|
mitigationNames = json.mitigation_names ?? {};
|
||||||
renderTimeline(json.aoe_events ?? [], json.fight_start ?? fightStart);
|
renderTimeline(json.aoe_events ?? [], json.fight_start ?? fightStart);
|
||||||
|
|
||||||
document.getElementById('analysis-loading').style.display = 'none';
|
document.getElementById('analysis-loading').style.display = 'none';
|
||||||
@ -717,6 +719,9 @@
|
|||||||
players: currentPlayers,
|
players: currentPlayers,
|
||||||
fightName: fight?.name ?? `Fight ${window.App?.fightId ?? '?'}`,
|
fightName: fight?.name ?? `Fight ${window.App?.fightId ?? '?'}`,
|
||||||
reportCode: window.App?.reportCode ?? '',
|
reportCode: window.App?.reportCode ?? '',
|
||||||
|
fightId: window.App?.fightId ?? 0,
|
||||||
|
fightEnd: window.App?.fightEnd ?? 0,
|
||||||
|
mitigationNames,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
reset() {
|
reset() {
|
||||||
@ -726,6 +731,7 @@
|
|||||||
refPlayers = [];
|
refPlayers = [];
|
||||||
extFights = [];
|
extFights = [];
|
||||||
extReportCode = '';
|
extReportCode = '';
|
||||||
|
mitigationNames = {};
|
||||||
document.getElementById('ref-player-section').style.display = 'none';
|
document.getElementById('ref-player-section').style.display = 'none';
|
||||||
refFightSelect.value = '';
|
refFightSelect.value = '';
|
||||||
refFightSelect.style.display = 'none';
|
refFightSelect.style.display = 'none';
|
||||||
|
|||||||
@ -137,6 +137,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
document.querySelector('.tabs .tab[data-tab="analysis"]')?.click();
|
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) {
|
function selectFight(id, updateUrl = true) {
|
||||||
const fight = allFights.find(f => f.id === id);
|
const fight = allFights.find(f => f.id === id);
|
||||||
if (!fight) return false;
|
if (!fight) return false;
|
||||||
@ -341,7 +347,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (initialUrlState.reportCode) {
|
if (initialUrlState.reportCode) {
|
||||||
form.elements['report_code'].value = initialUrlState.reportCode;
|
form.elements['report_code'].value = initialUrlState.reportCode;
|
||||||
loadReport(initialUrlState.reportCode, initialUrlState.fightId).then(() => {
|
loadReport(initialUrlState.reportCode, initialUrlState.fightId).then(() => {
|
||||||
if (initialUrlState.fightId) {
|
if (initialUrlState.fightId && shouldAutoOpenAnalysis()) {
|
||||||
openAnalysisTab();
|
openAnalysisTab();
|
||||||
}
|
}
|
||||||
if (initialUrlState.compareFightId) {
|
if (initialUrlState.compareFightId) {
|
||||||
|
|||||||
133
js/planner.js
133
js/planner.js
@ -1,6 +1,7 @@
|
|||||||
// ── Storage ───────────────────────────────────────────────────────────────────
|
// ── Storage ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const PLANNER_KEY = 'ff14-planner-plans';
|
const PLANNER_KEY = 'ff14-planner-plans';
|
||||||
|
const PLANNER_ACTIVE_KEY = 'ff14-planner-active-plan';
|
||||||
|
|
||||||
function loadPlans() {
|
function loadPlans() {
|
||||||
try { return JSON.parse(localStorage.getItem(PLANNER_KEY) || '[]'); }
|
try { return JSON.parse(localStorage.getItem(PLANNER_KEY) || '[]'); }
|
||||||
@ -20,6 +21,7 @@ function createPlan(name) {
|
|||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
source: null,
|
source: null,
|
||||||
|
mitigationNames: {},
|
||||||
jobComposition: Array(8).fill(''),
|
jobComposition: Array(8).fill(''),
|
||||||
mechanics: []
|
mechanics: []
|
||||||
};
|
};
|
||||||
@ -89,8 +91,22 @@ function fmtNumber(n) {
|
|||||||
return Number(n).toLocaleString('de-DE');
|
return Number(n).toLocaleString('de-DE');
|
||||||
}
|
}
|
||||||
|
|
||||||
function assignmentAbilityName(assignment) {
|
function assignmentAbilityName(assignment, plan = null) {
|
||||||
return assignment?.abilityName ?? assignment?.ability ?? '';
|
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 ──────────────────────────────────────────────────────
|
// ── Rendering: Plan List ──────────────────────────────────────────────────────
|
||||||
@ -226,7 +242,7 @@ function renderMechanicListHtml(plan) {
|
|||||||
: 'badge-assign-buff';
|
: 'badge-assign-buff';
|
||||||
const isMissing = !!a.job && !activeJobSet.has(a.job);
|
const isMissing = !!a.job && !activeJobSet.has(a.job);
|
||||||
const icon = MITIG_ICONS[a.ability] ?? '';
|
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 label = a.job ? `${escHtml(a.job)} · ${escHtml(ability)}` : escHtml(ability);
|
||||||
const title = isMissing ? `${escHtml(a.job)} nicht in Jobaufstellung` : '';
|
const title = isMissing ? `${escHtml(a.job)} nicht in Jobaufstellung` : '';
|
||||||
return `<span class="badge badge-assign ${cls}${isMissing ? ' badge-assign--missing-job' : ''}"${title ? ` title="${title}"` : ''}>
|
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) {
|
function openPlan(id) {
|
||||||
activePlanId = id;
|
activePlanId = id;
|
||||||
|
localStorage.setItem(PLANNER_ACTIVE_KEY, id);
|
||||||
renderPlanList();
|
renderPlanList();
|
||||||
renderPlanDetail(getPlan(id));
|
renderPlanDetail(getPlan(id));
|
||||||
|
refreshPlanLanguage(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── New plan form ─────────────────────────────────────────────────────────────
|
// ── New plan form ─────────────────────────────────────────────────────────────
|
||||||
@ -633,7 +651,7 @@ function mitigationKey(mitigation) {
|
|||||||
|
|
||||||
// ── AoE Events → Plan Mechanics ───────────────────────────────────────────────
|
// ── AoE Events → Plan Mechanics ───────────────────────────────────────────────
|
||||||
|
|
||||||
function aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitigations) {
|
function aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitigations, mitigationNames = {}) {
|
||||||
return aoeEvents.map(ev => {
|
return aoeEvents.map(ev => {
|
||||||
const relTs = ev.timestamp - fightStart;
|
const relTs = ev.timestamp - fightStart;
|
||||||
const phase = (phases ?? []).filter(p => p.id !== 0).find(p =>
|
const phase = (phases ?? []).filter(p => p.id !== 0).find(p =>
|
||||||
@ -657,7 +675,7 @@ function aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitiga
|
|||||||
seen.add(key);
|
seen.add(key);
|
||||||
assignments.push({
|
assignments.push({
|
||||||
ability: key,
|
ability: key,
|
||||||
abilityName: mitigationDisplayName(m),
|
abilityName: mitigationDisplayName(m) || mitigationNames[key],
|
||||||
job: guessJob(key, players),
|
job: guessJob(key, players),
|
||||||
buffType: m.buffType ?? '',
|
buffType: m.buffType ?? '',
|
||||||
});
|
});
|
||||||
@ -669,6 +687,7 @@ function aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitiga
|
|||||||
return {
|
return {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
name: ev.abilityName,
|
name: ev.abilityName,
|
||||||
|
abilityId: ev.abilityId,
|
||||||
timestamp: relTs,
|
timestamp: relTs,
|
||||||
phase: phase?.name ?? '',
|
phase: phase?.name ?? '',
|
||||||
unmitigatedDamage: avgUnmit,
|
unmitigatedDamage: avgUnmit,
|
||||||
@ -694,14 +713,16 @@ function extractJobComp(players) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function doImport(data, withMitigations, whereMode, mergeId, newName) {
|
function doImport(data, withMitigations, whereMode, mergeId, newName) {
|
||||||
const { aoeEvents, fightStart, phases, players, fightName, reportCode } = data;
|
const { aoeEvents, fightStart, fightEnd, phases, players, fightName, reportCode, fightId, mitigationNames = {} } = data;
|
||||||
const mechanics = aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitigations);
|
const mechanics = aoeEventsToMechanics(aoeEvents, fightStart, phases, players, withMitigations, mitigationNames);
|
||||||
|
const source = { reportCode, fightId, fightName, fightStart, fightEnd, language: plannerLanguage() };
|
||||||
|
|
||||||
if (whereMode === 'new') {
|
if (whereMode === 'new') {
|
||||||
const plan = createPlan(newName || fightName || 'Importierter Plan');
|
const plan = createPlan(newName || fightName || 'Importierter Plan');
|
||||||
return updatePlan(plan.id, {
|
return updatePlan(plan.id, {
|
||||||
mechanics,
|
mechanics,
|
||||||
source: { reportCode, fightName },
|
source,
|
||||||
|
mitigationNames,
|
||||||
jobComposition: extractJobComp(players),
|
jobComposition: extractJobComp(players),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -719,7 +740,87 @@ function doImport(data, withMitigations, whereMode, mergeId, newName) {
|
|||||||
}
|
}
|
||||||
merged.sort((a, b) => a.timestamp - b.timestamp);
|
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 ──────────────────────────────────────────────────
|
// ── Ability Assignment Modal ──────────────────────────────────────────────────
|
||||||
@ -775,8 +876,8 @@ function renderAbilityModalContent() {
|
|||||||
const otherClass = byOtherJob ? ' ability-chip--other-job' : '';
|
const otherClass = byOtherJob ? ' ability-chip--other-job' : '';
|
||||||
const title = byOtherJob ? `Bereits von ${escHtml(assigned.job)} zugewiesen` : '';
|
const title = byOtherJob ? `Bereits von ${escHtml(assigned.job)} zugewiesen` : '';
|
||||||
const icon = MITIG_ICONS[ab.name] ?? '';
|
const icon = MITIG_ICONS[ab.name] ?? '';
|
||||||
const assignedName = assigned ? assignmentAbilityName(assigned) : '';
|
const assignedName = assigned ? assignmentAbilityName(assigned, plan) : '';
|
||||||
const label = assignedName || ab.name;
|
const label = assignedName || plan.mitigationNames?.[ab.name] || ab.name;
|
||||||
return `<button class="ability-chip ${cls}${activeClass}${otherClass}"
|
return `<button class="ability-chip ${cls}${activeClass}${otherClass}"
|
||||||
data-ability="${escHtml(ab.name)}"
|
data-ability="${escHtml(ab.name)}"
|
||||||
data-job="${escHtml(job)}"
|
data-job="${escHtml(job)}"
|
||||||
@ -809,7 +910,7 @@ function toggleAbilityAssignment(abilityName, job, buffType) {
|
|||||||
mechanic.assignments[idx].job = job;
|
mechanic.assignments[idx].job = job;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mechanic.assignments.push({ ability: abilityName, job, buffType });
|
mechanic.assignments.push({ ability: abilityName, abilityName: plan.mitigationNames?.[abilityName], job, buffType });
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePlan(abilityModalPlanId, { mechanics: plan.mechanics });
|
updatePlan(abilityModalPlanId, { mechanics: plan.mechanics });
|
||||||
@ -950,11 +1051,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
initNewPlanForm();
|
initNewPlanForm();
|
||||||
initImportModal();
|
initImportModal();
|
||||||
initAbilityModal();
|
initAbilityModal();
|
||||||
|
activePlanId = localStorage.getItem(PLANNER_ACTIVE_KEY);
|
||||||
renderPlanList();
|
renderPlanList();
|
||||||
|
if (activePlanId && getPlan(activePlanId)) {
|
||||||
|
renderPlanDetail(getPlan(activePlanId));
|
||||||
|
} else {
|
||||||
|
activePlanId = null;
|
||||||
renderPlanDetail(null);
|
renderPlanDetail(null);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('ff14-language-change', () => {
|
window.addEventListener('ff14-language-change', () => {
|
||||||
if (!activePlanId) return;
|
if (!activePlanId) return;
|
||||||
renderPlanDetail(getPlan(activePlanId));
|
refreshPlanLanguage(activePlanId);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const tabs = document.querySelectorAll('.tabs .tab');
|
const tabs = document.querySelectorAll('.tabs .tab');
|
||||||
const contents = document.querySelectorAll('.tab-content');
|
const contents = document.querySelectorAll('.tab-content');
|
||||||
|
const validTabs = new Set([...tabs].map(btn => btn.dataset.tab));
|
||||||
|
|
||||||
function showTab(name) {
|
function showTab(name) {
|
||||||
|
if (!validTabs.has(name)) name = 'report';
|
||||||
contents.forEach(el => el.style.display = 'none');
|
contents.forEach(el => el.style.display = 'none');
|
||||||
tabs.forEach(btn => btn.classList.remove('active'));
|
tabs.forEach(btn => btn.classList.remove('active'));
|
||||||
|
|
||||||
@ -14,8 +16,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
if (name === 'analysis') window.analysisTab?.onTabOpen?.();
|
if (name === 'analysis') window.analysisTab?.onTabOpen?.();
|
||||||
if (name === 'planner') window.plannerTab?.onTabOpen?.();
|
if (name === 'planner') window.plannerTab?.onTabOpen?.();
|
||||||
|
|
||||||
|
localStorage.setItem('ff14-mitigator-active-tab', name);
|
||||||
}
|
}
|
||||||
|
|
||||||
tabs.forEach(btn => btn.addEventListener('click', () => showTab(btn.dataset.tab)));
|
tabs.forEach(btn => btn.addEventListener('click', () => showTab(btn.dataset.tab)));
|
||||||
window.showTab = showTab;
|
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