small fix, stay on playner page on refresh and translate planner
This commit is contained in:
commit
c3f18b38f5
@ -8,7 +8,7 @@ $state = bin2hex(random_bytes(16));
|
|||||||
|
|
||||||
$_SESSION['pkce_verifier'] = $verifier;
|
$_SESSION['pkce_verifier'] = $verifier;
|
||||||
$_SESSION['oauth_state'] = $state;
|
$_SESSION['oauth_state'] = $state;
|
||||||
$_SESSION['oauth_return'] = safe_return_path($_GET['return'] ?? ($_SERVER['HTTP_REFERER'] ?? null));
|
$_SESSION['oauth_return'] = null;
|
||||||
|
|
||||||
$params = http_build_query([
|
$params = http_build_query([
|
||||||
'response_type' => 'code',
|
'response_type' => 'code',
|
||||||
|
|||||||
@ -416,3 +416,96 @@
|
|||||||
.ability-chip.badge-assign-shield.ability-chip--active { background: rgba(74,158,255,.18); border-color: rgba(74,158,255,.6); color: var(--blue); }
|
.ability-chip.badge-assign-shield.ability-chip--active { background: rgba(74,158,255,.18); border-color: rgba(74,158,255,.6); color: var(--blue); }
|
||||||
|
|
||||||
.ability-chip--other-job { opacity: 0.45; }
|
.ability-chip--other-job { opacity: 0.45; }
|
||||||
|
|
||||||
|
/* ── Folder Sidebar ──────────────────────────────────────────────────────────── */
|
||||||
|
.folder-section { margin-bottom: 2px; }
|
||||||
|
|
||||||
|
.folder-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
border-radius: var(--r);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.12s;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.folder-row:hover { background: var(--bg2); }
|
||||||
|
|
||||||
|
.folder-chevron {
|
||||||
|
font-size: 9px;
|
||||||
|
color: var(--t3);
|
||||||
|
transition: transform 0.15s;
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.folder-chevron.open { transform: rotate(90deg); }
|
||||||
|
|
||||||
|
.folder-name-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--t1);
|
||||||
|
font-weight: 500;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-name-input {
|
||||||
|
font-size: 13px !important;
|
||||||
|
padding: 2px 6px !important;
|
||||||
|
flex: 1;
|
||||||
|
width: auto !important;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-count {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--t3);
|
||||||
|
background: var(--bg3);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1px 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.12s;
|
||||||
|
}
|
||||||
|
.folder-row:hover .folder-actions { opacity: 1; }
|
||||||
|
|
||||||
|
.folder-plans { padding-left: 10px; }
|
||||||
|
.folder-plans.collapsed { display: none; }
|
||||||
|
|
||||||
|
.folder-empty {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--t3);
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Folder Picker Dropdown ──────────────────────────────────────────────────── */
|
||||||
|
.folder-picker {
|
||||||
|
background: var(--bgcard);
|
||||||
|
border: 1px solid var(--borderem);
|
||||||
|
border-radius: var(--r);
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.5);
|
||||||
|
min-width: 160px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-picker-option {
|
||||||
|
padding: 7px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--t2);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.1s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.folder-picker-option:hover { background: var(--bg2); color: var(--t1); }
|
||||||
|
.folder-picker-option.active { color: var(--gold); }
|
||||||
|
|||||||
279
js/planner.js
279
js/planner.js
@ -2,14 +2,33 @@
|
|||||||
|
|
||||||
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';
|
||||||
|
|
||||||
function loadPlans() {
|
function loadPlans() {
|
||||||
try { return JSON.parse(localStorage.getItem(PLANNER_KEY) || '[]'); }
|
try { return JSON.parse(localStorage.getItem(PLANNER_KEY) || '[]'); }
|
||||||
catch { return []; }
|
catch { return []; }
|
||||||
}
|
}
|
||||||
|
function savePlans(plans) { localStorage.setItem(PLANNER_KEY, JSON.stringify(plans)); }
|
||||||
|
|
||||||
function savePlans(plans) {
|
function loadFolders() {
|
||||||
localStorage.setItem(PLANNER_KEY, JSON.stringify(plans));
|
try { return JSON.parse(localStorage.getItem(FOLDERS_KEY) || '[]'); }
|
||||||
|
catch { return []; }
|
||||||
|
}
|
||||||
|
function saveFolders(f) { localStorage.setItem(FOLDERS_KEY, JSON.stringify(f)); }
|
||||||
|
|
||||||
|
function createFolder(name) {
|
||||||
|
const folder = { id: crypto.randomUUID(), name: name.trim() || 'Neuer Ordner', createdAt: Date.now(), updatedAt: Date.now() };
|
||||||
|
const all = loadFolders(); all.push(folder); saveFolders(all); return folder;
|
||||||
|
}
|
||||||
|
function updateFolder(id, changes) {
|
||||||
|
const all = loadFolders(), idx = all.findIndex(f => f.id === id);
|
||||||
|
if (idx === -1) return;
|
||||||
|
all[idx] = { ...all[idx], ...changes, updatedAt: Date.now() };
|
||||||
|
saveFolders(all);
|
||||||
|
}
|
||||||
|
function deleteFolder(id) {
|
||||||
|
saveFolders(loadFolders().filter(f => f.id !== id));
|
||||||
|
savePlans(loadPlans().map(p => p.folderId === id ? { ...p, folderId: null, updatedAt: Date.now() } : p));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CRUD ──────────────────────────────────────────────────────────────────────
|
// ── CRUD ──────────────────────────────────────────────────────────────────────
|
||||||
@ -22,6 +41,7 @@ function createPlan(name) {
|
|||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
source: null,
|
source: null,
|
||||||
mitigationNames: {},
|
mitigationNames: {},
|
||||||
|
folderId: null,
|
||||||
jobComposition: Array(8).fill(''),
|
jobComposition: Array(8).fill(''),
|
||||||
mechanics: []
|
mechanics: []
|
||||||
};
|
};
|
||||||
@ -48,6 +68,14 @@ function deletePlan(id) {
|
|||||||
savePlans(loadPlans().filter(p => p.id !== id));
|
savePlans(loadPlans().filter(p => p.id !== id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uniquePlanName(baseName) {
|
||||||
|
const names = new Set(loadPlans().map(p => p.name));
|
||||||
|
if (!names.has(baseName)) return baseName;
|
||||||
|
let i = 2;
|
||||||
|
while (names.has(`${baseName} ${i}`)) i++;
|
||||||
|
return `${baseName} ${i}`;
|
||||||
|
}
|
||||||
|
|
||||||
function copyPlan(id) {
|
function copyPlan(id) {
|
||||||
const all = loadPlans();
|
const all = loadPlans();
|
||||||
const orig = all.find(p => p.id === id);
|
const orig = all.find(p => p.id === id);
|
||||||
@ -55,7 +83,7 @@ function copyPlan(id) {
|
|||||||
const copy = {
|
const copy = {
|
||||||
...JSON.parse(JSON.stringify(orig)),
|
...JSON.parse(JSON.stringify(orig)),
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
name: orig.name + ' (Kopie)',
|
name: uniquePlanName(orig.name + ' (Kopie)'),
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
updatedAt: Date.now()
|
updatedAt: Date.now()
|
||||||
};
|
};
|
||||||
@ -66,7 +94,8 @@ function copyPlan(id) {
|
|||||||
|
|
||||||
// ── State ─────────────────────────────────────────────────────────────────────
|
// ── State ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
let activePlanId = null;
|
let activePlanId = null;
|
||||||
|
let collapsedFolders = new Set();
|
||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@ -111,30 +140,68 @@ function sameMechanic(existing, incoming, source) {
|
|||||||
|
|
||||||
// ── Rendering: Plan List ──────────────────────────────────────────────────────
|
// ── Rendering: Plan List ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
function renderPlanList() {
|
function planItemHtml(p) {
|
||||||
const el = document.getElementById('plan-list');
|
return `
|
||||||
if (!el) return;
|
|
||||||
|
|
||||||
const plans = loadPlans();
|
|
||||||
|
|
||||||
if (plans.length === 0) {
|
|
||||||
el.innerHTML = '<div class="plan-list-empty">Noch keine Pläne vorhanden</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
el.innerHTML = plans.map(p => `
|
|
||||||
<div class="plan-item${p.id === activePlanId ? ' active' : ''}" data-id="${escHtml(p.id)}">
|
<div class="plan-item${p.id === activePlanId ? ' active' : ''}" data-id="${escHtml(p.id)}">
|
||||||
<div class="plan-item-info">
|
<div class="plan-item-info">
|
||||||
<div class="plan-item-name">${escHtml(p.name)}</div>
|
<div class="plan-item-name">${escHtml(p.name)}</div>
|
||||||
<div class="plan-item-meta">${p.mechanics.length} Mechaniken · ${fmtDate(p.updatedAt)}</div>
|
<div class="plan-item-meta">${p.mechanics.length} Mechaniken · ${fmtDate(p.updatedAt)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="plan-item-actions">
|
<div class="plan-item-actions">
|
||||||
|
<button class="plan-btn plan-btn-move" data-id="${escHtml(p.id)}" title="In Ordner verschieben">📁</button>
|
||||||
<button class="plan-btn plan-btn-copy" data-id="${escHtml(p.id)}" title="Kopieren">⎘</button>
|
<button class="plan-btn plan-btn-copy" data-id="${escHtml(p.id)}" title="Kopieren">⎘</button>
|
||||||
<button class="plan-btn plan-btn-danger plan-btn-delete" data-id="${escHtml(p.id)}" title="Löschen">✕</button>
|
<button class="plan-btn plan-btn-danger plan-btn-delete" data-id="${escHtml(p.id)}" title="Löschen">✕</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>`;
|
||||||
`).join('');
|
}
|
||||||
|
|
||||||
|
function renderPlanList() {
|
||||||
|
const el = document.getElementById('plan-list');
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
const plans = loadPlans();
|
||||||
|
const folders = loadFolders();
|
||||||
|
|
||||||
|
if (plans.length === 0 && folders.length === 0) {
|
||||||
|
el.innerHTML = '<div class="plan-list-empty">Noch keine Pläne vorhanden</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const byFolder = new Map();
|
||||||
|
const ungrouped = [];
|
||||||
|
for (const p of plans) {
|
||||||
|
const fid = p.folderId ?? null;
|
||||||
|
if (fid && folders.some(f => f.id === fid)) {
|
||||||
|
if (!byFolder.has(fid)) byFolder.set(fid, []);
|
||||||
|
byFolder.get(fid).push(p);
|
||||||
|
} else {
|
||||||
|
ungrouped.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = folders.map(folder => {
|
||||||
|
const folderPlans = byFolder.get(folder.id) ?? [];
|
||||||
|
const collapsed = collapsedFolders.has(folder.id);
|
||||||
|
return `
|
||||||
|
<div class="folder-section" data-folder-id="${escHtml(folder.id)}">
|
||||||
|
<div class="folder-row" data-folder-id="${escHtml(folder.id)}">
|
||||||
|
<span class="folder-chevron${collapsed ? '' : ' open'}">▶</span>
|
||||||
|
<span class="folder-name-text">${escHtml(folder.name)}</span>
|
||||||
|
<span class="folder-count">${folderPlans.length}</span>
|
||||||
|
<div class="folder-actions">
|
||||||
|
<button class="plan-btn folder-rename-btn" data-id="${escHtml(folder.id)}" title="Umbenennen">✎</button>
|
||||||
|
<button class="plan-btn plan-btn-danger folder-delete-btn" data-id="${escHtml(folder.id)}" title="Ordner löschen">✕</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="folder-plans${collapsed ? ' collapsed' : ''}">
|
||||||
|
${folderPlans.map(p => planItemHtml(p)).join('') || '<div class="folder-empty">Leer</div>'}
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}).join('') + ungrouped.map(p => planItemHtml(p)).join('');
|
||||||
|
|
||||||
|
el.innerHTML = html || '<div class="plan-list-empty">Noch keine Pläne vorhanden</div>';
|
||||||
|
|
||||||
|
// Plan item: open
|
||||||
el.querySelectorAll('.plan-item').forEach(row => {
|
el.querySelectorAll('.plan-item').forEach(row => {
|
||||||
row.addEventListener('click', e => {
|
row.addEventListener('click', e => {
|
||||||
if (e.target.closest('.plan-item-actions')) return;
|
if (e.target.closest('.plan-item-actions')) return;
|
||||||
@ -142,6 +209,15 @@ function renderPlanList() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Plan item: move to folder
|
||||||
|
el.querySelectorAll('.plan-btn-move').forEach(btn => {
|
||||||
|
btn.addEventListener('click', e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
showFolderPicker(btn.dataset.id, btn);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Plan item: copy
|
||||||
el.querySelectorAll('.plan-btn-copy').forEach(btn => {
|
el.querySelectorAll('.plan-btn-copy').forEach(btn => {
|
||||||
btn.addEventListener('click', e => {
|
btn.addEventListener('click', e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -150,6 +226,7 @@ function renderPlanList() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Plan item: delete
|
||||||
el.querySelectorAll('.plan-btn-delete').forEach(btn => {
|
el.querySelectorAll('.plan-btn-delete').forEach(btn => {
|
||||||
btn.addEventListener('click', e => {
|
btn.addEventListener('click', e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -157,10 +234,63 @@ function renderPlanList() {
|
|||||||
if (!plan) return;
|
if (!plan) return;
|
||||||
if (!confirm(`Plan "${plan.name}" löschen?`)) return;
|
if (!confirm(`Plan "${plan.name}" löschen?`)) return;
|
||||||
deletePlan(btn.dataset.id);
|
deletePlan(btn.dataset.id);
|
||||||
if (activePlanId === btn.dataset.id) {
|
if (activePlanId === btn.dataset.id) { activePlanId = null; renderPlanDetail(null); }
|
||||||
activePlanId = null;
|
renderPlanList();
|
||||||
renderPlanDetail(null);
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
|
// Folder row: toggle collapse
|
||||||
|
el.querySelectorAll('.folder-row').forEach(row => {
|
||||||
|
row.addEventListener('click', e => {
|
||||||
|
if (e.target.closest('.folder-actions')) return;
|
||||||
|
const fid = row.dataset.folderId;
|
||||||
|
if (collapsedFolders.has(fid)) collapsedFolders.delete(fid);
|
||||||
|
else collapsedFolders.add(fid);
|
||||||
|
renderPlanList();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Folder: rename
|
||||||
|
el.querySelectorAll('.folder-rename-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const fid = btn.dataset.id;
|
||||||
|
const folder = loadFolders().find(f => f.id === fid);
|
||||||
|
if (!folder) return;
|
||||||
|
const span = el.querySelector(`.folder-row[data-folder-id="${fid}"] .folder-name-text`);
|
||||||
|
if (!span) return;
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.className = 'folder-name-input';
|
||||||
|
input.value = folder.name;
|
||||||
|
span.replaceWith(input);
|
||||||
|
input.focus(); input.select();
|
||||||
|
let saved = false;
|
||||||
|
const save = () => {
|
||||||
|
if (saved) return; saved = true;
|
||||||
|
const name = input.value.trim();
|
||||||
|
if (name) updateFolder(fid, { name });
|
||||||
|
renderPlanList();
|
||||||
|
};
|
||||||
|
input.addEventListener('blur', save);
|
||||||
|
input.addEventListener('keydown', ev => {
|
||||||
|
if (ev.key === 'Enter') { ev.preventDefault(); save(); }
|
||||||
|
if (ev.key === 'Escape') { saved = true; renderPlanList(); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Folder: delete
|
||||||
|
el.querySelectorAll('.folder-delete-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const folder = loadFolders().find(f => f.id === btn.dataset.id);
|
||||||
|
if (!folder) return;
|
||||||
|
const count = (byFolder.get(btn.dataset.id) ?? []).length;
|
||||||
|
const msg = count > 0
|
||||||
|
? `Ordner "${folder.name}" löschen? Die ${count} enthaltenen Pläne werden nicht gelöscht.`
|
||||||
|
: `Ordner "${folder.name}" löschen?`;
|
||||||
|
if (!confirm(msg)) return;
|
||||||
|
deleteFolder(btn.dataset.id);
|
||||||
renderPlanList();
|
renderPlanList();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -360,6 +490,14 @@ function initMechanicClicks(planId) {
|
|||||||
if (!card) return;
|
if (!card) return;
|
||||||
showAbilityModal(planId, card.dataset.mechanicId);
|
showAbilityModal(planId, card.dataset.mechanicId);
|
||||||
});
|
});
|
||||||
|
list.addEventListener('contextmenu', e => {
|
||||||
|
const badge = e.target.closest('.badge-assign');
|
||||||
|
if (!badge) return;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const removeBtn = badge.querySelector('.badge-remove');
|
||||||
|
if (removeBtn) removeAssignment(planId, removeBtn.dataset.mechanicId, removeBtn.dataset.ability);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Rename ────────────────────────────────────────────────────────────────────
|
// ── Rename ────────────────────────────────────────────────────────────────────
|
||||||
@ -424,15 +562,105 @@ function initNewPlanForm() {
|
|||||||
|
|
||||||
cancel.addEventListener('click', () => { form.style.display = 'none'; });
|
cancel.addEventListener('click', () => { form.style.display = 'none'; });
|
||||||
|
|
||||||
|
const clearErr = () => {
|
||||||
|
input.style.borderColor = '';
|
||||||
|
document.getElementById('planner-new-name-err')?.remove();
|
||||||
|
};
|
||||||
|
|
||||||
const doCreate = () => {
|
const doCreate = () => {
|
||||||
const name = input.value.trim();
|
const name = input.value.trim();
|
||||||
if (!name) { input.focus(); return; }
|
if (!name) { input.focus(); return; }
|
||||||
|
if (loadPlans().some(p => p.name === name)) {
|
||||||
|
input.style.borderColor = 'var(--red)';
|
||||||
|
let err = document.getElementById('planner-new-name-err');
|
||||||
|
if (!err) {
|
||||||
|
err = document.createElement('div');
|
||||||
|
err.id = 'planner-new-name-err';
|
||||||
|
err.style.cssText = 'font-size:11px;color:var(--red);margin-top:3px';
|
||||||
|
input.insertAdjacentElement('afterend', err);
|
||||||
|
}
|
||||||
|
err.textContent = 'Name bereits vergeben';
|
||||||
|
input.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearErr();
|
||||||
const plan = createPlan(name);
|
const plan = createPlan(name);
|
||||||
form.style.display = 'none';
|
form.style.display = 'none';
|
||||||
renderPlanList();
|
renderPlanList();
|
||||||
openPlan(plan.id);
|
openPlan(plan.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
input.addEventListener('input', clearErr);
|
||||||
|
save.addEventListener('click', doCreate);
|
||||||
|
input.addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'Enter') doCreate();
|
||||||
|
if (e.key === 'Escape') { clearErr(); form.style.display = 'none'; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Folder Picker ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
let folderPickerCleanup = null;
|
||||||
|
|
||||||
|
function closeFolderPicker() {
|
||||||
|
document.getElementById('folder-picker')?.remove();
|
||||||
|
if (folderPickerCleanup) { folderPickerCleanup(); folderPickerCleanup = null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function showFolderPicker(planId, anchorEl) {
|
||||||
|
closeFolderPicker();
|
||||||
|
const plan = getPlan(planId);
|
||||||
|
const folders = loadFolders();
|
||||||
|
if (!plan) return;
|
||||||
|
if (folders.length === 0) return;
|
||||||
|
|
||||||
|
const rect = anchorEl.getBoundingClientRect();
|
||||||
|
const picker = document.createElement('div');
|
||||||
|
picker.id = 'folder-picker';
|
||||||
|
picker.className = 'folder-picker';
|
||||||
|
picker.style.cssText = `position:fixed;top:${rect.bottom + 4}px;left:${rect.left}px;z-index:9999`;
|
||||||
|
|
||||||
|
picker.innerHTML = [{ id: null, name: 'Kein Ordner' }, ...folders].map(f => `
|
||||||
|
<div class="folder-picker-option${(plan.folderId ?? null) === f.id ? ' active' : ''}" data-fid="${escHtml(f.id ?? '')}">
|
||||||
|
${f.id === null ? '— ' : '📁 '}${escHtml(f.name)}
|
||||||
|
</div>`).join('');
|
||||||
|
|
||||||
|
document.body.appendChild(picker);
|
||||||
|
|
||||||
|
picker.querySelectorAll('.folder-picker-option').forEach(opt => {
|
||||||
|
opt.addEventListener('click', e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
updatePlan(planId, { folderId: opt.dataset.fid || null });
|
||||||
|
closeFolderPicker();
|
||||||
|
renderPlanList();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const onOutside = e => { if (!picker.contains(e.target)) closeFolderPicker(); };
|
||||||
|
setTimeout(() => document.addEventListener('click', onOutside), 0);
|
||||||
|
folderPickerCleanup = () => document.removeEventListener('click', onOutside);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── New Folder Form ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function initNewFolderForm() {
|
||||||
|
const btn = document.getElementById('planner-new-folder-btn');
|
||||||
|
const form = document.getElementById('planner-new-folder-form');
|
||||||
|
const input = document.getElementById('planner-new-folder-name');
|
||||||
|
const save = document.getElementById('planner-new-folder-save');
|
||||||
|
const cancel = document.getElementById('planner-new-folder-cancel');
|
||||||
|
if (!btn) return;
|
||||||
|
|
||||||
|
btn.addEventListener('click', () => { form.style.display = ''; input.value = ''; input.focus(); });
|
||||||
|
cancel.addEventListener('click', () => { form.style.display = 'none'; });
|
||||||
|
|
||||||
|
const doCreate = () => {
|
||||||
|
const name = input.value.trim();
|
||||||
|
if (!name) { input.focus(); return; }
|
||||||
|
createFolder(name);
|
||||||
|
form.style.display = 'none';
|
||||||
|
renderPlanList();
|
||||||
|
};
|
||||||
save.addEventListener('click', doCreate);
|
save.addEventListener('click', doCreate);
|
||||||
input.addEventListener('keydown', e => {
|
input.addEventListener('keydown', e => {
|
||||||
if (e.key === 'Enter') doCreate();
|
if (e.key === 'Enter') doCreate();
|
||||||
@ -718,7 +946,7 @@ function doImport(data, withMitigations, whereMode, mergeId, newName) {
|
|||||||
const source = { reportCode, fightId, fightName, fightStart, fightEnd, language: plannerLanguage() };
|
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(uniquePlanName(newName || fightName || 'Importierter Plan'));
|
||||||
return updatePlan(plan.id, {
|
return updatePlan(plan.id, {
|
||||||
mechanics,
|
mechanics,
|
||||||
source,
|
source,
|
||||||
@ -942,9 +1170,9 @@ let pendingImportData = null;
|
|||||||
function showImportModal(data) {
|
function showImportModal(data) {
|
||||||
pendingImportData = data;
|
pendingImportData = data;
|
||||||
|
|
||||||
// Pre-fill name
|
// Pre-fill name — auto-unique so the user sees immediately what name will be used
|
||||||
const nameInput = document.getElementById('import-plan-name');
|
const nameInput = document.getElementById('import-plan-name');
|
||||||
if (nameInput) { nameInput.value = data.fightName || ''; }
|
if (nameInput) { nameInput.value = uniquePlanName(data.fightName || 'Importierter Plan'); }
|
||||||
|
|
||||||
// Populate merge dropdown
|
// Populate merge dropdown
|
||||||
const planSelect = document.getElementById('import-plan-select');
|
const planSelect = document.getElementById('import-plan-select');
|
||||||
@ -1049,6 +1277,7 @@ window.plannerTab = {
|
|||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
initNewPlanForm();
|
initNewPlanForm();
|
||||||
|
initNewFolderForm();
|
||||||
initImportModal();
|
initImportModal();
|
||||||
initAbilityModal();
|
initAbilityModal();
|
||||||
activePlanId = localStorage.getItem(PLANNER_ACTIVE_KEY);
|
activePlanId = localStorage.getItem(PLANNER_ACTIVE_KEY);
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a href="<?= htmlspecialchars(auth_start_href(), ENT_QUOTES) ?>" class="btn btn-gold btn-login">
|
<a href="auth/start.php" class="btn btn-gold btn-login">
|
||||||
<?= $tokenExpired ? 'Reconnect to FFLogs' : 'Connect to FFLogs' ?>
|
<?= $tokenExpired ? 'Reconnect to FFLogs' : 'Connect to FFLogs' ?>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
</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>
|
<a class="btn" href="auth/start.php" style="align-self:flex-end;text-decoration:none">Reconnect</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,9 +4,18 @@
|
|||||||
<div class="plan-sidebar">
|
<div class="plan-sidebar">
|
||||||
<div class="plan-sidebar-header">
|
<div class="plan-sidebar-header">
|
||||||
<div class="card-title">Pläne</div>
|
<div class="card-title">Pläne</div>
|
||||||
|
<button id="planner-new-folder-btn" class="btn btn-sm" title="Neuer Ordner">+ Ordner</button>
|
||||||
<button id="planner-new-btn" class="btn btn-sm btn-gold">+ Neu</button>
|
<button id="planner-new-btn" class="btn btn-sm btn-gold">+ Neu</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="planner-new-folder-form" class="plan-new-form" style="display:none">
|
||||||
|
<input type="text" id="planner-new-folder-name" placeholder="Ordner-Name…">
|
||||||
|
<div class="plan-new-actions">
|
||||||
|
<button id="planner-new-folder-save" class="btn btn-sm btn-gold">Erstellen</button>
|
||||||
|
<button id="planner-new-folder-cancel" class="btn btn-sm">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="planner-new-form" class="plan-new-form" style="display:none">
|
<div id="planner-new-form" class="plan-new-form" style="display:none">
|
||||||
<input type="text" id="planner-new-name" placeholder="Plan-Name…">
|
<input type="text" id="planner-new-name" placeholder="Plan-Name…">
|
||||||
<div class="plan-new-actions">
|
<div class="plan-new-actions">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user