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:
@@ -904,19 +904,29 @@ body.login, body.login-page, .xibo-login, #login, .login-wrapper {
|
|||||||
background-size: 48px 48px, cover;
|
background-size: 48px 48px, cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.login-page .container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.login-card,
|
.login-card,
|
||||||
.login-panel,
|
.login-panel,
|
||||||
.auth-card,
|
.auth-card,
|
||||||
.xibo-login-box,
|
.xibo-login-box,
|
||||||
#login-box {
|
#login-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 560px;
|
max-width: 520px;
|
||||||
border-radius: 12px;
|
border-radius: 16px;
|
||||||
padding: 32px 36px;
|
padding: 36px 40px 34px;
|
||||||
background: var(--login-panel-bg);
|
background: linear-gradient(180deg, rgba(7,12,22,0.92), rgba(10,16,30,0.9));
|
||||||
border: 1px solid rgba(255,255,255,0.04);
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
box-shadow: var(--ots-shadow-lg);
|
box-shadow: 0 28px 70px rgba(2, 6, 23, 0.55);
|
||||||
color: var(--ots-text);
|
color: var(--ots-text);
|
||||||
|
backdrop-filter: blur(14px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card .login-logo,
|
.login-card .login-logo,
|
||||||
@@ -942,23 +952,24 @@ body.login, body.login-page, .xibo-login, #login, .login-wrapper {
|
|||||||
/* Brand text next to logo on login */
|
/* Brand text next to logo on login */
|
||||||
.login-brand {
|
.login-brand {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 18px;
|
gap: 10px;
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-brand .login-logo {
|
.login-brand .login-logo {
|
||||||
width: 92px;
|
width: 72px;
|
||||||
height: 92px;
|
height: 72px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-brand-text {
|
.login-brand-text {
|
||||||
color: var(--ots-text);
|
color: var(--ots-text);
|
||||||
font-size: 1.5rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
letter-spacing: 0.01em;
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card h1,
|
.login-card h1,
|
||||||
@@ -972,8 +983,9 @@ body.login, body.login-page, .xibo-login, #login, .login-wrapper {
|
|||||||
.login-card .lead,
|
.login-card .lead,
|
||||||
.login-panel .lead {
|
.login-panel .lead {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--ots-text-muted);
|
color: rgba(226,232,240,0.74);
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card .form-group,
|
.login-card .form-group,
|
||||||
@@ -989,12 +1001,12 @@ body.login, body.login-page, .xibo-login, #login, .login-wrapper {
|
|||||||
.login-panel input[type="email"],
|
.login-panel input[type="email"],
|
||||||
.login-panel input[type="password"] {
|
.login-panel input[type="password"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: linear-gradient(180deg, rgba(255,255,255,0.016), rgba(255,255,255,0.01));
|
background: rgba(11, 18, 33, 0.65);
|
||||||
border: 1px solid rgba(255,255,255,0.06);
|
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||||
color: var(--ots-text);
|
color: var(--ots-text);
|
||||||
padding: 14px 16px;
|
padding: 12px 14px;
|
||||||
border-radius: 12px;
|
border-radius: 10px;
|
||||||
font-size: 1rem;
|
font-size: 0.98rem;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
transition: border-color 180ms ease, box-shadow 180ms ease, background 180ms ease;
|
transition: border-color 180ms ease, box-shadow 180ms ease, background 180ms ease;
|
||||||
}
|
}
|
||||||
@@ -1003,9 +1015,9 @@ body.login, body.login-page, .xibo-login, #login, .login-wrapper {
|
|||||||
.login-panel input:focus,
|
.login-panel input:focus,
|
||||||
.login-card .form-control:focus {
|
.login-card .form-control:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: rgba(255,138,0,0.95);
|
border-color: rgba(255,138,0,0.85);
|
||||||
background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));
|
background: rgba(11, 18, 33, 0.75);
|
||||||
box-shadow: 0 8px 22px rgba(0,0,0,0.32), 0 0 0 6px rgba(255,138,0,0.04);
|
box-shadow: 0 10px 26px rgba(0,0,0,0.38), 0 0 0 5px rgba(255,138,0,0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card input::placeholder,
|
.login-card input::placeholder,
|
||||||
@@ -1034,13 +1046,13 @@ body.login, body.login-page, .xibo-login, #login, .login-wrapper {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
background: rgba(255,255,255,0.03);
|
background: #f8fafc;
|
||||||
color: var(--ots-text);
|
color: #0b1221 !important;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid rgba(255,255,255,0.12);
|
border: 1px solid rgba(255,255,255,0.8);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
box-shadow: none;
|
box-shadow: 0 10px 22px rgba(2,6,23,0.35);
|
||||||
transition: background 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease;
|
transition: background 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card .btn-signin .icon,
|
.login-card .btn-signin .icon,
|
||||||
@@ -1054,15 +1066,16 @@ body.login, body.login-page, .xibo-login, #login, .login-wrapper {
|
|||||||
|
|
||||||
.login-card .btn-signin:hover,
|
.login-card .btn-signin:hover,
|
||||||
.login-panel .btn-signin:hover {
|
.login-panel .btn-signin:hover {
|
||||||
background: rgba(255,255,255,0.06);
|
background: #ffffff;
|
||||||
border-color: rgba(255,255,255,0.18);
|
border-color: rgba(255,255,255,0.95);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card .btn-signin:focus,
|
.login-card .btn-signin:focus,
|
||||||
.login-panel .btn-signin:focus {
|
.login-panel .btn-signin:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 6px 18px rgba(0,0,0,0.28), 0 0 0 6px rgba(255,138,0,0.04);
|
box-shadow: 0 10px 24px rgba(0,0,0,0.32), 0 0 0 5px rgba(255,138,0,0.18);
|
||||||
border-color: rgba(255,138,0,0.28);
|
border-color: rgba(255,138,0,0.45);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card .forgot-link,
|
.login-card .forgot-link,
|
||||||
@@ -1075,7 +1088,7 @@ body.login, body.login-page, .xibo-login, #login, .login-wrapper {
|
|||||||
|
|
||||||
/* Small screens: compress card padding */
|
/* Small screens: compress card padding */
|
||||||
@media (max-width: 520px) {
|
@media (max-width: 520px) {
|
||||||
.login-card, .login-panel { padding: 20px; border-radius: 10px; }
|
.login-card, .login-panel { padding: 24px; border-radius: 12px; }
|
||||||
.login-card .login-logo .logo-icon { width: 72px; height: 72px; }
|
.login-card .login-logo .logo-icon { width: 72px; height: 72px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1129,7 +1142,7 @@ body.login-page::before {
|
|||||||
left: -8%;
|
left: -8%;
|
||||||
top: -6%;
|
top: -6%;
|
||||||
background: radial-gradient(circle at 30% 30%, rgba(79,140,255,0.65), rgba(79,140,255,0.18) 35%, transparent 50%);
|
background: radial-gradient(circle at 30% 30%, rgba(79,140,255,0.65), rgba(79,140,255,0.18) 35%, transparent 50%);
|
||||||
animation: ots-blob-move-1 20s ease-in-out infinite alternate !important;
|
animation: ots-blob-move-1 16s ease-in-out infinite alternate !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-login-blob--2 {
|
.ots-login-blob--2 {
|
||||||
@@ -1138,7 +1151,7 @@ body.login-page::before {
|
|||||||
right: 6%;
|
right: 6%;
|
||||||
bottom: 18%;
|
bottom: 18%;
|
||||||
background: radial-gradient(circle at 60% 40%, rgba(255,138,0,0.45), rgba(255,138,0,0.14) 36%, transparent 55%);
|
background: radial-gradient(circle at 60% 40%, rgba(255,138,0,0.45), rgba(255,138,0,0.14) 36%, transparent 55%);
|
||||||
animation: ots-blob-move-2 26s ease-in-out infinite alternate !important;
|
animation: ots-blob-move-2 20s ease-in-out infinite alternate !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-login-blob--3 {
|
.ots-login-blob--3 {
|
||||||
@@ -1147,7 +1160,11 @@ body.login-page::before {
|
|||||||
left: 18%;
|
left: 18%;
|
||||||
bottom: -4%;
|
bottom: -4%;
|
||||||
background: radial-gradient(circle at 40% 60%, rgba(94,200,255,0.28), rgba(94,200,255,0.08) 40%, transparent 60%);
|
background: radial-gradient(circle at 40% 60%, rgba(94,200,255,0.28), rgba(94,200,255,0.08) 40%, transparent 60%);
|
||||||
animation: ots-blob-move-3 22s ease-in-out infinite alternate !important;
|
animation: ots-blob-move-3 18s ease-in-out infinite alternate !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-login-bg {
|
||||||
|
animation-duration: 10s !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Disable other animations/transitions on the login page so only blobs animate */
|
/* Disable other animations/transitions on the login page so only blobs animate */
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -109,8 +109,11 @@
|
|||||||
body.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
body.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
||||||
document.documentElement.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
document.documentElement.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
||||||
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false');
|
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false');
|
||||||
|
syncSubmenuDisplayForState(nowCollapsed);
|
||||||
updateSidebarNavOffset();
|
updateSidebarNavOffset();
|
||||||
updateSidebarStateClass();
|
updateSidebarStateClass();
|
||||||
|
updateSidebarWidth();
|
||||||
|
setTimeout(updateSidebarWidth, 250);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,14 +124,20 @@
|
|||||||
body.classList.remove('ots-sidebar-collapsed');
|
body.classList.remove('ots-sidebar-collapsed');
|
||||||
document.documentElement.classList.remove('ots-sidebar-collapsed');
|
document.documentElement.classList.remove('ots-sidebar-collapsed');
|
||||||
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, 'false');
|
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, 'false');
|
||||||
|
syncSubmenuDisplayForState(false);
|
||||||
updateSidebarNavOffset();
|
updateSidebarNavOffset();
|
||||||
updateSidebarStateClass();
|
updateSidebarStateClass();
|
||||||
|
updateSidebarWidth();
|
||||||
|
setTimeout(updateSidebarWidth, 250);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize sidebar section toggles
|
// Initialize sidebar section toggles
|
||||||
initSidebarSectionToggles();
|
initSidebarSectionToggles();
|
||||||
|
|
||||||
|
// Inject flyout headers (icon + label) into each submenu for collapsed state
|
||||||
|
buildFlyoutHeaders();
|
||||||
|
|
||||||
// Close sidebar when clicking outside on mobile
|
// Close sidebar when clicking outside on mobile
|
||||||
document.addEventListener('click', function(e) {
|
document.addEventListener('click', function(e) {
|
||||||
if (window.innerWidth <= 768) {
|
if (window.innerWidth <= 768) {
|
||||||
@@ -147,12 +156,10 @@
|
|||||||
* This function is kept as a no-op for backward compatibility.
|
* This function is kept as a no-op for backward compatibility.
|
||||||
*/
|
*/
|
||||||
function updateSidebarWidth() {
|
function updateSidebarWidth() {
|
||||||
// No-op: CSS handles layout via body.ots-sidebar-collapsed class
|
const sidebar = document.querySelector('.ots-sidebar');
|
||||||
if (window.__otsDebug) {
|
if (!sidebar) return;
|
||||||
const sidebar = document.querySelector('.ots-sidebar');
|
const w = sidebar.offsetWidth;
|
||||||
const collapsed = sidebar ? sidebar.classList.contains('collapsed') : false;
|
document.documentElement.style.setProperty('--ots-sidebar-actual-width', w + 'px');
|
||||||
console.log('[OTS] updateSidebarWidth (no-op, CSS-driven)', { collapsed });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,39 +167,12 @@
|
|||||||
* so nav items always begin below the header (logo + buttons).
|
* so nav items always begin below the header (logo + buttons).
|
||||||
*/
|
*/
|
||||||
function updateSidebarNavOffset() {
|
function updateSidebarNavOffset() {
|
||||||
const sidebar = document.querySelector('.ots-sidebar');
|
/* No-op: sidebar uses flex-direction:column so the header and
|
||||||
if (!sidebar) return;
|
nav content are separate flex children that never overlap.
|
||||||
const header = sidebar.querySelector('.sidebar-header');
|
Previously this set padding-top:~72px which created a huge gap. */
|
||||||
const nav = sidebar.querySelector('.sidebar-nav, .ots-sidebar-nav');
|
var nav = document.querySelector('.ots-sidebar .sidebar-nav, .ots-sidebar .ots-sidebar-nav');
|
||||||
if (!nav) return;
|
if (nav) {
|
||||||
// Calculate header bottom relative to the sidebar top (so it works with scrolling)
|
try { nav.style.removeProperty('padding-top'); } catch(e) { nav.style.paddingTop = ''; }
|
||||||
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 = '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
|
* Initialize sidebar section collapse/expand functionality
|
||||||
*/
|
*/
|
||||||
@@ -231,7 +280,16 @@
|
|||||||
const caret = toggle.querySelector('.sidebar-group-caret');
|
const caret = toggle.querySelector('.sidebar-group-caret');
|
||||||
if (submenu) {
|
if (submenu) {
|
||||||
const isOpen = group.classList.contains('is-open');
|
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());
|
toggle.setAttribute('aria-expanded', isOpen.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +300,12 @@
|
|||||||
const submenu = group ? group.querySelector('.sidebar-submenu') : null;
|
const submenu = group ? group.querySelector('.sidebar-submenu') : null;
|
||||||
if (!submenu) return;
|
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');
|
const isOpen = group.classList.contains('is-open');
|
||||||
group.classList.toggle('is-open', !isOpen);
|
group.classList.toggle('is-open', !isOpen);
|
||||||
toggle.setAttribute('aria-expanded', (!isOpen).toString());
|
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() {
|
function syncSidebarActiveStates() {
|
||||||
|
|||||||
@@ -19,12 +19,8 @@
|
|||||||
{% trans "About" %}
|
{% trans "About" %}
|
||||||
<a href="https://ots-signs.com" target="_blank" rel="noopener noreferrer">OTS Signs</a>
|
<a href="https://ots-signs.com" target="_blank" rel="noopener noreferrer">OTS Signs</a>
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
|
||||||
{% trans "An" %}
|
<p class="text-muted">{% trans "OTS Signs provides a compact, focused interface for your digital signage network" %}</p>
|
||||||
<a href="https://oribi-tech.com" target="_blank" rel="noopener noreferrer">Oribi Technology Services</a>
|
|
||||||
{% trans "product." %}
|
|
||||||
</p>
|
|
||||||
<p class="text-muted">{% trans "OTS Signs provides a compact, focused interface and hosting for Xibo CMS" %}</p>
|
|
||||||
|
|
||||||
{% set appVersion = version|default("dev") %}
|
{% set appVersion = version|default("dev") %}
|
||||||
{% set appEnvironment = appEnvironment|default(environment|default("local")) %}
|
{% set appEnvironment = appEnvironment|default(environment|default("local")) %}
|
||||||
@@ -46,9 +42,9 @@
|
|||||||
|
|
||||||
<div class="about-disclaimer">
|
<div class="about-disclaimer">
|
||||||
<strong>{% trans "Disclaimer:" %}</strong>
|
<strong>{% trans "Disclaimer:" %}</strong>
|
||||||
{% trans "OTS Signs is an independent product developed by Oribi Technology Services. It is not affiliated with or endorsed by the Xibo project or its maintainers. Xibo is a trademark of Xibo Digital Signage Ltd. Use of Xibo APIs is subject to their terms and conditions." %}
|
{% trans "OTS Signs is an custom front end developed by Oribi Technology Services for the Xibo CMS. It is not affiliated with the Xibo project or its maintainers. Xibo is a trademark of Xibo Digital Signage Ltd. Use of Xibo is subject to their terms and conditions." %}
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<a href="https://github.com/xibosignage/xibo" target="_blank" rel="noopener noreferrer">{% trans "Xibo CMS on GitHub" %}</a>
|
<a href="https://github.com/xibosignage/xibo" target="_blank" rel="noopener noreferrer">{% trans "View the Xibo CMS source on GitHub" %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,8 +39,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add an Application" %}" href="{{ url_for("application.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Application" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add an Application" %}" href="{{ url_for("application.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="applications" class="table table-striped">
|
<table id="applications" class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -30,8 +30,8 @@
|
|||||||
|
|
||||||
{% set scheduleCount = currentUser.featureEnabledCount(["schedule.view", "daypart.view"]) %}
|
{% set scheduleCount = currentUser.featureEnabledCount(["schedule.view", "daypart.view"]) %}
|
||||||
{% if scheduleCount > 0 %}
|
{% if scheduleCount > 0 %}
|
||||||
<li class="sidebar-group is-open">
|
<li class="sidebar-group">
|
||||||
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="scheduling">
|
<a class="sidebar-group-toggle" href="#" aria-expanded="false" data-group="scheduling">
|
||||||
<span class="ots-nav-icon fa fa-calendar" aria-hidden="true"></span>
|
<span class="ots-nav-icon fa fa-calendar" aria-hidden="true"></span>
|
||||||
<span class="ots-nav-text">{% trans "Scheduling" %}</span>
|
<span class="ots-nav-text">{% trans "Scheduling" %}</span>
|
||||||
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
||||||
@@ -60,8 +60,8 @@
|
|||||||
|
|
||||||
{% set countViewable = currentUser.featureEnabledCount(["library.view", "playlist.view", "dataset.view", "menuBoard.view"]) %}
|
{% set countViewable = currentUser.featureEnabledCount(["library.view", "playlist.view", "dataset.view", "menuBoard.view"]) %}
|
||||||
{% if countViewable > 0 %}
|
{% if countViewable > 0 %}
|
||||||
<li class="sidebar-group is-open">
|
<li class="sidebar-group">
|
||||||
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="media">
|
<a class="sidebar-group-toggle" href="#" aria-expanded="false" data-group="media">
|
||||||
<span class="ots-nav-icon fa fa-picture-o" aria-hidden="true"></span>
|
<span class="ots-nav-icon fa fa-picture-o" aria-hidden="true"></span>
|
||||||
<span class="ots-nav-text">{% trans "Media" %}</span>
|
<span class="ots-nav-text">{% trans "Media" %}</span>
|
||||||
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
||||||
@@ -108,8 +108,8 @@
|
|||||||
|
|
||||||
{% set countViewable = currentUser.featureEnabledCount(["campaign.view", "layout.view", "template.view", "resolution.view"]) %}
|
{% set countViewable = currentUser.featureEnabledCount(["campaign.view", "layout.view", "template.view", "resolution.view"]) %}
|
||||||
{% if countViewable > 0 %}
|
{% if countViewable > 0 %}
|
||||||
<li class="sidebar-group is-open">
|
<li class="sidebar-group">
|
||||||
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="design">
|
<a class="sidebar-group-toggle" href="#" aria-expanded="false" data-group="design">
|
||||||
<span class="ots-nav-icon fa fa-paint-brush" aria-hidden="true"></span>
|
<span class="ots-nav-icon fa fa-paint-brush" aria-hidden="true"></span>
|
||||||
<span class="ots-nav-text">{% trans "Design" %}</span>
|
<span class="ots-nav-text">{% trans "Design" %}</span>
|
||||||
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
||||||
@@ -156,8 +156,8 @@
|
|||||||
|
|
||||||
{% set countViewable = currentUser.featureEnabledCount(["displays.view", "displaygroup.view", "displayprofile.view", "playersoftware.view", "command.view", "display.syncView"]) %}
|
{% set countViewable = currentUser.featureEnabledCount(["displays.view", "displaygroup.view", "displayprofile.view", "playersoftware.view", "command.view", "display.syncView"]) %}
|
||||||
{% if countViewable > 0 %}
|
{% if countViewable > 0 %}
|
||||||
<li class="sidebar-group is-open">
|
<li class="sidebar-group">
|
||||||
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="displays">
|
<a class="sidebar-group-toggle" href="#" aria-expanded="false" data-group="displays">
|
||||||
<span class="ots-nav-icon fa fa-desktop" aria-hidden="true"></span>
|
<span class="ots-nav-icon fa fa-desktop" aria-hidden="true"></span>
|
||||||
<span class="ots-nav-text">{% trans "Displays" %}</span>
|
<span class="ots-nav-text">{% trans "Displays" %}</span>
|
||||||
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
||||||
@@ -228,8 +228,8 @@
|
|||||||
|
|
||||||
{% set countViewable = currentUser.featureEnabledCount(["usergroup.view", "module.view", "transition.view", "task.view", "tag.view", "font.view"]) %}
|
{% set countViewable = currentUser.featureEnabledCount(["usergroup.view", "module.view", "transition.view", "task.view", "tag.view", "font.view"]) %}
|
||||||
{% if countViewable > 0 or userMenuViewable %}
|
{% if countViewable > 0 or userMenuViewable %}
|
||||||
<li class="sidebar-group is-open">
|
<li class="sidebar-group">
|
||||||
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="settings">
|
<a class="sidebar-group-toggle" href="#" aria-expanded="false" data-group="settings">
|
||||||
<span class="ots-nav-icon fa fa-cog" aria-hidden="true"></span>
|
<span class="ots-nav-icon fa fa-cog" aria-hidden="true"></span>
|
||||||
<span class="ots-nav-text">{% trans "Settings" %}</span>
|
<span class="ots-nav-text">{% trans "Settings" %}</span>
|
||||||
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
||||||
@@ -330,8 +330,8 @@
|
|||||||
|
|
||||||
{% set countViewable = currentUser.featureEnabledCount(["report.view", "report.scheduling", "report.saving"]) %}
|
{% set countViewable = currentUser.featureEnabledCount(["report.view", "report.scheduling", "report.saving"]) %}
|
||||||
{% if countViewable > 0 %}
|
{% if countViewable > 0 %}
|
||||||
<li class="sidebar-group is-open">
|
<li class="sidebar-group">
|
||||||
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="reporting">
|
<a class="sidebar-group-toggle" href="#" aria-expanded="false" data-group="reporting">
|
||||||
<span class="ots-nav-icon fa fa-bar-chart" aria-hidden="true"></span>
|
<span class="ots-nav-icon fa fa-bar-chart" aria-hidden="true"></span>
|
||||||
<span class="ots-nav-text">{% trans "Reporting" %}</span>
|
<span class="ots-nav-text">{% trans "Reporting" %}</span>
|
||||||
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
||||||
@@ -369,8 +369,8 @@
|
|||||||
|
|
||||||
{% set countViewable = currentUser.featureEnabledCount(["log.view", "sessions.view", "auditlog.view", "fault.view"]) %}
|
{% set countViewable = currentUser.featureEnabledCount(["log.view", "sessions.view", "auditlog.view", "fault.view"]) %}
|
||||||
{% if countViewable > 0 %}
|
{% if countViewable > 0 %}
|
||||||
<li class="sidebar-group is-open">
|
<li class="sidebar-group">
|
||||||
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="advanced">
|
<a class="sidebar-group-toggle" href="#" aria-expanded="false" data-group="advanced">
|
||||||
<span class="ots-nav-icon fa fa-shield" aria-hidden="true"></span>
|
<span class="ots-nav-icon fa fa-shield" aria-hidden="true"></span>
|
||||||
<span class="ots-nav-text">{% trans "Advanced" %}</span>
|
<span class="ots-nav-text">{% trans "Advanced" %}</span>
|
||||||
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
||||||
@@ -417,8 +417,8 @@
|
|||||||
|
|
||||||
{% set countViewable = currentUser.featureEnabledCount(["developer.edit"]) %}
|
{% set countViewable = currentUser.featureEnabledCount(["developer.edit"]) %}
|
||||||
{% if countViewable > 0 %}
|
{% if countViewable > 0 %}
|
||||||
<li class="sidebar-group is-open">
|
<li class="sidebar-group">
|
||||||
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="developer">
|
<a class="sidebar-group-toggle" href="#" aria-expanded="false" data-group="developer">
|
||||||
<span class="ots-nav-icon fa fa-code" aria-hidden="true"></span>
|
<span class="ots-nav-icon fa fa-code" aria-hidden="true"></span>
|
||||||
<span class="ots-nav-text">{% trans "Developer" %}</span>
|
<span class="ots-nav-text">{% trans "Developer" %}</span>
|
||||||
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
|
||||||
|
|||||||
@@ -33,7 +33,8 @@
|
|||||||
<i class="fa fa-moon-o" id="ots-theme-icon" aria-hidden="true"></i>
|
<i class="fa fa-moon-o" id="ots-theme-icon" aria-hidden="true"></i>
|
||||||
<span id="ots-theme-label">Dark Mode</span>
|
<span id="ots-theme-label">Dark Mode</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="dropdown-item" id="reshowWelcomeMenuItem" href="{{ url_for("welcome.view") }}">{% trans "Reshow welcome" %}</a>
|
|
||||||
|
<a class="dropdown-item" href="https://portal.oribi-tech.com" target="_blank" rel="noopener noreferrer" title="{% trans "Client Portal" %}">{% trans "Client Portal" %}</a>
|
||||||
|
|
||||||
<a class="dropdown-item XiboFormButton" href="{{ url_for("about") }}" title="{% trans "About the CMS" %}">{% trans "About" %}</a>
|
<a class="dropdown-item XiboFormButton" href="{{ url_for("about") }}" title="{% trans "About the CMS" %}">{% trans "About" %}</a>
|
||||||
|
|
||||||
|
|||||||
@@ -104,10 +104,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div id="content-wrapper">
|
<div id="content-wrapper"{% if hideNavigation == "1" %} class="no-nav"{% endif %}>
|
||||||
{# Floating top-right actions: notification bell + user menu #}
|
{# Floating top-right actions: notification bell + user menu #}
|
||||||
{% if not forceHide %}
|
{% if not forceHide %}
|
||||||
<div class="ots-page-actions">
|
<div class="ots-page-actions"{% if hideNavigation == "1" %} style="display:none!important"{% endif %}>
|
||||||
{% include "authed-theme-topbar.twig" ignore missing %}
|
{% include "authed-theme-topbar.twig" ignore missing %}
|
||||||
{% if currentUser.featureEnabled("drawer") %}
|
{% if currentUser.featureEnabled("drawer") %}
|
||||||
<div class="ots-topbar-action">
|
<div class="ots-topbar-action">
|
||||||
|
|||||||
@@ -103,17 +103,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-controller d-none ots-grid-controller">
|
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
|
||||||
</div>
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder"></i></button>
|
||||||
|
<div id="breadcrumbs"></div>
|
||||||
{% if currentUser.featureEnabled("campaign.add") %}
|
{% if currentUser.featureEnabled("campaign.add") %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Campaign" %}" href="{{ url_for("campaign.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Campaign" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new Campaign" %}" href="{{ url_for("campaign.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="campaigns" class="table table-striped" data-content-type="campaign" data-content-id-name="campaignId" data-state-preference-name="campaignGrid" style="width: 100%;">
|
<table id="campaigns" class="table table-striped" data-content-type="campaign" data-content-id-name="campaignId" data-state-preference-name="campaignGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -60,9 +60,9 @@
|
|||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
{% if currentUser.featureEnabled("command.add") %}
|
{% if currentUser.featureEnabled("command.add") %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add Command" %}" href="{{ url_for("command.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Command" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add Command" %}" href="{{ url_for("command.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="commands" class="table table-striped" data-state-preference-name="commandGrid">
|
<table id="commands" class="table table-striped" data-state-preference-name="commandGrid">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -88,17 +88,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-controller d-none ots-grid-controller">
|
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
|
||||||
</div>
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder"></i></button>
|
||||||
|
<div id="breadcrumbs"></div>
|
||||||
{% if currentUser.featureEnabled("dataset.add") %}
|
{% if currentUser.featureEnabled("dataset.add") %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new DataSet" %}" href="{{ url_for("dataSet.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add DataSet" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new DataSet" %}" href="{{ url_for("dataSet.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="datasets" class="table table-striped" data-state-preference-name="dataSetGrid" style="width: 100%;">
|
<table id="datasets" class="table table-striped" data-state-preference-name="dataSetGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -62,9 +62,9 @@
|
|||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
{% if currentUser.featureEnabled("daypart.add") %}
|
{% if currentUser.featureEnabled("daypart.add") %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Daypart" %}" href="{{ url_for("daypart.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Daypart" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new Daypart" %}" href="{{ url_for("daypart.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="dayparts" class="table table-striped" data-state-preference-name="daypartGrid">
|
<table id="dayparts" class="table table-striped" data-state-preference-name="daypartGrid">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -256,24 +256,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-controller d-none ots-grid-controller">
|
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{{ "Open / Close Folder Search options"|trans }}"><i class="fas fa-folder fa-1x"></i></button>
|
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
|
||||||
</div>
|
|
||||||
<div class="map-controller d-none pl-1 ots-grid-controller">
|
|
||||||
<button type="button" id="map_button" class="btn btn-icon btn-primary" title="{{ "Map"|trans }}"><i class="fa fa-map"></i></button>
|
|
||||||
</div>
|
|
||||||
<div class="list-controller d-none pl-1 ots-grid-controller">
|
|
||||||
<button type="button" id="list_button" class="btn btn-icon btn-primary" title="{{ "List"|trans }}"><i class="fa fa-list"></i></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{{ "Open / Close Folder Search options"|trans }}"><i class="fas fa-folder"></i></button>
|
||||||
|
<div id="breadcrumbs"></div>
|
||||||
|
<button type="button" id="map_button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{{ "Map"|trans }}"><i class="fa fa-map"></i></button>
|
||||||
|
<button type="button" id="list_button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{{ "List"|trans }}"><i class="fa fa-list"></i></button>
|
||||||
{% if currentUser.featureEnabled("displays.add") %}
|
{% if currentUser.featureEnabled("displays.add") %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a Display via user_code displayed on the Player screen" %}" href="{{ url_for("display.addViaCode.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Display" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a Display via user_code displayed on the Player screen" %}" href="{{ url_for("display.addViaCode.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="displays" class="table table-striped" data-content-type="display" data-content-id-name="displayId" data-state-preference-name="displayGrid" style="width: 100%;">
|
<table id="displays" class="table table-striped" data-content-type="display" data-content-id-name="displayId" data-state-preference-name="displayGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -102,18 +102,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-controller d-none ots-grid-controller">
|
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder"></i></button>
|
||||||
|
<div id="breadcrumbs"></div>
|
||||||
{% if currentUser.featureEnabled("displaygroup.add") %}
|
{% if currentUser.featureEnabled("displaygroup.add") %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add Display Group" %}" href="{{ url_for("displayGroup.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Group" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add Display Group" %}" href="{{ url_for("displayGroup.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="displaygroups" class="table table-striped" data-content-type="displayGroup" data-content-id-name="displayGroupId" data-state-preference-name="displayGroupGrid" style="width: 100%;">
|
<table id="displaygroups" class="table table-striped" data-content-type="displayGroup" data-content-id-name="displayGroupId" data-state-preference-name="displayGroupGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -60,9 +60,9 @@
|
|||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
{% if currentUser.featureEnabled("displayprofile.add") %}
|
{% if currentUser.featureEnabled("displayprofile.add") %}
|
||||||
<button class="btn btn-sm btn-info XiboFormButton" title="{% trans "Add a new Display Settings Profile" %}" href="{{ url_for("displayProfile.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Profile" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new Display Settings Profile" %}" href="{{ url_for("displayProfile.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="displayProfiles" class="table table-striped" data-state-preference-name="displayProfileGrid">
|
<table id="displayProfiles" class="table table-striped" data-state-preference-name="displayProfileGrid">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -43,9 +43,9 @@
|
|||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
{% if currentUser.featureEnabled("font.add") %}
|
{% if currentUser.featureEnabled("font.add") %}
|
||||||
<button class="btn btn-sm btn-success" href="#" id="fontUploadForm" title="{% trans "Add a new Font" %}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Upload Font" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn" href="#" id="fontUploadForm" title="{% trans "Add a new Font" %}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="fonts" class="table table-striped" data-state-preference-name="fontGrid">
|
<table id="fonts" class="table table-striped" data-state-preference-name="fontGrid">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -157,19 +157,16 @@
|
|||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="folder-controller d-none ots-grid-controller">
|
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder"></i></button>
|
||||||
|
<div id="breadcrumbs"></div>
|
||||||
{% if currentUser.featureEnabled("layout.add") %}
|
{% if currentUser.featureEnabled("layout.add") %}
|
||||||
<button class="btn btn-sm btn-success layout-add-button" title="{% trans "Add a new Layout and jump to the layout editor." %}" href="{{ url_for("layout.add") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Layout" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn layout-add-button" title="{% trans "Add a new Layout and jump to the layout editor." %}" href="{{ url_for("layout.add") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
<button class="btn btn-sm btn-info" id="layoutUploadForm" title="{% trans "Import a Layout from a ZIP file." %}" href="#"><i class="fa fa-cloud-download" aria-hidden="true"></i> {% trans "Import" %}</button>
|
<button class="btn btn-sm btn-info ots-toolbar-btn" id="layoutUploadForm" title="{% trans "Import a Layout from a ZIP file." %}" href="#"><i class="fa fa-cloud-download" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="layouts" class="table table-striped responsive nowrap" data-content-type="layout" data-content-id-name="layoutId" data-state-preference-name="layoutGrid" style="width: 100%;">
|
<table id="layouts" class="table table-striped responsive nowrap" data-content-type="layout" data-content-id-name="layoutId" data-state-preference-name="layoutGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -133,23 +133,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-controller d-none ots-grid-controller">
|
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
|
||||||
</div>
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder"></i></button>
|
||||||
|
<div id="breadcrumbs"></div>
|
||||||
{% if currentUser.featureEnabledCount(["library.add", "library.modify"]) > 0 or settings.SETTING_LIBRARY_TIDY_ENABLED == 1 %}
|
{% if currentUser.featureEnabledCount(["library.add", "library.modify"]) > 0 or settings.SETTING_LIBRARY_TIDY_ENABLED == 1 %}
|
||||||
{% if currentUser.featureEnabled("library.add") %}
|
{% if currentUser.featureEnabled("library.add") %}
|
||||||
<button class="btn btn-sm btn-success" href="#" id="libraryUploadForm" title="{% trans "Add a new media item to the library" %}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Media" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn" href="#" id="libraryUploadForm" title="{% trans "Add a new media item to the library" %}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new media item to the library via external URL" %}" href="{{ url_for("library.uploadUrl.form") }}"><i class="fa fa-link" aria-hidden="true"></i> {% trans "Add URL" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new media item to the library via external URL" %}" href="{{ url_for("library.uploadUrl.form") }}"><i class="fa fa-link" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if settings.SETTING_LIBRARY_TIDY_ENABLED == 1 and currentUser.featureEnabled("library.modify") %}
|
{% if settings.SETTING_LIBRARY_TIDY_ENABLED == 1 and currentUser.featureEnabled("library.modify") %}
|
||||||
<button class="btn btn-sm btn-warning XiboFormButton btn-tidy" title="{% trans "Run through the library and remove unused and unnecessary files" %}" href="{{ url_for("library.tidy.form") }}"><i class="fa fa-broom" aria-hidden="true"></i> {% trans "Tidy" %}</button>
|
<button class="btn btn-sm btn-warning ots-toolbar-btn XiboFormButton btn-tidy" title="{% trans "Run through the library and remove unused and unnecessary files" %}" href="{{ url_for("library.tidy.form") }}"><i class="fa fa-broom" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="libraryItems" class="table table-striped responsive nowrap" data-content-type="media" data-content-id-name="mediaId" data-state-preference-name="libraryGrid" style="width: 100%;">
|
<table id="libraryItems" class="table table-striped responsive nowrap" data-content-type="media" data-content-id-name="mediaId" data-state-preference-name="libraryGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -89,17 +89,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-controller d-none ots-grid-controller">
|
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
|
||||||
</div>
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder"></i></button>
|
||||||
|
<div id="breadcrumbs"></div>
|
||||||
{% if currentUser.featureEnabled("menuBoard.add") %}
|
{% if currentUser.featureEnabled("menuBoard.add") %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Menu Board" %}" href="{{ url_for("menuBoard.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Menu Board" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new Menu Board" %}" href="{{ url_for("menuBoard.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="menuBoards" class="table table-striped responsive nowrap" data-content-type="menuBoard" data-content-id-name="menuId" data-state-preference-name="menuBoardGrid" style="width: 100%;">
|
<table id="menuBoards" class="table table-striped responsive nowrap" data-content-type="menuBoard" data-content-id-name="menuId" data-state-preference-name="menuBoardGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -121,6 +119,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="modules" class="table table-striped" data-state-preference-name="moduleGrid">
|
<table id="modules" class="table table-striped" data-state-preference-name="moduleGrid">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
--color-text-tertiary: #e2e8f0;
|
--color-text-tertiary: #e2e8f0;
|
||||||
--color-text-inverse: #ffffff;
|
--color-text-inverse: #ffffff;
|
||||||
--color-on-primary: #ffffff;
|
--color-on-primary: #ffffff;
|
||||||
|
--ots-sidebar-content-gap: 12px;
|
||||||
|
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
@@ -173,24 +174,82 @@ body {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-nav {
|
.sidebar-nav,
|
||||||
|
.ots-sidebar-nav {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 64px 0 60px;
|
padding: 4px 0 16px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Extra top padding when sidebar is collapsed or expanded so items clear header */
|
.ots-sidebar ul.sidebar.ots-sidebar-nav {
|
||||||
.ots-sidebar.collapsed .sidebar-nav,
|
position: relative !important;
|
||||||
.ots-sidebar-collapsed .sidebar-nav,
|
top: auto !important;
|
||||||
.ots-sidebar:not(.collapsed) .sidebar-nav,
|
bottom: auto !important;
|
||||||
.ots-sidebar-collapsed:not(.collapsed) .sidebar-nav {
|
}
|
||||||
padding-top: 72px !important;
|
|
||||||
|
/* Ensure nav list starts below header in expanded/collapsed states */
|
||||||
|
.ots-sidebar.collapsed .ots-sidebar-nav,
|
||||||
|
.ots-sidebar-collapsed .ots-sidebar-nav {
|
||||||
|
padding-top: 4px !important;
|
||||||
|
padding-bottom: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-sidebar:not(.collapsed) .ots-sidebar-nav {
|
||||||
|
padding-top: 4px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-nav li {
|
.sidebar-nav li {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Reset any fixed heights from base theme that cause overlapping */
|
||||||
|
.ots-sidebar .sidebar-list {
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Expanded sidebar submenu styling (prevents overlap) */
|
||||||
|
.ots-sidebar .sidebar-submenu {
|
||||||
|
list-style: none;
|
||||||
|
margin: 4px 0 8px;
|
||||||
|
padding: 0 0 0 12px;
|
||||||
|
border-left: 1px solid var(--ots-sidebar-border);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-sidebar .sidebar-group.is-open .sidebar-submenu,
|
||||||
|
.ots-sidebar .sidebar-group-toggle[aria-expanded="true"] ~ .sidebar-submenu,
|
||||||
|
.ots-sidebar .sidebar-group-toggle[aria-expanded="true"] + .sidebar-submenu {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-sidebar .sidebar-submenu .sidebar-list {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-sidebar .sidebar-submenu .sidebar-list > a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 4px 0;
|
||||||
|
background: var(--ots-sidebar-submenu-bg);
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 8px 48px 8px 28px;
|
||||||
|
border-left: 4px solid var(--ots-sidebar-submenu-border);
|
||||||
|
border-radius: var(--ots-sidebar-item-radius);
|
||||||
|
color: var(--ots-sidebar-link);
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
min-height: var(--ots-sidebar-item-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-sidebar .sidebar-submenu .sidebar-list > a:hover {
|
||||||
|
background: var(--ots-sidebar-submenu-hover-bg);
|
||||||
|
color: var(--ots-sidebar-link-hover-text);
|
||||||
|
}
|
||||||
|
|
||||||
/* Compatibility: Xibo sidebar markup uses `sidebar-list`, `sidebar-main`, `sidebar-title`.
|
/* Compatibility: Xibo sidebar markup uses `sidebar-list`, `sidebar-main`, `sidebar-title`.
|
||||||
Map those into the modern `.nav-item/.nav-text/.nav-icon` style system so styles apply.
|
Map those into the modern `.nav-item/.nav-text/.nav-icon` style system so styles apply.
|
||||||
*/
|
*/
|
||||||
@@ -620,10 +679,10 @@ body:not(.ots-sidebar-collapsed) .ots-topbar-strip {
|
|||||||
display: inline-flex !important;
|
display: inline-flex !important;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
justify-content: center !important;
|
justify-content: center !important;
|
||||||
width: 36px !important;
|
width: 28px !important;
|
||||||
height: 36px !important;
|
height: 28px !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 6px !important;
|
||||||
color: var(--color-text-secondary) !important;
|
color: var(--color-text-secondary) !important;
|
||||||
transition: all 150ms ease !important;
|
transition: all 150ms ease !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
@@ -648,11 +707,11 @@ body:not(.ots-sidebar-collapsed) .ots-topbar-strip {
|
|||||||
/* Avatar in topbar */
|
/* Avatar in topbar */
|
||||||
.ots-topbar-action img.nav-avatar {
|
.ots-topbar-action img.nav-avatar {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
width: 32px !important;
|
width: 24px !important;
|
||||||
height: 32px !important;
|
height: 24px !important;
|
||||||
border-radius: 50% !important;
|
border-radius: 50% !important;
|
||||||
object-fit: cover !important;
|
object-fit: cover !important;
|
||||||
border: 2px solid transparent !important;
|
border: 1px solid transparent !important;
|
||||||
transition: border-color 150ms ease !important;
|
transition: border-color 150ms ease !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,6 +857,12 @@ body:not(.ots-sidebar-collapsed) .ots-topbar-strip {
|
|||||||
/* Ensure sidebar items are visible and above header when sidebar is collapsed */
|
/* Ensure sidebar items are visible and above header when sidebar is collapsed */
|
||||||
.ots-sidebar.collapsed {
|
.ots-sidebar.collapsed {
|
||||||
z-index: 1200;
|
z-index: 1200;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allow flyout menus to escape the sidebar-content scroll container */
|
||||||
|
.ots-sidebar.collapsed .sidebar-content {
|
||||||
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-sidebar.collapsed .ots-nav-icon {
|
.ots-sidebar.collapsed .ots-nav-icon {
|
||||||
@@ -1965,6 +2030,80 @@ body .panel .panel-heading,
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
TOOLBAR - Icon-only action buttons
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.ots-table-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0 4px 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-table-toolbar .ots-toolbar-btn,
|
||||||
|
.ots-table-toolbar .btn.ots-toolbar-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-table-toolbar .ots-toolbar-btn:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-table-toolbar .ots-toolbar-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Folder toggle in toolbar */
|
||||||
|
.ots-table-toolbar .folder-controller.btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Breadcrumbs in toolbar */
|
||||||
|
.ots-table-toolbar #breadcrumbs {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-tertiary);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* View toggle buttons (map/list) */
|
||||||
|
.ots-table-toolbar .map-controller,
|
||||||
|
.ots-table-toolbar .list-controller {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
#datatable-container {
|
#datatable-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
@@ -3063,6 +3202,44 @@ small {
|
|||||||
border-color: var(--color-primary) !important;
|
border-color: var(--color-primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Properties Panel - Override layout-editor bundle backgrounds */
|
||||||
|
.properties-panel-container #properties-panel {
|
||||||
|
background-color: var(--color-surface) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.properties-panel-container #properties-panel #properties-panel-form-container {
|
||||||
|
background: var(--color-surface) !important;
|
||||||
|
background-color: var(--color-surface) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.properties-panel-container #properties-panel .loading-container {
|
||||||
|
background: var(--color-surface) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Properties Panel Tabs - Override layout-editor bundle (2-ID specificity) */
|
||||||
|
.properties-panel-container #properties-panel #properties-panel-form-container .form-container .nav > li > a {
|
||||||
|
background-color: var(--color-surface) !important;
|
||||||
|
background: var(--color-surface) !important;
|
||||||
|
color: var(--color-text-secondary) !important;
|
||||||
|
border-color: var(--color-primary) !important;
|
||||||
|
border-width: 0 0 2px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.properties-panel-container #properties-panel #properties-panel-form-container .form-container .nav > li > a:hover {
|
||||||
|
background-color: rgba(59, 130, 246, 0.15) !important;
|
||||||
|
background: rgba(59, 130, 246, 0.15) !important;
|
||||||
|
color: var(--color-text-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.properties-panel-container #properties-panel #properties-panel-form-container .form-container .nav > li > a.active,
|
||||||
|
.properties-panel-container #properties-panel #properties-panel-form-container .form-container .nav > li > a.active:hover {
|
||||||
|
background-color: var(--color-primary) !important;
|
||||||
|
background: var(--color-primary) !important;
|
||||||
|
color: white !important;
|
||||||
|
border-color: var(--color-primary) !important;
|
||||||
|
opacity: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* Cards */
|
/* Cards */
|
||||||
.card {
|
.card {
|
||||||
background-color: var(--color-surface) !important;
|
background-color: var(--color-surface) !important;
|
||||||
@@ -3168,7 +3345,7 @@ a.text-muted:hover {
|
|||||||
:root {
|
:root {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
--ots-bg: #0b111a;
|
--ots-bg: var(--color-background);
|
||||||
--ots-surface: #141c2b;
|
--ots-surface: #141c2b;
|
||||||
--ots-surface-2: #1b2436;
|
--ots-surface-2: #1b2436;
|
||||||
--ots-surface-3: #222c3f;
|
--ots-surface-3: #222c3f;
|
||||||
@@ -3195,6 +3372,25 @@ a.text-muted:hover {
|
|||||||
--ots-transition: 160ms ease;
|
--ots-transition: 160ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Light mode token overrides so layout backgrounds follow theme */
|
||||||
|
body.ots-light-mode {
|
||||||
|
--ots-bg: var(--color-background);
|
||||||
|
--ots-surface: var(--color-surface);
|
||||||
|
--ots-surface-2: var(--color-surface-elevated);
|
||||||
|
--ots-surface-3: var(--color-surface-elevated);
|
||||||
|
--ots-border: var(--color-border);
|
||||||
|
--ots-border-soft: var(--color-border-light);
|
||||||
|
--ots-text: var(--color-text-primary);
|
||||||
|
--ots-text-muted: var(--color-text-secondary);
|
||||||
|
--ots-text-faint: var(--color-text-tertiary);
|
||||||
|
--ots-primary: var(--color-primary);
|
||||||
|
--ots-primary-2: var(--color-primary-dark);
|
||||||
|
--ots-success: var(--color-success);
|
||||||
|
--ots-warning: var(--color-warning);
|
||||||
|
--ots-danger: var(--color-danger);
|
||||||
|
--ots-info: var(--color-info);
|
||||||
|
}
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
GLOBAL
|
GLOBAL
|
||||||
============================================================================= */
|
============================================================================= */
|
||||||
@@ -3254,6 +3450,11 @@ hr {
|
|||||||
background: var(--ots-bg);
|
background: var(--ots-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#content-wrapper {
|
||||||
|
padding-left: var(--ots-sidebar-content-gap);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
.page-content {
|
.page-content {
|
||||||
padding-top: 24px;
|
padding-top: 24px;
|
||||||
}
|
}
|
||||||
@@ -3777,3 +3978,101 @@ textarea:focus {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--ots-text-muted);
|
color: var(--ots-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
NUCLEAR: Collapsed Sidebar Icon Centering
|
||||||
|
=============================================================================
|
||||||
|
This section is placed at the VERY END of the last-loaded stylesheet
|
||||||
|
(override-styles.twig is inlined after all linked CSS files).
|
||||||
|
It uses high-specificity selectors with !important to guarantee
|
||||||
|
these rules win the cascade over everything else.
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
/* The 44×44 icon button — grid centering is the most robust method */
|
||||||
|
#sidebar-wrapper.ots-sidebar.collapsed > .sidebar-content > .ots-sidebar-nav > .sidebar-list > a,
|
||||||
|
#sidebar-wrapper.ots-sidebar.collapsed > .sidebar-content > .ots-sidebar-nav > .sidebar-group > .sidebar-group-toggle {
|
||||||
|
display: grid !important;
|
||||||
|
place-items: center !important;
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
grid-template-rows: 1fr !important;
|
||||||
|
width: 40px !important;
|
||||||
|
height: 40px !important;
|
||||||
|
min-width: 40px !important;
|
||||||
|
max-width: 40px !important;
|
||||||
|
min-height: 40px !important;
|
||||||
|
max-height: 40px !important;
|
||||||
|
margin: 2px auto !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
padding-right: 0 !important;
|
||||||
|
border: 0 !important;
|
||||||
|
border-left: 0 !important;
|
||||||
|
border-right: 0 !important;
|
||||||
|
float: none !important;
|
||||||
|
text-indent: 0 !important;
|
||||||
|
gap: 0 !important;
|
||||||
|
column-gap: 0 !important;
|
||||||
|
row-gap: 0 !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The icon span — centered by grid parent, centers its own ::before glyph */
|
||||||
|
#sidebar-wrapper.ots-sidebar.collapsed > .sidebar-content > .ots-sidebar-nav > .sidebar-list > a > .ots-nav-icon,
|
||||||
|
#sidebar-wrapper.ots-sidebar.collapsed > .sidebar-content > .ots-sidebar-nav > .sidebar-group > .sidebar-group-toggle > .ots-nav-icon {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
width: 20px !important;
|
||||||
|
height: 20px !important;
|
||||||
|
font-size: 20px !important;
|
||||||
|
line-height: 1 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
text-indent: 0 !important;
|
||||||
|
text-align: center !important;
|
||||||
|
float: none !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The FA ::before glyph — block-level and centered */
|
||||||
|
#sidebar-wrapper.ots-sidebar.collapsed > .sidebar-content > .ots-sidebar-nav > .sidebar-list > a > .ots-nav-icon::before,
|
||||||
|
#sidebar-wrapper.ots-sidebar.collapsed > .sidebar-content > .ots-sidebar-nav > .sidebar-group > .sidebar-group-toggle > .ots-nav-icon::before {
|
||||||
|
display: block !important;
|
||||||
|
text-align: center !important;
|
||||||
|
width: 100% !important;
|
||||||
|
line-height: 1 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
text-indent: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The <li> wrapper — full width, centers the button */
|
||||||
|
#sidebar-wrapper.ots-sidebar.collapsed > .sidebar-content > .ots-sidebar-nav > .sidebar-list,
|
||||||
|
#sidebar-wrapper.ots-sidebar.collapsed > .sidebar-content > .ots-sidebar-nav > .sidebar-group {
|
||||||
|
display: flex !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
align-items: center !important;
|
||||||
|
width: 100% !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
padding-right: 0 !important;
|
||||||
|
text-indent: 0 !important;
|
||||||
|
float: none !important;
|
||||||
|
height: auto !important;
|
||||||
|
list-style: none !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The <ul> nav list — full width, no spacing that could offset children */
|
||||||
|
#sidebar-wrapper.ots-sidebar.collapsed > .sidebar-content > .ots-sidebar-nav {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
align-items: center !important;
|
||||||
|
width: 100% !important;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
padding-right: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
text-indent: 0 !important;
|
||||||
|
list-style: none !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,9 +62,9 @@
|
|||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
{% if currentUser.featureEnabled("playersoftware.add") %}
|
{% if currentUser.featureEnabled("playersoftware.add") %}
|
||||||
<button class="btn btn-sm btn-success" href="#" id="playerSoftwareUploadForm" title="{% trans "Upload a new Player Software file" %}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Upload Software" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn" href="#" id="playerSoftwareUploadForm" title="{% trans "Upload a new Player Software file" %}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="playerSoftwareItems" class="table table-striped" data-state-preference-name="playerSoftwareGrid">
|
<table id="playerSoftwareItems" class="table table-striped" data-state-preference-name="playerSoftwareGrid">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -132,17 +132,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-controller d-none ots-grid-controller">
|
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
|
||||||
</div>
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder"></i></button>
|
||||||
|
<div id="breadcrumbs"></div>
|
||||||
{% if currentUser.featureEnabled("playlist.add") %}
|
{% if currentUser.featureEnabled("playlist.add") %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add Playlist" %}" href="{{ url_for("playlist.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Playlist" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add Playlist" %}" href="{{ url_for("playlist.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="playlists" class="table table-striped" data-content-type="playlist"
|
<table id="playlists" class="table table-striped" data-content-type="playlist"
|
||||||
data-content-id-name="playlistId" data-state-preference-name="playlistGrid" style="width: 100%;">
|
data-content-id-name="playlistId" data-state-preference-name="playlistGrid" style="width: 100%;">
|
||||||
|
|||||||
@@ -60,9 +60,9 @@
|
|||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
{% if currentUser.featureEnabled("resolution.add") %}
|
{% if currentUser.featureEnabled("resolution.add") %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new resolution for use on layouts" %}" href="{{ url_for("resolution.add.form") }}"><i class="fa fa-plus" aria-hidden="true"></i> {% trans "Add Resolution" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new resolution for use on layouts" %}" href="{{ url_for("resolution.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="resolutions" class="table table-striped" data-state-preference-name="resolutionGrid">
|
<table id="resolutions" class="table table-striped" data-state-preference-name="resolutionGrid">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -199,9 +199,9 @@
|
|||||||
<div class="XiboSchedule card dashboard-card ots-table-card">
|
<div class="XiboSchedule card dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
{% if currentUser.featureEnabled("schedule.add") %}
|
{% if currentUser.featureEnabled("schedule.add") %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Scheduled event" %}" href="{{ url_for("schedule.add.form") }}"><i class="fa fa-plus" aria-hidden="true"></i> {% trans "Add Event" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new Scheduled event" %}" href="{{ url_for("schedule.add.form") }}"><i class="fa fa-plus" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<ul class="nav nav-tabs card-header-tabs">
|
<ul class="nav nav-tabs card-header-tabs">
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<div class="widget-body ots-displays-body">
|
<div class="widget-body ots-displays-body">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
{% if settings.SETTING_LIBRARY_TIDY_ENABLED %}
|
{% if settings.SETTING_LIBRARY_TIDY_ENABLED %}
|
||||||
<button class="btn btn-sm btn-danger XiboFormButton" title="{% trans "Run through the library and remove unused and unnecessary files" %}" href="{{ url_for("maintenance.libraryTidy.form") }}"><i class="fa fa-trash" aria-hidden="true"></i> {% trans "Tidy Library" %}</button>
|
<button class="btn btn-sm btn-danger ots-toolbar-btn XiboFormButton" title="{% trans "Run through the library and remove unused and unnecessary files" %}" href="{{ url_for("maintenance.libraryTidy.form") }}"><i class="fa fa-trash" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -74,17 +74,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-controller d-none ots-grid-controller">
|
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
|
||||||
</div>
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder"></i></button>
|
||||||
|
<div id="breadcrumbs"></div>
|
||||||
{% if currentUser.featureEnabled("display.syncAdd") %}
|
{% if currentUser.featureEnabled("display.syncAdd") %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Sync Group" %}" href="{{ url_for("syncgroup.form.add") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Sync Group" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new Sync Group" %}" href="{{ url_for("syncgroup.form.add") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="syncgroups" class="table table-striped" data-content-type="syncGroup" data-content-id-name="syncGroupId" data-state-preference-name="syncGroupGrid" style="width: 100%;">
|
<table id="syncgroups" class="table table-striped" data-content-type="syncGroup" data-content-id-name="syncGroupId" data-state-preference-name="syncGroupGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -48,8 +48,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Tag" %}" href="{{ url_for("tag.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Tag" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new Tag" %}" href="{{ url_for("tag.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="tags" class="table table-striped">
|
<table id="tags" class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -30,9 +30,9 @@
|
|||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
{% if settings.TASK_CONFIG_LOCKED_CHECKB == 0 or settings.TASK_CONFIG_LOCKED_CHECKB == "Unchecked" %}
|
{% if settings.TASK_CONFIG_LOCKED_CHECKB == 0 or settings.TASK_CONFIG_LOCKED_CHECKB == "Unchecked" %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" href="{{ url_for("task.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Task" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" href="{{ url_for("task.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="tasks" class="table table-striped" data-state-preference-name="taskGrid">
|
<table id="tasks" class="table table-striped" data-state-preference-name="taskGrid">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -74,17 +74,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-controller d-none ots-grid-controller">
|
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
|
||||||
</div>
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder"></i></button>
|
||||||
|
<div id="breadcrumbs"></div>
|
||||||
{% if currentUser.featureEnabled("template.add") %}
|
{% if currentUser.featureEnabled("template.add") %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Template and jump to the layout editor." %}" href="{{ url_for("template.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Template" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new Template and jump to the layout editor." %}" href="{{ url_for("template.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="templates" class="table table-striped" data-content-type="layout" data-content-id-name="layoutId" data-state-preference-name="templateGrid" style="width: 100%;">
|
<table id="templates" class="table table-striped" data-content-type="layout" data-content-id-name="layoutId" data-state-preference-name="templateGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -34,12 +34,10 @@
|
|||||||
* This function is kept as a no-op for backward compatibility.
|
* This function is kept as a no-op for backward compatibility.
|
||||||
*/
|
*/
|
||||||
function updateSidebarWidth() {
|
function updateSidebarWidth() {
|
||||||
// No-op: CSS handles layout via body.ots-sidebar-collapsed class
|
const sidebar = document.querySelector('.ots-sidebar');
|
||||||
if (window.__otsDebug) {
|
if (!sidebar) return;
|
||||||
const sidebar = document.querySelector('.ots-sidebar');
|
const w = sidebar.offsetWidth;
|
||||||
const collapsed = sidebar ? sidebar.classList.contains('collapsed') : false;
|
document.documentElement.style.setProperty('--ots-sidebar-actual-width', w + 'px');
|
||||||
console.log('[OTS] updateSidebarWidth (no-op, CSS-driven)', { collapsed });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,38 +45,41 @@
|
|||||||
* so nav items always begin below the header (logo + buttons).
|
* so nav items always begin below the header (logo + buttons).
|
||||||
*/
|
*/
|
||||||
function updateSidebarNavOffset() {
|
function updateSidebarNavOffset() {
|
||||||
const sidebar = document.querySelector('.ots-sidebar');
|
/* No-op: sidebar uses flex-direction:column so the header and
|
||||||
if (!sidebar) return;
|
nav content are separate flex children that never overlap.
|
||||||
const header = sidebar.querySelector('.sidebar-header');
|
Previously this set padding-top:~72px which created a huge gap. */
|
||||||
const nav = sidebar.querySelector('.sidebar-nav, .ots-sidebar-nav');
|
var nav = document.querySelector('.ots-sidebar .sidebar-nav, .ots-sidebar .ots-sidebar-nav');
|
||||||
if (!nav) return;
|
if (nav) {
|
||||||
const sidebarRect = sidebar.getBoundingClientRect();
|
try { nav.style.removeProperty('padding-top'); } catch(e) { nav.style.paddingTop = ''; }
|
||||||
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;
|
|
||||||
}
|
|
||||||
const gap = 8;
|
|
||||||
const paddingTop = offset > 0 ? offset + gap : '';
|
|
||||||
if (paddingTop) {
|
|
||||||
try {
|
|
||||||
nav.style.setProperty('padding-top', `${paddingTop}px`, 'important');
|
|
||||||
} catch (err) {
|
|
||||||
nav.style.paddingTop = `${paddingTop}px`;
|
|
||||||
}
|
|
||||||
if (window.__otsDebug) console.log('[OTS] updateSidebarNavOffset applied', { paddingTop });
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
nav.style.removeProperty('padding-top');
|
|
||||||
} catch (err) {
|
|
||||||
nav.style.paddingTop = '';
|
|
||||||
}
|
|
||||||
if (window.__otsDebug) console.log('[OTS] updateSidebarNavOffset cleared');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect whether the playlist/layout editor modal is open and toggle
|
||||||
|
* body.ots-playlist-editor-active accordingly. Because the editor is
|
||||||
|
* loaded via AJAX into #editor-container, a one-shot check at page-load
|
||||||
|
* is not enough – we use a MutationObserver that watches for DOM changes.
|
||||||
|
*/
|
||||||
|
function updatePlaylistEditorBackground() {
|
||||||
|
var isActive = !!document.querySelector('.editor-modal, #playlist-editor, #layout-editor');
|
||||||
|
document.body.classList.toggle('ots-playlist-editor-active', isActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start a MutationObserver that fires updatePlaylistEditorBackground
|
||||||
|
whenever children are added to or removed from the page. */
|
||||||
|
(function initEditorObserver() {
|
||||||
|
// Run once immediately
|
||||||
|
updatePlaylistEditorBackground();
|
||||||
|
|
||||||
|
var target = document.body;
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
var editorObs = new MutationObserver(function() {
|
||||||
|
updatePlaylistEditorBackground();
|
||||||
|
});
|
||||||
|
editorObs.observe(target, { childList: true, subtree: true });
|
||||||
|
})();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DISABLED: Cleanup function to remove inline styles that were forcing incorrect margins
|
* DISABLED: Cleanup function to remove inline styles that were forcing incorrect margins
|
||||||
* The sidebar layout is now controlled entirely by CSS variables and margin-left.
|
* The sidebar layout is now controlled entirely by CSS variables and margin-left.
|
||||||
@@ -225,8 +226,12 @@
|
|||||||
body.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
body.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
||||||
document.documentElement.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
document.documentElement.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
||||||
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false');
|
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false');
|
||||||
|
syncSubmenuDisplayForState(nowCollapsed);
|
||||||
updateSidebarNavOffset();
|
updateSidebarNavOffset();
|
||||||
updateSidebarStateClass();
|
updateSidebarStateClass();
|
||||||
|
// Update measured width immediately and again after CSS transition
|
||||||
|
updateSidebarWidth();
|
||||||
|
setTimeout(updateSidebarWidth, 250);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,8 +242,12 @@
|
|||||||
body.classList.remove('ots-sidebar-collapsed');
|
body.classList.remove('ots-sidebar-collapsed');
|
||||||
document.documentElement.classList.remove('ots-sidebar-collapsed');
|
document.documentElement.classList.remove('ots-sidebar-collapsed');
|
||||||
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, 'false');
|
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, 'false');
|
||||||
|
syncSubmenuDisplayForState(false);
|
||||||
updateSidebarNavOffset();
|
updateSidebarNavOffset();
|
||||||
updateSidebarStateClass();
|
updateSidebarStateClass();
|
||||||
|
// Update measured width immediately and again after CSS transition
|
||||||
|
updateSidebarWidth();
|
||||||
|
setTimeout(updateSidebarWidth, 250);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +268,69 @@
|
|||||||
updateSidebarStateClass();
|
updateSidebarStateClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
if (submenu.querySelector('.flyout-header')) return;
|
||||||
|
|
||||||
|
var toggle = group.querySelector('.sidebar-group-toggle');
|
||||||
|
if (!toggle) return;
|
||||||
|
|
||||||
|
var iconEl = toggle.querySelector('.ots-nav-icon');
|
||||||
|
var textEl = toggle.querySelector('.ots-nav-text');
|
||||||
|
if (!textEl) return;
|
||||||
|
|
||||||
|
var label = textEl.textContent.trim();
|
||||||
|
|
||||||
|
var header = document.createElement('li');
|
||||||
|
header.className = 'flyout-header';
|
||||||
|
header.setAttribute('aria-hidden', 'true');
|
||||||
|
|
||||||
|
if (iconEl) {
|
||||||
|
var icon = document.createElement('span');
|
||||||
|
icon.className = iconEl.className;
|
||||||
|
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) {
|
||||||
|
submenu.style.removeProperty('display');
|
||||||
|
} else {
|
||||||
|
var isOpen = group.classList.contains('is-open');
|
||||||
|
submenu.style.display = isOpen ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize sidebar section collapse/expand functionality
|
* Initialize sidebar section collapse/expand functionality
|
||||||
*/
|
*/
|
||||||
@@ -268,10 +340,15 @@
|
|||||||
groupToggles.forEach(toggle => {
|
groupToggles.forEach(toggle => {
|
||||||
const group = toggle.closest('.sidebar-group');
|
const group = toggle.closest('.sidebar-group');
|
||||||
const submenu = group ? group.querySelector('.sidebar-submenu') : null;
|
const submenu = group ? group.querySelector('.sidebar-submenu') : null;
|
||||||
const caret = toggle.querySelector('.sidebar-group-caret');
|
|
||||||
if (submenu) {
|
if (submenu) {
|
||||||
const isOpen = group.classList.contains('is-open');
|
const isOpen = group.classList.contains('is-open');
|
||||||
submenu.style.display = isOpen ? 'block' : 'none';
|
const sidebarEl = document.querySelector('.ots-sidebar');
|
||||||
|
const isCollapsed = sidebarEl && sidebarEl.classList.contains('collapsed');
|
||||||
|
if (!isCollapsed) {
|
||||||
|
submenu.style.display = isOpen ? 'block' : 'none';
|
||||||
|
} else {
|
||||||
|
submenu.style.removeProperty('display');
|
||||||
|
}
|
||||||
toggle.setAttribute('aria-expanded', isOpen.toString());
|
toggle.setAttribute('aria-expanded', isOpen.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,39 +359,16 @@
|
|||||||
const submenu = group ? group.querySelector('.sidebar-submenu') : null;
|
const submenu = group ? group.querySelector('.sidebar-submenu') : null;
|
||||||
if (!submenu) return;
|
if (!submenu) return;
|
||||||
|
|
||||||
|
const sidebarEl = document.querySelector('.ots-sidebar');
|
||||||
|
const isCollapsed = sidebarEl && sidebarEl.classList.contains('collapsed');
|
||||||
|
if (isCollapsed) return;
|
||||||
|
|
||||||
const isOpen = group.classList.contains('is-open');
|
const isOpen = group.classList.contains('is-open');
|
||||||
group.classList.toggle('is-open', !isOpen);
|
group.classList.toggle('is-open', !isOpen);
|
||||||
toggle.setAttribute('aria-expanded', (!isOpen).toString());
|
toggle.setAttribute('aria-expanded', (!isOpen).toString());
|
||||||
submenu.style.display = isOpen ? 'none' : 'block';
|
submenu.style.display = isOpen ? 'none' : 'block';
|
||||||
|
requestAnimationFrame(updateSidebarWidth);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (caret) {
|
|
||||||
caret.addEventListener('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
toggle.click();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1001,92 +1055,145 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move DataTable row dropdown menus to <body> so they escape
|
* DataTable row action dropdowns — fully managed by OTS theme.
|
||||||
* any overflow:hidden / overflow:auto ancestor containers.
|
|
||||||
*
|
*
|
||||||
* Xibo renders the row action button as:
|
* Bootstrap 4 + Popper.js positions menus with transform: translate3d(),
|
||||||
* <div class="btn-group pull-right dropdown-menu-container">
|
* but the theme CSS sets transform: none !important which breaks that.
|
||||||
* <button class="btn btn-white dropdown-toggle" data-toggle="dropdown">
|
* Detaching the menu to <body> also triggers Bootstrap's hide event.
|
||||||
* <div class="dropdown-menu dropdown-menu-right">...items...</div>
|
|
||||||
* </div>
|
|
||||||
*
|
*
|
||||||
* Bootstrap fires shown/hide.bs.dropdown on the toggle's parent element
|
* Solution: intercept the click on the toggle button in the capture phase
|
||||||
* (the .btn-group). We listen on document with a selector that matches
|
* (before Bootstrap sees it), prevent Bootstrap from handling it, and
|
||||||
* the actual Xibo markup.
|
* manage show/hide/position entirely ourselves.
|
||||||
*/
|
*/
|
||||||
function initRowDropdowns() {
|
function initRowDropdowns() {
|
||||||
var DROPDOWN_PARENT_SEL = '.dropdown-menu-container, .btn-group';
|
var TOGGLE_SEL = '.dropdown-menu-container [data-toggle="dropdown"], .btn-group [data-toggle="dropdown"]';
|
||||||
var SCOPE_SEL = '.XiboData ' + DROPDOWN_PARENT_SEL
|
var activeMenu = null; // currently open menu element (in <body>)
|
||||||
+ ', .XiboGrid ' + DROPDOWN_PARENT_SEL
|
var activeParent = null; // original parent (.btn-group / .dropdown-menu-container)
|
||||||
+ ', #datatable-container ' + DROPDOWN_PARENT_SEL
|
var activeTrigger = null;
|
||||||
+ ', .dataTables_wrapper ' + DROPDOWN_PARENT_SEL;
|
|
||||||
|
|
||||||
$(document).on('shown.bs.dropdown', SCOPE_SEL, function(e) {
|
function openMenu(trigger) {
|
||||||
var $container = $(this);
|
closeMenu(); // close any previously open menu first
|
||||||
var $trigger = $container.find('[data-toggle="dropdown"]');
|
|
||||||
var $menu = $container.find('.dropdown-menu');
|
|
||||||
if (!$menu.length || !$trigger.length) return;
|
|
||||||
|
|
||||||
// Mark the menu so we can style it and find it later
|
var $trigger = $(trigger);
|
||||||
$menu.addClass('ots-row-dropdown');
|
var $parent = $trigger.closest('.dropdown-menu-container, .btn-group');
|
||||||
|
var $menu = $parent.find('.dropdown-menu').first();
|
||||||
|
if (!$menu.length) return;
|
||||||
|
|
||||||
// Store original parent so we can put it back on close
|
activeTrigger = trigger;
|
||||||
$menu.data('ots-original-parent', $container);
|
activeParent = $parent[0];
|
||||||
|
|
||||||
// Get trigger position in viewport
|
// Snapshot button position while menu is still in the normal DOM
|
||||||
var btnRect = $trigger[0].getBoundingClientRect();
|
var btnRect = trigger.getBoundingClientRect();
|
||||||
|
|
||||||
// Move to body
|
// Detach menu and append to body so it escapes overflow:hidden
|
||||||
$menu.detach().appendTo('body');
|
$menu.detach().appendTo('body');
|
||||||
|
activeMenu = $menu[0];
|
||||||
|
|
||||||
// Position below the trigger button, aligned to the right edge
|
// Make visible-but-hidden so we can measure it
|
||||||
|
activeMenu.style.cssText = 'display:block !important; visibility:hidden !important; position:fixed !important; transform:none !important;';
|
||||||
|
var menuW = $menu.outerWidth() || 180;
|
||||||
|
var menuH = $menu.outerHeight() || 200;
|
||||||
|
|
||||||
|
// Compute position: below button, right-aligned to button's right edge
|
||||||
var top = btnRect.bottom + 2;
|
var top = btnRect.bottom + 2;
|
||||||
var left = btnRect.right - $menu.outerWidth();
|
var left = btnRect.right - menuW;
|
||||||
|
|
||||||
// Keep within viewport bounds
|
// Viewport bounds check
|
||||||
if (left < 8) left = 8;
|
if (left < 8) left = 8;
|
||||||
if (top + $menu.outerHeight() > window.innerHeight - 8) {
|
if (left + menuW > window.innerWidth - 8) left = window.innerWidth - menuW - 8;
|
||||||
// Open upward if no room below
|
if (top + menuH > window.innerHeight - 8) {
|
||||||
top = btnRect.top - $menu.outerHeight() - 2;
|
top = btnRect.top - menuH - 2; // flip above the button
|
||||||
}
|
}
|
||||||
if (top < 8) top = 8;
|
if (top < 8) top = 8;
|
||||||
|
|
||||||
$menu.css({
|
// Apply final position — every property with !important
|
||||||
position: 'fixed',
|
activeMenu.style.cssText = [
|
||||||
top: top + 'px',
|
'position:fixed !important',
|
||||||
left: left + 'px',
|
'top:' + top + 'px !important',
|
||||||
display: 'block'
|
'left:' + left + 'px !important',
|
||||||
});
|
'right:auto !important',
|
||||||
});
|
'bottom:auto !important',
|
||||||
|
'display:block !important',
|
||||||
|
'visibility:visible !important',
|
||||||
|
'transform:none !important',
|
||||||
|
'will-change:auto !important',
|
||||||
|
'margin:0 !important',
|
||||||
|
'z-index:2147483647 !important'
|
||||||
|
].join(';') + ';';
|
||||||
|
|
||||||
// When the dropdown closes, move the menu back to its original parent
|
$menu.addClass('ots-row-dropdown show');
|
||||||
$(document).on('hide.bs.dropdown', SCOPE_SEL, function(e) {
|
$parent.addClass('show');
|
||||||
var $container = $(this);
|
}
|
||||||
var $menu = $('body > .dropdown-menu.ots-row-dropdown').filter(function() {
|
|
||||||
var orig = $(this).data('ots-original-parent');
|
function closeMenu() {
|
||||||
return orig && orig[0] === $container[0];
|
if (!activeMenu) return;
|
||||||
});
|
var $menu = $(activeMenu);
|
||||||
if ($menu.length) {
|
var $parent = $(activeParent);
|
||||||
$menu.removeClass('ots-row-dropdown').css({ position: '', top: '', left: '', display: '' });
|
|
||||||
$menu.detach().appendTo($container);
|
// Clear all inline styles
|
||||||
|
activeMenu.style.cssText = '';
|
||||||
|
$menu.removeClass('ots-row-dropdown show');
|
||||||
|
|
||||||
|
// Move menu back to its original parent
|
||||||
|
$menu.detach().appendTo($parent);
|
||||||
|
$parent.removeClass('show open');
|
||||||
|
|
||||||
|
activeMenu = null;
|
||||||
|
activeParent = null;
|
||||||
|
activeTrigger = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept clicks in CAPTURE phase — runs BEFORE Bootstrap's handler.
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
var toggle = e.target.closest(TOGGLE_SEL);
|
||||||
|
if (!toggle) {
|
||||||
|
// Click was not on a toggle — close any open menu
|
||||||
|
// (unless click is inside the open menu itself)
|
||||||
|
if (activeMenu && !e.target.closest('.ots-row-dropdown')) {
|
||||||
|
closeMenu();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only handle toggles inside DataTable areas
|
||||||
|
var inTable = toggle.closest('.XiboData') || toggle.closest('.XiboGrid')
|
||||||
|
|| toggle.closest('#datatable-container') || toggle.closest('.dataTables_wrapper');
|
||||||
|
if (!inTable) return; // not a row dropdown — let Bootstrap handle it
|
||||||
|
|
||||||
|
// Prevent Bootstrap from handling this dropdown
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
|
// Toggle behaviour: if same trigger, close; otherwise open
|
||||||
|
if (activeTrigger === toggle && activeMenu) {
|
||||||
|
closeMenu();
|
||||||
|
} else {
|
||||||
|
openMenu(toggle);
|
||||||
|
}
|
||||||
|
}, true); // ← true = capture phase
|
||||||
|
|
||||||
|
// Close on Escape key
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape' && activeMenu) {
|
||||||
|
closeMenu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Also close any orphaned body-appended dropdown on outside click
|
// Close on any scroll (window or scrollable ancestor)
|
||||||
$(document).on('click', function(e) {
|
window.addEventListener('scroll', function() {
|
||||||
var $openMenus = $('body > .dropdown-menu.ots-row-dropdown');
|
if (activeMenu) closeMenu();
|
||||||
if (!$openMenus.length) return;
|
}, true);
|
||||||
// If the click is inside the menu itself, let it through
|
|
||||||
if ($(e.target).closest('.ots-row-dropdown').length) return;
|
// Block Bootstrap's show/hide events for DataTable row dropdowns
|
||||||
$openMenus.each(function() {
|
// so it doesn't interfere with our manual management.
|
||||||
var $menu = $(this);
|
$(document).on('show.bs.dropdown hide.bs.dropdown', function(e) {
|
||||||
var $parent = $menu.data('ots-original-parent');
|
var toggle = e.relatedTarget;
|
||||||
if ($parent && $parent.length) {
|
if (!toggle) return;
|
||||||
$menu.removeClass('ots-row-dropdown').css({ position: '', top: '', left: '', display: '' });
|
var inTable = toggle.closest('.XiboData') || toggle.closest('.XiboGrid')
|
||||||
$menu.detach().appendTo($parent);
|
|| toggle.closest('#datatable-container') || toggle.closest('.dataTables_wrapper');
|
||||||
$parent.removeClass('open show');
|
if (inTable) {
|
||||||
}
|
e.preventDefault();
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1096,6 +1203,7 @@
|
|||||||
function init() {
|
function init() {
|
||||||
initSidebarToggle();
|
initSidebarToggle();
|
||||||
initSidebarSectionToggles();
|
initSidebarSectionToggles();
|
||||||
|
buildFlyoutHeaders();
|
||||||
initThemeToggle();
|
initThemeToggle();
|
||||||
initDropdowns();
|
initDropdowns();
|
||||||
initRowDropdowns();
|
initRowDropdowns();
|
||||||
@@ -1107,11 +1215,13 @@
|
|||||||
initChartSafeguard();
|
initChartSafeguard();
|
||||||
updateSidebarWidth();
|
updateSidebarWidth();
|
||||||
updateSidebarNavOffset();
|
updateSidebarNavOffset();
|
||||||
|
updatePlaylistEditorBackground();
|
||||||
// updateSidebarGap() disabled - use CSS variables instead
|
// updateSidebarGap() disabled - use CSS variables instead
|
||||||
initUserProfileQrFix();
|
initUserProfileQrFix();
|
||||||
var debouncedUpdate = debounce(function() {
|
var debouncedUpdate = debounce(function() {
|
||||||
updateSidebarNavOffset();
|
updateSidebarNavOffset();
|
||||||
updateSidebarWidth();
|
updateSidebarWidth();
|
||||||
|
updatePlaylistEditorBackground();
|
||||||
// updateSidebarGap() disabled - use CSS variables instead
|
// updateSidebarGap() disabled - use CSS variables instead
|
||||||
}, 120);
|
}, 120);
|
||||||
window.addEventListener('resize', debouncedUpdate);
|
window.addEventListener('resize', debouncedUpdate);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="transitions" class="table table-striped">
|
<table id="transitions" class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -58,9 +58,9 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{% set addUserFormUrl = url_for("user.onboarding.form") %}
|
{% set addUserFormUrl = url_for("user.onboarding.form") %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button id="user-add-button" class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new User" %}" href="{{ addUserFormUrl }}"><i class="fa fa-user-plus" aria-hidden="true"></i> {% trans "Add User" %}</button>
|
<button id="user-add-button" class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new User" %}" href="{{ addUserFormUrl }}"><i class="fa fa-user-plus" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="users" class="table table-striped" data-state-preference-name="userGrid">
|
<table id="users" class="table table-striped" data-state-preference-name="userGrid">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -40,9 +40,9 @@
|
|||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<div class="ots-table-toolbar">
|
<div class="ots-table-toolbar">
|
||||||
{% if currentUser.isSuperAdmin() %}
|
{% if currentUser.isSuperAdmin() %}
|
||||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new User Group" %}" href="{{ url_for("group.add.form") }}"><i class="fa fa-users" aria-hidden="true"></i> {% trans "Add Group" %}</button>
|
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title="{% trans "Add a new User Group" %}" href="{{ url_for("group.add.form") }}"><i class="fa fa-users" aria-hidden="true"></i></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<table id="userGroups" class="table table-striped" data-state-preference-name="userGroupGrid">
|
<table id="userGroups" class="table table-striped" data-state-preference-name="userGroupGrid">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
Reference in New Issue
Block a user