You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

414 lines
11 KiB

1 year ago
/*
** 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);
}
}