semi functional
This commit is contained in:
368
custom/otssignange/views/theme-scripts.twig
Normal file
368
custom/otssignange/views/theme-scripts.twig
Normal file
@@ -0,0 +1,368 @@
|
||||
/**
|
||||
* OTS Signage Modern Theme - Client-Side Utilities
|
||||
* Sidebar toggle, dropdown menus, and UI interactions
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const STORAGE_KEYS = {
|
||||
sidebarCollapsed: 'otsTheme:sidebarCollapsed'
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize sidebar toggle functionality
|
||||
*/
|
||||
function initSidebarToggle() {
|
||||
const toggleBtn = document.querySelector('[data-action="toggle-sidebar"]');
|
||||
const sidebar = document.querySelector('.ots-sidebar');
|
||||
|
||||
if (!toggleBtn || !sidebar) return;
|
||||
|
||||
toggleBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
sidebar.classList.toggle('active');
|
||||
});
|
||||
|
||||
// Close sidebar when clicking outside on mobile
|
||||
document.addEventListener('click', function(e) {
|
||||
if (window.innerWidth <= 768) {
|
||||
const isClickInsideSidebar = sidebar.contains(e.target);
|
||||
const isClickOnToggle = toggleBtn.contains(e.target);
|
||||
|
||||
if (!isClickInsideSidebar && !isClickOnToggle && sidebar.classList.contains('active')) {
|
||||
sidebar.classList.remove('active');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize dropdown menus
|
||||
*/
|
||||
function initDropdowns() {
|
||||
const dropdowns = document.querySelectorAll('.dropdown');
|
||||
|
||||
dropdowns.forEach(dropdown => {
|
||||
const button = dropdown.querySelector('.dropdown-menu');
|
||||
|
||||
if (!button) return;
|
||||
|
||||
const menu = dropdown.querySelector('.dropdown-menu');
|
||||
|
||||
// Toggle menu on button click
|
||||
dropdown.addEventListener('click', function(e) {
|
||||
if (e.target.closest('.user-btn') || e.target.closest('[aria-label="User menu"]')) {
|
||||
e.preventDefault();
|
||||
dropdown.classList.toggle('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Close menu when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!dropdown.contains(e.target)) {
|
||||
dropdown.classList.remove('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize search functionality
|
||||
*/
|
||||
function initSearch() {
|
||||
const searchForm = document.querySelector('.topbar-search');
|
||||
if (!searchForm) return;
|
||||
|
||||
const input = searchForm.querySelector('.search-input');
|
||||
if (input) {
|
||||
input.addEventListener('focus', function() {
|
||||
searchForm.style.borderColor = 'var(--color-primary)';
|
||||
});
|
||||
input.addEventListener('blur', function() {
|
||||
searchForm.style.borderColor = 'var(--color-border)';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize page specific interactions
|
||||
*/
|
||||
function initPageInteractions() {
|
||||
// Displays page - folder selection
|
||||
const folderItems = document.querySelectorAll('.folder-item');
|
||||
folderItems.forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
folderItems.forEach(f => f.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Filter collapse toggle
|
||||
const filterCollapseBtn = document.querySelector('#ots-filter-collapse-btn');
|
||||
const filterContent = document.querySelector('#ots-filter-content');
|
||||
|
||||
if (filterCollapseBtn && filterContent) {
|
||||
let isCollapsed = false;
|
||||
|
||||
filterCollapseBtn.addEventListener('click', function() {
|
||||
isCollapsed = !isCollapsed;
|
||||
filterContent.classList.toggle('collapsed', isCollapsed);
|
||||
|
||||
// Rotate icon
|
||||
const icon = filterCollapseBtn.querySelector('i');
|
||||
icon.classList.toggle('fa-chevron-up');
|
||||
icon.classList.toggle('fa-chevron-down');
|
||||
|
||||
// Save preference to localStorage
|
||||
localStorage.setItem('ots-filter-collapsed', isCollapsed);
|
||||
});
|
||||
|
||||
// Restore saved preference
|
||||
const savedState = localStorage.getItem('ots-filter-collapsed');
|
||||
if (savedState === 'true') {
|
||||
isCollapsed = true;
|
||||
filterContent.classList.add('collapsed');
|
||||
const icon = filterCollapseBtn.querySelector('i');
|
||||
icon.classList.remove('fa-chevron-up');
|
||||
icon.classList.add('fa-chevron-down');
|
||||
}
|
||||
}
|
||||
|
||||
// Displays page - folder tree toggle layout
|
||||
const folderToggleBtn = document.querySelector('#folder-tree-select-folder-button');
|
||||
const folderContainer = document.querySelector('.grid-with-folders-container');
|
||||
const folderTree = document.querySelector('#grid-folder-filter');
|
||||
|
||||
if (folderToggleBtn && folderContainer && folderTree) {
|
||||
let debounceTimeout;
|
||||
|
||||
const syncFolderLayout = () => {
|
||||
// Check actual visibility using computed styles
|
||||
const computedStyle = window.getComputedStyle(folderTree);
|
||||
const isHidden = computedStyle.display === 'none' ||
|
||||
computedStyle.visibility === 'hidden' ||
|
||||
folderTree.offsetHeight === 0;
|
||||
|
||||
console.log('Folder collapse sync:', {
|
||||
isHidden,
|
||||
display: computedStyle.display,
|
||||
visibility: computedStyle.visibility,
|
||||
offsetHeight: folderTree.offsetHeight
|
||||
});
|
||||
|
||||
folderContainer.classList.toggle('ots-folder-collapsed', !!isHidden);
|
||||
|
||||
// Log the result
|
||||
console.log('Container classes:', folderContainer.className);
|
||||
console.log('Grid template columns:', window.getComputedStyle(folderContainer).gridTemplateColumns);
|
||||
|
||||
// Force reflow
|
||||
folderContainer.offsetHeight;
|
||||
};
|
||||
|
||||
const debouncedSync = () => {
|
||||
clearTimeout(debounceTimeout);
|
||||
debounceTimeout = setTimeout(syncFolderLayout, 50);
|
||||
};
|
||||
|
||||
// Watch for style/class changes on folderTree (let Xibo's code run first)
|
||||
const treeObserver = new MutationObserver(() => {
|
||||
console.log('Folder tree mutation detected, debouncing sync...');
|
||||
debouncedSync();
|
||||
});
|
||||
treeObserver.observe(folderTree, {
|
||||
attributes: true,
|
||||
attributeFilter: ['style', 'class']
|
||||
});
|
||||
|
||||
// Initial sync
|
||||
syncFolderLayout();
|
||||
|
||||
// Monitor the folder tree's parent for display changes
|
||||
const parentObserver = new MutationObserver(debouncedSync);
|
||||
const treeParent = folderTree.parentElement;
|
||||
if (treeParent) {
|
||||
parentObserver.observe(treeParent, {
|
||||
childList: false,
|
||||
attributes: true,
|
||||
subtree: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Media page - item selection
|
||||
const mediaItems = document.querySelectorAll('.media-item');
|
||||
mediaItems.forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
this.style.opacity = '0.7';
|
||||
setTimeout(() => this.style.opacity = '1', 200);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sidebar responsive
|
||||
*/
|
||||
function makeResponsive() {
|
||||
const sidebar = document.querySelector('.ots-sidebar');
|
||||
const main = document.querySelector('.ots-main');
|
||||
|
||||
if (!sidebar) return;
|
||||
|
||||
// Add toggle button for mobile
|
||||
if (window.innerWidth <= 768) {
|
||||
sidebar.classList.add('mobile');
|
||||
}
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
if (window.innerWidth > 768) {
|
||||
sidebar.classList.remove('mobile', 'active');
|
||||
} else {
|
||||
sidebar.classList.add('mobile');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent Chart.js errors when chart elements are missing
|
||||
*/
|
||||
function initChartSafeguard() {
|
||||
if (!window.Chart) return;
|
||||
|
||||
if (typeof window.Chart.acquireContext === 'function') {
|
||||
window.Chart.acquireContext = function(item) {
|
||||
if (!item) return null;
|
||||
|
||||
const candidate = item.length ? item[0] : item;
|
||||
if (candidate && typeof candidate.getContext === 'function') {
|
||||
return candidate.getContext('2d');
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.Chart.prototype && typeof window.Chart.prototype.acquireContext === 'function') {
|
||||
window.Chart.prototype.acquireContext = function(item) {
|
||||
if (!item) return null;
|
||||
|
||||
const candidate = item.length ? item[0] : item;
|
||||
if (candidate && typeof candidate.getContext === 'function') {
|
||||
return candidate.getContext('2d');
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance tables: wrap in card, add per-table search box, client-side filtering
|
||||
* Non-destructive: skips tables already enhanced
|
||||
*/
|
||||
function enhanceTables() {
|
||||
const selector = '.ots-content table, .content table, .container table, .card table, table';
|
||||
const tables = Array.from(document.querySelectorAll(selector));
|
||||
let counter = 0;
|
||||
|
||||
tables.forEach(table => {
|
||||
// only enhance tables that have a thead and tbody
|
||||
if (!table || table.classList.contains('modern-table')) return;
|
||||
if (!table.querySelector('thead') || !table.querySelector('tbody')) return;
|
||||
|
||||
counter += 1;
|
||||
table.classList.add('modern-table');
|
||||
|
||||
// Build wrapper structure
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'modern-table-card';
|
||||
|
||||
const controls = document.createElement('div');
|
||||
controls.className = 'table-controls';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'search';
|
||||
input.placeholder = 'Search…';
|
||||
input.className = 'table-search-input';
|
||||
input.setAttribute('aria-label', 'Table search');
|
||||
input.style.minWidth = '180px';
|
||||
|
||||
controls.appendChild(input);
|
||||
|
||||
const tableWrapper = document.createElement('div');
|
||||
tableWrapper.className = 'table-wrapper';
|
||||
tableWrapper.style.overflow = 'auto';
|
||||
|
||||
// Insert wrapper into DOM in place of the table
|
||||
const parent = table.parentNode;
|
||||
parent.replaceChild(wrapper, table);
|
||||
wrapper.appendChild(controls);
|
||||
wrapper.appendChild(tableWrapper);
|
||||
tableWrapper.appendChild(table);
|
||||
|
||||
// Simple, light-weight search filtering for this table only
|
||||
input.addEventListener('input', function (e) {
|
||||
const term = (e.target.value || '').toLowerCase();
|
||||
table.querySelectorAll('tbody tr').forEach(tr => {
|
||||
const text = tr.textContent.toLowerCase();
|
||||
tr.style.display = term === '' || text.includes(term) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize DataTables for enhanced behavior when available.
|
||||
* Falls back gracefully if DataTables or jQuery are not present.
|
||||
*/
|
||||
function initDataTables() {
|
||||
if (!window.jQuery) return;
|
||||
const $ = window.jQuery;
|
||||
if (!$.fn || !$.fn.dataTable) return;
|
||||
|
||||
// Skip Xibo-managed grids to avoid double initialization
|
||||
if (document.querySelector('.XiboGrid')) return;
|
||||
|
||||
$('.modern-table, table').each(function () {
|
||||
try {
|
||||
if (this.closest('.XiboGrid')) return;
|
||||
if (!$.fn.dataTable.isDataTable(this)) {
|
||||
$(this).DataTable({
|
||||
responsive: true,
|
||||
lengthChange: false,
|
||||
pageLength: 10,
|
||||
autoWidth: false,
|
||||
dom: '<"table-controls"f>rt<"table-meta"ip>',
|
||||
language: { search: '' }
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
// If initialization fails, ignore and allow fallback enhancer
|
||||
console.warn('DataTables init failed for table', this, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all features when DOM is ready
|
||||
*/
|
||||
function init() {
|
||||
initSidebarToggle();
|
||||
initDropdowns();
|
||||
initSearch();
|
||||
initPageInteractions();
|
||||
initDataTables();
|
||||
enhanceTables();
|
||||
makeResponsive();
|
||||
initChartSafeguard();
|
||||
}
|
||||
|
||||
// Wait for DOM to be ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user