mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
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>
213 lines
17 KiB
Django/Jinja
213 lines
17 KiB
Django/Jinja
{# 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. 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>
|
|
<button type="button" class="btn-close d-lg-none" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" aria-label="Close menu"></button>
|
|
</div>
|
|
<div class="list-group list-group-flush mb-2">
|
|
<a href="/" class="list-group-item list-group-item-action{% if current_page == "home" %} active{% endif %}">Home</a>
|
|
{% if can.accounts_view %}<a href="/accounts" class="list-group-item list-group-item-action{% if current_page == "accounts" %} active{% endif %}">Accounts</a>{% endif %}
|
|
{% if can.characters_view %}<a href="/characters" class="list-group-item list-group-item-action{% if current_page == "characters" %} active{% endif %}">Characters</a>{% endif %}
|
|
{% if can.properties_view %}<a href="/properties" class="list-group-item list-group-item-action{% if current_page == "properties" %} active{% endif %}">Properties</a>{% endif %}
|
|
{% if can.properties_view %}<a href="/ugc_search" class="list-group-item list-group-item-action{% if current_page == "ugc_search" %} active{% endif %}">UGC Search</a>{% endif %}
|
|
</div>
|
|
|
|
{% set inboxPages = ["moderation", "player_reports", "bug_reports"] %}
|
|
{% if can.moderate_names or can.moderate_pet_names or can.moderate_properties or can.player_reports_view or can.bug_reports_view %}
|
|
<div class="nav-group">
|
|
<button class="nav-group-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#nav-inbox" aria-expanded="{% if current_page in inboxPages %}true{% else %}false{% endif %}" aria-controls="nav-inbox">
|
|
Inbox<span class="nav-group-dot" title="Something in here needs attention"></span>
|
|
</button>
|
|
<div class="collapse{% if current_page in inboxPages %} show{% endif %}" id="nav-inbox">
|
|
<div class="list-group list-group-flush">
|
|
{% if can.moderate_names or can.moderate_pet_names or can.moderate_properties %}<a href="/moderation" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center{% if current_page == "moderation" %} active{% endif %}">Review Queue <span class="badge rounded-pill text-bg-warning d-none" data-badge="moderation"></span></a>{% endif %}
|
|
{% if can.player_reports_view %}<a href="/player_reports" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center{% if current_page == "player_reports" %} active{% endif %}">Player Reports <span class="badge rounded-pill text-bg-danger d-none" data-badge="player_reports"></span></a>{% endif %}
|
|
{% if can.bug_reports_view %}<a href="/bug_reports" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center{% if current_page == "bug_reports" %} active{% endif %}">Bug Reports <span class="badge rounded-pill text-bg-danger d-none" data-badge="bug_reports"></span></a>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% set moderationPages = ["players", "chat_log", "chat_filter", "pet_names", "mail", "send_mail"] %}
|
|
{% if can.players_view or can.chat_view or can.chat_filter_manage or can.moderate_pet_names or can.characters_mail or can.mail_send %}
|
|
<div class="nav-group">
|
|
<button class="nav-group-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#nav-moderation" aria-expanded="{% if current_page in moderationPages %}true{% else %}false{% endif %}" aria-controls="nav-moderation">
|
|
Moderation<span class="nav-group-dot" title="Something in here needs attention"></span>
|
|
</button>
|
|
<div class="collapse{% if current_page in moderationPages %} show{% endif %}" id="nav-moderation">
|
|
<div class="list-group list-group-flush">
|
|
{% if can.players_view %}<a href="/players" class="list-group-item list-group-item-action{% if current_page == "players" %} active{% endif %}">Online Players</a>{% endif %}
|
|
{% if can.chat_view %}<a href="/chat_log" class="list-group-item list-group-item-action{% if current_page == "chat_log" %} active{% endif %}">Chat Log</a>{% endif %}
|
|
{% if can.chat_filter_manage %}<a href="/chat_filter" class="list-group-item list-group-item-action{% if current_page == "chat_filter" %} active{% endif %}">Chat Filter</a>{% endif %}
|
|
{% if can.moderate_pet_names %}<a href="/pet_names" class="list-group-item list-group-item-action{% if current_page == "pet_names" %} active{% endif %}">Pet Names</a>{% endif %}
|
|
{% if can.characters_mail %}<a href="/mail" class="list-group-item list-group-item-action{% if current_page == "mail" %} active{% endif %}">Mail</a>{% endif %}
|
|
{% if can.mail_send %}<a href="/send_mail" class="list-group-item list-group-item-action{% if current_page == "send_mail" %} active{% endif %}">Send Mail</a>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% set worldPages = ["world3d", "reports", "contraband", "property_rent"] %}
|
|
{% if can.players_view or can.reports_view or can.properties_view %}
|
|
<div class="nav-group">
|
|
<button class="nav-group-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#nav-world" aria-expanded="{% if current_page in worldPages %}true{% else %}false{% endif %}" aria-controls="nav-world">
|
|
World & Economy<span class="nav-group-dot" title="Something in here needs attention"></span>
|
|
</button>
|
|
<div class="collapse{% if current_page in worldPages %} show{% endif %}" id="nav-world">
|
|
<div class="list-group list-group-flush">
|
|
{% if can.players_view %}<a href="/world3d" class="list-group-item list-group-item-action{% if current_page == "world3d" %} active{% endif %}">World 3D</a>{% endif %}
|
|
{% if can.reports_view %}<a href="/reports" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center{% if current_page == "reports" %} active{% endif %}">Economy & Map <span class="badge rounded-pill text-bg-danger d-none" data-badge="reports" title="Open economy flags"></span></a>{% endif %}
|
|
{% if can.reports_view %}<a href="/contraband" class="list-group-item list-group-item-action{% if current_page == "contraband" %} active{% endif %}">Contraband</a>{% endif %}
|
|
{% if can.properties_view %}<a href="/property_rent" class="list-group-item list-group-item-action{% if current_page == "property_rent" %} active{% endif %}">Property Rent</a>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% set liveOpsPages = ["live_events", "announcements", "events", "vanity"] %}
|
|
{% if can.live_events_manage or can.announcements_schedule or can.events_manage or can.vanity_manage or can.server_restart %}
|
|
<div class="nav-group">
|
|
<button class="nav-group-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#nav-liveops" aria-expanded="{% if current_page in liveOpsPages %}true{% else %}false{% endif %}" aria-controls="nav-liveops">
|
|
Live Ops<span class="nav-group-dot" title="Something in here needs attention"></span>
|
|
</button>
|
|
<div class="collapse{% if current_page in liveOpsPages %} show{% endif %}" id="nav-liveops">
|
|
<div class="list-group list-group-flush">
|
|
<a href="/events" class="list-group-item list-group-item-action{% if current_page == "events" %} active{% endif %}">Scheduled Events</a>
|
|
{% if can.live_events_manage %}<a href="/live_events" class="list-group-item list-group-item-action{% if current_page == "live_events" %} active{% endif %}">Live Events</a>{% endif %}
|
|
{% if can.announcements_schedule %}<a href="/announcements" class="list-group-item list-group-item-action{% if current_page == "announcements" %} active{% endif %}">Scheduled Announcements</a>{% endif %}
|
|
{% if can.vanity_manage %}<a href="/vanity" class="list-group-item list-group-item-action{% if current_page == "vanity" %} active{% endif %}">Vanity</a>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% set communityPages = ["leaderboards", "challenges", "showcase"] %}
|
|
{% if can.leaderboards_view or can.challenges_view or can.showcase_view %}
|
|
<div class="nav-group">
|
|
<button class="nav-group-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#nav-community" aria-expanded="{% if current_page in communityPages or not can.accounts_view %}true{% else %}false{% endif %}" aria-controls="nav-community">
|
|
Community<span class="nav-group-dot" title="Something in here needs attention"></span>
|
|
</button>
|
|
{# Players have little else in the menu, so it starts open for them #}
|
|
<div class="collapse{% if current_page in communityPages or not can.accounts_view %} show{% endif %}" id="nav-community">
|
|
<div class="list-group list-group-flush">
|
|
{% if can.leaderboards_view %}<a href="/leaderboards" class="list-group-item list-group-item-action{% if current_page == "leaderboards" %} active{% endif %}">Leaderboards</a>{% endif %}
|
|
{% if can.challenges_view %}<a href="/challenges" class="list-group-item list-group-item-action{% if current_page == "challenges" %} active{% endif %}">Challenges</a>{% endif %}
|
|
{% if can.showcase_view %}<a href="/showcase" class="list-group-item list-group-item-action{% if current_page == "showcase" %} active{% endif %}">Property Showcase</a>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% set logsPages = ["health", "instances", "diagnostics", "activity_log", "command_log", "audit_log", "system_log"] %}
|
|
{% if can.health_view or can.logs_activity or can.logs_command or can.logs_audit or can.logs_system %}
|
|
<div class="nav-group">
|
|
<button class="nav-group-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#nav-logs" aria-expanded="{% if current_page in logsPages %}true{% else %}false{% endif %}" aria-controls="nav-logs">
|
|
Logs & Health<span class="nav-group-dot" title="Something in here needs attention"></span>
|
|
</button>
|
|
<div class="collapse{% if current_page in logsPages %} show{% endif %}" id="nav-logs">
|
|
<div class="list-group list-group-flush">
|
|
{% if can.health_view %}<a href="/health" class="list-group-item list-group-item-action{% if current_page == "health" %} active{% endif %}">Server Health</a><a href="/instances" class="list-group-item list-group-item-action{% if current_page == "instances" %} active{% endif %}">Instance Load</a><a href="/diagnostics" class="list-group-item list-group-item-action{% if current_page == "diagnostics" %} active{% endif %}">Diagnostics</a>{% endif %}
|
|
{% if can.logs_activity %}<a href="/activity_log" class="list-group-item list-group-item-action{% if current_page == "activity_log" %} active{% endif %}">Activity Log</a>{% endif %}
|
|
{% if can.logs_command %}<a href="/command_log" class="list-group-item list-group-item-action{% if current_page == "command_log" %} active{% endif %}">Command Log</a>{% endif %}
|
|
{% if can.logs_audit %}<a href="/audit_log" class="list-group-item list-group-item-action{% if current_page == "audit_log" %} active{% endif %}">Audit Log</a>{% endif %}
|
|
{% if can.logs_system %}<a href="/system_log" class="list-group-item list-group-item-action{% if current_page == "system_log" %} active{% endif %}">System Log</a>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% set adminPages = ["settings", "settings_history", "permissions", "tasks", "play_keys", "backups", "webhooks", "client_assets", "ugc"] %}
|
|
{% if can.settings or can.permissions_manage or can.grants_manage or can.tasks_view or can.play_keys_manage or can.backups or can.webhooks or can.client_files or can.ugc_manage %}
|
|
<div class="nav-group">
|
|
<button class="nav-group-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#nav-admin" aria-expanded="{% if current_page in adminPages %}true{% else %}false{% endif %}" aria-controls="nav-admin">
|
|
Server Admin<span class="nav-group-dot" title="Something in here needs attention"></span>
|
|
</button>
|
|
<div class="collapse{% if current_page in adminPages %} show{% endif %}" id="nav-admin">
|
|
<div class="list-group list-group-flush">
|
|
{% if can.settings %}<a href="/settings" class="list-group-item list-group-item-action{% if current_page == "settings" or current_page == "settings_history" %} active{% endif %}">Settings</a>{% endif %}
|
|
{% if can.permissions_manage or can.grants_manage %}<a href="/permissions" class="list-group-item list-group-item-action{% if current_page == "permissions" %} active{% endif %}">Permissions</a>{% endif %}
|
|
{% if can.tasks_view %}<a href="/tasks" class="list-group-item list-group-item-action{% if current_page == "tasks" %} active{% endif %}">Scheduled Tasks</a>{% endif %}
|
|
{% if can.play_keys_manage %}<a href="/play_keys" class="list-group-item list-group-item-action{% if current_page == "play_keys" %} active{% endif %}">Play Keys</a>{% endif %}
|
|
{% if can.backups %}<a href="/backups" class="list-group-item list-group-item-action{% if current_page == "backups" %} active{% endif %}">Backups</a>{% endif %}
|
|
{% if can.webhooks %}<a href="/webhooks" class="list-group-item list-group-item-action{% if current_page == "webhooks" %} active{% endif %}">Webhooks</a>{% endif %}
|
|
{% if can.ugc_manage %}<a href="/ugc" class="list-group-item list-group-item-action{% if current_page == "ugc" %} active{% endif %}">UGC Server</a>{% endif %}
|
|
{% if can.client_files %}<a href="/client_assets" class="list-group-item list-group-item-action{% if current_page == "client_assets" %} active{% endif %}">Client Files</a>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% set devPages = ["inspector", "cdclient"] %}
|
|
{% if can.dev_message_inspector or can.dev_cdclient %}
|
|
<div class="nav-group">
|
|
<button class="nav-group-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#nav-dev" aria-expanded="{% if current_page in devPages %}true{% else %}false{% endif %}" aria-controls="nav-dev">
|
|
Developer Tools<span class="nav-group-dot" title="Something in here needs attention"></span>
|
|
</button>
|
|
<div class="collapse{% if current_page in devPages %} show{% endif %}" id="nav-dev">
|
|
<div class="list-group list-group-flush">
|
|
{% if can.dev_message_inspector %}<a href="/inspector" class="list-group-item list-group-item-action{% if current_page == "inspector" %} active{% endif %}">Message Inspector</a>{% endif %}
|
|
{% if can.dev_cdclient %}<a href="/cdclient" class="list-group-item list-group-item-action{% if current_page == "cdclient" %} active{% endif %}">CDClient Browser</a>{% endif %}
|
|
</div>
|
|
</div>
|
|
</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>
|