forked from xziino/ff14-mitigator
cactbot export
This commit is contained in:
parent
76f9baec40
commit
9cd3278b80
@ -1240,6 +1240,27 @@
|
||||
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 {
|
||||
padding: 4px 12px;
|
||||
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-title-row">
|
||||
<div class="card-title">Mechaniken</div>
|
||||
<div class="view-toggle-btns">
|
||||
<button class="view-toggle-btn active" data-view="mechanics">Mechaniken</button>
|
||||
<button class="view-toggle-btn" data-view="myspells">★ Meine Spells</button>
|
||||
<div class="planner-card-actions">
|
||||
<div class="view-toggle-btns">
|
||||
<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 id="mechanic-list">
|
||||
@ -495,6 +502,7 @@ function renderPlanDetail(plan) {
|
||||
initTimeline(plan.id);
|
||||
initMechanicClicks(plan.id);
|
||||
initMySpells(plan.id);
|
||||
initCactbotExport(plan.id);
|
||||
renderInfoPanel(plan);
|
||||
ensureActionMetaLoaded().then(() => refreshMechanicList(plan.id));
|
||||
}
|
||||
@ -555,6 +563,103 @@ function mySpellsPlainText(plan, job) {
|
||||
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) {
|
||||
const viewBtns = document.querySelectorAll('.view-toggle-btn');
|
||||
const mechList = document.getElementById('mechanic-list');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user