Merge Official Source
2
htdocs/luci-static/argon/background/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
Drop background here!
|
||||
accept jpg png gif mp4 webm
|
||||
1
htdocs/luci-static/argon/css/cascade.css
Normal file
1
htdocs/luci-static/argon/css/dark.css
Normal file
BIN
htdocs/luci-static/argon/favicon.ico
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
htdocs/luci-static/argon/fonts/GoogleSans-Regular.woff
Normal file
BIN
htdocs/luci-static/argon/fonts/GoogleSans-Regular.woff2
Normal file
BIN
htdocs/luci-static/argon/fonts/TypoGraphica.woff
Normal file
BIN
htdocs/luci-static/argon/fonts/TypoGraphica.woff2
Normal file
BIN
htdocs/luci-static/argon/fonts/argon.woff
Normal file
BIN
htdocs/luci-static/argon/fonts/argon.woff2
Normal file
BIN
htdocs/luci-static/argon/icon/android-icon-192x192.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
htdocs/luci-static/argon/icon/apple-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
htdocs/luci-static/argon/icon/apple-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
htdocs/luci-static/argon/icon/apple-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
3
htdocs/luci-static/argon/icon/arrow.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path fill="#adaeaf" d="m8,10.033663l-6.898535,-6.013274l-1.060688,0.972974l7.959223,6.986249l7.959223,-6.986249l-1.060688,-0.972974l-6.898535,6.013274z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 225 B |
2
htdocs/luci-static/argon/icon/browserconfig.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
|
||||
BIN
htdocs/luci-static/argon/icon/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
htdocs/luci-static/argon/icon/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
htdocs/luci-static/argon/icon/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
41
htdocs/luci-static/argon/icon/manifest.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "Openwrt",
|
||||
"icons": [
|
||||
{
|
||||
"src": "\/android-icon-36x36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "0.75"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
htdocs/luci-static/argon/icon/ms-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
3
htdocs/luci-static/argon/icon/spinner.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path fill="#888" d="M8,0c-4.355,0-7.898,3.481-7.998,7.812,0.092-3.779,2.966-6.812,6.498-6.812,3.59,0,6.5,3.134,6.5,7,0,0.828,0.672,1.5,1.5,1.5s1.5-0.672,1.5-1.5c0-4.418-3.582-8-8-8zM8,16c4.355,0,7.898-3.481,7.998-7.812-0.092,3.779-2.966,6.812-6.498,6.812-3.59,0-6.5-3.134-6.5-7,0-0.828-0.672-1.5-1.5-1.5s-1.5,0.672-1.5,1.5c0,4.418,3.582,8,8,8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
37
htdocs/luci-static/argon/img/argon.svg
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 256 256"
|
||||
style="enable-background:new 0 0 256 256;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:url(#svg_2_00000009581766544743910510000007087157279682564742_);}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#svg_3_00000013155245276689480680000010334395393893521599_);}
|
||||
.st2{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g id="svg_1">
|
||||
|
||||
<linearGradient id="svg_2_00000043442590260727270070000016472210641679865270_" gradientUnits="userSpaceOnUse" x1="11.1563" y1="247.3437" x2="245.4437" y2="13.0563" gradientTransform="matrix(1 0 0 -1 0 258)">
|
||||
<stop offset="0" style="stop-color:#5E72E4"/>
|
||||
<stop offset="1" style="stop-color:#778AFF"/>
|
||||
</linearGradient>
|
||||
|
||||
<path id="svg_2" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#svg_2_00000043442590260727270070000016472210641679865270_);" d="
|
||||
M36.4,0.2h183.8c19.7,0,35.7,16,35.7,35.7v183.8c0,19.7-16,35.7-35.7,35.7H36.4c-19.7,0-35.7-16-35.7-35.7V35.9
|
||||
C0.7,16.2,16.7,0.2,36.4,0.2z"/>
|
||||
|
||||
<linearGradient id="svg_3_00000010280352489557108120000000938545297310085033_" gradientUnits="userSpaceOnUse" x1="0.7" y1="257.8" x2="0.7" y2="257.8" gradientTransform="matrix(1 0 0 -1 0 258)">
|
||||
<stop offset="0" style="stop-color:#5E72E4"/>
|
||||
<stop offset="1" style="stop-color:#778AFF"/>
|
||||
</linearGradient>
|
||||
|
||||
<path id="svg_3" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#svg_3_00000010280352489557108120000000938545297310085033_);" d="
|
||||
M0.7,0.2"/>
|
||||
</g>
|
||||
<path id="svg_4" class="st2" d="M128.3,45.4c-46.7,0-84.4,37.8-84.4,84.4c0,32.2,18.1,60.2,44.6,74.4c6.8,3.7,15.3-0.2,17.2-7.7
|
||||
l4.3-17.6c1.5-6.2-1-12.6-6.1-16.4c-10-7.4-16.4-19.3-16.4-32.7c0-22.5,18.3-40.7,40.7-40.7c22.5,0,40.7,18.3,40.7,40.7
|
||||
c0,13.4-6.4,25.2-16.4,32.7c-5.1,3.8-7.6,10.2-6.1,16.5l4.4,17.6c1.9,7.5,10.3,11.4,17.2,7.7c26.6-14.2,44.6-42.2,44.6-74.5
|
||||
C212.8,83.3,174.9,45.4,128.3,45.4L128.3,45.4z"/>
|
||||
</g>
|
||||
<circle class="st2" cx="128.3" cy="131.6" r="18.3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
BIN
htdocs/luci-static/argon/img/bg1.jpg
Normal file
|
After Width: | Height: | Size: 816 KiB |
BIN
htdocs/luci-static/argon/img/blank.png
Normal file
|
After Width: | Height: | Size: 938 B |
3
htdocs/luci-static/argon/img/volume_high.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024">
|
||||
<path fill="#fff" d="M484.430769 51.2 236.307692 354.461538H118.153846c-43.323077 0-78.769231 35.446154-78.769231 78.769231v157.538462c0 43.323077 35.446154 78.769231 78.769231 78.769231h118.153846L484.430769 972.8c25.6 25.6 66.953846 7.876923 66.953846-27.569231V78.769231c0-35.446154-43.323077-53.169231-66.953846-27.569231zm354.461539 120.123077c-7.876923-7.876923-19.692308-7.876923-27.569231 0l-27.569231 27.569231c-7.876923 7.876923-7.876923 21.661538 0 27.56923C858.584615 299.323077 905.846154 399.753846 905.846154 512c0 112.246154-47.261538 212.676923-122.092308 285.538462-7.876923 7.876923-7.876923 19.692308 0 27.56923l27.569231 27.569231c7.876923 7.876923 19.692308 7.876923 27.569231 0C927.507692 768 984.615385 645.907692 984.615385 512s-55.138462-256-145.723077-340.676923zM714.830769 297.353846c-7.876923-7.876923-19.692308-7.876923-27.569231 0l-27.56923 27.569231c-7.876923 7.876923-7.876923 19.692308 0 27.569231C703.015385 391.876923 728.615385 448.984616 728.615385 512c0 63.015385-27.569231 120.123077-70.892308 159.507692-7.876923 7.876923-7.876923 19.692308 0 27.569231l27.569231 27.569231c7.876923 7.876923 19.692308 7.876923 27.56923 0 57.107692-53.169231 94.523077-129.969231 94.523077-216.615385 0-82.707692-35.446154-159.507692-92.553846-212.676923z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
3
htdocs/luci-static/argon/img/volume_off.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024">
|
||||
<path fill="#fff" d="M484.430769 51.2 236.307692 354.461538H118.153846c-43.323077 0-78.769231 35.446154-78.769231 78.769231v157.538462c0 43.323077 35.446154 78.769231 78.769231 78.769231h118.153846L484.430769 972.8c25.6 25.6 66.953846 7.876923 66.953846-27.569231V78.769231c0-35.446154-43.323077-53.169231-66.953846-27.569231zM882.215385 512l96.492307-96.492308c7.876923-7.876923 7.876923-19.692308 0-27.56923l-27.56923-27.569231c-7.876923-7.876923-19.692308-7.876923-27.569231 0l-96.492308 96.492307-96.492308-96.492307c-7.876923-7.876923-19.692308-7.876923-27.56923 0l-27.569231 27.569231c-7.876923 7.876923-7.876923 19.692308 0 27.56923L771.938462 512l-96.492308 96.492308c-7.876923 7.876923-7.876923 19.692308 0 27.56923l27.569231 27.569231c7.876923 7.876923 19.692308 7.876923 27.56923 0l96.492308-96.492307 96.492308 96.492307c7.876923 7.876923 19.692308 7.876923 27.569231 0l27.56923-27.569231c7.876923-7.876923 7.876923-19.692308 0-27.56923L882.215385 512z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
448
htdocs/luci-static/resources/menu-argon.js
Normal file
@@ -0,0 +1,448 @@
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
'require ui';
|
||||
|
||||
/**
|
||||
* Native JavaScript slide animation utilities
|
||||
* Replaces jQuery slideUp/slideDown functionality with better performance
|
||||
*/
|
||||
const SlideAnimations = {
|
||||
/**
|
||||
* Animation durations in milliseconds
|
||||
*/
|
||||
durations: {
|
||||
fast: 200,
|
||||
normal: 400,
|
||||
slow: 600
|
||||
},
|
||||
|
||||
/**
|
||||
* Map to track running animations and their cleanup functions
|
||||
*/
|
||||
runningAnimations: new WeakMap(),
|
||||
|
||||
/**
|
||||
* Slide element down (show) with animation
|
||||
* @param {Element} element - DOM element to animate
|
||||
* @param {string|number} duration - Animation duration ('fast', 'normal', 'slow' or milliseconds)
|
||||
* @param {function} callback - Optional callback function when animation completes
|
||||
*/
|
||||
slideDown: function(element, duration, callback) {
|
||||
if (!element) {
|
||||
console.warn('SlideAnimations.slideDown: No element provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop any existing animation on this element
|
||||
this.stop(element);
|
||||
|
||||
// Convert duration string to milliseconds
|
||||
const animDuration = typeof duration === 'string' ?
|
||||
this.durations[duration] || this.durations.normal :
|
||||
(duration || this.durations.normal);
|
||||
|
||||
// Store original styles
|
||||
const originalStyles = {
|
||||
display: element.style.display,
|
||||
overflow: element.style.overflow,
|
||||
height: element.style.height,
|
||||
transition: element.style.transition
|
||||
};
|
||||
|
||||
// Set initial state for animation
|
||||
element.style.display = 'block';
|
||||
element.style.overflow = 'hidden';
|
||||
element.style.height = '0px';
|
||||
element.style.transition = `height ${animDuration}ms ease-out`;
|
||||
|
||||
// Force reflow to ensure initial state is applied
|
||||
element.offsetHeight;
|
||||
|
||||
// Get the target height
|
||||
const targetHeight = element.scrollHeight;
|
||||
|
||||
// Animate to full height
|
||||
element.style.height = targetHeight + 'px';
|
||||
|
||||
// Set up cleanup function
|
||||
const cleanup = () => {
|
||||
element.style.height = originalStyles.height || '';
|
||||
element.style.overflow = originalStyles.overflow || '';
|
||||
element.style.transition = originalStyles.transition || '';
|
||||
|
||||
// Remove from running animations map
|
||||
this.runningAnimations.delete(element);
|
||||
|
||||
if (callback && typeof callback === 'function') {
|
||||
try {
|
||||
callback.call(element);
|
||||
} catch (e) {
|
||||
console.error('SlideAnimations callback error:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Store cleanup function for potential cancellation
|
||||
const timeoutId = setTimeout(cleanup, animDuration);
|
||||
this.runningAnimations.set(element, { timeoutId, cleanup });
|
||||
},
|
||||
|
||||
/**
|
||||
* Slide element up (hide) with animation
|
||||
* @param {Element} element - DOM element to animate
|
||||
* @param {string|number} duration - Animation duration ('fast', 'normal', 'slow' or milliseconds)
|
||||
* @param {function} callback - Optional callback function when animation completes
|
||||
*/
|
||||
slideUp: function(element, duration, callback) {
|
||||
if (!element) {
|
||||
console.warn('SlideAnimations.slideUp: No element provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop any existing animation on this element
|
||||
this.stop(element);
|
||||
|
||||
// Convert duration string to milliseconds
|
||||
const animDuration = typeof duration === 'string' ?
|
||||
this.durations[duration] || this.durations.normal :
|
||||
(duration || this.durations.normal);
|
||||
|
||||
// Store original styles
|
||||
const originalStyles = {
|
||||
display: element.style.display,
|
||||
overflow: element.style.overflow,
|
||||
height: element.style.height,
|
||||
transition: element.style.transition
|
||||
};
|
||||
|
||||
// Get current height before hiding
|
||||
const currentHeight = element.scrollHeight;
|
||||
|
||||
// Set initial state for animation
|
||||
element.style.overflow = 'hidden';
|
||||
element.style.height = currentHeight + 'px';
|
||||
element.style.transition = `height ${animDuration}ms ease-out`;
|
||||
|
||||
// Force reflow to ensure initial state is applied
|
||||
element.offsetHeight;
|
||||
|
||||
// Animate to zero height
|
||||
element.style.height = '0px';
|
||||
|
||||
// Set up cleanup function
|
||||
const cleanup = () => {
|
||||
element.style.display = 'none';
|
||||
element.style.height = originalStyles.height || '';
|
||||
element.style.overflow = originalStyles.overflow || '';
|
||||
element.style.transition = originalStyles.transition || '';
|
||||
|
||||
// Remove from running animations map
|
||||
this.runningAnimations.delete(element);
|
||||
|
||||
if (callback && typeof callback === 'function') {
|
||||
try {
|
||||
callback.call(element);
|
||||
} catch (e) {
|
||||
console.error('SlideAnimations callback error:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Store cleanup function for potential cancellation
|
||||
const timeoutId = setTimeout(cleanup, animDuration);
|
||||
this.runningAnimations.set(element, { timeoutId, cleanup });
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop all running animations on an element
|
||||
* @param {Element} element - DOM element to stop animations on
|
||||
*/
|
||||
stop: function(element) {
|
||||
if (!element) return;
|
||||
|
||||
const animationData = this.runningAnimations.get(element);
|
||||
if (animationData) {
|
||||
// Clear the timeout
|
||||
clearTimeout(animationData.timeoutId);
|
||||
|
||||
// Run cleanup immediately
|
||||
animationData.cleanup();
|
||||
}
|
||||
|
||||
// Clear transition to immediately stop any CSS animation
|
||||
element.style.transition = '';
|
||||
|
||||
// Force reflow to apply changes immediately
|
||||
element.offsetHeight;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if element has running animation
|
||||
* @param {Element} element - DOM element to check
|
||||
* @returns {boolean} - True if element has running animation
|
||||
*/
|
||||
isAnimating: function(element) {
|
||||
return this.runningAnimations.has(element);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Argon Theme Menu Module
|
||||
* Handles rendering and interaction of the main navigation menu and sidebar
|
||||
*/
|
||||
return baseclass.extend({
|
||||
/**
|
||||
* Initialize the menu module
|
||||
* Load menu data and trigger rendering
|
||||
*/
|
||||
__init__: function () {
|
||||
ui.menu.load().then(L.bind(this.render, this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Main render function for the menu system
|
||||
* @param {Object} tree - Menu tree structure from LuCI
|
||||
*/
|
||||
render: function (tree) {
|
||||
var node = tree,
|
||||
url = '',
|
||||
children = ui.menu.getChildren(tree);
|
||||
|
||||
// Find and render the active main menu item
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var isActive = (L.env.requestpath.length ? children[i].name == L.env.requestpath[0] : i == 0);
|
||||
|
||||
if (isActive) {
|
||||
this.renderMainMenu(children[i], children[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
// Render tab menu if we're deep enough in the navigation hierarchy
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach event listeners for sidebar toggle functionality
|
||||
var sidebarToggle = document.querySelector('a.showSide');
|
||||
var darkMask = document.querySelector('.darkMask');
|
||||
|
||||
if (sidebarToggle) {
|
||||
sidebarToggle.addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));
|
||||
}
|
||||
if (darkMask) {
|
||||
darkMask.addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle menu expand/collapse functionality
|
||||
* Manages the sliding animation and active states of menu items
|
||||
* @param {Event} ev - Click event from menu item
|
||||
*/
|
||||
handleMenuExpand: function (ev) {
|
||||
var target = ev.target;
|
||||
var slide = target.parentNode;
|
||||
var slideMenu = target.nextElementSibling;
|
||||
var shouldCollapse = false;
|
||||
|
||||
// Close all currently active submenus
|
||||
var activeMenus = document.querySelectorAll('.main .main-left .nav > li > ul.active');
|
||||
activeMenus.forEach(function (ul) {
|
||||
// Stop any running animations and slide up
|
||||
SlideAnimations.stop(ul);
|
||||
// Remove active classes immediately when starting slideUp animation
|
||||
ul.classList.remove('active');
|
||||
ul.previousElementSibling.classList.remove('active');
|
||||
SlideAnimations.slideUp(ul, 'fast');
|
||||
|
||||
// Check if we're clicking on an already open menu (should collapse it)
|
||||
if (!shouldCollapse && ul === slideMenu) {
|
||||
shouldCollapse = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Exit if there's no submenu to show
|
||||
if (!slideMenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the submenu if it's not already open
|
||||
if (!shouldCollapse) {
|
||||
// Find the slide menu within the slide element
|
||||
var slideMenuElement = slide.querySelector(".slide-menu");
|
||||
if (slideMenuElement) {
|
||||
// Add active classes immediately when starting slideDown animation
|
||||
slideMenu.classList.add('active');
|
||||
target.classList.add('active');
|
||||
SlideAnimations.slideDown(slideMenuElement, 'fast');
|
||||
}
|
||||
target.blur(); // Remove focus from the clicked element
|
||||
}
|
||||
|
||||
// Prevent default link behavior and event bubbling
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the main navigation menu
|
||||
* Creates hierarchical menu structure with active states and click handlers
|
||||
* @param {Object} tree - Menu tree node to render
|
||||
* @param {string} url - Base URL for menu items
|
||||
* @param {number} level - Current nesting level (0-based)
|
||||
* @returns {Element} - Generated menu element
|
||||
*/
|
||||
renderMainMenu: function (tree, url, level) {
|
||||
var currentLevel = (level || 0) + 1;
|
||||
var menuContainer = E('ul', { 'class': level ? 'slide-menu' : 'nav' });
|
||||
var children = ui.menu.getChildren(tree);
|
||||
|
||||
// Don't render empty menus or menus deeper than 2 levels
|
||||
if (children.length === 0 || currentLevel > 2) {
|
||||
return E([]);
|
||||
}
|
||||
|
||||
// Generate menu items for each child
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
var isActive = (
|
||||
(L.env.dispatchpath[currentLevel] === child.name) &&
|
||||
(L.env.dispatchpath[currentLevel - 1] === tree.name)
|
||||
);
|
||||
|
||||
// Recursively render submenu
|
||||
var submenu = this.renderMainMenu(child, url + '/' + child.name, currentLevel);
|
||||
var hasChildren = submenu.children.length > 0;
|
||||
|
||||
// Determine CSS classes based on state
|
||||
var slideClass = hasChildren ? 'slide' : null;
|
||||
var menuClass = hasChildren ? 'menu' : 'food';
|
||||
|
||||
if (isActive) {
|
||||
menuContainer.classList.add('active');
|
||||
slideClass += " active";
|
||||
menuClass += " active";
|
||||
}
|
||||
|
||||
// Create menu item with link and submenu
|
||||
var menuItem = E('li', { 'class': slideClass }, [
|
||||
E('a', {
|
||||
'href': L.url(url, child.name),
|
||||
'click': (currentLevel === 1) ? ui.createHandlerFn(this, 'handleMenuExpand') : null,
|
||||
'class': menuClass,
|
||||
'data-title': child.title.replace(/ /g, "_"), // More robust space replacement
|
||||
}, [_(child.title)]),
|
||||
submenu
|
||||
]);
|
||||
|
||||
menuContainer.appendChild(menuItem);
|
||||
}
|
||||
|
||||
// Append to main menu container if this is the top level
|
||||
if (currentLevel === 1) {
|
||||
var mainMenuElement = document.querySelector('#mainmenu');
|
||||
if (mainMenuElement) {
|
||||
mainMenuElement.appendChild(menuContainer);
|
||||
mainMenuElement.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
return menuContainer;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render tab navigation menu
|
||||
* Creates horizontal tab menu for deeper navigation levels
|
||||
* @param {Object} tree - Menu tree node to render
|
||||
* @param {string} url - Base URL for tab items
|
||||
* @param {number} level - Current nesting level (0-based)
|
||||
* @returns {Element} - Generated tab menu element
|
||||
*/
|
||||
renderTabMenu: function (tree, url, level) {
|
||||
var container = document.querySelector('#tabmenu');
|
||||
var currentLevel = (level || 0) + 1;
|
||||
var tabContainer = E('ul', { 'class': 'tabs' });
|
||||
var children = ui.menu.getChildren(tree);
|
||||
var activeNode = null;
|
||||
|
||||
// Don't render empty tab menus
|
||||
if (children.length === 0) {
|
||||
return E([]);
|
||||
}
|
||||
|
||||
// Generate tab items for each child
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
var isActive = (L.env.dispatchpath[currentLevel + 2] === child.name);
|
||||
var activeClass = isActive ? ' active' : '';
|
||||
var className = 'tabmenu-item-%s %s'.format(child.name, activeClass);
|
||||
|
||||
var tabItem = E('li', { 'class': className }, [
|
||||
E('a', { 'href': L.url(url, child.name) }, [_(child.title)])
|
||||
]);
|
||||
|
||||
tabContainer.appendChild(tabItem);
|
||||
|
||||
// Store reference to active node for recursive rendering
|
||||
if (isActive) {
|
||||
activeNode = child;
|
||||
}
|
||||
}
|
||||
|
||||
// Append tab container to main tab menu element
|
||||
if (container) {
|
||||
container.appendChild(tabContainer);
|
||||
container.style.display = '';
|
||||
|
||||
// Recursively render nested tab menus if there's an active node
|
||||
if (activeNode) {
|
||||
var nestedTabs = this.renderTabMenu(activeNode, url + '/' + activeNode.name, currentLevel);
|
||||
if (nestedTabs.children.length > 0) {
|
||||
container.appendChild(nestedTabs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tabContainer;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle sidebar toggle functionality
|
||||
* Toggles the mobile/responsive sidebar menu visibility
|
||||
* @param {Event} ev - Click event from sidebar toggle button or dark mask
|
||||
*/
|
||||
handleSidebarToggle: function (ev) {
|
||||
var showSideButton = document.querySelector('a.showSide');
|
||||
var sidebar = document.querySelector('#mainmenu');
|
||||
var darkMask = document.querySelector('.darkMask');
|
||||
var scrollbarArea = document.querySelector('.main-right');
|
||||
|
||||
// Check if any required elements are missing
|
||||
if (!showSideButton || !sidebar || !darkMask || !scrollbarArea) {
|
||||
console.warn('Sidebar toggle elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle sidebar visibility and related states
|
||||
if (showSideButton.classList.contains('active')) {
|
||||
// Close sidebar
|
||||
showSideButton.classList.remove('active');
|
||||
sidebar.classList.remove('active');
|
||||
scrollbarArea.classList.remove('active');
|
||||
darkMask.classList.remove('active');
|
||||
} else {
|
||||
// Open sidebar
|
||||
showSideButton.classList.add('active');
|
||||
sidebar.classList.add('active');
|
||||
scrollbarArea.classList.add('active');
|
||||
darkMask.classList.add('active');
|
||||
}
|
||||
}
|
||||
});
|
||||