mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-05-18 07:15:49 +02:00
feat(ui): user-configurable sidebar breakpoint width
The threshold above which the channel/DM list shows as a sidebar (vs. collapsing to a top dropdown) is now user-configurable in Settings -> Interface -> Layout. Persisted per device in LocalStorage (key: mc-webui-sidebar-breakpoint, default: 992px, range: 600-2000). Implementation: replaced hardcoded `@media (min-width: 992px)` with a `.layout-wide` class on <html>, toggled by JS based on window.innerWidth vs. the user's breakpoint. An inline script in <head> applies the class synchronously to prevent layout flash on page load (same pattern as theme). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+15
-19
@@ -130,14 +130,12 @@ main {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Show sidebar and hide dropdown on wide screens */
|
||||
@media (min-width: 992px) {
|
||||
.channel-sidebar {
|
||||
display: flex;
|
||||
}
|
||||
#channelSelectorWrapper {
|
||||
display: none !important;
|
||||
}
|
||||
/* Show sidebar and hide dropdown on wide screens (toggled by JS via .layout-wide on <html>) */
|
||||
.layout-wide .channel-sidebar {
|
||||
display: flex;
|
||||
}
|
||||
.layout-wide #channelSelectorWrapper {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Channel Selector Dropdown (base.html navbar, narrow screens) */
|
||||
@@ -266,17 +264,15 @@ main {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Show DM sidebar and hide mobile selector on wide screens */
|
||||
@media (min-width: 992px) {
|
||||
.dm-sidebar {
|
||||
display: flex;
|
||||
}
|
||||
.dm-mobile-selector {
|
||||
display: none !important;
|
||||
}
|
||||
.dm-desktop-header {
|
||||
display: block !important;
|
||||
}
|
||||
/* Show DM sidebar and hide mobile selector on wide screens (toggled by JS via .layout-wide on <html>) */
|
||||
.layout-wide .dm-sidebar {
|
||||
display: flex;
|
||||
}
|
||||
.layout-wide .dm-mobile-selector {
|
||||
display: none !important;
|
||||
}
|
||||
.layout-wide .dm-desktop-header {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.dm-desktop-header {
|
||||
|
||||
@@ -558,6 +558,10 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
applyItemPlacements();
|
||||
initializeItemPlacementSettings();
|
||||
|
||||
// Sidebar breakpoint: re-apply (covers no-localStorage case) and wire up settings UI + resize listener
|
||||
applySidebarBreakpoint();
|
||||
initializeSidebarBreakpointSettings();
|
||||
|
||||
// Initialize FAB toggle
|
||||
initializeFabToggle();
|
||||
|
||||
@@ -5382,6 +5386,70 @@ function initializeFabToggle() {
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Sidebar breakpoint (channel/DM list as sidebar vs. dropdown)
|
||||
// =============================================================================
|
||||
|
||||
const SIDEBAR_BREAKPOINT_DEFAULT = 992;
|
||||
const SIDEBAR_BREAKPOINT_MIN = 600;
|
||||
const SIDEBAR_BREAKPOINT_MAX = 2000;
|
||||
const SIDEBAR_BREAKPOINT_KEY = 'mc-webui-sidebar-breakpoint';
|
||||
|
||||
function readSidebarBreakpoint() {
|
||||
const stored = parseInt(localStorage.getItem(SIDEBAR_BREAKPOINT_KEY), 10);
|
||||
if (isNaN(stored) || stored < SIDEBAR_BREAKPOINT_MIN || stored > SIDEBAR_BREAKPOINT_MAX) {
|
||||
return SIDEBAR_BREAKPOINT_DEFAULT;
|
||||
}
|
||||
return stored;
|
||||
}
|
||||
|
||||
function applySidebarBreakpoint() {
|
||||
const bp = readSidebarBreakpoint();
|
||||
document.documentElement.classList.toggle('layout-wide', window.innerWidth >= bp);
|
||||
}
|
||||
|
||||
let _sidebarBreakpointRaf = null;
|
||||
function onSidebarBreakpointResize() {
|
||||
if (_sidebarBreakpointRaf) return;
|
||||
_sidebarBreakpointRaf = requestAnimationFrame(() => {
|
||||
_sidebarBreakpointRaf = null;
|
||||
applySidebarBreakpoint();
|
||||
});
|
||||
}
|
||||
|
||||
function syncSidebarBreakpointUI() {
|
||||
const input = document.getElementById('settSidebarBreakpoint');
|
||||
if (input) input.value = readSidebarBreakpoint();
|
||||
}
|
||||
|
||||
function initializeSidebarBreakpointSettings() {
|
||||
window.addEventListener('resize', onSidebarBreakpointResize);
|
||||
|
||||
const input = document.getElementById('settSidebarBreakpoint');
|
||||
if (input) {
|
||||
input.addEventListener('input', () => {
|
||||
const val = parseInt(input.value, 10);
|
||||
if (isNaN(val) || val < SIDEBAR_BREAKPOINT_MIN || val > SIDEBAR_BREAKPOINT_MAX) return;
|
||||
localStorage.setItem(SIDEBAR_BREAKPOINT_KEY, String(val));
|
||||
applySidebarBreakpoint();
|
||||
});
|
||||
}
|
||||
|
||||
const resetBtn = document.getElementById('settSidebarBreakpointReset');
|
||||
if (resetBtn) {
|
||||
resetBtn.addEventListener('click', () => {
|
||||
localStorage.removeItem(SIDEBAR_BREAKPOINT_KEY);
|
||||
if (input) input.value = SIDEBAR_BREAKPOINT_DEFAULT;
|
||||
applySidebarBreakpoint();
|
||||
});
|
||||
}
|
||||
|
||||
const settingsModal = document.getElementById('settingsModal');
|
||||
if (settingsModal) {
|
||||
settingsModal.addEventListener('show.bs.modal', syncSidebarBreakpointUI);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Chat Filter Functionality
|
||||
// =============================================================================
|
||||
|
||||
@@ -21,6 +21,19 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Sidebar breakpoint: apply saved preference before CSS loads to prevent layout flash -->
|
||||
<script>
|
||||
(function() {
|
||||
try {
|
||||
var stored = parseInt(localStorage.getItem('mc-webui-sidebar-breakpoint'), 10);
|
||||
var bp = (isNaN(stored) || stored < 600 || stored > 2000) ? 992 : stored;
|
||||
if (window.innerWidth >= bp) {
|
||||
document.documentElement.classList.add('layout-wide');
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Bootstrap 5 CSS (local) -->
|
||||
<link href="{{ url_for('static', filename='vendor/bootstrap/css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
<!-- Bootstrap Icons (local) -->
|
||||
@@ -666,6 +679,22 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="tabSettingsInterface">
|
||||
<h6 class="text-muted mb-2">Layout</h6>
|
||||
<p class="text-muted small mb-2">Window width above which the channel/contact list is shown as a sidebar. Below this width it collapses to a dropdown at the top of the screen. Saved per device (browser).</p>
|
||||
<table class="table table-sm table-borderless mb-3 align-middle">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="ps-0">Sidebar breakpoint (px) <span class="badge rounded-pill text-muted" data-bs-toggle="tooltip" title="Default: 992. Range: 600-2000."><i class="bi bi-info-circle"></i></span></td>
|
||||
<td class="pe-0" style="width:10rem">
|
||||
<div class="d-flex gap-1">
|
||||
<input type="number" class="form-control form-control-sm" id="settSidebarBreakpoint" min="600" max="2000" step="1" value="992">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="settSidebarBreakpointReset" title="Reset to default (992)"><i class="bi bi-arrow-counterclockwise"></i></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr>
|
||||
<form id="uiSettingsForm">
|
||||
<h6 class="text-muted mb-2">Notifications</h6>
|
||||
<p class="text-muted small mb-2">Controls the small toasts shown after actions (e.g. "Advert Sent", errors).</p>
|
||||
|
||||
Reference in New Issue
Block a user