mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-28 11:41:24 +08:00
413 lines
11 KiB
JavaScript
413 lines
11 KiB
JavaScript
'use strict';
|
|
'require baseclass';
|
|
'require ui';
|
|
|
|
return baseclass.extend({
|
|
__init__() {
|
|
this.suppressClick = null;
|
|
ui.menu.load().then((tree) => this.render(tree));
|
|
this.initNavigationShell();
|
|
this.initDashboardTables();
|
|
},
|
|
|
|
createRipple(ev, target) {
|
|
const oldRipple = target.querySelector('.ripple');
|
|
|
|
if (oldRipple)
|
|
oldRipple.remove();
|
|
|
|
const rect = target.getBoundingClientRect();
|
|
const ripple = E('span', { 'class': 'ripple' });
|
|
|
|
ripple.style.left = '%dpx'.format(ev.clientX - rect.left);
|
|
ripple.style.top = '%dpx'.format(ev.clientY - rect.top);
|
|
target.appendChild(ripple);
|
|
ripple.addEventListener('animationend', () => ripple.remove());
|
|
},
|
|
|
|
toggleDropdown(li) {
|
|
const submenu = Array.prototype.find.call(li.children, child => child.classList.contains('dropdown-menu'));
|
|
|
|
if (!submenu)
|
|
return;
|
|
|
|
if (li.classList.contains('open')) {
|
|
submenu.style.height = '%dpx'.format(submenu.scrollHeight);
|
|
submenu.offsetHeight;
|
|
li.classList.remove('open');
|
|
submenu.style.height = '0px';
|
|
}
|
|
else {
|
|
this.closeOtherMenus(li);
|
|
li.classList.add('open');
|
|
submenu.style.height = '%dpx'.format(submenu.scrollHeight);
|
|
}
|
|
},
|
|
|
|
shouldSuppressClick(ev) {
|
|
const click = this.suppressClick;
|
|
|
|
if (!click || Date.now() >= click.until)
|
|
return false;
|
|
|
|
const dx = Math.abs(ev.clientX - click.x);
|
|
const dy = Math.abs(ev.clientY - click.y);
|
|
const suppress = dx < 24 && dy < 24;
|
|
|
|
if (suppress)
|
|
this.suppressClick = null;
|
|
|
|
return suppress;
|
|
},
|
|
|
|
bindFastTap(target, handler) {
|
|
let startX = 0;
|
|
let startY = 0;
|
|
let moved = false;
|
|
|
|
target.addEventListener('pointerdown', ev => {
|
|
if (ev.pointerType == 'mouse' || ev.button)
|
|
return;
|
|
|
|
startX = ev.clientX;
|
|
startY = ev.clientY;
|
|
moved = false;
|
|
}, { passive: true });
|
|
|
|
target.addEventListener('pointermove', ev => {
|
|
if (Math.abs(ev.clientX - startX) > 8 || Math.abs(ev.clientY - startY) > 8)
|
|
moved = true;
|
|
}, { passive: true });
|
|
|
|
target.addEventListener('pointerup', ev => {
|
|
if (ev.pointerType == 'mouse' || ev.button || moved)
|
|
return;
|
|
|
|
this.suppressClick = {
|
|
x: ev.clientX,
|
|
y: ev.clientY,
|
|
until: Date.now() + 500
|
|
};
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
handler(ev);
|
|
});
|
|
|
|
target.addEventListener('click', ev => {
|
|
if (this.shouldSuppressClick(ev)) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
return;
|
|
}
|
|
|
|
handler(ev);
|
|
});
|
|
},
|
|
|
|
closeOtherMenus(currentMenu) {
|
|
document.querySelectorAll('#topmenu > li.open').forEach(menu => {
|
|
if (menu === currentMenu)
|
|
return;
|
|
|
|
const submenu = menu.querySelector('.dropdown-menu');
|
|
|
|
if (!submenu)
|
|
return;
|
|
|
|
submenu.style.height = '%dpx'.format(submenu.scrollHeight);
|
|
submenu.offsetHeight;
|
|
submenu.style.height = '0px';
|
|
menu.classList.remove('open');
|
|
});
|
|
},
|
|
|
|
initNavigationShell() {
|
|
const button = document.querySelector('.menu-btn');
|
|
const sidebar = document.querySelector('.sidebar');
|
|
const overlay = document.querySelector('.sidebar-overlay');
|
|
const header = document.querySelector('header');
|
|
const collapsedQuery = window.matchMedia('(max-width: 854px), (max-device-width: 854px)');
|
|
|
|
if (header) {
|
|
const updateHeaderShadow = () => {
|
|
header.classList.toggle('with-shadow', collapsedQuery.matches && window.scrollY > 8);
|
|
};
|
|
|
|
updateHeaderShadow();
|
|
window.addEventListener('scroll', updateHeaderShadow, { passive: true });
|
|
collapsedQuery.addEventListener('change', updateHeaderShadow);
|
|
}
|
|
|
|
if (!button || !sidebar || !overlay)
|
|
return;
|
|
|
|
document.addEventListener('click', ev => {
|
|
if (!this.shouldSuppressClick(ev))
|
|
return;
|
|
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
}, true);
|
|
|
|
const close = () => {
|
|
sidebar.classList.remove('active');
|
|
overlay.classList.remove('active');
|
|
button.classList.remove('active');
|
|
document.body.classList.remove('sidebar-open');
|
|
document.body.style.overflow = '';
|
|
button.setAttribute('aria-expanded', 'false');
|
|
};
|
|
|
|
this.bindFastTap(button, () => {
|
|
sidebar.classList.toggle('active');
|
|
overlay.classList.toggle('active');
|
|
const isOpen = sidebar.classList.contains('active');
|
|
|
|
button.classList.toggle('active', isOpen);
|
|
document.body.classList.toggle('sidebar-open', isOpen);
|
|
document.body.style.overflow = isOpen ? 'hidden' : '';
|
|
button.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
|
});
|
|
|
|
overlay.addEventListener('click', close);
|
|
|
|
document.addEventListener('keydown', ev => {
|
|
if (ev.key == 'Escape')
|
|
close();
|
|
});
|
|
|
|
sidebar.addEventListener('pointerdown', ev => {
|
|
const link = ev.target.closest('a[href]');
|
|
|
|
if (!link)
|
|
return;
|
|
|
|
this.createRipple(ev, link);
|
|
}, { passive: true });
|
|
|
|
sidebar.addEventListener('click', ev => {
|
|
const link = ev.target.closest('a[href]');
|
|
|
|
if (!link)
|
|
return;
|
|
|
|
if (link.getAttribute('href') != '#')
|
|
close();
|
|
});
|
|
|
|
document.addEventListener('pointerdown', ev => {
|
|
const target = ev.target.closest('.tabs > li, .cbi-tabmenu > li');
|
|
|
|
if (target)
|
|
this.createRipple(ev, target);
|
|
}, { passive: true });
|
|
|
|
sidebar.addEventListener('mouseover', ev => {
|
|
const link = ev.target.closest('.nav a');
|
|
const title = link ? link.querySelector('.nav-menu-title') : null;
|
|
|
|
if (title && title.scrollWidth > title.clientWidth)
|
|
title.scrollTo({ left: title.scrollWidth - title.clientWidth, behavior: 'smooth' });
|
|
});
|
|
|
|
sidebar.addEventListener('mouseout', ev => {
|
|
const link = ev.target.closest('.nav a');
|
|
const title = link ? link.querySelector('.nav-menu-title') : null;
|
|
|
|
if (title && !link.contains(ev.relatedTarget))
|
|
title.scrollTo({ left: 0, behavior: 'smooth' });
|
|
});
|
|
},
|
|
|
|
isDashboardPage() {
|
|
const path = (L.env.requestpath || []).join('/');
|
|
|
|
return path == '' || path == 'admin' || path == 'admin/dashboard';
|
|
},
|
|
|
|
initDashboardTables() {
|
|
if (!this.isDashboardPage())
|
|
return;
|
|
|
|
const sync = () => this.updateDashboardTables();
|
|
|
|
if (document.readyState == 'loading')
|
|
document.addEventListener('DOMContentLoaded', sync, { once: true });
|
|
else
|
|
sync();
|
|
|
|
const target = document.querySelector('#maincontent') || document.body;
|
|
let queued = false;
|
|
const observer = new MutationObserver(() => {
|
|
if (queued)
|
|
return;
|
|
|
|
queued = true;
|
|
window.setTimeout(() => {
|
|
queued = false;
|
|
sync();
|
|
}, 100);
|
|
});
|
|
|
|
if (target)
|
|
observer.observe(target, { childList: true, subtree: true });
|
|
},
|
|
|
|
updateDashboardTables() {
|
|
document.querySelectorAll('.Dashboard, .dashboard-bg.box-s1').forEach(scope => {
|
|
scope.querySelectorAll('.table').forEach(table => {
|
|
const rows = Array.prototype.filter.call(table.querySelectorAll('.tr, tr'), row =>
|
|
row.closest('.table') === table);
|
|
const headerRow = Array.prototype.find.call(table.children, child =>
|
|
child.querySelector && child.querySelector('.th, th')) ||
|
|
rows.find(row => row.querySelector('.th, th'));
|
|
|
|
if (!headerRow)
|
|
return;
|
|
|
|
const titleCells = headerRow.querySelectorAll('.th, th');
|
|
const titles = Array.prototype.map.call(titleCells, cell => cell.textContent.trim());
|
|
|
|
headerRow.classList.add('dashboard-table-titles');
|
|
|
|
rows.forEach(row => {
|
|
if (row === headerRow || row.querySelector('.th, th'))
|
|
return;
|
|
|
|
const cells = Array.prototype.filter.call(row.children, cell =>
|
|
(cell.classList && cell.classList.contains('td')) || cell.tagName == 'TD');
|
|
|
|
cells.forEach((cell, index) => {
|
|
if (!titles[index])
|
|
return;
|
|
|
|
if (!cell.getAttribute('data-title'))
|
|
cell.setAttribute('data-title', titles[index]);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
},
|
|
|
|
render(tree) {
|
|
let node = tree;
|
|
let url = '';
|
|
|
|
this.renderModeMenu(tree);
|
|
|
|
if (L.env.dispatchpath.length >= 3) {
|
|
for (var i = 0; i < 3 && node; i++) {
|
|
node = node.children[L.env.dispatchpath[i]];
|
|
url = url + (url ? '/' : '') + L.env.dispatchpath[i];
|
|
}
|
|
|
|
if (node)
|
|
this.renderTabMenu(node, url);
|
|
}
|
|
},
|
|
|
|
renderTabMenu(tree, url, level) {
|
|
const container = document.querySelector('#tabmenu');
|
|
const ul = E('ul', { 'class': 'tabs' });
|
|
const children = ui.menu.getChildren(tree);
|
|
let activeNode = null;
|
|
|
|
children.forEach(child => {
|
|
const isActive = (L.env.dispatchpath[3 + (level || 0)] == child.name);
|
|
const activeClass = isActive ? ' active' : '';
|
|
const className = 'tabmenu-item-%s %s'.format(child.name, activeClass);
|
|
|
|
ul.appendChild(E('li', { 'class': className }, [
|
|
E('a', { 'href': L.url(url, child.name) }, [_(child.title)])]));
|
|
|
|
if (isActive)
|
|
activeNode = child;
|
|
});
|
|
|
|
if (ul.children.length == 0)
|
|
return E([]);
|
|
|
|
container.appendChild(ul);
|
|
container.style.display = '';
|
|
|
|
if (activeNode)
|
|
this.renderTabMenu(activeNode, url + '/' + activeNode.name, (level || 0) + 1);
|
|
|
|
return ul;
|
|
},
|
|
|
|
renderMainMenu(tree, url, level) {
|
|
const ul = level ? E('ul', { 'class': 'dropdown-menu' }) : document.querySelector('#topmenu');
|
|
const children = ui.menu.getChildren(tree);
|
|
|
|
if (children.length == 0 || level > 1)
|
|
return E([]);
|
|
|
|
children.forEach(child => {
|
|
const submenu = this.renderMainMenu(child, url + '/' + child.name, (level || 0) + 1);
|
|
const itemPath = (url + '/' + child.name).replace(/^\/+/, '');
|
|
const currentPath = L.env.requestpath.join('/');
|
|
const isActive = currentPath == itemPath || currentPath.indexOf(itemPath + '/') == 0;
|
|
const hasSubmenu = !!submenu.firstElementChild;
|
|
const subclass = [
|
|
hasSubmenu ? 'dropdown' : '',
|
|
isActive ? 'active' : '',
|
|
(!level && hasSubmenu && isActive) ? 'open' : ''
|
|
].filter(Boolean).join(' ');
|
|
const linkclass = (!level && hasSubmenu) ? 'menu' : '';
|
|
const linkurl = hasSubmenu ? '#' : L.url(url, child.name);
|
|
const attrs = {
|
|
'class': subclass,
|
|
'data-path': itemPath
|
|
};
|
|
const linkAttrs = {
|
|
'class': linkclass,
|
|
'href': linkurl
|
|
};
|
|
|
|
const li = E('li', attrs, [
|
|
E('a', linkAttrs, [
|
|
E('span', { 'class': 'nav-menu-title' }, [_(child.title)]),
|
|
]),
|
|
submenu
|
|
]);
|
|
|
|
if (!level && hasSubmenu)
|
|
this.bindFastTap(li.firstElementChild, ev => {
|
|
ev.preventDefault();
|
|
this.toggleDropdown(li);
|
|
});
|
|
|
|
if (!level && hasSubmenu && isActive)
|
|
submenu.style.height = 'auto';
|
|
|
|
ul.appendChild(li);
|
|
});
|
|
|
|
ul.style.display = '';
|
|
|
|
return ul;
|
|
},
|
|
|
|
renderModeMenu(tree) {
|
|
const ul = document.querySelector('#modemenu');
|
|
const children = ui.menu.getChildren(tree);
|
|
|
|
children.forEach((child, index) => {
|
|
const isActive = L.env.requestpath.length
|
|
? child.name === L.env.requestpath[0]
|
|
: index === 0;
|
|
|
|
ul.appendChild(E('li', { 'class': isActive ? 'active' : '' }, [
|
|
E('a', { 'href': L.url(child.name) }, [_(child.title)])
|
|
]));
|
|
|
|
if (isActive)
|
|
this.renderMainMenu(child, child.name);
|
|
});
|
|
|
|
if (ul.children.length > 1)
|
|
ul.style.display = '';
|
|
}
|
|
});
|