forked from xziino/ff14-mitigator
cactbot export
This commit is contained in:
parent
76f9baec40
commit
9cd3278b80
@ -1240,6 +1240,27 @@
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.planner-card-actions {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cactbot-export-option {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
color: var(--t2);
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.cactbot-export-option input {
|
||||||
|
width: auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.view-toggle-btn {
|
.view-toggle-btn {
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|||||||
111
js/planner.js
111
js/planner.js
@ -461,9 +461,16 @@ function renderPlanDetail(plan) {
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-title-row">
|
<div class="card-title-row">
|
||||||
<div class="card-title">Mechaniken</div>
|
<div class="card-title">Mechaniken</div>
|
||||||
<div class="view-toggle-btns">
|
<div class="planner-card-actions">
|
||||||
<button class="view-toggle-btn active" data-view="mechanics">Mechaniken</button>
|
<div class="view-toggle-btns">
|
||||||
<button class="view-toggle-btn" data-view="myspells">★ Meine Spells</button>
|
<button class="view-toggle-btn active" data-view="mechanics">Mechaniken</button>
|
||||||
|
<button class="view-toggle-btn" data-view="myspells">★ Meine Spells</button>
|
||||||
|
</div>
|
||||||
|
<label class="cactbot-export-option">
|
||||||
|
<input type="checkbox" id="cactbot-export-split">
|
||||||
|
<span>Separate Entries</span>
|
||||||
|
</label>
|
||||||
|
<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">
|
||||||
@ -495,6 +502,7 @@ function renderPlanDetail(plan) {
|
|||||||
initTimeline(plan.id);
|
initTimeline(plan.id);
|
||||||
initMechanicClicks(plan.id);
|
initMechanicClicks(plan.id);
|
||||||
initMySpells(plan.id);
|
initMySpells(plan.id);
|
||||||
|
initCactbotExport(plan.id);
|
||||||
renderInfoPanel(plan);
|
renderInfoPanel(plan);
|
||||||
ensureActionMetaLoaded().then(() => refreshMechanicList(plan.id));
|
ensureActionMetaLoaded().then(() => refreshMechanicList(plan.id));
|
||||||
}
|
}
|
||||||
@ -555,6 +563,103 @@ function mySpellsPlainText(plan, job) {
|
|||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cactbotEscape(text) {
|
||||||
|
return String(text ?? '')
|
||||||
|
.replace(/\\/g, '\\\\')
|
||||||
|
.replace(/"/g, '\\"');
|
||||||
|
}
|
||||||
|
|
||||||
|
function cactbotTime(ms) {
|
||||||
|
const seconds = Math.max(0, Number(ms) || 0) / 1000;
|
||||||
|
return seconds.toFixed(1).replace(/\.0$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function cactbotExportFilename(plan) {
|
||||||
|
const base = String(plan?.name || 'mitigation-plan')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '') || 'mitigation-plan';
|
||||||
|
return `${base}-mitigations.txt`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cactbotAssignmentLabel(assignment, plan) {
|
||||||
|
const ability = assignmentAbilityName(assignment, plan);
|
||||||
|
return assignment.job ? `${assignment.job} ${ability}` : ability;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cactbotFilterHints(plan) {
|
||||||
|
const jobs = [...new Set((plan.jobComposition ?? []).filter(Boolean))].sort();
|
||||||
|
if (!jobs.length) return [];
|
||||||
|
return ['# Optional job filters - uncomment lines you do not want to see:', ...jobs.map(job => `# hideall ".*${job} .*"`)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function cactbotTimelineText(plan, splitEntries = false) {
|
||||||
|
const byStart = new Map();
|
||||||
|
for (const mechanic of visiblePlanMechanics(plan)) {
|
||||||
|
const planned = sortedAssignments(plannedAssignmentsForMechanic(plan, mechanic));
|
||||||
|
if (!planned.length) continue;
|
||||||
|
|
||||||
|
planned.forEach(assignment => {
|
||||||
|
const start = Number(assignment.sourceStart ?? assignmentStartMs(mechanic, assignment)) || 0;
|
||||||
|
const startKey = Math.round(start);
|
||||||
|
if (!byStart.has(startKey)) byStart.set(startKey, { assignments: new Map() });
|
||||||
|
const group = byStart.get(startKey);
|
||||||
|
const assignmentKey = `${assignment.job ?? ''}::${assignment.ability}`;
|
||||||
|
group.assignments.set(assignmentKey, cactbotAssignmentLabel(assignment, plan));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = [];
|
||||||
|
for (const [time, group] of byStart.entries()) {
|
||||||
|
const assignments = [...group.assignments.values()];
|
||||||
|
if (splitEntries) {
|
||||||
|
assignments.forEach(text => rows.push({ time, text }));
|
||||||
|
} else {
|
||||||
|
rows.push({ time, text: assignments.join(', ') });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows.sort((a, b) => a.time - b.time || a.text.localeCompare(b.text));
|
||||||
|
const header = [
|
||||||
|
`# Exported from FF14 Mitigator`,
|
||||||
|
`# Plan: ${plan.name}`,
|
||||||
|
plan.source?.fightName ? `# Fight: ${plan.source.fightName}` : null,
|
||||||
|
`# Format: cactbot timeline entries`,
|
||||||
|
...cactbotFilterHints(plan),
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
return [
|
||||||
|
...header,
|
||||||
|
...rows.map(row => `${cactbotTime(row.time)} "${cactbotEscape(row.text)}"`),
|
||||||
|
].join('\n') + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadTextFile(filename, text) {
|
||||||
|
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initCactbotExport(planId) {
|
||||||
|
document.getElementById('cactbot-export-btn')?.addEventListener('click', () => {
|
||||||
|
const plan = getPlan(planId);
|
||||||
|
if (!plan) return;
|
||||||
|
const splitEntries = !!document.getElementById('cactbot-export-split')?.checked;
|
||||||
|
const text = cactbotTimelineText(plan, splitEntries);
|
||||||
|
if (!text.split('\n').some(line => /^\d/.test(line))) {
|
||||||
|
alert('Keine Mitigations zum Exportieren gefunden.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
downloadTextFile(cactbotExportFilename(plan), text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function initMySpells(planId) {
|
function initMySpells(planId) {
|
||||||
const viewBtns = document.querySelectorAll('.view-toggle-btn');
|
const viewBtns = document.querySelectorAll('.view-toggle-btn');
|
||||||
const mechList = document.getElementById('mechanic-list');
|
const mechList = document.getElementById('mechanic-list');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user