forked from xziino/ff14-mitigator
move log to analys, rename report to debug, fix planer simpleview
This commit is contained in:
parent
d42bde81f6
commit
e5c12b6915
@ -98,6 +98,43 @@
|
|||||||
color: var(--t2);
|
color: var(--t2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.report-load-row {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-code-field {
|
||||||
|
flex: 1 1 360px;
|
||||||
|
min-width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-language-field {
|
||||||
|
flex: 0 0 120px;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-fight-field {
|
||||||
|
flex: 1 1 360px;
|
||||||
|
min-width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-fight-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-fight-label a {
|
||||||
|
color: var(--gold);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.report-load-row {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Topbar user badge ───────────────────────────────────────────────────────── */
|
/* ── Topbar user badge ───────────────────────────────────────────────────────── */
|
||||||
.topbar-user {
|
.topbar-user {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|||||||
@ -1762,11 +1762,6 @@
|
|||||||
|
|
||||||
/* ── View Toggle ─────────────────────────────────────────────────────────── */
|
/* ── View Toggle ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.view-toggle-btns {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.planner-card-actions {
|
.planner-card-actions {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1775,6 +1770,31 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.planner-class-filter-wrap {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
color: var(--t3);
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.planner-class-filter {
|
||||||
|
width: auto;
|
||||||
|
min-width: 88px;
|
||||||
|
padding: 4px 9px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--r);
|
||||||
|
background: var(--bg2);
|
||||||
|
color: var(--t1);
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.planner-class-filter:hover {
|
||||||
|
border-color: var(--t3);
|
||||||
|
}
|
||||||
|
|
||||||
.cactbot-export-modal-box {
|
.cactbot-export-modal-box {
|
||||||
max-width: 720px;
|
max-width: 720px;
|
||||||
max-height: min(86vh, 780px);
|
max-height: min(86vh, 780px);
|
||||||
@ -1971,7 +1991,7 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Meine Spells ────────────────────────────────────────────────────────── */
|
/* ── Simple View Rows ────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.myspells-controls {
|
.myspells-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -2040,6 +2060,18 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.myspells-ability .badge-remove {
|
||||||
|
opacity: .45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.myspells-ability:hover .badge-remove {
|
||||||
|
opacity: .8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.myspells-ability .badge-remove:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.myspells-ability.myspells-type--debuff { color: var(--orange); border-color: rgba(255,140,0,0.3); }
|
.myspells-ability.myspells-type--debuff { color: var(--orange); border-color: rgba(255,140,0,0.3); }
|
||||||
.myspells-ability.myspells-type--shield { color: var(--blue); border-color: rgba(88,166,255,0.3); }
|
.myspells-ability.myspells-type--shield { color: var(--blue); border-color: rgba(88,166,255,0.3); }
|
||||||
.myspells-ability.myspells-type--personal { color: #dbc7ff; border-color: rgba(177,112,255,0.4); }
|
.myspells-ability.myspells-type--personal { color: #dbc7ff; border-color: rgba(177,112,255,0.4); }
|
||||||
|
|||||||
233
js/planner.js
233
js/planner.js
@ -3,6 +3,8 @@
|
|||||||
const PLANNER_KEY = 'ff14-planner-plans';
|
const PLANNER_KEY = 'ff14-planner-plans';
|
||||||
const PLANNER_ACTIVE_KEY = 'ff14-planner-active-plan';
|
const PLANNER_ACTIVE_KEY = 'ff14-planner-active-plan';
|
||||||
const FOLDERS_KEY = 'ff14-planner-folders';
|
const FOLDERS_KEY = 'ff14-planner-folders';
|
||||||
|
const PLANNER_SIMPLE_VIEW_KEY = 'ff14-planner-simple-view';
|
||||||
|
const PLANNER_CLASS_FILTER_KEY = 'ff14-planner-class-filter';
|
||||||
|
|
||||||
function loadPlans() {
|
function loadPlans() {
|
||||||
try { return JSON.parse(localStorage.getItem(PLANNER_KEY) || '[]'); }
|
try { return JSON.parse(localStorage.getItem(PLANNER_KEY) || '[]'); }
|
||||||
@ -255,6 +257,33 @@ function visiblePlanMechanics(plan) {
|
|||||||
.sort((a, b) => a.timestamp - b.timestamp);
|
.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function plannerClassFilterJobs(plan) {
|
||||||
|
return [...new Set((plan?.jobComposition ?? []).filter(Boolean))];
|
||||||
|
}
|
||||||
|
|
||||||
|
function plannerViewOptions(plan = null) {
|
||||||
|
const savedJob = localStorage.getItem(PLANNER_CLASS_FILTER_KEY) ?? '';
|
||||||
|
const jobs = plan ? plannerClassFilterJobs(plan) : [];
|
||||||
|
const job = plan && savedJob && !jobs.includes(savedJob) ? '' : savedJob;
|
||||||
|
if (savedJob && savedJob !== job) localStorage.setItem(PLANNER_CLASS_FILTER_KEY, job);
|
||||||
|
return {
|
||||||
|
simple: localStorage.getItem(PLANNER_SIMPLE_VIEW_KEY) === '1',
|
||||||
|
job,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderClassFilterOptions(plan, selectedJob = '') {
|
||||||
|
const jobs = plannerClassFilterJobs(plan);
|
||||||
|
return [
|
||||||
|
`<option value="">All</option>`,
|
||||||
|
...jobs.map(job => `<option value="${escHtml(job)}"${job === selectedJob ? ' selected' : ''}>${escHtml(job)}</option>`),
|
||||||
|
].join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignmentMatchesClassFilter(assignment, job) {
|
||||||
|
return !job || assignment.job === job;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Rendering: Plan List ──────────────────────────────────────────────────────
|
// ── Rendering: Plan List ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
function planItemHtml(p) {
|
function planItemHtml(p) {
|
||||||
@ -431,6 +460,7 @@ function renderPlanDetail(plan) {
|
|||||||
content.style.display = '';
|
content.style.display = '';
|
||||||
|
|
||||||
const visibleMechanics = visiblePlanMechanics(plan);
|
const visibleMechanics = visiblePlanMechanics(plan);
|
||||||
|
const viewOptions = plannerViewOptions(plan);
|
||||||
|
|
||||||
content.innerHTML = `
|
content.innerHTML = `
|
||||||
<div class="card section-gap">
|
<div class="card section-gap">
|
||||||
@ -485,28 +515,19 @@ function renderPlanDetail(plan) {
|
|||||||
<div class="card-title-row">
|
<div class="card-title-row">
|
||||||
<div class="card-title">Mechaniken</div>
|
<div class="card-title">Mechaniken</div>
|
||||||
<div class="planner-card-actions">
|
<div class="planner-card-actions">
|
||||||
<div class="view-toggle-btns">
|
<button id="planner-simple-view-btn" class="view-toggle-btn${viewOptions.simple ? ' active' : ''}" type="button">Simple View</button>
|
||||||
<button class="view-toggle-btn active" data-view="mechanics">Mechaniken</button>
|
<label class="planner-class-filter-wrap">
|
||||||
<button class="view-toggle-btn" data-view="myspells">★ Meine Spells</button>
|
<span>Class Filter</span>
|
||||||
</div>
|
<select id="planner-class-filter" class="planner-class-filter" title="Class Filter">
|
||||||
|
${renderClassFilterOptions(plan, viewOptions.job)}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<button id="cactbot-export-btn" class="btn btn-sm" title="Cactbot Timeline exportieren">Cactbot Export</button>
|
<button id="cactbot-export-btn" class="btn btn-sm" title="Cactbot Timeline exportieren">Cactbot Export</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="mechanic-list">
|
<div id="mechanic-list">
|
||||||
${renderMechanicListHtml(plan)}
|
${renderMechanicListHtml(plan)}
|
||||||
</div>
|
</div>
|
||||||
<div id="myspells-panel" style="display:none">
|
|
||||||
<div class="myspells-controls">
|
|
||||||
<select id="myspells-job-select">
|
|
||||||
<option value="">— Job wählen —</option>
|
|
||||||
${(plan.jobComposition ?? []).filter(Boolean).filter((j, i, a) => a.indexOf(j) === i).map(j =>
|
|
||||||
`<option value="${escHtml(j)}">${escHtml(j)}</option>`
|
|
||||||
).join('')}
|
|
||||||
</select>
|
|
||||||
<button id="myspells-copy-btn" class="btn btn-sm" title="Als Text kopieren">⎘ Kopieren</button>
|
|
||||||
</div>
|
|
||||||
<div id="myspells-list"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -523,68 +544,12 @@ function renderPlanDetail(plan) {
|
|||||||
initTimelineOptions(plan.id);
|
initTimelineOptions(plan.id);
|
||||||
initTimeline(plan.id);
|
initTimeline(plan.id);
|
||||||
initMechanicClicks(plan.id);
|
initMechanicClicks(plan.id);
|
||||||
initMySpells(plan.id);
|
initPlannerViewControls(plan.id);
|
||||||
initCactbotExport(plan.id);
|
initCactbotExport(plan.id);
|
||||||
renderInfoPanel(plan);
|
renderInfoPanel(plan);
|
||||||
ensureActionMetaLoaded().then(() => refreshMechanicList(plan.id));
|
ensureActionMetaLoaded().then(() => refreshMechanicList(plan.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Meine Spells ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
function renderMySpellsHtml(plan, job) {
|
|
||||||
if (!job) return '<div class="myspells-empty">Job auswählen um Assignments zu sehen.</div>';
|
|
||||||
|
|
||||||
const mechanics = visiblePlanMechanics(plan);
|
|
||||||
const rows = [];
|
|
||||||
for (const mechanic of mechanics) {
|
|
||||||
const mine = (mechanic.assignments ?? []).filter(a => a.job === job);
|
|
||||||
if (!mine.length) continue;
|
|
||||||
rows.push({ mechanic, assignments: mine });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows.length) {
|
|
||||||
return `<div class="myspells-empty">Keine Assignments für ${escHtml(job)} in diesem Plan.</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows.map(({ mechanic, assignments }) => {
|
|
||||||
const time = escHtml(fmtTimestamp(mechanic.timestamp));
|
|
||||||
const mechName = escHtml(mechanic.name);
|
|
||||||
const abilities = assignments.map(a => {
|
|
||||||
const iconSrc = abilityIcon(a.ability);
|
|
||||||
const icon = iconSrc
|
|
||||||
? `<img src="${escHtml(iconSrc)}" class="myspells-icon" alt="">`
|
|
||||||
: '';
|
|
||||||
const name = escHtml(assignmentAbilityName(a, plan));
|
|
||||||
const isPersonal = TIMELINE_PERSONAL_ABILITIES.has(a.ability);
|
|
||||||
const typeClass = a.buffType === 'debuff' ? 'myspells-type--debuff'
|
|
||||||
: a.buffType === 'shield' ? 'myspells-type--shield'
|
|
||||||
: isPersonal ? 'myspells-type--personal'
|
|
||||||
: 'myspells-type--buff';
|
|
||||||
return `<span class="myspells-ability ${typeClass}">${icon}${name}</span>`;
|
|
||||||
}).join('');
|
|
||||||
return `
|
|
||||||
<div class="myspells-row">
|
|
||||||
<span class="myspells-time">${time}</span>
|
|
||||||
<span class="myspells-mechanic">${mechName}</span>
|
|
||||||
<div class="myspells-abilities">${abilities}</div>
|
|
||||||
</div>`;
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function mySpellsPlainText(plan, job) {
|
|
||||||
if (!job) return '';
|
|
||||||
const mechanics = visiblePlanMechanics(plan);
|
|
||||||
const lines = [`Meine Spells — ${job} (${plan.name})`, '─'.repeat(36)];
|
|
||||||
for (const mechanic of mechanics) {
|
|
||||||
const mine = (mechanic.assignments ?? []).filter(a => a.job === job);
|
|
||||||
if (!mine.length) continue;
|
|
||||||
const time = fmtTimestamp(mechanic.timestamp);
|
|
||||||
const names = mine.map(a => assignmentAbilityName(a, plan)).join(', ');
|
|
||||||
lines.push(`${time.padEnd(6)} ${mechanic.name.padEnd(22)} → ${names}`);
|
|
||||||
}
|
|
||||||
return lines.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function cactbotEscape(text) {
|
function cactbotEscape(text) {
|
||||||
return String(text ?? '')
|
return String(text ?? '')
|
||||||
.replace(/\\/g, '\\\\')
|
.replace(/\\/g, '\\\\')
|
||||||
@ -900,55 +865,30 @@ function initCactbotExport(planId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initMySpells(planId) {
|
function initPlannerViewControls(planId) {
|
||||||
const viewBtns = document.querySelectorAll('.view-toggle-btn');
|
const simpleBtn = document.getElementById('planner-simple-view-btn');
|
||||||
const mechList = document.getElementById('mechanic-list');
|
const classFilter = document.getElementById('planner-class-filter');
|
||||||
const myPanel = document.getElementById('myspells-panel');
|
|
||||||
const jobSelect = document.getElementById('myspells-job-select');
|
|
||||||
const myList = document.getElementById('myspells-list');
|
|
||||||
const copyBtn = document.getElementById('myspells-copy-btn');
|
|
||||||
if (!mechList || !myPanel || !jobSelect || !myList) return;
|
|
||||||
|
|
||||||
viewBtns.forEach(btn => {
|
simpleBtn?.addEventListener('click', () => {
|
||||||
btn.addEventListener('click', () => {
|
const enabled = !simpleBtn.classList.contains('active');
|
||||||
viewBtns.forEach(b => b.classList.remove('active'));
|
simpleBtn.classList.toggle('active', enabled);
|
||||||
btn.classList.add('active');
|
localStorage.setItem(PLANNER_SIMPLE_VIEW_KEY, enabled ? '1' : '0');
|
||||||
const view = btn.dataset.view;
|
refreshMechanicList(planId, false);
|
||||||
mechList.style.display = view === 'mechanics' ? '' : 'none';
|
|
||||||
myPanel.style.display = view === 'myspells' ? '' : 'none';
|
|
||||||
if (view === 'myspells') {
|
|
||||||
const plan = loadPlans().find(p => p.id === planId);
|
|
||||||
if (plan) myList.innerHTML = renderMySpellsHtml(plan, jobSelect.value || '');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gespeicherten Job wiederherstellen
|
classFilter?.addEventListener('change', () => {
|
||||||
const savedJob = localStorage.getItem('ff14-planner-myspells-job') ?? '';
|
localStorage.setItem(PLANNER_CLASS_FILTER_KEY, classFilter.value);
|
||||||
if (savedJob && [...jobSelect.options].some(o => o.value === savedJob)) {
|
refreshMechanicList(planId, false);
|
||||||
jobSelect.value = savedJob;
|
|
||||||
const plan = loadPlans().find(p => p.id === planId);
|
|
||||||
if (plan) myList.innerHTML = renderMySpellsHtml(plan, savedJob);
|
|
||||||
}
|
|
||||||
|
|
||||||
jobSelect.addEventListener('change', () => {
|
|
||||||
localStorage.setItem('ff14-planner-myspells-job', jobSelect.value);
|
|
||||||
const plan = loadPlans().find(p => p.id === planId);
|
|
||||||
if (plan) myList.innerHTML = renderMySpellsHtml(plan, jobSelect.value);
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
copyBtn?.addEventListener('click', () => {
|
function refreshPlannerClassFilter(planId) {
|
||||||
const plan = loadPlans().find(p => p.id === planId);
|
const classFilter = document.getElementById('planner-class-filter');
|
||||||
if (!plan) return;
|
const plan = getPlan(planId);
|
||||||
const text = mySpellsPlainText(plan, jobSelect.value);
|
if (!classFilter || !plan) return;
|
||||||
navigator.clipboard.writeText(text).then(() => {
|
const selected = plannerViewOptions(plan).job;
|
||||||
copyBtn.textContent = '✓ Kopiert';
|
classFilter.innerHTML = renderClassFilterOptions(plan, selected);
|
||||||
setTimeout(() => { copyBtn.innerHTML = '⎘ Kopieren'; }, 1800);
|
classFilter.value = selected;
|
||||||
}).catch(() => {
|
|
||||||
copyBtn.textContent = '✗ Fehler';
|
|
||||||
setTimeout(() => { copyBtn.innerHTML = '⎘ Kopieren'; }, 1800);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function avgNonTankMaxHp(plan) {
|
function avgNonTankMaxHp(plan) {
|
||||||
@ -1037,6 +977,46 @@ function plannedAssignmentsForMechanic(plan, targetMechanic) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderSimpleMechanicListHtml(plan, mechanics, selectedJob) {
|
||||||
|
const rows = [];
|
||||||
|
for (const mechanic of mechanics) {
|
||||||
|
const assignments = sortedAssignments(plannedAssignmentsForMechanic(plan, mechanic))
|
||||||
|
.filter(a => assignmentMatchesClassFilter(a, selectedJob));
|
||||||
|
if (!assignments.length) continue;
|
||||||
|
rows.push({ mechanic, assignments });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
const scope = selectedJob ? ` für ${escHtml(selectedJob)}` : '';
|
||||||
|
return `<div class="myspells-empty">Keine Assignments${scope} in diesem Plan.</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows.map(({ mechanic, assignments }) => {
|
||||||
|
const abilities = assignments.map(a => {
|
||||||
|
const iconSrc = abilityIcon(a.ability);
|
||||||
|
const icon = iconSrc ? `<img src="${escHtml(iconSrc)}" class="myspells-icon" alt="">` : '';
|
||||||
|
const name = escHtml(assignmentAbilityName(a, plan));
|
||||||
|
const label = selectedJob || !a.job ? name : `${escHtml(a.job)} · ${name}`;
|
||||||
|
const isPersonal = TIMELINE_PERSONAL_ABILITIES.has(a.ability);
|
||||||
|
const typeClass = a.buffType === 'debuff' ? 'myspells-type--debuff'
|
||||||
|
: a.buffType === 'shield' ? 'myspells-type--shield'
|
||||||
|
: isPersonal ? 'myspells-type--personal'
|
||||||
|
: 'myspells-type--buff';
|
||||||
|
return `<span class="myspells-ability ${typeClass}">
|
||||||
|
${icon}${label}
|
||||||
|
<button class="badge-remove" data-mechanic-id="${escHtml(a.sourceMechanicId ?? mechanic.id)}" data-ability="${escHtml(a.ability)}" data-job="${escHtml(a.job ?? '')}" title="Entfernen">×</button>
|
||||||
|
</span>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="myspells-row mechanic-card mechanic-card--simple" data-mechanic-id="${escHtml(mechanic.id)}">
|
||||||
|
<span class="myspells-time">${escHtml(fmtTimestamp(mechanic.timestamp))}</span>
|
||||||
|
<span class="myspells-mechanic" title="${escHtml(mechanic.name)}">${escHtml(mechanic.name)}</span>
|
||||||
|
<div class="myspells-abilities">${abilities}</div>
|
||||||
|
</div>`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
function renderMechanicListHtml(plan) {
|
function renderMechanicListHtml(plan) {
|
||||||
const mechanics = visiblePlanMechanics(plan);
|
const mechanics = visiblePlanMechanics(plan);
|
||||||
if (mechanics.length === 0) {
|
if (mechanics.length === 0) {
|
||||||
@ -1054,14 +1034,21 @@ function renderMechanicListHtml(plan) {
|
|||||||
const activeJobSet = new Set(plan.jobComposition.filter(j => j));
|
const activeJobSet = new Set(plan.jobComposition.filter(j => j));
|
||||||
const nonTankAvgHp = avgNonTankMaxHp(plan);
|
const nonTankAvgHp = avgNonTankMaxHp(plan);
|
||||||
const tankAvgHp = avgTankMaxHp(plan);
|
const tankAvgHp = avgTankMaxHp(plan);
|
||||||
|
const viewOptions = plannerViewOptions(plan);
|
||||||
|
|
||||||
return mechanics.map(m => {
|
if (viewOptions.simple) {
|
||||||
|
return renderSimpleMechanicListHtml(plan, mechanics, viewOptions.job);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = mechanics.map(m => {
|
||||||
// Tankbuster: gespeicherter maxHP des getroffenen Tanks hat Vorrang (präziser als Roster-Durchschnitt)
|
// Tankbuster: gespeicherter maxHP des getroffenen Tanks hat Vorrang (präziser als Roster-Durchschnitt)
|
||||||
const avgHp = m.isHeavyTankbuster
|
const avgHp = m.isHeavyTankbuster
|
||||||
? ((m.tankMaxHp ?? 0) > 0 ? m.tankMaxHp : tankAvgHp)
|
? ((m.tankMaxHp ?? 0) > 0 ? m.tankMaxHp : tankAvgHp)
|
||||||
: nonTankAvgHp;
|
: nonTankAvgHp;
|
||||||
const planned = plannedAssignmentsForMechanic(plan, m);
|
const planned = plannedAssignmentsForMechanic(plan, m);
|
||||||
const sorted = sortedAssignments(planned);
|
const visiblePlanned = planned.filter(a => assignmentMatchesClassFilter(a, viewOptions.job));
|
||||||
|
const sorted = sortedAssignments(visiblePlanned);
|
||||||
|
if (viewOptions.job && !sorted.length) return '';
|
||||||
const assignHtml = sorted.length === 0
|
const assignHtml = sorted.length === 0
|
||||||
? '<span class="mechanic-no-assign">Keine Zuweisung</span>'
|
? '<span class="mechanic-no-assign">Keine Zuweisung</span>'
|
||||||
: sorted.map(a => {
|
: sorted.map(a => {
|
||||||
@ -1120,7 +1107,12 @@ function renderMechanicListHtml(plan) {
|
|||||||
</div>
|
</div>
|
||||||
<button class="mechanic-delete-btn plan-btn plan-btn-danger" data-mechanic-id="${escHtml(m.id)}" title="Mechanik löschen">✕</button>
|
<button class="mechanic-delete-btn plan-btn plan-btn-danger" data-mechanic-id="${escHtml(m.id)}" title="Mechanik löschen">✕</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).filter(Boolean);
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
return `<div class="myspells-empty">Keine Assignments für ${escHtml(viewOptions.job)} in diesem Plan.</div>`;
|
||||||
|
}
|
||||||
|
return rows.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Job Slots ─────────────────────────────────────────────────────────────────
|
// ── Job Slots ─────────────────────────────────────────────────────────────────
|
||||||
@ -1163,6 +1155,7 @@ function initJobSlots(planId) {
|
|||||||
slot.className = 'job-slot' + (role ? ` job-slot--${role}` : '');
|
slot.className = 'job-slot' + (role ? ` job-slot--${role}` : '');
|
||||||
}
|
}
|
||||||
refreshMechanicList(planId);
|
refreshMechanicList(planId);
|
||||||
|
refreshPlannerClassFilter(planId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
<div class="card section-gap" id="fight-select-card" style="display:none">
|
|
||||||
<div class="card-title-row">
|
|
||||||
<div class="card-title">Fight auswählen</div>
|
|
||||||
<a id="fflogs-report-link" class="btn btn-sm" href="#" target="_blank" rel="noopener" style="display:none;text-decoration:none;margin-left:auto">FFLogs öffnen</a>
|
|
||||||
</div>
|
|
||||||
<select id="fight-select">
|
|
||||||
<option value="">— Fight auswählen —</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
<div class="card section-gap">
|
<div class="card section-gap">
|
||||||
<div class="card-title">Report laden</div>
|
<div class="card-title">Report laden</div>
|
||||||
<form id="report-form">
|
<form id="report-form">
|
||||||
<div class="form-row">
|
<div class="form-row report-load-row">
|
||||||
<div class="fg">
|
<div class="fg report-code-field">
|
||||||
<label>Report Code</label>
|
<label>Report Code</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -13,8 +13,8 @@
|
|||||||
required
|
required
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="fg">
|
<div class="fg report-language-field">
|
||||||
<label>Namen</label>
|
<label>Language</label>
|
||||||
<select name="language" id="language-select">
|
<select name="language" id="language-select">
|
||||||
<option value="en">EN</option>
|
<option value="en">EN</option>
|
||||||
<option value="de">DE</option>
|
<option value="de">DE</option>
|
||||||
@ -23,7 +23,15 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-gold" type="submit" style="align-self:flex-end">Fetch</button>
|
<button class="btn btn-gold" type="submit" style="align-self:flex-end">Fetch</button>
|
||||||
<a class="btn" href="<?= htmlspecialchars(auth_start_href(), ENT_QUOTES) ?>" style="align-self:flex-end;text-decoration:none">Reconnect</a>
|
<div class="fg report-fight-field" id="fight-select-card" style="display:none">
|
||||||
|
<label class="report-fight-label">
|
||||||
|
Fight
|
||||||
|
<a id="fflogs-report-link" href="#" target="_blank" rel="noopener" style="display:none;text-decoration:none">FFLogs öffnen</a>
|
||||||
|
</label>
|
||||||
|
<select id="fight-select">
|
||||||
|
<option value="">— Fight auswählen —</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
<?php require __DIR__ . '/report-form.php'; ?>
|
||||||
|
|
||||||
<div id="analysis-loading" class="loading" style="display:none">
|
<div id="analysis-loading" class="loading" style="display:none">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<span>Analysiere Fight-Daten...</span>
|
<span>Analysiere Fight-Daten...</span>
|
||||||
@ -5,7 +7,7 @@
|
|||||||
|
|
||||||
<div id="analysis-empty" class="empty">
|
<div id="analysis-empty" class="empty">
|
||||||
<div class="empty-icon">📊</div>
|
<div class="empty-icon">📊</div>
|
||||||
<h3 id="analysis-empty-msg">Bitte zuerst einen Fight im Report-Tab auswählen</h3>
|
<h3 id="analysis-empty-msg">Bitte zuerst einen Fight auswählen</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="analysis-content" style="display:none">
|
<div id="analysis-content" style="display:none">
|
||||||
|
|||||||
@ -1,4 +1,2 @@
|
|||||||
<?php require __DIR__ . '/report-form.php'; ?>
|
|
||||||
<?php require __DIR__ . '/fight-select.php'; ?>
|
|
||||||
<?php require __DIR__ . '/event-explorer.php'; ?>
|
<?php require __DIR__ . '/event-explorer.php'; ?>
|
||||||
<?php require __DIR__ . '/output-card.php'; ?>
|
<?php require __DIR__ . '/output-card.php'; ?>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<header id="topbar">
|
<header id="topbar">
|
||||||
<div class="logo">REPORT VIEWER <span>Final Fantasy XIV</span></div>
|
<div class="logo">REPORT VIEWER <span>Final Fantasy XIV</span></div>
|
||||||
<nav class="tabs">
|
<nav class="tabs">
|
||||||
<button class="tab active" data-tab="report">⚔ Report</button>
|
|
||||||
<button class="tab" data-tab="analysis">⚖ Analyse</button>
|
<button class="tab" data-tab="analysis">⚖ Analyse</button>
|
||||||
<button class="tab" data-tab="planner">☰ Planer</button>
|
<button class="tab" data-tab="planner">☰ Planer</button>
|
||||||
|
<button class="tab active" data-tab="report">⚔ Debug</button>
|
||||||
</nav>
|
</nav>
|
||||||
<?php $fflogsUser = current_fflogs_user(); ?>
|
<?php $fflogsUser = current_fflogs_user(); ?>
|
||||||
<div class="topbar-actions">
|
<div class="topbar-actions">
|
||||||
@ -16,5 +16,6 @@
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
Token gültig bis: <?= date('Y-m-d H:i:s', $_SESSION['token_expires']) ?>
|
Token gültig bis: <?= date('Y-m-d H:i:s', $_SESSION['token_expires']) ?>
|
||||||
</div>
|
</div>
|
||||||
|
<a class="topbar-link" href="<?= htmlspecialchars(auth_start_href(), ENT_QUOTES) ?>">Reconnect</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user