Refactor toolbar buttons across various pages to unify styling

- Updated button classes for consistency in the playersoftware-page, playlist-page, resolution-page, schedule-page, settings-page, syncgroup-page, tag-page, task-page, template-page, transition-page, user-page, and usergroup-page.
- Removed unnecessary text from button titles and ensured all buttons have the 'ots-toolbar-btn' class for uniformity.
- Cleaned up the code by removing commented-out sections and ensuring proper indentation.
This commit is contained in:
Matt Batchelder
2026-02-07 14:50:40 -05:00
parent 1c5c23f100
commit 86030cb881
34 changed files with 2614 additions and 554 deletions

View File

@@ -109,8 +109,11 @@
body.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
document.documentElement.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false');
syncSubmenuDisplayForState(nowCollapsed);
updateSidebarNavOffset();
updateSidebarStateClass();
updateSidebarWidth();
setTimeout(updateSidebarWidth, 250);
});
}
@@ -121,14 +124,20 @@
body.classList.remove('ots-sidebar-collapsed');
document.documentElement.classList.remove('ots-sidebar-collapsed');
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, 'false');
syncSubmenuDisplayForState(false);
updateSidebarNavOffset();
updateSidebarStateClass();
updateSidebarWidth();
setTimeout(updateSidebarWidth, 250);
});
}
// Initialize sidebar section toggles
initSidebarSectionToggles();
// Inject flyout headers (icon + label) into each submenu for collapsed state
buildFlyoutHeaders();
// Close sidebar when clicking outside on mobile
document.addEventListener('click', function(e) {
if (window.innerWidth <= 768) {
@@ -147,12 +156,10 @@
* This function is kept as a no-op for backward compatibility.
*/
function updateSidebarWidth() {
// No-op: CSS handles layout via body.ots-sidebar-collapsed class
if (window.__otsDebug) {
const sidebar = document.querySelector('.ots-sidebar');
const collapsed = sidebar ? sidebar.classList.contains('collapsed') : false;
console.log('[OTS] updateSidebarWidth (no-op, CSS-driven)', { collapsed });
}
const sidebar = document.querySelector('.ots-sidebar');
if (!sidebar) return;
const w = sidebar.offsetWidth;
document.documentElement.style.setProperty('--ots-sidebar-actual-width', w + 'px');
}
/**
@@ -160,39 +167,12 @@
* so nav items always begin below the header (logo + buttons).
*/
function updateSidebarNavOffset() {
const sidebar = document.querySelector('.ots-sidebar');
if (!sidebar) return;
const header = sidebar.querySelector('.sidebar-header');
const nav = sidebar.querySelector('.sidebar-nav, .ots-sidebar-nav');
if (!nav) return;
// Calculate header bottom relative to the sidebar top (so it works with scrolling)
const sidebarRect = sidebar.getBoundingClientRect();
const headerRect = header ? header.getBoundingClientRect() : null;
let offset = 0;
if (headerRect) {
offset = Math.max(0, Math.ceil(headerRect.bottom - sidebarRect.top));
} else if (header) {
offset = header.offsetHeight || 0;
}
// Add a small gap so nav doesn't touch the header edge
const gap = 8;
const paddingTop = offset > 0 ? offset + gap : '';
// apply as inline style to ensure it overrides static CSS rules
if (paddingTop) {
// Use setProperty with priority so it overrides stylesheet !important rules
try {
nav.style.setProperty('padding-top', `${paddingTop}px`, 'important');
} catch (err) {
// fallback
nav.style.paddingTop = `${paddingTop}px`;
}
} else {
// remove inline override
try {
nav.style.removeProperty('padding-top');
} catch (err) {
nav.style.paddingTop = '';
}
/* No-op: sidebar uses flex-direction:column so the header and
nav content are separate flex children that never overlap.
Previously this set padding-top:~72px which created a huge gap. */
var nav = document.querySelector('.ots-sidebar .sidebar-nav, .ots-sidebar .ots-sidebar-nav');
if (nav) {
try { nav.style.removeProperty('padding-top'); } catch(e) { nav.style.paddingTop = ''; }
}
}
@@ -217,6 +197,75 @@
}
}
/**
* Build flyout headers for each sidebar-submenu.
* Pulls the icon class(es) and label from the parent group toggle
* and injects a styled header <li> at the top of the submenu.
* Idempotent — skips submenus that already have a header.
*/
function buildFlyoutHeaders() {
var groups = document.querySelectorAll('.sidebar-group');
groups.forEach(function(group) {
var submenu = group.querySelector('.sidebar-submenu');
if (!submenu) return;
// Don't inject twice
if (submenu.querySelector('.flyout-header')) return;
var toggle = group.querySelector('.sidebar-group-toggle');
if (!toggle) return;
// Grab the icon element's class list and the label text
var iconEl = toggle.querySelector('.ots-nav-icon');
var textEl = toggle.querySelector('.ots-nav-text');
if (!textEl) return;
var label = textEl.textContent.trim();
// Build the header <li>
var header = document.createElement('li');
header.className = 'flyout-header';
header.setAttribute('aria-hidden', 'true');
// Clone the icon
if (iconEl) {
var icon = document.createElement('span');
icon.className = iconEl.className; // copies all fa classes
icon.classList.add('flyout-header-icon');
icon.setAttribute('aria-hidden', 'true');
header.appendChild(icon);
}
var text = document.createElement('span');
text.className = 'flyout-header-text';
text.textContent = label;
header.appendChild(text);
submenu.insertBefore(header, submenu.firstChild);
});
}
/**
* When toggling between collapsed/expanded, sync all submenu inline
* display styles so that:
* - Collapsed: no inline display → CSS :hover handles flyouts
* - Expanded: inline display block/none based on is-open state
*/
function syncSubmenuDisplayForState(isCollapsed) {
var groups = document.querySelectorAll('.sidebar-group');
groups.forEach(function(group) {
var submenu = group.querySelector('.sidebar-submenu');
if (!submenu) return;
if (isCollapsed) {
// Remove inline display so CSS visibility/opacity hover rules work
submenu.style.removeProperty('display');
} else {
// Expanded mode: show/hide based on is-open class
var isOpen = group.classList.contains('is-open');
submenu.style.display = isOpen ? 'block' : 'none';
}
});
}
/**
* Initialize sidebar section collapse/expand functionality
*/
@@ -231,7 +280,16 @@
const caret = toggle.querySelector('.sidebar-group-caret');
if (submenu) {
const isOpen = group.classList.contains('is-open');
submenu.style.display = isOpen ? 'block' : 'none';
// Only set inline display when sidebar is NOT collapsed;
// collapsed state uses CSS :hover to show flyout menus.
const sidebarEl = document.querySelector('.ots-sidebar');
const isCollapsed = sidebarEl && sidebarEl.classList.contains('collapsed');
if (!isCollapsed) {
submenu.style.display = isOpen ? 'block' : 'none';
} else {
// Clear any leftover inline display so CSS :hover can work
submenu.style.removeProperty('display');
}
toggle.setAttribute('aria-expanded', isOpen.toString());
}
@@ -242,6 +300,12 @@
const submenu = group ? group.querySelector('.sidebar-submenu') : null;
if (!submenu) return;
const sidebarEl = document.querySelector('.ots-sidebar');
const isCollapsed = sidebarEl && sidebarEl.classList.contains('collapsed');
// When collapsed, don't toggle submenus on click — hover handles it
if (isCollapsed) return;
const isOpen = group.classList.contains('is-open');
group.classList.toggle('is-open', !isOpen);
toggle.setAttribute('aria-expanded', (!isOpen).toString());
@@ -257,27 +321,6 @@
});
}
});
// Capture-phase handler to override any conflicting listeners
document.addEventListener('click', function(e) {
const caret = e.target.closest('.sidebar-group-caret');
const toggle = e.target.closest('.sidebar-group-toggle');
const target = toggle || (caret ? caret.closest('.sidebar-group-toggle') : null);
if (!target) return;
e.preventDefault();
e.stopPropagation();
const group = target.closest('.sidebar-group');
const submenu = group ? group.querySelector('.sidebar-submenu') : null;
if (!submenu) return;
const isOpen = group.classList.contains('is-open');
group.classList.toggle('is-open', !isOpen);
target.setAttribute('aria-expanded', (!isOpen).toString());
submenu.style.display = isOpen ? 'none' : 'block';
syncSidebarActiveStates();
}, true);
}
function syncSidebarActiveStates() {