mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-25 22:22:48 +00:00
feat(dashboard): the sidebar keeps its open groups and can be hidden
The menu's groups stay open or closed from page to page (localStorage "dash.sidebar", this browser only), applied by an inline script right after the menu so the first paint already shows them. The group of the current page opens and stays open until it is closed. A new top bar button hides the menu on wide screens; that choice is applied in <head>, also before the first paint. Check: open and close a few menu groups, switch pages and reload; the groups stay as left, with no flicker. On a wide window the menu button left of the user menu hides the menu, and it stays hidden across pages and reloads. Narrow screens: the Menu button still slides the menu out. Test: SidebarStateJs (ctest, needs node). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -233,3 +233,8 @@ details.about-text > summary::before {
|
||||
details.about-text > summary:hover { color: var(--bs-body-color); }
|
||||
details.about-text[open] > summary { margin-bottom: .35rem; }
|
||||
details.about-text > :not(summary) { color: var(--bs-secondary-color); font-size: .875rem; }
|
||||
|
||||
/* Wide screens: the menu can be hidden (the top bar's menu button; header.jinja2 keeps the choice) */
|
||||
@media (min-width: 992px) {
|
||||
html.sidebar-hidden nav.sidebar { display: none !important; }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}DarkflameServer{% endblock %}</title>
|
||||
<link rel="icon" href="data:,">
|
||||
{# The menu hidden on wide screens stays hidden from page to page (header.jinja2), set before the first paint #}
|
||||
<script>try { if ((JSON.parse(localStorage.getItem('dash.sidebar') || '{}') || {}).hidden) document.documentElement.classList.add('sidebar-hidden'); } catch (e) {}</script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
|
||||
<link href="https://cdn.datatables.net/v/bs5/jq-3.7.0/dt-2.3.7/b-3.2.6/fh-4.0.6/sc-2.4.3/datatables.min.css" rel="stylesheet" integrity="sha384-XMNDGLb5fN9IqhIrVXOAtGKcz4KCr+JSHXGZ1TDXQPDukbEAfmLPjHdCXhgK93fv" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="/css/dashboard.css">
|
||||
@@ -17,6 +19,7 @@
|
||||
<div class="flex-grow-1 d-flex flex-column min-vh-100 main-column">
|
||||
<header class="topbar d-flex align-items-center gap-2 border-bottom px-3 py-2 sticky-top bg-body-tertiary">
|
||||
<button class="btn btn-sm btn-outline-secondary d-lg-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebar" aria-controls="sidebar">☰ Menu</button>
|
||||
<button class="btn btn-sm btn-outline-secondary d-none d-lg-inline-block sidebar-toggle" type="button" data-sidebar-toggle aria-controls="sidebar" title="Show or hide the menu"><span aria-hidden="true">☰</span><span class="visually-hidden">Show or hide the menu</span></button>
|
||||
<a href="/" class="fw-semibold text-body text-decoration-none d-lg-none">DarkflameServer</a>
|
||||
<div class="dropdown ms-auto">
|
||||
<button class="btn btn-sm btn-link text-body text-decoration-none dropdown-toggle d-flex align-items-center gap-2" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{# Sidebar navigation. Entries show only with the permission their page needs (see Permissions.cpp). #}
|
||||
{# Grouped by what staff are doing: the most used pages always show, the rest sit in groups that fold away (the group of #}
|
||||
{# the current page starts open). Inbox holds what players sent in and staff still have to answer. #}
|
||||
{# On narrow screens this is a slide-out menu opened from the top bar in base.jinja2 #}
|
||||
{# On narrow screens this is a slide-out menu opened from the top bar in base.jinja2. Which groups are open, and whether #}
|
||||
{# the menu is hidden on wide screens, is kept in this browser (the script at the end, and the <head> of base.jinja2) #}
|
||||
<nav class="offcanvas-lg offcanvas-start d-flex flex-column flex-shrink-0 px-2 py-3 border-end sidebar" id="sidebar" tabindex="-1" aria-label="Navigation">
|
||||
<div class="d-flex align-items-center justify-content-between px-2 mb-3">
|
||||
<a href="/" class="text-decoration-none text-body"><span class="fs-5 fw-semibold">DarkflameServer</span></a>
|
||||
@@ -155,3 +156,57 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</nav>
|
||||
<script data-sidebar-state>
|
||||
/*
|
||||
* The sidebar's groups open or closed as they were last left, in this browser (localStorage "dash.sidebar":
|
||||
* {groups: {id: open}, hidden}). Runs right after the menu is parsed, before the first paint, so it never shows the
|
||||
* server's default first. The group holding the current page always opens, and stays open on the next page until it
|
||||
* is closed. Sidebar.apply() does it again after the page changes without a reload (nav.js); Sidebar.setHidden()
|
||||
* hides the menu on wide screens.
|
||||
*/
|
||||
window.Sidebar = (function () {
|
||||
var KEY = 'dash.sidebar';
|
||||
function load() {
|
||||
try { var s = JSON.parse(localStorage.getItem(KEY) || '{}'); return s && typeof s === 'object' ? s : {}; } catch (e) { return {}; }
|
||||
}
|
||||
function save(state) { try { localStorage.setItem(KEY, JSON.stringify(state)); } catch (e) { /* storage unavailable */ } }
|
||||
function setOpen(el, open) {
|
||||
el.classList.toggle('show', open);
|
||||
document.querySelectorAll('[data-bs-target="#' + el.id + '"]').forEach(function (b) {
|
||||
b.classList.toggle('collapsed', !open);
|
||||
b.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
function apply() {
|
||||
var state = load();
|
||||
var groups = state.groups = state.groups || {};
|
||||
document.querySelectorAll('#sidebar .nav-group > .collapse[id]').forEach(function (el) {
|
||||
var open = (el.id in groups ? !!groups[el.id] : el.classList.contains('show')) || !!el.querySelector('.active');
|
||||
setOpen(el, open);
|
||||
groups[el.id] = open;
|
||||
});
|
||||
save(state);
|
||||
}
|
||||
function remember(e) {
|
||||
var el = e.target;
|
||||
if (!el.id || !el.closest || !el.closest('#sidebar')) return;
|
||||
var state = load();
|
||||
state.groups = state.groups || {};
|
||||
state.groups[el.id] = e.type === 'shown.bs.collapse';
|
||||
save(state);
|
||||
}
|
||||
function setHidden(hidden) {
|
||||
var state = load();
|
||||
state.hidden = !!hidden;
|
||||
save(state);
|
||||
document.documentElement.classList.toggle('sidebar-hidden', !!hidden);
|
||||
}
|
||||
apply();
|
||||
document.addEventListener('shown.bs.collapse', remember);
|
||||
document.addEventListener('hidden.bs.collapse', remember);
|
||||
document.addEventListener('click', function (e) {
|
||||
if (e.target.closest && e.target.closest('[data-sidebar-toggle]')) setHidden(!document.documentElement.classList.contains('sidebar-hidden'));
|
||||
});
|
||||
return { apply: apply, setHidden: setHidden, load: load };
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user