mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-21 01:28:47 +02:00
N°2847 - Rework iTopWebPage layout (WIP Part III)
- iTopWebPage: Extract processing in dedicated functions - iTopWebPage: Add tooltips base feature - iTopWebPage: Fix JS dictionaries - iTopWebPage: Extract inline JS to dedicated file - Top bar: Visual improvements - Nav. menu: Visual improvements - Nav. menu: Tooltip improvement - Nav. menu: Toggle state preference saved - Nav. menu: Add menus filter feature - Breadcrumbs: Handle elements overflow
This commit is contained in:
@@ -90,6 +90,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
$this->add_linked_stylesheet("../css/jquery.multiselect.css");
|
||||
$this->add_linked_stylesheet("../css/magnific-popup.css");
|
||||
$this->add_linked_stylesheet("../css/c3.min.css");
|
||||
$this->add_linked_stylesheet("../node_modules/tippy.js/dist/tippy.css");
|
||||
$this->add_linked_stylesheet("../node_modules/tippy.js/animations/shift-away-subtle.css");
|
||||
$this->add_linked_stylesheet("../css/font-awesome/css/all.min.css");
|
||||
$this->add_linked_stylesheet("../css/font-combodo/font-combodo.css");
|
||||
$this->add_linked_stylesheet("../js/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css");
|
||||
@@ -107,7 +109,10 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
$this->add_linked_script("../js/ckeditor/ckeditor.js");
|
||||
$this->add_linked_script("../js/ckeditor/adapters/jquery.js");
|
||||
$this->add_linked_script("../js/ckeditor/plugins/codesnippet/lib/highlight/highlight.pack.js");
|
||||
/* @deprecated qTip will be removed in 2.9.0, use Tippy.js instead */
|
||||
$this->add_linked_script("../js/jquery.qtip-1.0.min.js");
|
||||
$this->add_linked_script("../node_modules/@popperjs/core/dist/umd/popper.js");
|
||||
$this->add_linked_script("../node_modules/tippy.js/dist/tippy-bundle.umd.js");
|
||||
$this->add_linked_script('../js/property_field.js');
|
||||
$this->add_linked_script('../js/icon_select.js');
|
||||
$this->add_linked_script('../js/raphael-min.js');
|
||||
@@ -119,6 +124,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
$this->add_linked_script('../js/jquery.magnific-popup.min.js');
|
||||
$this->add_linked_script('../js/moment-with-locales.min.js');
|
||||
$this->add_linked_script('../js/showdown.min.js');
|
||||
$this->add_linked_script('../js/pages/backoffice.js');
|
||||
$this->add_linked_script('../js/newsroom_menu.js');
|
||||
|
||||
$this->add_dict_entry('UI:FillAllMandatoryFields');
|
||||
@@ -133,60 +139,34 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
if (!$this->IsPrintableVersion())
|
||||
{
|
||||
$this->PrepareLayout();
|
||||
$this->add_script(
|
||||
<<<EOF
|
||||
function ShowAboutBox()
|
||||
{
|
||||
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'about_box'}, function(data){
|
||||
$('body').append(data);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
function ArchiveMode(bEnable)
|
||||
{
|
||||
var sPrevUrl = StripArchiveArgument(window.location.search);
|
||||
if (bEnable)
|
||||
{
|
||||
window.location.search = sPrevUrl + '&with-archive=1';
|
||||
}
|
||||
else
|
||||
{
|
||||
window.location.search = sPrevUrl + '&with-archive=0';
|
||||
}
|
||||
}
|
||||
function StripArchiveArgument(sUrl)
|
||||
{
|
||||
var res = sUrl.replace(/&with-archive=[01]/g, '');
|
||||
return res;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true is the navigation menu should be expanded
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function IsMenuPaneVisible()
|
||||
{
|
||||
$bLeftPaneOpen = true;
|
||||
$bIsExpanded = false;
|
||||
if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
{
|
||||
// Leave the pane opened
|
||||
// Leave the menu collapsed
|
||||
}
|
||||
else
|
||||
{
|
||||
if (utils::ReadParam('force_menu_pane', null) === 0)
|
||||
{
|
||||
$bLeftPaneOpen = false;
|
||||
$bIsExpanded = false;
|
||||
}
|
||||
elseif (appUserPreferences::GetPref('menu_pane', 'open') == 'closed')
|
||||
elseif (appUserPreferences::GetPref('menu_pane', 'closed') === 'opened')
|
||||
{
|
||||
$bLeftPaneOpen = false;
|
||||
$bIsExpanded = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $bLeftPaneOpen;
|
||||
return $bIsExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,22 +175,22 @@ EOF
|
||||
protected function PrepareLayout()
|
||||
{
|
||||
// TODO: Move this to the menu renderer
|
||||
if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
{
|
||||
// No pin button
|
||||
$sConfigureWestPane = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sConfigureWestPane =
|
||||
<<<EOF
|
||||
if (typeof myLayout !== "undefined")
|
||||
{
|
||||
myLayout.addPinBtn( "#tPinMenu", "west" );
|
||||
}
|
||||
EOF;
|
||||
}
|
||||
$sInitClosed = $this->IsMenuPaneVisible() ? '' : 'initClosed: true,';
|
||||
// if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
// {
|
||||
// // No pin button
|
||||
// $sConfigureWestPane = '';
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// $sConfigureWestPane =
|
||||
// <<<EOF
|
||||
// if (typeof myLayout !== "undefined")
|
||||
// {
|
||||
// myLayout.addPinBtn( "#tPinMenu", "west" );
|
||||
// }
|
||||
//EOF;
|
||||
// }
|
||||
// $sInitClosed = $this->IsMenuPaneVisible() ? '' : 'initClosed: true,';
|
||||
|
||||
$sJSDisconnectedMessage = json_encode(Dict::S('UI:DisconnectedDlgMessage'));
|
||||
$sJSTitle = json_encode(Dict::S('UI:DisconnectedDlgTitle'));
|
||||
@@ -815,6 +795,33 @@ JS
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the language for the page metadata based on the current user
|
||||
*
|
||||
* @return string
|
||||
* @since 2.8.0
|
||||
*/
|
||||
protected function GetLanguageForMetadata()
|
||||
{
|
||||
$sUserLang = UserRights::GetUserLanguage();
|
||||
|
||||
return strtolower(substr($sUserLang, 0 ,2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the absolute URL for the favicon
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
* @since 2.8.0
|
||||
*/
|
||||
protected function GetFaviconAbsoluteUrl()
|
||||
{
|
||||
// TODO: Make it a property so it can be changed programmatically
|
||||
// TODO: How to set both dark/light mode favicons
|
||||
return utils::GetAbsoluteUrlAppRoot().'images/favicon.ico';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the complete revision number of the application
|
||||
*
|
||||
@@ -856,6 +863,7 @@ JS
|
||||
'sAppFullIconUrl' => Branding::GetFullMainLogoAbsoluteUrl(),
|
||||
'sAppIconLink' => MetaModel::GetConfig()->Get('app_icon_url'),
|
||||
'aMenuGroups' => ApplicationMenu::GetMenuGroups($oAppContext->GetAsHash()),
|
||||
'bIsExpanded' => $this->IsMenuPaneVisible(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1075,11 +1083,12 @@ EOF
|
||||
|
||||
// Prepare page metadata
|
||||
$sAbsoluteUrlAppRoot = addslashes($this->m_sRootUrl);
|
||||
// TODO: Make it a property so it can be changed programmatically
|
||||
// TODO: How to set both dark/light mode favicons
|
||||
$sFaviconUrl = $sAbsoluteUrlAppRoot.'images/favicon.ico';
|
||||
// TODO: Get this for the current user language
|
||||
$sMetadataLanguage = 'en';
|
||||
$sFaviconUrl = $this->GetFaviconAbsoluteUrl();
|
||||
$sMetadataLanguage = $this->GetLanguageForMetadata();
|
||||
|
||||
// Prepare internal parts (js files, css files, js snippets, css snippets, ...)
|
||||
// - Generate necessary dict. files
|
||||
$this->output_dict_entries();
|
||||
|
||||
// Base structure of data to pass to the TWIG template
|
||||
$aData['aPage'] = [
|
||||
@@ -1468,7 +1477,7 @@ EOF;
|
||||
$sHtml .= ' <div id="top-bar" class="ui-helper-clearfix" style="width:100%">';
|
||||
$sHtml .= self::FilterXSS($sApplicationBanner);
|
||||
|
||||
$GoHomeInitialStyle = $this->IsMenuPaneVisible() ? 'display: none;' : '';
|
||||
// $GoHomeInitialStyle = $this->IsMenuPaneVisible() ? 'display: none;' : '';
|
||||
|
||||
$sHtml .= ' <table id="top-bar-table">';
|
||||
$sHtml .= ' <tr>';
|
||||
|
||||
@@ -22,6 +22,8 @@ $ibo-breadcrumbs--item-icon--margin-x: 8px !default;
|
||||
$ibo-breadcrumbs--item-icon--max-width: 16px !default;
|
||||
$ibo-breadcrumbs--item-icon--text-color: $ibo-color-grey-600 !default;
|
||||
|
||||
$ibo-breadcrumbs--item-label--max-width: 100px !default;
|
||||
|
||||
$ibo-breadcrumbs--item-separator--margin-x: 12px !default;
|
||||
$ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default;
|
||||
|
||||
@@ -72,3 +74,8 @@ $ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default;
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
}
|
||||
.ibo-breadcrumbs--item-label{
|
||||
display: inline; /* Otherwise the "truncate" doesn't work with th default display: flex */
|
||||
max-width: $ibo-breadcrumbs--item-label--max-width;
|
||||
@extend %ibo-text-truncated-with-ellipsis;
|
||||
}
|
||||
|
||||
@@ -72,12 +72,14 @@ $ibo-navigation-menu--menu-group-icon--text-color--is-active: $ibo-color-primary
|
||||
$ibo-navigation-menu--menu-group-title--margin-left: $ibo-navigation-menu--middle-part--padding-x !default;
|
||||
$ibo-navigation-menu--menu-group-title--text-color--is-active: $ibo-color-blue-grey-800 !default;
|
||||
|
||||
$ibo-navigation-menu--drawer--background-color: $ibo-color-grey-100 !default;
|
||||
$ibo-navigation-menu--drawer--width: 312px !default;
|
||||
$ibo-navigation-menu--drawer--padding-x: 20px !default;
|
||||
$ibo-navigation-menu--drawer--padding-y: 32px !default;
|
||||
$ibo-navigation-menu--drawer--background-color: $ibo-color-grey-100 !default;
|
||||
$ibo-navigation-menu--drawer--border-right: 1px solid $ibo-color-grey-300 !default;
|
||||
|
||||
$ibo-navigation-menu--menu-filter--margin-bottom: 55px !default;
|
||||
$ibo-navigation-menu--menu-filter--width: $ibo-navigation-menu--drawer--width - $ibo-navigation-menu--drawer--padding-x * 2 !default;
|
||||
$ibo-navigation-menu--menu-filter--margin-bottom: 50px !default;
|
||||
$ibo-navigation-menu--menu-filter-input--padding-right: 64px !default;
|
||||
$ibo-navigation-menu--menu-filter-hotkey--border: 1px solid $ibo-color-grey-400 !default;
|
||||
|
||||
@@ -91,14 +93,18 @@ $ibo-navigation-menu--menu-filter-input--background-color: $ibo-color-white-100
|
||||
$ibo-navigation-menu--menu-filter-input--border: 1px solid $ibo-color-grey-300 !default;
|
||||
$ibo-navigation-menu--menu-filter-input--border-radius: $ibo-border-radius-300 !default;
|
||||
|
||||
$ibo-navigation-menu--menu-groups--margin-top: $ibo-navigation-menu--drawer--padding-y + $ibo-navigation-menu--menu-filter--margin-bottom !default;
|
||||
|
||||
$ibo-navigation-menu--menu-nodes--margin-bottom--on-filtered: 48px !default;
|
||||
|
||||
$ibo-navigation-menu--menu-nodes-title--margin-top: 0 !default;
|
||||
$ibo-navigation-menu--menu-nodes-title--margin-bottom: 32px !default;
|
||||
$ibo-navigation-menu--menu-nodes-title--margin-bottom--on-filtered: 8px !default;
|
||||
|
||||
$ibo-navigation-menu--menu-node--padding-x: 10px !default;
|
||||
$ibo-navigation-menu--menu-node--padding-y: 6px !default;
|
||||
$ibo-navigation-menu--menu-node--margin-x: -1 * $ibo-navigation-menu--menu-node--padding-x !default;
|
||||
$ibo-navigation-menu--menu-node--margin-y: 0 !default;
|
||||
$ibo-navigation-menu--menu-node--margin-top: 8px !default;
|
||||
$ibo-navigation-menu--menu-node--text-color: $ibo-color-grey-700 !default;
|
||||
$ibo-navigation-menu--menu-node--hyperlink-color: inherit !default;
|
||||
$ibo-navigation-menu--menu-node--background-color: $ibo-color-grey-200 !default;
|
||||
@@ -155,6 +161,15 @@ $ibo-navigation-menu--menu-node--border-radius: $ibo-border-radius-500 !default;
|
||||
right: calc(-1 * #{$ibo-navigation-menu--drawer--width});
|
||||
}
|
||||
}
|
||||
&.ibo-navigation-menu--is-filtered{
|
||||
.ibo-navigation-menu--menu-nodes{
|
||||
margin-bottom: $ibo-navigation-menu--menu-nodes--margin-bottom--on-filtered;
|
||||
|
||||
.ibo-navigation-menu--menu-nodes-title{
|
||||
margin-bottom: $ibo-navigation-menu--menu-nodes-title--margin-bottom--on-filtered;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-navigation-menu--body,
|
||||
@@ -340,12 +355,13 @@ $ibo-navigation-menu--menu-node--border-radius: $ibo-border-radius-500 !default;
|
||||
overflow-y: auto;
|
||||
|
||||
background-color: $ibo-navigation-menu--drawer--background-color;
|
||||
border-right: $ibo-navigation-menu--drawer--border-right;
|
||||
transition: right 0.2s ease-in-out;
|
||||
}
|
||||
/* - Menu filter */
|
||||
.ibo-navigation-menu--menu-filter{
|
||||
position: relative;
|
||||
margin-bottom: $ibo-navigation-menu--menu-filter--margin-bottom;
|
||||
position: fixed;
|
||||
width: $ibo-navigation-menu--menu-filter--width;
|
||||
}
|
||||
.ibo-navigation-menu--menu-filter-input{
|
||||
/* TODO: Refactor this into the standard field input */
|
||||
@@ -380,6 +396,10 @@ $ibo-navigation-menu--menu-node--border-radius: $ibo-border-radius-500 !default;
|
||||
padding: 2px 4px;
|
||||
@extend %ibo-font-ral-nor-50;
|
||||
}
|
||||
/* - Menu groups */
|
||||
.ibo-navigation-menu--menu-groups{
|
||||
margin-top: $ibo-navigation-menu--menu-groups--margin-top;
|
||||
}
|
||||
/* - Menu nodes */
|
||||
.ibo-navigation-menu--menu-nodes{
|
||||
display: none;
|
||||
@@ -405,7 +425,6 @@ $ibo-navigation-menu--menu-node--border-radius: $ibo-border-radius-500 !default;
|
||||
}
|
||||
}
|
||||
ul{
|
||||
margin-top: $ibo-navigation-menu--menu-node--margin-top;
|
||||
padding-left: $ibo-navigation-menu--drawer--padding-x;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,10 @@ $ibo-top-bar-background-color: $ibo-color-white-100 !default;
|
||||
height: var(--ibo-top-bar-height);
|
||||
padding: var(--ibo-top-bar-padding-y) var(--ibo-top-bar-padding-right) var(--ibo-top-bar-padding-y) var(--ibo-top-bar-padding-left);
|
||||
background-color: var(--ibo-top-bar-background-color);
|
||||
@extend %ibo-elevation-100;
|
||||
|
||||
.ibo-breadcrumbs{
|
||||
flex-grow: 1; /* Occupy as much width as possible */
|
||||
overflow-x: hidden; /* Avoid glitches when too many items */
|
||||
}
|
||||
}
|
||||
@@ -293,6 +293,9 @@
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
.ibo-top-bar {
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 1px 3px rgba(0, 0, 0, 0.12); }
|
||||
|
||||
/*!
|
||||
* Copyright (C) 2013-2020 Combodo SARL
|
||||
*
|
||||
@@ -8148,12 +8151,13 @@ a {
|
||||
padding: 32px 20px;
|
||||
overflow-y: auto;
|
||||
background-color: #f8f9fa;
|
||||
border-right: 1px solid #d5dde5;
|
||||
transition: right 0.2s ease-in-out; }
|
||||
|
||||
/* - Menu filter */
|
||||
.ibo-navigation-menu--menu-filter {
|
||||
position: relative;
|
||||
margin-bottom: 55px; }
|
||||
margin-bottom: 50px; }
|
||||
|
||||
.ibo-navigation-menu--menu-filter-input {
|
||||
/* TODO: Refactor this into the standard field input */
|
||||
@@ -8297,10 +8301,11 @@ body {
|
||||
background-color: var(--ibo-body-background-color); }
|
||||
|
||||
#ibo-navigation-menu {
|
||||
z-index: 1; }
|
||||
z-index: 20; }
|
||||
|
||||
#ibo-page-container {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
flex-grow: 1; }
|
||||
|
||||
@@ -42,10 +42,11 @@ body{
|
||||
@extend %ibo-font-ral-nor-100;
|
||||
}
|
||||
#ibo-navigation-menu{
|
||||
z-index: 1;
|
||||
z-index: 20;
|
||||
}
|
||||
#ibo-page-container{
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
|
||||
@@ -100,7 +100,7 @@ $(function()
|
||||
else
|
||||
{
|
||||
var sSanitizedUrl = StripArchiveArgument(oEntry['url']);
|
||||
sBreadcrumbsItemHtml += '<a class="ibo-breadcrumbs--item" data-breadcrumb-entry-number="'+iEntry+'" href="'+sSanitizedUrl+'" title="'+sTitle+'">'+sIconSpec+'<span class="truncate">'+oEntry['label']+'</span></a>';
|
||||
sBreadcrumbsItemHtml += '<a class="ibo-breadcrumbs--item" data-breadcrumb-entry-number="'+iEntry+'" href="'+sSanitizedUrl+'" title="'+sTitle+'">'+sIconSpec+'<span class="ibo-breadcrumbs--item-label">'+oEntry['label']+'</span></a>';
|
||||
}
|
||||
}
|
||||
this.element.append(sBreadcrumbsItemHtml);
|
||||
|
||||
@@ -24,21 +24,32 @@ $(function()
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
init_expanded: false,
|
||||
active_menu_group: null,
|
||||
filter_keyup_throttle: 200, // In milliseconds
|
||||
},
|
||||
css_classes:
|
||||
{
|
||||
menu_expanded: 'ibo-navigation-menu--is-expanded',
|
||||
menu_active: 'ibo-navigation-menu--is-active',
|
||||
menu_filtered: 'ibo-navigation-menu--is-filtered',
|
||||
menu_group_active: 'ibo-navigation-menu--menu-group--is-active',
|
||||
menu_nodes_active: 'ibo-navigation-menu--menu-nodes--is-active'
|
||||
},
|
||||
js_selectors:
|
||||
{
|
||||
menu_toggler: '[data-role="ibo-navigation-menu--toggler"]',
|
||||
menu_group: '[data-role="ibo-navigation-menu--menu-group"]',
|
||||
menu_drawer: '[data-role="ibo-navigation-menu--drawer"]',
|
||||
menu_filter_input: '[data-role="ibo-navigation-menu--menu-filter-input"]',
|
||||
menu_filter_clear: '[data-role="ibo-navigation-menu--menu-filter-clear"]',
|
||||
},
|
||||
filter_throttle_timeout: null,
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
this.element.addClass('ibo-navigation-menu');
|
||||
|
||||
this._bindEvents();
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
@@ -53,17 +64,31 @@ $(function()
|
||||
var oBodyElem = $('body');
|
||||
|
||||
// Click on collapse/expand toggler
|
||||
this.element.find('[data-role="ibo-navigation-menu--toggler"]').on('click', function(oEvent){
|
||||
this.element.find(this.js_selectors.menu_toggler).on('click', function(oEvent){
|
||||
me._onTogglerClick(oEvent);
|
||||
});
|
||||
// Click on menu group
|
||||
this.element.find('[data-role="ibo-navigation-menu--menu-group"]').on('click', function(oEvent){
|
||||
this.element.find(this.js_selectors.menu_group).on('click', function(oEvent){
|
||||
me._onMenuGroupClick(oEvent, $(this))
|
||||
});
|
||||
// Mostly for outside clicks that should close elements
|
||||
oBodyElem.on('click', function(oEvent){
|
||||
me._onBodyClick(oEvent);
|
||||
});
|
||||
// Mostly for hotkeys
|
||||
oBodyElem.on('keyup', function(oEvent){
|
||||
me._onBodyKeyUp(oEvent);
|
||||
});
|
||||
|
||||
// Menus filter
|
||||
// - Input itself
|
||||
this.element.find(this.js_selectors.menu_filter_input).on('keyup', function(oEvent){
|
||||
me._onFilterKeyUp(oEvent);
|
||||
});
|
||||
// - Clear icon
|
||||
this.element.find(this.js_selectors.menu_filter_clear).on('click', function(oEvent){
|
||||
me._onFilterClearClick(oEvent);
|
||||
})
|
||||
},
|
||||
|
||||
// Events callbacks
|
||||
@@ -72,8 +97,12 @@ $(function()
|
||||
// Avoid anchor glitch
|
||||
oEvent.preventDefault();
|
||||
|
||||
// Toggle menu
|
||||
this.element.toggleClass(this.css_classes.menu_expanded);
|
||||
// TODO: Save preference
|
||||
|
||||
// Save state in user preferences
|
||||
const sPrefValue = this.element.hasClass(this.css_classes.menu_expanded) ? 'opened' : 'closed';
|
||||
SetUserPreference('menu_pane', sPrefValue, true);
|
||||
},
|
||||
_onMenuGroupClick: function(oEvent, oMenuGroupElem)
|
||||
{
|
||||
@@ -90,19 +119,86 @@ $(function()
|
||||
this._closeDrawer();
|
||||
}
|
||||
},
|
||||
_onBodyKeyUp: function(oEvent)
|
||||
{
|
||||
// Note: We thought about extracting the oEvent.key in a variable to lower case it, but this would be done
|
||||
// on every single key up in the application, which might not be what we want... (time consuming)
|
||||
if((oEvent.altKey === true) && (oEvent.key === 'm' || oEvent.key === 'M'))
|
||||
{
|
||||
const me = this;
|
||||
|
||||
if(this._getActiveMenuGroupId() === null)
|
||||
{
|
||||
const sFirstMenuGroupId = this.element.find(this.js_selectors.menu_group+':first').attr('data-menu-group-id');
|
||||
this._openDrawer(sFirstMenuGroupId);
|
||||
}
|
||||
|
||||
this._focusFilter();
|
||||
}
|
||||
},
|
||||
|
||||
_onFilterKeyUp: function(oEvent)
|
||||
{
|
||||
const me = this;
|
||||
const oInputElem = this.element.find(this.js_selectors.menu_filter_input);
|
||||
const sValue = oInputElem.val();
|
||||
|
||||
if((sValue === '') || (oEvent.key === 'Escape'))
|
||||
{
|
||||
// Prevent other behaviors
|
||||
oEvent.stopPropagation();
|
||||
|
||||
this._clearFiltering();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset throttle timeout on key stroke
|
||||
clearTimeout(this.filter_throttle_timeout);
|
||||
this.filter_throttle_timeout = setTimeout(function(){
|
||||
me._doFiltering(sValue);
|
||||
}, this.options.filter_keyup_throttle);
|
||||
}
|
||||
},
|
||||
_onFilterClearClick: function(oEvent)
|
||||
{
|
||||
// Avoid anchor glitch
|
||||
oEvent.preventDefault();
|
||||
|
||||
// Remove current filter value
|
||||
this._clearFiltering();
|
||||
// Position focus in the input for better UX
|
||||
this._focusFilter();
|
||||
},
|
||||
|
||||
// Methods
|
||||
_checkIfClickShouldCloseDrawer: function(oEvent)
|
||||
{
|
||||
if(
|
||||
$(oEvent.target.closest('[data-role="ibo-navigation-menu--drawer"]')).length === 0
|
||||
$(oEvent.target.closest(this.js_selectors.menu_drawer)).length === 0
|
||||
&& $(oEvent.target.closest('[data-role="ibo-navigation-menu--menu-group"]')).length === 0
|
||||
&& $(oEvent.target.closest('[data-role="ibo-navigation-menu--toggler"]')).length === 0
|
||||
&& $(oEvent.target.closest(this.js_selectors.menu_toggler)).length === 0
|
||||
)
|
||||
{
|
||||
this._closeDrawer();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return the ID of the active menu group, or null if none (typically when the drawer is closed)
|
||||
* @returns {null|*}
|
||||
* @private
|
||||
*/
|
||||
_getActiveMenuGroupId: function()
|
||||
{
|
||||
const oActiveMenuGroup = this.element.find('.'+this.css_classes.menu_group_active);
|
||||
if(oActiveMenuGroup.length > 0)
|
||||
{
|
||||
return oActiveMenuGroup.attr('data-menu-group-id');
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Clear the current active menu group but does NOT close the drawer
|
||||
* @private
|
||||
@@ -120,6 +216,8 @@ $(function()
|
||||
_openDrawer: function(sMenuGroupId)
|
||||
{
|
||||
this._clearActiveMenuGroup();
|
||||
// Note: This causes the filter to be cleared event when using the hotkey to reopen a previously filled filter
|
||||
this._clearFiltering();
|
||||
|
||||
// Set new active group
|
||||
this.element.find('[data-role="ibo-navigation-menu--menu-group"][data-menu-group-id="'+sMenuGroupId+'"]').addClass(this.css_classes.menu_group_active);
|
||||
@@ -138,6 +236,79 @@ $(function()
|
||||
|
||||
// Set menu as non active
|
||||
this.element.removeClass(this.css_classes.menu_active);
|
||||
},
|
||||
|
||||
// Menus filter methods
|
||||
_focusFilter: function()
|
||||
{
|
||||
this.element.find(this.js_selectors.menu_filter_input).trigger('focus');
|
||||
},
|
||||
/**
|
||||
* Remove the current filter value and reset the menu nodes display
|
||||
* @private
|
||||
*/
|
||||
_clearFiltering: function()
|
||||
{
|
||||
this.element.find(this.js_selectors.menu_filter_input).val('');
|
||||
|
||||
// Reset display of everything
|
||||
// Note: We work on the 'display' property directly as there is a CSS rule managing the visibility of the active menu group
|
||||
this.element.find('[data-role="ibo-navigation-menu--menu-nodes"]').css('display', '');
|
||||
this.element.find('[data-role="ibo-navigation-menu--menu-node"]').css('display', '');
|
||||
|
||||
// Mark menu as unfiltered
|
||||
this.element.removeClass(this.css_classes.menu_filtered);
|
||||
},
|
||||
/**
|
||||
* Filter the displayed menu nodes regarding the current filter value
|
||||
* @param sRawFilterValue string
|
||||
* @private
|
||||
*/
|
||||
_doFiltering: function(sRawFilterValue)
|
||||
{
|
||||
const me = this;
|
||||
const aFilterValueParts = this._formatValueForFilterComparison(sRawFilterValue).split(' ');
|
||||
|
||||
// Mark menu as filtered
|
||||
this.element.addClass(this.css_classes.menu_filtered);
|
||||
|
||||
// Hide everything
|
||||
this.element.find('[data-role="ibo-navigation-menu--menu-nodes"]').hide();
|
||||
this.element.find('[data-role="ibo-navigation-menu--menu-node"]').hide();
|
||||
|
||||
// Show matching menu node
|
||||
this.element.find('[data-role="ibo-navigation-menu--menu-node"]').each(function(){
|
||||
const sNodeValue = me._formatValueForFilterComparison($(this).children('[data-role="ibo-navigation-menu--menu-node-title"]:first').text());
|
||||
let bMatches = true;
|
||||
|
||||
// On first non matching part, we consider that the menu node is not a match
|
||||
for(let iIdx in aFilterValueParts)
|
||||
{
|
||||
if(sNodeValue.indexOf(aFilterValueParts[iIdx]) === -1)
|
||||
{
|
||||
bMatches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(bMatches)
|
||||
{
|
||||
// Note: Selector must be recursive
|
||||
$(this).parents('[data-role="ibo-navigation-menu--menu-nodes"], [data-role="ibo-navigation-menu--menu-node"]').show();
|
||||
$(this).show();
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Format sOriginalValue for an easier comparison (change accents, capitalized letters, ...)
|
||||
*
|
||||
* @param sOriginalValue string
|
||||
* @returns string
|
||||
* @private
|
||||
*/
|
||||
_formatValueForFilterComparison: function(sOriginalValue)
|
||||
{
|
||||
return sOriginalValue.toLowerCase().latinise();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
75
js/pages/backoffice.js
Normal file
75
js/pages/backoffice.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2020 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
// Helpers
|
||||
function ShowAboutBox()
|
||||
{
|
||||
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'about_box'}, function(data){
|
||||
$('body').append(data);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
function ArchiveMode(bEnable)
|
||||
{
|
||||
var sPrevUrl = StripArchiveArgument(window.location.search);
|
||||
if (bEnable)
|
||||
{
|
||||
window.location.search = sPrevUrl + '&with-archive=1';
|
||||
}
|
||||
else
|
||||
{
|
||||
window.location.search = sPrevUrl + '&with-archive=0';
|
||||
}
|
||||
}
|
||||
|
||||
function StripArchiveArgument(sUrl)
|
||||
{
|
||||
var res = sUrl.replace(/&with-archive=[01]/g, '');
|
||||
return res;
|
||||
}
|
||||
|
||||
// Processing
|
||||
$(document).ready(function(){
|
||||
// Enable tooltips (abstraction layer between iTop markup and tooltip plugin to ease its replacement in the future)
|
||||
// - Existing HTML markup, won't work on markup added dynamically after DOM ready (AJAX, ...)
|
||||
$('[data-tooltip-content]').each(function(){
|
||||
const oOptions = {};
|
||||
|
||||
oOptions['content'] = $(this).attr('data-tooltip-content');
|
||||
oOptions['placement'] = $(this).attr('data-tooltip-placement') ?? 'top';
|
||||
oOptions['trigger'] = $(this).attr('data-tooltip-trigger') ?? 'mouseenter focus';
|
||||
|
||||
const sShiftingOffset = $(this).attr('data-tooltip-shifting-offset');
|
||||
const sDistanceOffset = $(this).attr('data-tooltip-distance-offset');
|
||||
oOptions['offset'] = [
|
||||
(sShiftingOffset === undefined) ? 0 : parseInt(sShiftingOffset),
|
||||
(sDistanceOffset === undefined) ? 10 : parseInt(sDistanceOffset),
|
||||
];
|
||||
|
||||
oOptions['animation'] = $(this).attr('data-tooltip-animation') ?? 'shift-away-subtle';
|
||||
|
||||
const sShowDelay = $(this).attr('data-tooltip-show-delay');
|
||||
const sHideDelay = $(this).attr('data-tooltip-hide-delay');
|
||||
oOptions['delay'] = [
|
||||
(typeof sShowDelay === 'undefined') ? 200 : parseInt(sShowDelay),
|
||||
(typeof sHideDelay === 'undefined') ? null : parseInt(sHideDelay),
|
||||
];
|
||||
|
||||
tippy(this, oOptions);
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,10 @@
|
||||
<nav id="{{ aNavigationMenu.sId }}" class="ibo-navigation-menu">
|
||||
<nav id="{{ aNavigationMenu.sId }}" class="ibo-navigation-menu {% if aNavigationMenu.bIsExpanded == true %}ibo-navigation-menu--is-expanded{% endif %}">
|
||||
<div class="ibo-navigation-menu--body">
|
||||
<div class="ibo-navigation-menu--top-part">
|
||||
<a class="ibo-navigation-menu--square-company-logo" title="{{ aNavigationMenu.sAppRevisionNumber }}" href="{{ aNavigationMenu.sAppIconLink }}">
|
||||
<img src="{{ aNavigationMenu.sAppSquareIconUrl }}" alt="{{ 'UI:Layout:NavigationMenu:CompanyLogo:AltText'|dict_s }}" />
|
||||
</a>
|
||||
<a class="ibo-navigation-menu--toggler" data-role="ibo-navigation-menu--toggler" title="{{ 'UI:Layout:NavigationMenu:Toggler:Tooltip'|dict_s }}" href="#">
|
||||
<a class="ibo-navigation-menu--toggler" data-role="ibo-navigation-menu--toggler" data-tooltip-content="{{ 'UI:Layout:NavigationMenu:Toggler:Tooltip'|dict_s }}" data-tooltip-placement="right" data-tooltip-distance-offset="20" href="#">
|
||||
<span class="ibo-navigation-menu--toggler-icon">
|
||||
<span class="ibo-navigation-menu--toggler-bar"></span>
|
||||
<span class="ibo-navigation-menu--toggler-bar"></span>
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="ibo-navigation-menu--drawer" data-role="ibo-navigation-menu--drawer">
|
||||
<div class="ibo-navigation-menu--menu-filter" data-role="ibo-nav-menu--menu-filter">
|
||||
{# TODO: Retrieve input style from a component? #}
|
||||
<input class="ibo-navigation-menu--menu-filter-input" data-role="ibo-navigation-menu--menu-filter-input" type="text" placeholder="{{ 'UI:Layout:NavigationMenu:MenuFilter:Input:Placeholder'|dict_s }}" />
|
||||
<input class="ibo-navigation-menu--menu-filter-input" data-role="ibo-navigation-menu--menu-filter-input" type="text" placeholder="{{ 'UI:Layout:NavigationMenu:MenuFilter:Input:Placeholder'|dict_s }}" data-tooltip-content="{{ 'UI:Layout:NavigationMenu:MenuFilter:Input:Tooltip'|dict_s }}" data-tooltip-trigger="mouseenter" />
|
||||
<a class="ibo-navigation-menu--menu-filter-clear" data-role="ibo-navigation-menu--menu-filter-clear" href="#"></a>
|
||||
<span class="ibo-navigation-menu--menu-filter-hotkey">{{ 'UI:Layout:NavigationMenu:MenuFilter:Input:Hotkey'|dict_s }}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<a class="ibo-navigation-menu--menu-group" data-role="ibo-navigation-menu--menu-group" data-menu-group-id="{{ aMenuGroup.sId }}" href="#">
|
||||
<span class="ibo-navigation-menu--menu-group-icon fa-fw {{ aMenuGroup.sIconCssClasses }}" title="{{ aMenuGroup.sTitle }}"></span>
|
||||
<span class="ibo-navigation-menu--menu-group-icon fa-fw {{ aMenuGroup.sIconCssClasses }}" data-tooltip-content="{{ aMenuGroup.sTitle }}" data-tooltip-placement="right" data-tooltip-distance-offset="25"></span>
|
||||
<span class="ibo-navigation-menu--menu-group-title">{{ aMenuGroup.sTitle }}</span>
|
||||
</a>
|
||||
@@ -1,9 +1,9 @@
|
||||
<li class="ibo-navigation-menu--menu-node" data-menu-node-id="{{ aMenuNode.sId }}">
|
||||
<li class="ibo-navigation-menu--menu-node" data-role="ibo-navigation-menu--menu-node" data-menu-node-id="{{ aMenuNode.sId }}">
|
||||
{% if aMenuNode.sUrl is not empty %}
|
||||
{% set sTarget = (aMenuNode.bOpenInNewWindow == true) ? 'target="_blank"' : '' %}
|
||||
<a class="ibo-navigation-menu--menu-node-title" href="{{ aMenuNode.sUrl }}" {{ sTarget|raw }}>{{ aMenuNode.sTitle }}</a>
|
||||
<a class="ibo-navigation-menu--menu-node-title" data-role="ibo-navigation-menu--menu-node-title" href="{{ aMenuNode.sUrl }}" {{ sTarget|raw }}>{{ aMenuNode.sTitle }}</a>
|
||||
{% else %}
|
||||
<span class="ibo-navigation-menu--menu-node-title">{{ aMenuNode.sTitle }}</span>
|
||||
<span class="ibo-navigation-menu--menu-node-title" data-role="ibo-navigation-menu--menu-node-title">{{ aMenuNode.sTitle }}</span>
|
||||
{% endif %}
|
||||
{% if aMenuNode.aSubMenuNodes is defined and aMenuNode.aSubMenuNodes|length > 0 %}
|
||||
<ul>
|
||||
|
||||
Reference in New Issue
Block a user