Files
AudioCore/modules/admin/views/navigation.php

340 lines
13 KiB
PHP
Raw Normal View History

<?php
$pageTitle = 'Navigation';
$links = $links ?? [];
$pages = $pages ?? [];
$error = $error ?? '';
$saved = ($saved ?? '') === '1';
ob_start();
?>
<section class="admin-card">
<div class="badge">Navigation</div>
<h1 style="margin-top:16px; font-size:28px;">Site Navigation</h1>
<p style="color: var(--muted); margin-top:8px;">Build your main menu. Add items on the left, then drag to reorder.</p>
<?php if ($error): ?>
<div style="margin-top:16px; color: #f3b0b0; font-size:13px;"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></div>
<?php elseif ($saved): ?>
<div style="margin-top:16px; color: var(--accent); font-size:13px;">Navigation saved.</div>
<?php endif; ?>
<style>
.nav-grid { display: grid; grid-template-columns: 320px 1fr; gap: 18px; }
.nav-list { display: grid; gap: 10px; }
.nav-item {
display: grid;
grid-template-columns: 34px 1.3fr 2fr 90px 90px 90px;
gap: 12px;
align-items: center;
padding: 10px 12px;
border-radius: 16px;
border: 1px solid var(--stroke);
background: rgba(14,14,16,0.9);
}
.nav-item.dragging { opacity: 0.6; }
.drag-handle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.12);
background: rgba(255,255,255,0.04);
font-size: 12px;
color: var(--muted);
cursor: grab;
user-select: none;
}
.nav-meta {
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.2em;
}
.nav-item input[readonly] { opacity: 0.7; }
@media (max-width: 980px) {
.nav-grid { grid-template-columns: 1fr; }
.nav-item { grid-template-columns: 28px 1fr; grid-auto-rows: auto; }
.nav-item > *:nth-child(n+3) { grid-column: 2 / -1; }
}
</style>
<form method="post" action="/admin/navigation" style="margin-top:20px; display:grid; gap:18px;">
<div class="nav-grid">
<aside class="admin-card" style="padding:16px;">
<div class="badge" style="opacity:0.7;">Add menu items</div>
<div style="margin-top:12px; display:grid; gap:12px;">
<label class="label">Page picker</label>
<select id="navPageSelect" class="input" style="text-transform:none;">
<option value="" data-label="" data-url="">Select page</option>
<option value="home" data-label="Home" data-url="/">Home</option>
<option value="artists" data-label="Artists" data-url="/artists">Artists</option>
<option value="releases" data-label="Releases" data-url="/releases">Releases</option>
<option value="store" data-label="Store" data-url="/store">Store</option>
<option value="contact" data-label="Contact" data-url="/contact">Contact</option>
<?php foreach ($pages as $page): ?>
<option value="page-<?= htmlspecialchars((string)($page['slug'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"
data-label="<?= htmlspecialchars((string)($page['title'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"
data-url="/<?= htmlspecialchars((string)($page['slug'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<?= htmlspecialchars((string)($page['title'] ?? ''), ENT_QUOTES, 'UTF-8') ?>
</option>
<?php endforeach; ?>
<option value="custom" data-label="" data-url="">Custom link</option>
</select>
<label style="display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:0.2em;">
<input id="navActiveInput" type="checkbox" checked>
Active
</label>
<button type="button" id="navAddButton" class="btn" style="padding:8px 14px;">Add to menu</button>
<div style="font-size:12px; color:var(--muted);">Use Custom for external links or anchors.</div>
</div>
</aside>
<section class="admin-card" style="padding:16px;">
<div class="badge" style="opacity:0.7;">Menu structure</div>
<div style="margin-top:12px; display:grid; gap:12px;">
<div class="nav-item" style="background: transparent; border: none; padding: 0;">
<div></div>
<div class="nav-meta">Label</div>
<div class="nav-meta">URL</div>
<div class="nav-meta">Order</div>
<div class="nav-meta">Active</div>
<div class="nav-meta">Delete</div>
</div>
<div id="navMenuEmpty" style="color: var(--muted); font-size:13px; display: <?= $links ? 'none' : 'block' ?>;">No navigation links yet.</div>
<div id="navMenuList" class="nav-list">
<?php foreach ($links as $link): ?>
<div class="nav-item" draggable="true">
<div class="drag-handle" title="Drag to reorder">||</div>
<input class="input" name="items[<?= (int)$link['id'] ?>][label]" value="<?= htmlspecialchars((string)($link['label'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<input class="input" name="items[<?= (int)$link['id'] ?>][url]" value="<?= htmlspecialchars((string)($link['url'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<input class="input" name="items[<?= (int)$link['id'] ?>][sort_order]" data-sort-input value="<?= htmlspecialchars((string)($link['sort_order'] ?? 0), ENT_QUOTES, 'UTF-8') ?>" readonly>
<label style="display:flex; justify-content:center;">
<input type="checkbox" name="items[<?= (int)$link['id'] ?>][is_active]" value="1" <?= ((int)($link['is_active'] ?? 0) === 1) ? 'checked' : '' ?>>
</label>
<label style="display:flex; justify-content:center;">
<input type="checkbox" name="delete_ids[]" value="<?= (int)$link['id'] ?>">
</label>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
</div>
<div style="display:flex; justify-content:flex-end;">
<button type="submit" class="btn">Save navigation</button>
</div>
</form>
</section>
<div id="navModal" style="position:fixed; inset:0; background:rgba(0,0,0,0.55); display:none; align-items:center; justify-content:center; padding:24px; z-index:40;">
<div class="admin-card" style="max-width:520px; width:100%; position:relative;">
<div class="badge">Custom link</div>
<h2 style="margin-top:12px; font-size:24px;">Add custom link</h2>
<div style="display:grid; gap:14px; margin-top:16px;">
<div>
<label class="label">Label</label>
<input id="modalLabel" class="input" placeholder="Press Kit">
</div>
<div>
<label class="label">URL</label>
<input id="modalUrl" class="input" placeholder="https://example.com/press">
</div>
<label style="display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:0.2em;">
<input id="modalActive" type="checkbox" checked>
Active
</label>
<div style="display:flex; gap:12px; justify-content:flex-end;">
<button type="button" id="modalCancel" class="btn" style="background:transparent; color:var(--text); border:1px solid rgba(255,255,255,0.2);">Cancel</button>
<button type="button" id="modalAdd" class="btn">Add link</button>
</div>
</div>
</div>
</div>
<script>
(function () {
const menuList = document.getElementById('navMenuList');
const menuEmpty = document.getElementById('navMenuEmpty');
const addButton = document.getElementById('navAddButton');
const pageSelect = document.getElementById('navPageSelect');
const activeInput = document.getElementById('navActiveInput');
const modal = document.getElementById('navModal');
const modalLabel = document.getElementById('modalLabel');
const modalUrl = document.getElementById('modalUrl');
const modalActive = document.getElementById('modalActive');
const modalAdd = document.getElementById('modalAdd');
const modalCancel = document.getElementById('modalCancel');
let stagedIndex = 0;
function showModal() {
modal.style.display = 'flex';
modalLabel.value = '';
modalUrl.value = '';
modalActive.checked = true;
modalLabel.focus();
}
function hideModal() {
modal.style.display = 'none';
}
function updateOrder() {
const items = menuList.querySelectorAll('.nav-item');
items.forEach((item, index) => {
const sortInput = item.querySelector('[data-sort-input]');
if (sortInput) {
sortInput.value = String(index + 1);
}
});
menuEmpty.style.display = items.length ? 'none' : 'block';
}
function addStagedLink(label, url, isActive) {
if (!label || !url) {
return;
}
stagedIndex += 1;
const row = document.createElement('div');
row.className = 'nav-item';
row.setAttribute('draggable', 'true');
const handle = document.createElement('div');
handle.className = 'drag-handle';
handle.textContent = '||';
handle.title = 'Drag to reorder';
const labelInput = document.createElement('input');
labelInput.className = 'input';
labelInput.name = `new[${stagedIndex}][label]`;
labelInput.value = label;
const urlInput = document.createElement('input');
urlInput.className = 'input';
urlInput.name = `new[${stagedIndex}][url]`;
urlInput.value = url;
const orderInputEl = document.createElement('input');
orderInputEl.className = 'input';
orderInputEl.name = `new[${stagedIndex}][sort_order]`;
orderInputEl.value = '0';
orderInputEl.setAttribute('data-sort-input', '');
orderInputEl.readOnly = true;
const activeLabel = document.createElement('label');
activeLabel.style.display = 'flex';
activeLabel.style.justifyContent = 'center';
const activeCheckbox = document.createElement('input');
activeCheckbox.type = 'checkbox';
activeCheckbox.name = `new[${stagedIndex}][is_active]`;
activeCheckbox.value = '1';
activeCheckbox.checked = !!isActive;
activeLabel.appendChild(activeCheckbox);
const removeButton = document.createElement('button');
removeButton.type = 'button';
removeButton.className = 'btn';
removeButton.textContent = 'Remove';
removeButton.style.padding = '6px 12px';
removeButton.style.background = 'transparent';
removeButton.style.color = 'var(--text)';
removeButton.style.border = '1px solid rgba(255,255,255,0.2)';
row.appendChild(handle);
row.appendChild(labelInput);
row.appendChild(urlInput);
row.appendChild(orderInputEl);
row.appendChild(activeLabel);
row.appendChild(removeButton);
removeButton.addEventListener('click', () => {
row.remove();
updateOrder();
});
menuList.appendChild(row);
enableDrag(row);
updateOrder();
}
addButton.addEventListener('click', () => {
const selected = pageSelect.options[pageSelect.selectedIndex];
if (!selected || !selected.value) {
return;
}
if (selected.value === 'custom') {
showModal();
return;
}
const label = selected.getAttribute('data-label') || '';
const url = selected.getAttribute('data-url') || '';
const isActive = activeInput.checked;
addStagedLink(label, url, isActive);
pageSelect.value = '';
activeInput.checked = true;
});
modalAdd.addEventListener('click', () => {
const label = modalLabel.value.trim();
const url = modalUrl.value.trim();
const isActive = modalActive.checked;
addStagedLink(label, url, isActive);
hideModal();
});
modalCancel.addEventListener('click', hideModal);
modal.addEventListener('click', (event) => {
if (event.target === modal) {
hideModal();
}
});
function getDragAfterElement(container, y) {
const elements = [...container.querySelectorAll('.nav-item:not(.dragging)')];
return elements.reduce((closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset, element: child };
}
return closest;
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
function enableDrag(item) {
item.addEventListener('dragstart', () => {
item.classList.add('dragging');
});
item.addEventListener('dragend', () => {
item.classList.remove('dragging');
updateOrder();
});
}
menuList.addEventListener('dragover', (event) => {
event.preventDefault();
const dragging = menuList.querySelector('.dragging');
if (!dragging) {
return;
}
const afterElement = getDragAfterElement(menuList, event.clientY);
if (afterElement == null) {
menuList.appendChild(dragging);
} else {
menuList.insertBefore(dragging, afterElement);
}
});
menuList.querySelectorAll('.nav-item').forEach(enableDrag);
updateOrder();
})();
</script>
<?php
$content = ob_get_clean();
require __DIR__ . '/layout.php';