/* ** Zabbix ** Copyright (C) 2001-2023 Zabbix SIA ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ const SIDEBAR_VIEW_MODE_FULL = 0; const SIDEBAR_VIEW_MODE_COMPACT = 1; const SIDEBAR_VIEW_MODE_HIDDEN = 2; const SIDEBAR_HOVER_DELAY = 1000; const SIDEBAR_EVENT_BLUR = 'blur'; const SIDEBAR_EVENT_CLOSE = 'close'; const SIDEBAR_EVENT_FOCUS = 'focus'; const SIDEBAR_EVENT_OPEN = 'open'; const SIDEBAR_EVENT_VIEWMODECHANGE = 'viewmodechange'; class CSidebar extends CBaseComponent { constructor(target) { super(target); this.init(); this.registerEvents(); } init() { this._sidebar_toggle = document.getElementById('sidebar-button-toggle'); this._sidebar_scrollable = this._target.querySelector('.scrollable'); this._is_focused = false; this._is_opened = false; const sidebar_header = this._target.querySelector('.sidebar-header'), sidebar_header_style = window.getComputedStyle(sidebar_header), sidebar_header_style_position = sidebar_header_style.position; sidebar_header.style.position = 'absolute'; let max_width = sidebar_header.clientWidth + parseInt(sidebar_header_style.getPropertyValue('margin-left')) + parseInt(sidebar_header_style.getPropertyValue('margin-right')); sidebar_header.style.position = sidebar_header_style_position; for (const child of this._target.querySelectorAll('nav > ul')) { const position = window.getComputedStyle(child).position; child.style.position = 'absolute'; max_width = Math.max(max_width, child.clientWidth); child.style.position = position; } this._target.style.maxWidth = max_width + 'px'; const server_name = this._target.querySelector('.server-name'); if (server_name) { server_name.style.width = 'auto'; server_name.style.maxWidth = max_width + 'px'; } this._view_mode = SIDEBAR_VIEW_MODE_FULL; if (this.hasClass('is-compact')) { this._view_mode = SIDEBAR_VIEW_MODE_COMPACT; } else if (this.hasClass('is-hidden')) { this._view_mode = SIDEBAR_VIEW_MODE_HIDDEN; this.addClass('focus-off'); } this.setViewMode(this._view_mode); } open() { clearTimeout(this._opened_timer); if (!this._is_opened) { setTimeout(() => this._is_opened = true); if (this._view_mode === SIDEBAR_VIEW_MODE_HIDDEN) { this.removeClass('focus-off'); ZABBIX.MenuMain.focusSelected(1); } if ([SIDEBAR_VIEW_MODE_COMPACT, SIDEBAR_VIEW_MODE_HIDDEN].includes(this._view_mode)) { this._target.style.zIndex = '10001'; setTimeout(() => { document.addEventListener('keyup', this._events.escape); document.addEventListener('click', this._events.click); }); } setTimeout(() => this.addClass('is-opened')); this.fire(SIDEBAR_EVENT_OPEN); } return this; } close() { clearTimeout(this._opened_timer); if (this._is_opened) { this._is_opened = false; if (this._view_mode === SIDEBAR_VIEW_MODE_COMPACT) { ZABBIX.MenuMain.collapseExpanded(); ZABBIX.UserMain.collapseExpanded(); this._sidebar_scrollable.scrollTop = 0; } if (this._view_mode === SIDEBAR_VIEW_MODE_HIDDEN) { ZABBIX.MenuMain.collapseExpanded(1); ZABBIX.UserMain.collapseExpanded(1); } if ([SIDEBAR_VIEW_MODE_COMPACT, SIDEBAR_VIEW_MODE_HIDDEN].includes(this._view_mode)) { const active_item = document.activeElement; if (active_item != null && active_item.parentElement != null && active_item.parentElement.classList.contains('has-submenu')) { active_item.blur(); } this._opened_timer = setTimeout(() => { if (this._view_mode === SIDEBAR_VIEW_MODE_HIDDEN) { this.addClass('focus-off'); } this._target.style.zIndex = null; }, UI_TRANSITION_DURATION); document.removeEventListener('keyup', this._events.escape); document.removeEventListener('click', this._events.click); } this.removeClass('is-opened'); this.fire(SIDEBAR_EVENT_CLOSE); } return this; } /** * Set view mode {0: full, 1: compact, 2: hidden}. * * @param {number} view_mode * * @returns {CSidebar} */ setViewMode(view_mode) { if (view_mode === SIDEBAR_VIEW_MODE_FULL) { this._is_opened = false; this.removeClass('is-opened'); } this.toggleClass('is-compact', view_mode === SIDEBAR_VIEW_MODE_COMPACT); this.toggleClass('is-hidden', view_mode === SIDEBAR_VIEW_MODE_HIDDEN); if (this._view_mode !== view_mode) { this._view_mode = view_mode; this.fire(SIDEBAR_EVENT_VIEWMODECHANGE, {view_mode: this._view_mode}); } return this; } updateSubmenuPosition(force) { const update = () => { let menu_item = ZABBIX.MenuMain.getExpanded(); if (menu_item) { menu_item = menu_item.getSubmenu().getExpanded(); menu_item && menu_item.getSubmenu().updateRect(menu_item._target, { top: document.querySelector('.sidebar-nav').getBoundingClientRect().top, bottom: document.getElementById('msg-global-footer').offsetHeight }); } }; if (force) { update(); } else if (!this._animation_frame_running) { this._animation_frame_running = true; window.requestAnimationFrame(() => { update(); this._animation_frame_running = false; }); } } /** * Register all DOM events. */ registerEvents() { this._events = { mouseenter: () => { this.open(); }, mouseleave: () => { if (!this._is_focused || document.activeElement.parentElement === null || document.activeElement.parentElement.classList.contains('has-submenu')) { clearTimeout(this._opened_timer); this._opened_timer = setTimeout(() => this.close(), SIDEBAR_HOVER_DELAY); } }, focusin: (e) => { if (!this._target.contains(e.relatedTarget)) { this._is_focused = true; if ([SIDEBAR_VIEW_MODE_COMPACT, SIDEBAR_VIEW_MODE_HIDDEN].includes(this._view_mode)) { this.open(); } this.fire(SIDEBAR_EVENT_FOCUS); } }, focusout: (e) => { if (!this._target.contains(e.relatedTarget) && !this._target.matches(':hover')) { this._is_focused = false; if ([SIDEBAR_VIEW_MODE_COMPACT, SIDEBAR_VIEW_MODE_HIDDEN].includes(this._view_mode)) { setTimeout(() => this.close()); } this.fire(SIDEBAR_EVENT_BLUR); } }, click: (e) => { if (this._is_opened && !this._target.contains(e.target)) { this.close(); } }, escape: (e) => { if (e.key === 'Escape') { this.close(); } }, toggle: (e) => { if (!this._is_opened) { this.open(); } else { this.close(); } e.preventDefault(); }, expandSelected: () => { this._expand_timer = setTimeout(() => { ZABBIX.MenuMain.expandSelected(); ZABBIX.UserMain.expandSelected(); }, MENU_EXPAND_SELECTED_DELAY); }, expandOver: (item) => { !this._is_opened && ZABBIX.MenuMain.getExpanded() === null && item.expandSubmenu(); }, cancelExpandSelected: () => { clearTimeout(this._expand_timer); }, expand: (e) => { if (this._sidebar_scrollable.scrollHeight > this._sidebar_scrollable.clientHeight) { setTimeout(() => { e.detail.menu_item._target.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }, UI_TRANSITION_DURATION); } this.updateSubmenuPosition(true); }, collapseSubmenu: (e) => { if (!this._target.contains(e.target)) { ZABBIX.MenuMain.collapseExpanded(1); } }, updateSubmenuPosition: () => { this.updateSubmenuPosition(false); }, scroll: () => { ZABBIX.MenuMain.collapseExpanded(1); }, viewmodeChange: (e) => { if (e.target.classList.contains('button-compact')) { ZABBIX.MenuMain.collapseExpanded(); ZABBIX.UserMain.collapseExpanded(); clearTimeout(this._expand_timer); this.setViewMode(SIDEBAR_VIEW_MODE_COMPACT); } else if (e.target.classList.contains('button-hide')) { ZABBIX.MenuMain.collapseExpanded(1); ZABBIX.UserMain.collapseExpanded(1); this.setViewMode(SIDEBAR_VIEW_MODE_HIDDEN); } else { ZABBIX.MenuMain.expandSelected(1); ZABBIX.UserMain.expandSelected(1); this.setViewMode(SIDEBAR_VIEW_MODE_FULL); } this._events._update(this._view_mode); e.preventDefault(); }, /** * Update event listeners based on view mode. * * @param view_mode * * @private */ _update: (view_mode) => { if (view_mode === SIDEBAR_VIEW_MODE_COMPACT) { this.on('mouseenter', this._events.mouseenter); this.on('mouseleave', this._events.mouseleave); for (const item of ZABBIX.MenuMain.getItems()) { item.hasSubmenu() && item.on('mouseenter', () => this._events.expandOver(item)); } for (const item of ZABBIX.UserMain.getItems()) { item.hasSubmenu() && item.on('mouseenter', () => this._events.expandOver(item)); } } else { this.off('mouseenter', this._events.mouseenter); this.off('mouseleave', this._events.mouseleave); for (const item of ZABBIX.MenuMain.getItems()) { item.hasSubmenu() && item.off('mouseenter', () => this._events.expandOver(item)); } for (const item of ZABBIX.UserMain.getItems()) { item.hasSubmenu() && item.off('mouseenter', () => this._events.expandOver(item)); } } if (this._sidebar_toggle !== null) { if (view_mode === SIDEBAR_VIEW_MODE_HIDDEN) { this._sidebar_toggle.addEventListener('click', this._events.toggle); } else { this._sidebar_toggle.removeEventListener('click', this._events.toggle); } } if ([SIDEBAR_VIEW_MODE_FULL, SIDEBAR_VIEW_MODE_HIDDEN].includes(view_mode)) { this.on('mouseleave', this._events.expandSelected); this.on('mouseenter', this._events.cancelExpandSelected); } else { this.off('mouseleave', this._events.expandSelected); this.off('mouseenter', this._events.cancelExpandSelected); } if (view_mode === SIDEBAR_VIEW_MODE_FULL) { document.removeEventListener('keyup', this._events.escape); document.removeEventListener('click', this._events.click); document.addEventListener('click', this._events.collapseSubmenu); window.addEventListener('resize', this._events.updateSubmenuPosition); } else { document.removeEventListener('click', this._events.collapseSubmenu); window.removeEventListener('resize', this._events.updateSubmenuPosition); } } }; this.on('focusin', this._events.focusin); this.on('focusout', this._events.focusout); for (const el of this._target.querySelectorAll('.js-sidebar-mode')) { el.addEventListener('click', this._events.viewmodeChange); } ZABBIX.MenuMain.on('expand', this._events.expand); this._sidebar_scrollable.addEventListener('scroll', this._events.updateSubmenuPosition); document.querySelector('.wrapper').addEventListener('scroll', this._events.collapseSubmenu); this._events._update(this._view_mode); } }