mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
N°2847 - Rework of the global iTopWebPage layout (Part I)
- Rework of the iTopWebPage class - Use of TWIG templates - Preliminary work of the navigation menu
This commit is contained in:
@@ -1019,7 +1019,7 @@ class JSButtonItem extends JSPopupMenuItem
|
||||
interface iPageUIExtension
|
||||
{
|
||||
/**
|
||||
* Add content to the North pane
|
||||
* Add content to the header of the page
|
||||
*
|
||||
* @param iTopWebPage $oPage The page to insert stuff into.
|
||||
*
|
||||
@@ -1028,7 +1028,7 @@ interface iPageUIExtension
|
||||
public function GetNorthPaneHtml(iTopWebPage $oPage);
|
||||
|
||||
/**
|
||||
* Add content to the South pane
|
||||
* Add content to the footer of the page
|
||||
*
|
||||
* @param iTopWebPage $oPage The page to insert stuff into.
|
||||
*
|
||||
|
||||
@@ -551,8 +551,8 @@ EOF
|
||||
$oLayout->Render($oPage, $this->aCells, $bEditMode, $aExtraParams);
|
||||
if (!$bEditMode)
|
||||
{
|
||||
$oPage->add_linked_script('../js/dashlet.js');
|
||||
$oPage->add_linked_script('../js/dashboard.js');
|
||||
$oPage->add_linked_script('js/dashlet.js');
|
||||
$oPage->add_linked_script('js/dashboard.js');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,11 +21,32 @@ require_once(APPROOT."/application/nicewebpage.class.inc.php");
|
||||
require_once(APPROOT."/application/applicationcontext.class.inc.php");
|
||||
require_once(APPROOT."/application/user.preferences.class.inc.php");
|
||||
|
||||
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
|
||||
|
||||
/**
|
||||
* Web page with some associated CSS and scripts (jquery) for a fancier display
|
||||
*/
|
||||
class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
{
|
||||
/** @var string ENUM_APP_ICON_SHAPE_SQUARE */
|
||||
const ENUM_APP_ICON_SHAPE_SQUARE = 'square';
|
||||
/** @var string ENUM_APP_ICON_SHAPE_FULL */
|
||||
const ENUM_APP_ICON_SHAPE_FULL = 'full';
|
||||
/** @var string DEFAULT_APP_ICON_SHAPE */
|
||||
const DEFAULT_APP_ICON_SHAPE = self::ENUM_APP_ICON_SHAPE_FULL;
|
||||
|
||||
/** @var array Default and branding filenames for the app. icon in the backoffice */
|
||||
protected static $aAppIconFilenames = [
|
||||
self::ENUM_APP_ICON_SHAPE_SQUARE => [
|
||||
'default' => 'itop-logo-square.png',
|
||||
'branding' => 'backoffice-square-logo.png',
|
||||
],
|
||||
self::ENUM_APP_ICON_SHAPE_FULL => [
|
||||
'default' => 'itop-logo.png',
|
||||
'branding' => 'main-logo.png',
|
||||
],
|
||||
];
|
||||
|
||||
private $m_sMenu;
|
||||
// private $m_currentOrganization;
|
||||
private $m_aMessages;
|
||||
@@ -71,41 +92,44 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
$this->m_aMessages = array();
|
||||
$this->SetRootUrl(utils::GetAbsoluteUrlAppRoot());
|
||||
$this->add_header("Content-type: text/html; charset=".self::PAGES_CHARSET);
|
||||
$this->add_header("Cache-control: no-cache");
|
||||
$this->add_linked_stylesheet("../css/jquery.treeview.css");
|
||||
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");
|
||||
$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("../css/font-awesome/css/all.min.css");
|
||||
$this->add_linked_stylesheet("../js/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css");
|
||||
// TODO: Should we keep this? Makes no sense
|
||||
//$this->add_header("Cache-control: no-cache");
|
||||
// TODO: Add only what's necessary
|
||||
$this->add_linked_stylesheet("css/jquery.treeview.css");
|
||||
$this->add_linked_stylesheet("css/jquery-ui-timepicker-addon.css");
|
||||
$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("css/font-awesome/css/all.min.css");
|
||||
$this->add_linked_stylesheet("js/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css");
|
||||
|
||||
$this->add_linked_script('../js/jquery.layout.min.js');
|
||||
$this->add_linked_script('../js/jquery.ba-bbq.min.js');
|
||||
$this->add_linked_script("../js/jquery.treeview.js");
|
||||
$this->add_linked_script("../js/date.js");
|
||||
$this->add_linked_script("../js/jquery-ui-timepicker-addon.js");
|
||||
$this->add_linked_script("../js/jquery-ui-timepicker-addon-i18n.min.js");
|
||||
$this->add_linked_script("../js/jquery.blockUI.js");
|
||||
$this->add_linked_script("../js/utils.js");
|
||||
$this->add_linked_script("../js/swfobject.js");
|
||||
$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");
|
||||
$this->add_linked_script("../js/jquery.qtip-1.0.min.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');
|
||||
$this->add_linked_script('../js/d3.js');
|
||||
$this->add_linked_script('../js/c3.js');
|
||||
$this->add_linked_script('../js/jquery.multiselect.js');
|
||||
$this->add_linked_script('../js/ajaxfileupload.js');
|
||||
$this->add_linked_script('../js/jquery.mousewheel.js');
|
||||
$this->add_linked_script('../js/jquery.magnific-popup.min.js');
|
||||
$this->add_linked_script('../js/breadcrumb.js');
|
||||
$this->add_linked_script('../js/moment-with-locales.min.js');
|
||||
$this->add_linked_script('../js/showdown.min.js');
|
||||
$this->add_linked_script('../js/newsroom_menu.js');
|
||||
// TODO: Add only what's necessary
|
||||
$this->add_linked_script('js/jquery.layout.min.js');
|
||||
$this->add_linked_script('js/jquery.ba-bbq.min.js');
|
||||
$this->add_linked_script("js/jquery.treeview.js");
|
||||
$this->add_linked_script("js/date.js");
|
||||
$this->add_linked_script("js/jquery-ui-timepicker-addon.js");
|
||||
$this->add_linked_script("js/jquery-ui-timepicker-addon-i18n.min.js");
|
||||
$this->add_linked_script("js/jquery.blockUI.js");
|
||||
$this->add_linked_script("js/utils.js");
|
||||
$this->add_linked_script("js/swfobject.js");
|
||||
$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");
|
||||
$this->add_linked_script("js/jquery.qtip-1.0.min.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');
|
||||
$this->add_linked_script('js/d3.js');
|
||||
$this->add_linked_script('js/c3.js');
|
||||
$this->add_linked_script('js/jquery.multiselect.js');
|
||||
$this->add_linked_script('js/ajaxfileupload.js');
|
||||
$this->add_linked_script('js/jquery.mousewheel.js');
|
||||
$this->add_linked_script('js/jquery.magnific-popup.min.js');
|
||||
$this->add_linked_script('js/breadcrumb.js');
|
||||
$this->add_linked_script('js/moment-with-locales.min.js');
|
||||
$this->add_linked_script('js/showdown.min.js');
|
||||
$this->add_linked_script('js/newsroom_menu.js');
|
||||
|
||||
$this->add_dict_entry('UI:FillAllMandatoryFields');
|
||||
|
||||
@@ -180,6 +204,7 @@ EOF
|
||||
*/
|
||||
protected function PrepareLayout()
|
||||
{
|
||||
// TODO: Move this to the menu renderer
|
||||
if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
{
|
||||
// No pin button
|
||||
@@ -368,66 +393,6 @@ JS
|
||||
<<< JS
|
||||
try
|
||||
{
|
||||
var myLayout; // a var is required because this page utilizes: myLayout.allowOverflow() method
|
||||
|
||||
// Layout
|
||||
paneSize = GetUserPreference('menu_size', 300);
|
||||
if ($('body').length > 0)
|
||||
{
|
||||
myLayout = $('body').layout({
|
||||
west : {
|
||||
$sInitClosed minSize: 200, size: paneSize, spacing_open: 16, spacing_close: 16, slideTrigger_open: "click", hideTogglerOnSlide: true, enableCursorHotkey: false,
|
||||
onclose_end: function(name, elt, state, options, layout)
|
||||
{
|
||||
if (state.isSliding == false)
|
||||
{
|
||||
$('.menu-pane-exclusive').show();
|
||||
SetUserPreference('menu_pane', 'closed', true);
|
||||
}
|
||||
},
|
||||
onresize_end: function(name, elt, state, options, layout)
|
||||
{
|
||||
if (state.isSliding == false)
|
||||
{
|
||||
SetUserPreference('menu_size', state.size, true);
|
||||
}
|
||||
},
|
||||
|
||||
onopen_end: function(name, elt, state, options, layout)
|
||||
{
|
||||
if (state.isSliding == false)
|
||||
{
|
||||
$('.menu-pane-exclusive').hide();
|
||||
SetUserPreference('menu_pane', 'open', true);
|
||||
}
|
||||
}
|
||||
},
|
||||
center: {
|
||||
onresize_end: function(name, elt, state, options, layout)
|
||||
{
|
||||
$('.v-resizable').each( function() {
|
||||
var fixedWidth = $(this).parent().innerWidth() - 6;
|
||||
$(this).width(fixedWidth);
|
||||
// Make sure it cannot be resized horizontally
|
||||
$(this).resizable('options', { minWidth: fixedWidth, maxWidth: fixedWidth });
|
||||
// Now adjust all the child 'items'
|
||||
var innerWidth = $(this).innerWidth() - 10;
|
||||
$(this).find('.item').width(innerWidth);
|
||||
});
|
||||
$('.panel-resized').trigger('resized');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
window.clearTimeout(iPaneVisWatchDog);
|
||||
//myLayout.open( "west" );
|
||||
$('.ui-layout-resizer-west .ui-layout-toggler').css({background: 'transparent'});
|
||||
$sConfigureWestPane
|
||||
if ($('#left-pane').length > 0)
|
||||
{
|
||||
$('#left-pane').layout({ resizable: false, spacing_open: 0, south: { size: 94 }, enableCursorHotkey: false });
|
||||
}
|
||||
// Tabs, using JQuery BBQ to store the history
|
||||
// The "tab widgets" to handle.
|
||||
var tabs = $('div[id^=tabbedContent]');
|
||||
@@ -478,6 +443,7 @@ JS
|
||||
JS
|
||||
);
|
||||
|
||||
// TODO: What is this for?
|
||||
$this->add_ready_script(
|
||||
<<< JS
|
||||
|
||||
@@ -642,13 +608,17 @@ JS
|
||||
});
|
||||
JS
|
||||
);
|
||||
|
||||
// TODO: To preserve
|
||||
$this->add_ready_script(InlineImage::FixImagesWidth());
|
||||
|
||||
/*
|
||||
* Not used since the sorting of the tables is always performed server-side
|
||||
AttributeDateTime::InitTableSorter($this, 'custom_date_time');
|
||||
AttributeDate::InitTableSorter($this, 'custom_date');
|
||||
*/
|
||||
|
||||
// TODO: What is this for?
|
||||
$sUserPrefs = appUserPreferences::GetAsJSON();
|
||||
$this->add_script(
|
||||
<<<JS
|
||||
@@ -725,6 +695,20 @@ JS
|
||||
);
|
||||
}
|
||||
|
||||
protected function LoadTheme()
|
||||
{
|
||||
// TODO: Reuse theming mecanism
|
||||
// $sCssThemeUrl = ThemeHandler::GetCurrentThemeUrl();
|
||||
// $this->add_linked_stylesheet($sCssThemeUrl);
|
||||
|
||||
$sCssRelPath = utils::GetCSSFromSASS(
|
||||
'css/backoffice/main.scss',
|
||||
array(
|
||||
APPROOT.'css/backoffice/',
|
||||
)
|
||||
);
|
||||
$this->add_saas($sCssRelPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sId Identifies the item, to search after it in the current breadcrumb
|
||||
@@ -833,15 +817,75 @@ JS
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \DictExceptionMissingString
|
||||
* Return the complete revision number of the application
|
||||
*
|
||||
* @return string
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public function DisplayMenu()
|
||||
public function GetApplicationRevisionNumber()
|
||||
{
|
||||
// Display the menu
|
||||
$oAppContext = new ApplicationContext();
|
||||
$iAccordionIndex = 0;
|
||||
if (ITOP_REVISION == 'svn')
|
||||
{
|
||||
// This is NOT a version built using the build system, just display the main version
|
||||
$sRevisionNumber = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a build made from SVN, let display the full information
|
||||
$sRevisionNumber = Dict::Format('UI:iTopVersion:Long', ITOP_APPLICATION, ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
|
||||
}
|
||||
|
||||
ApplicationMenu::DisplayMenu($this, $oAppContext->GetAsHash());
|
||||
return $sRevisionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the absolute URL to the application logo of $sShape
|
||||
*
|
||||
* @see static::ENUM_APP_ICON_SHAPE_SQUARE, static::ENUM_APP_ICON_SHAPE_FULL, ...
|
||||
*
|
||||
* @param string $sShape Shape of the icon to return
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public function GetApplicationIconUrl($sShape = self::DEFAULT_APP_ICON_SHAPE)
|
||||
{
|
||||
$sIconDefaultFilename = static::$aAppIconFilenames[$sShape]['default'];
|
||||
$sIconBrandingFilename = static::$aAppIconFilenames[$sShape]['branding'];
|
||||
|
||||
$sIconAbsUrl = utils::GetAbsoluteUrlAppRoot().'images/'.$sIconDefaultFilename;
|
||||
// Check if icon is overloaded by the branding
|
||||
if (file_exists(MODULESROOT.'branding/'.$sIconBrandingFilename))
|
||||
{
|
||||
$sIconAbsUrl = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sIconBrandingFilename;
|
||||
}
|
||||
|
||||
$sIconAbsUrl .= '?t='.utils::GetCacheBusterTimestamp();
|
||||
|
||||
return $sIconAbsUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the navigation menu data (id, menu groups, ...)
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @throws \DictExceptionMissingString
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public function GetNavigationMenuData()
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
|
||||
return [
|
||||
'sId' => 'ibo-navigation-menu',
|
||||
'sAppRevisionNumber' => $this->GetApplicationRevisionNumber(),
|
||||
'sAppSquareIconUrl' => $this->GetApplicationIconUrl(static::ENUM_APP_ICON_SHAPE_SQUARE),
|
||||
'sAppFullIconUrl' => $this->GetApplicationIconUrl(static::ENUM_APP_ICON_SHAPE_FULL),
|
||||
'sAppIconLink' => MetaModel::GetConfig()->Get('app_icon_url'),
|
||||
'aMenuGroups' => ApplicationMenu::GetMenuGroups($oAppContext->GetAsHash()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -905,6 +949,72 @@ EOF
|
||||
return $sNewsroomInitialImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the banner HTML which can come from both iTop itself and from extensions
|
||||
*
|
||||
* @see \iPageUIExtension::GetBannerHtml()
|
||||
*
|
||||
* @return string
|
||||
* @since 2.8.0
|
||||
*/
|
||||
protected function RenderBannerHtml()
|
||||
{
|
||||
$sBannerHtml = '';
|
||||
|
||||
// Call the extensions to add content to the page, warning they can also add styles or scripts through as they have access to the \iTopWebPage
|
||||
foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sBannerHtml .= $oExtensionInstance->GetBannerHtml($this);
|
||||
}
|
||||
|
||||
return $sBannerHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the header HTML which can come from both iTop itself and from extensions
|
||||
*
|
||||
* @see \iPageUIExtension::GetNorthPaneHtml()
|
||||
*
|
||||
* @return string
|
||||
* @since 2.8.0
|
||||
*/
|
||||
protected function RenderHeaderHtml()
|
||||
{
|
||||
$sHeaderHtml = '';
|
||||
|
||||
if (UserRights::IsAdministrator() && ExecutionKPI::IsEnabled())
|
||||
{
|
||||
// TODO: Don't forget this dude!
|
||||
$sHeaderHtml .= '<div class="app-message"><span style="padding:5px;">'.ExecutionKPI::GetDescription().'<span></div>';
|
||||
}
|
||||
|
||||
// Call the extensions to add content to the page, warning they can also add styles or scripts through as they have access to the \iTopWebPage
|
||||
foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sHeaderHtml .= $oExtensionInstance->GetNorthPaneHtml($this);
|
||||
}
|
||||
|
||||
return $sHeaderHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the footer HTML which can come from both iTop itself and from extensions
|
||||
*
|
||||
* @see \iPageUIExtension::GetSouthPaneHtml()
|
||||
*
|
||||
* @return string
|
||||
* @since 2.8.0
|
||||
*/
|
||||
protected function RenderFooterHtml()
|
||||
{
|
||||
$sFooterHtml = '';
|
||||
|
||||
// Call the extensions to add content to the page, warning they can also add styles or scripts through as they have access to the \iTopWebPage
|
||||
foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sFooterHtml .= $oExtensionInstance->GetSouthPaneHtml($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
@@ -912,36 +1022,78 @@ EOF
|
||||
*/
|
||||
public function output()
|
||||
{
|
||||
$sAbsURLAppRoot = addslashes($this->m_sRootUrl);
|
||||
// Data to be passed to the view
|
||||
$aData = [];
|
||||
|
||||
// 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';
|
||||
|
||||
// Base structure of data to pass to the TWIG template
|
||||
$aData['aPage'] = [
|
||||
'sAbsoluteUrlAppRoot' => $sAbsoluteUrlAppRoot,
|
||||
'sTitle' => $this->s_title,
|
||||
'sFaviconUrl' => $sFaviconUrl,
|
||||
'aMetadata' => [
|
||||
'sCharset' => static::PAGES_CHARSET,
|
||||
'sLang' => $sMetadataLanguage,
|
||||
],
|
||||
'aCssFiles' => $this->a_linked_stylesheets,
|
||||
'aCssInline' => $this->a_styles,
|
||||
'aJsFiles' => $this->a_linked_scripts,
|
||||
'aJsInlineOnInit' => $this->m_aInitScript,
|
||||
'aJsInlineOnDomReady' => $this->m_aReadyScripts,
|
||||
'aJsInlineLive' => $this->a_scripts,
|
||||
];
|
||||
|
||||
// Layouts
|
||||
$aData['aLayouts'] = [
|
||||
'sBanner' => $this->RenderBannerHtml(),
|
||||
'sHeader' => $this->RenderHeaderHtml(),
|
||||
'sFooter' => $this->RenderFooterHtml(),
|
||||
];
|
||||
|
||||
// - Navigation menu
|
||||
$aData['aLayouts']['aNavigationMenu'] = $this->GetNavigationMenuData();
|
||||
|
||||
$oTwigEnv = TwigHelper::GetTwigEnvironment(APPROOT.'templates/');
|
||||
$sTemplateRelPath = 'pages/backoffice/layout';
|
||||
|
||||
// Send headers
|
||||
if ($this->GetOutputFormat() === 'html')
|
||||
{
|
||||
foreach ($this->a_headers as $sHeader)
|
||||
{
|
||||
header($sHeader);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Render final TWIG into global HTML
|
||||
$oKpi = new ExecutionKPI();
|
||||
$sHtml = TwigHelper::RenderTemplate($oTwigEnv, $aData, $sTemplateRelPath);
|
||||
$oKpi->ComputeAndReport('TWIG rendering');
|
||||
|
||||
// Echo global HTML
|
||||
$oKpi = new ExecutionKPI();
|
||||
echo $sHtml;
|
||||
$oKpi->ComputeAndReport('Echoing ('.round(strlen($sHtml) / 1024).' Kb)');
|
||||
|
||||
DBSearch::RecordQueryTrace();
|
||||
ExecutionKPI::ReportStats();
|
||||
|
||||
return;
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
////////////////// ☢ DANGER ZONE ☢ /////////////////////
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
//$this->set_base($this->m_sRootUrl.'pages/');
|
||||
$sForm = $this->GetSiloSelectionForm();
|
||||
$this->DisplayMenu(); // Compute the menu
|
||||
|
||||
// Call the extensions to add content to the page, so that they can also add styles or scripts
|
||||
$sBannerExtraHtml = '';
|
||||
foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sBannerExtraHtml .= $oExtensionInstance->GetBannerHtml($this);
|
||||
}
|
||||
|
||||
$sNorthPane = '';
|
||||
foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sNorthPane .= $oExtensionInstance->GetNorthPaneHtml($this);
|
||||
}
|
||||
|
||||
if (UserRights::IsAdministrator() && ExecutionKPI::IsEnabled())
|
||||
{
|
||||
$sNorthPane .= '<div class="app-message"><span style="padding:5px;">'.ExecutionKPI::GetDescription().'<span></div>';
|
||||
}
|
||||
|
||||
//$sSouthPane = '<p>Peak memory Usage: '.sprintf('%.3f MB', memory_get_peak_usage(true) / (1024*1024)).'</p>';
|
||||
$sSouthPane = '';
|
||||
foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sSouthPane .= $oExtensionInstance->GetSouthPaneHtml($this);
|
||||
}
|
||||
|
||||
// Render the tabs in the page (if any)
|
||||
$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
|
||||
@@ -969,6 +1121,7 @@ EOF
|
||||
EOF
|
||||
);
|
||||
|
||||
// TODO: Extract this in a dedicated component
|
||||
$iBreadCrumbMaxCount = utils::GetConfig()->Get('breadcrumb.max_count');
|
||||
if ($iBreadCrumbMaxCount > 1)
|
||||
{
|
||||
@@ -1004,82 +1157,31 @@ EOF
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Extract in a dedicated component and call it in the nav menu
|
||||
$sNewsRoomInitialImage = $this->InitNewsroom();
|
||||
|
||||
$this->outputCollapsibleSectionInit();
|
||||
|
||||
if ($this->GetOutputFormat() == 'html')
|
||||
{
|
||||
foreach ($this->a_headers as $s_header)
|
||||
{
|
||||
header($s_header);
|
||||
}
|
||||
}
|
||||
// TODO: Is this for the "Debug" popup? We should do a helper to display a popup in various cases (welcome message for example)
|
||||
$s_captured_output = $this->ob_get_clean_safe();
|
||||
$sHtml = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
|
||||
$sHtml .= "<html>\n";
|
||||
$sHtml .= "<head>\n";
|
||||
// Make sure that Internet Explorer renders the page using its latest/highest/greatest standards !
|
||||
$sHtml .= "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n";
|
||||
$sPageCharset = self::PAGES_CHARSET;
|
||||
$sHtml .= "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=$sPageCharset\" />\n";
|
||||
$sHtml .= "<title>".htmlentities($this->s_title, ENT_QUOTES, $sPageCharset)."</title>\n";
|
||||
$sHtml .= $this->get_base_tag();
|
||||
// Stylesheets MUST be loaded before any scripts otherwise
|
||||
// jQuery scripts may face some spurious problems (like failing on a 'reload')
|
||||
foreach ($this->a_linked_stylesheets as $a_stylesheet)
|
||||
{
|
||||
if (strpos($a_stylesheet['link'], '?') === false)
|
||||
{
|
||||
$s_stylesheet = $a_stylesheet['link']."?t=".utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
else
|
||||
{
|
||||
$s_stylesheet = $a_stylesheet['link']."&t=".utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
if ($a_stylesheet['condition'] != "")
|
||||
{
|
||||
$sHtml .= "<!--[if {$a_stylesheet['condition']}]>\n";
|
||||
}
|
||||
$sHtml .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$s_stylesheet}\" />\n";
|
||||
if ($a_stylesheet['condition'] != "")
|
||||
{
|
||||
$sHtml .= "<![endif]-->\n";
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Stylesheet for printing instead of having all those "IsPrintableVersion()" ifs
|
||||
// TODO: Careful! In the print view, we can actually choose which part to print or not, so it's not just a print stylesheet...
|
||||
// special stylesheet for printing, hides the navigation gadgets
|
||||
$sHtml .= "<link rel=\"stylesheet\" media=\"print\" type=\"text/css\" href=\"../css/print.css?t=".utils::GetCacheBusterTimestamp()."\" />\n";
|
||||
|
||||
if ($this->GetOutputFormat() == 'html')
|
||||
{
|
||||
$sHtml .= $this->output_dict_entries(true); // before any script so that they can benefit from the translations
|
||||
foreach ($this->a_linked_scripts as $s_script)
|
||||
{
|
||||
// Make sure that the URL to the script contains the application's version number
|
||||
// so that the new script do NOT get reloaded from the cache when the application is upgraded
|
||||
if (strpos($s_script, '?') === false)
|
||||
{
|
||||
$s_script .= "?t=".utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
else
|
||||
{
|
||||
$s_script .= "&t=".utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
$sHtml .= "<script type=\"text/javascript\" src=\"$s_script\"></script>\n";
|
||||
}
|
||||
|
||||
if (!$this->IsPrintableVersion())
|
||||
{
|
||||
$this->add_script("var iPaneVisWatchDog = window.setTimeout('FixPaneVis()',5000);");
|
||||
}
|
||||
$sInitScripts = "";
|
||||
if (count($this->m_aInitScript) > 0)
|
||||
{
|
||||
foreach ($this->m_aInitScript as $m_sInitScript)
|
||||
{
|
||||
$sInitScripts .= "$m_sInitScript\n";
|
||||
}
|
||||
}
|
||||
$this->add_script("\$(document).ready(function() {\n{$sInitScripts};\nwindow.setTimeout('onDelayedReady()',10)\n});");
|
||||
|
||||
|
||||
// TODO: Should we still do this init vs ready separation?
|
||||
// $this->add_script("\$(document).ready(function() {\n{$sInitScripts};\nwindow.setTimeout('onDelayedReady()',10)\n});");
|
||||
if ($this->IsPrintableVersion())
|
||||
{
|
||||
$this->add_ready_script(
|
||||
@@ -1109,32 +1211,13 @@ $('legend').css('cursor', 'pointer').click(function(){
|
||||
EOF
|
||||
);
|
||||
}
|
||||
if (count($this->m_aReadyScripts) > 0)
|
||||
{
|
||||
$this->add_script("\nonDelayedReady = function() {\n".implode("\n", $this->m_aReadyScripts)."\n}\n");
|
||||
}
|
||||
if (count($this->a_scripts) > 0)
|
||||
{
|
||||
$sHtml .= "<script type=\"text/javascript\">\n";
|
||||
foreach ($this->a_scripts as $s_script)
|
||||
{
|
||||
$sHtml .= "$s_script\n";
|
||||
}
|
||||
$sHtml .= "</script>\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (count($this->a_styles) > 0)
|
||||
{
|
||||
$sHtml .= "<style>\n";
|
||||
foreach ($this->a_styles as $s_style)
|
||||
{
|
||||
$sHtml .= "$s_style\n";
|
||||
}
|
||||
$sHtml .= "</style>\n";
|
||||
}
|
||||
|
||||
// TODO: Does this still work?
|
||||
$sHtml .= "<link rel=\"search\" type=\"application/opensearchdescription+xml\" title=\"iTop\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/opensearch.xml.php\" />\n";
|
||||
$sHtml .= "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico?t=".utils::GetCacheBusterTimestamp()."\" />\n";
|
||||
|
||||
|
||||
$sHtml .= "</head>\n";
|
||||
$sBodyClass = "";
|
||||
@@ -1178,18 +1261,6 @@ EOF;
|
||||
$sHtml .= "<div class=\"printable-content\" style=\"width: $sDefaultResolution;\">";
|
||||
}
|
||||
|
||||
// Render the revision number
|
||||
if (ITOP_REVISION == 'svn')
|
||||
{
|
||||
// This is NOT a version built using the buil system, just display the main version
|
||||
$sVersionString = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a build made from SVN, let display the full information
|
||||
$sVersionString = Dict::Format('UI:iTopVersion:Long', ITOP_APPLICATION, ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
|
||||
}
|
||||
|
||||
// Render the text of the global search form
|
||||
$sText = htmlentities(utils::ReadParam('text', '', false, 'raw_data'), ENT_QUOTES, self::PAGES_CHARSET);
|
||||
$sOnClick = " onclick=\"if ($('#global-search-input').val() != '') { $('#global-search form').submit(); } \"";
|
||||
@@ -1206,6 +1277,7 @@ EOF;
|
||||
$oAppContext = new ApplicationContext();
|
||||
|
||||
$sUserName = UserRights::GetUser();
|
||||
// TODO: BEGIN USER MENU
|
||||
$sIsAdmin = UserRights::IsAdministrator() ? '(Administrator)' : '';
|
||||
if (UserRights::IsAdministrator())
|
||||
{
|
||||
@@ -1272,8 +1344,9 @@ EOF;
|
||||
$aActions[$oAbout->GetUID()] = $oAbout->GetMenuItem();
|
||||
|
||||
$sLogOffMenu .= $this->RenderPopupMenuItems($aActions);
|
||||
// TODO: END USER MENU
|
||||
|
||||
|
||||
// TODO: Move this in the Header method
|
||||
$sRestrictions = '';
|
||||
if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE))
|
||||
{
|
||||
@@ -1305,6 +1378,7 @@ EOF;
|
||||
$this->AddApplicationMessage($sRestrictions, $sIcon);
|
||||
}
|
||||
|
||||
// TODO: Move this in the header method
|
||||
$sApplicationMessages = '';
|
||||
foreach ($this->m_aMessages as $aMessage)
|
||||
{
|
||||
@@ -1326,7 +1400,7 @@ EOF;
|
||||
$sSouthPane = '<div id="bottom-pane" class="ui-layout-south">'.$sSouthPane.'</div>';
|
||||
}
|
||||
|
||||
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
|
||||
// TODO: What do we do with this?
|
||||
$sOnlineHelpUrl = MetaModel::GetConfig()->Get('online_help');
|
||||
//$sLogOffMenu = "<span id=\"logOffBtn\" style=\"height:55px;padding:0;margin:0;\"><img src=\"../images/onOffBtn.png\"></span>";
|
||||
|
||||
@@ -1438,9 +1512,9 @@ EOF;
|
||||
|
||||
if ($this->GetOutputFormat() == 'html')
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
echo $sHtml;
|
||||
$oKPI->ComputeAndReport('Echoing ('.round(strlen($sHtml) / 1024).' Kb)');
|
||||
// $oKpi = new ExecutionKPI();
|
||||
// echo $sHtml;
|
||||
// $oKpi->ComputeAndReport('Echoing ('.round(strlen($sHtml) / 1024).' Kb)');
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1474,8 +1548,8 @@ EOF;
|
||||
}
|
||||
}
|
||||
}
|
||||
DBSearch::RecordQueryTrace();
|
||||
ExecutionKPI::ReportStats();
|
||||
// DBSearch::RecordQueryTrace();
|
||||
// ExecutionKPI::ReportStats();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -200,11 +200,96 @@ class ApplicationMenu
|
||||
return self::$aMenusIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of menu groups
|
||||
*
|
||||
* @param array $aExtraParams
|
||||
*
|
||||
* @return array
|
||||
* @throws \DictExceptionMissingString
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public static function GetMenuGroups($aExtraParams = array())
|
||||
{
|
||||
self::LoadAdditionalMenus();
|
||||
|
||||
// Sort the root menu based on the rank
|
||||
usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
|
||||
|
||||
$aMenuGroups = [];
|
||||
foreach(static::$aRootMenus as $aMenuGroup)
|
||||
{
|
||||
if(!static::CanDisplayMenu($aMenuGroup))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$sMenuGroupIdx = $aMenuGroup['index'];
|
||||
$oMenuNode = static::GetMenuNode($sMenuGroupIdx);
|
||||
|
||||
$aMenuGroups[] = [
|
||||
'sId' => $oMenuNode->GetMenuID(),
|
||||
'sIconCssClasses' => 'fas fa-fw fa-home', // TODO: Get the classes from the datamodel
|
||||
'sTitle' => $oMenuNode->GetTitle(),
|
||||
'aSubMenuNodes' => static::GetSubMenuNodes($sMenuGroupIdx, $aExtraParams),
|
||||
];
|
||||
}
|
||||
|
||||
return $aMenuGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of sub-menu nodes for $sMenuGroupIdx
|
||||
*
|
||||
* @param string $sMenuGroupIdx
|
||||
* @param array $aExtraParams
|
||||
*
|
||||
* @return array
|
||||
* @throws \DictExceptionMissingString
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public static function GetSubMenuNodes($sMenuGroupIdx, $aExtraParams = array())
|
||||
{
|
||||
$aSubMenuItems = self::GetChildren($sMenuGroupIdx);
|
||||
|
||||
// Sort the children based on the rank
|
||||
usort($aSubMenuItems, array('ApplicationMenu', 'CompareOnRank'));
|
||||
|
||||
$aSubMenuNodes = [];
|
||||
foreach($aSubMenuItems as $aSubMenuItem)
|
||||
{
|
||||
if(!static::CanDisplayMenu($aSubMenuItem))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$sSubMenuItemIdx = $aSubMenuItem['index'];
|
||||
$oSubMenuNode = static::GetMenuNode($sSubMenuItemIdx);
|
||||
|
||||
if(!$oSubMenuNode->IsEnabled())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$aSubMenuNodes[] = [
|
||||
'sId' => $oSubMenuNode->GetMenuId(),
|
||||
'sTitle' => $oSubMenuNode->GetTitle(),
|
||||
'sUrl' => $oSubMenuNode->GetHyperlink($aExtraParams),
|
||||
'bOpenInNewWindow' => $oSubMenuNode->IsHyperLinkInNewWindow(),
|
||||
'aSubMenuNodes' => static::GetSubMenuNodes($sSubMenuItemIdx, $aExtraParams),
|
||||
];
|
||||
}
|
||||
|
||||
return $aSubMenuNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point to display the whole menu into the web page, used by iTopWebPage
|
||||
* @param \WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
* @throws DictExceptionMissingString
|
||||
*
|
||||
* @deprecated Will be removed in 2.8.0
|
||||
*/
|
||||
public static function DisplayMenu($oPage, $aExtraParams)
|
||||
{
|
||||
@@ -280,6 +365,7 @@ EOF
|
||||
* @return bool True if the currently selected menu is one of the submenus
|
||||
* @throws DictExceptionMissingString
|
||||
* @throws \Exception
|
||||
* @deprecated Will be removed in 2.8.0
|
||||
*/
|
||||
protected static function DisplaySubMenu($oPage, $aMenus, $aExtraParams, $iActiveMenu = -1)
|
||||
{
|
||||
|
||||
422
css/backoffice/layout/_navigation-menu.scss
Normal file
422
css/backoffice/layout/_navigation-menu.scss
Normal file
@@ -0,0 +1,422 @@
|
||||
/*!
|
||||
* 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
|
||||
*/
|
||||
|
||||
/* SCSS variables (can be overloaded) */
|
||||
$ibo-navigation-menu-height: 100vh !default;
|
||||
|
||||
$ibo-navigation-menu--body--padding-x: 16px !default;
|
||||
$ibo-navigation-menu--body--padding-y: 16px !default;
|
||||
$ibo-navigation-menu--body--width-collapsed: 60px !default;
|
||||
$ibo-navigation-menu--body--width-expanded: 310px !default;
|
||||
$ibo-navigation-menu--body--background-color: $ibo-color-blue-grey-900 !default;
|
||||
$ibo-navigation-menu--body--text-color: $ibo-color-grey-300 !default;
|
||||
|
||||
$ibo-navigation-menu--top-part--padding-y: $ibo-navigation-menu--body--padding-y !default;
|
||||
$ibo-navigation-menu--top-part--padding-x: $ibo-navigation-menu--body--padding-x !default;
|
||||
$ibo-navigation-menu--top-part--elements-spacing: 20px !default;
|
||||
|
||||
$ibo-navigation-menu--middle-part--padding-top: 40px !default;
|
||||
$ibo-navigation-menu--middle-part--padding-bottom: 16px !default;
|
||||
$ibo-navigation-menu--middle-part--padding-x: $ibo-navigation-menu--body--padding-x !default;
|
||||
$ibo-navigation-menu--middle-part--elements-spacing: 20px !default;
|
||||
$ibo-navigation-menu--middle-part--scrollbar-width: 5px !default;
|
||||
$ibo-navigation-menu--middle-part--scrollbar-track-background-color: $ibo-color-transparent !default;
|
||||
$ibo-navigation-menu--middle-part--scrollbar-thumb-background-color: $ibo-color-grey-800 !default;
|
||||
$ibo-navigation-menu--middle-part--scrollbar-thumb-border: none !default;
|
||||
$ibo-navigation-menu--middle-part--scrollbar-thumb-border-radius: $ibo-border-radius-500 !default;
|
||||
|
||||
$ibo-navigation-menu--bottom-part--background-color: $ibo-color-grey-800 !default;
|
||||
|
||||
$ibo-navigation-menu--action--padding-x: 8px !default;
|
||||
$ibo-navigation-menu--action--padding-y: 10px !default;
|
||||
$ibo-navigation-menu--action--border-radius: $ibo-border-radius-500 !default;
|
||||
$ibo-navigation-menu--action--border-radius--on-active: $ibo-border-radius-full !default;
|
||||
$ibo-navigation-menu--action--border-radius--on-active-expanded: $ibo-border-radius-900 !default;
|
||||
$ibo-navigation-menu--action--text-color--on-hover: $ibo-color-white-100 !default;
|
||||
$ibo-navigation-menu--action--background-color--on-hover: $ibo-color-blue-grey-700 !default;
|
||||
$ibo-navigation-menu--action--background-color--on-active: $ibo-color-grey-100 !default;
|
||||
$ibo-navigation-menu--action-icon--width: 28px !default;
|
||||
$ibo-navigation-menu--action-icon--text-color--on-hover: $ibo-color-white-100 !default;
|
||||
|
||||
$ibo-navigation-menu--square-company-logo--width: 38px !default;
|
||||
$ibo-navigation-menu--square-company-logo--margin-top: 0 !default;
|
||||
$ibo-navigation-menu--square-company-logo--margin-x: -5px !default;
|
||||
$ibo-navigation-menu--square-company-logo--margin-bottom: $ibo-navigation-menu--body--padding-y * 2 !default;
|
||||
|
||||
$ibo-navigation-menu--toggler-icon--height: 20px !default;
|
||||
$ibo-navigation-menu--toggler-icon--width: $ibo-navigation-menu--action-icon--width !default;
|
||||
$ibo-navigation-menu--toggler-bar--height: 3px !default;
|
||||
$ibo-navigation-menu--toggler-bar--width: 100% !default;
|
||||
$ibo-navigation-menu--toggler--width: $ibo-navigation-menu--action-icon--width + $ibo-navigation-menu--top-part--padding-x !default;
|
||||
|
||||
$ibo-navigation-menu--menu-group--background-color--is-active: $ibo-color-grey-100 !default;
|
||||
$ibo-navigation-menu--menu-group--border-radius--is-active: $ibo-border-radius-500 0 0 $ibo-border-radius-500 !default;
|
||||
|
||||
$ibo-navigation-menu--menu-group-icon--font-size: $ibo-font-size-350 !default;
|
||||
$ibo-navigation-menu--menu-group-icon--text-color--is-active: $ibo-color-primary-500 !default;
|
||||
$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--menu-filter--margin-bottom: 55px !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;
|
||||
|
||||
/* TODO: Refactor this into the standard field input */
|
||||
$ibo-navigation-menu--menu-filter-input--padding-x: 10px !default;
|
||||
$ibo-navigation-menu--menu-filter-input--padding-y: 8px !default;
|
||||
$ibo-navigation-menu--menu-filter-input--width: 100% !default;
|
||||
$ibo-navigation-menu--menu-filter-input--placeholder-color: $ibo-color-grey-500 !default;
|
||||
$ibo-navigation-menu--menu-filter-input--text-color: $ibo-color-grey-900 !default;
|
||||
$ibo-navigation-menu--menu-filter-input--background-color: $ibo-color-white-100 !default;
|
||||
$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-nodes-title--margin-top: 0 !default;
|
||||
$ibo-navigation-menu--menu-nodes-title--margin-bottom: 32px !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;
|
||||
$ibo-navigation-menu--menu-node--border-radius: $ibo-border-radius-500 !default;
|
||||
|
||||
|
||||
/* CSS variables (can be changed directly from the browser) */
|
||||
:root {
|
||||
/* TODO: Introduce variables once SCSS variables are set */
|
||||
}
|
||||
|
||||
/* IMPORTANT: Rules are made for the collapsed mode by default */
|
||||
/* Expanded rules should only be in the dedicated section */
|
||||
|
||||
.ibo-navigation-menu{
|
||||
position: relative;
|
||||
height: $ibo-navigation-menu-height;
|
||||
|
||||
&.ibo-navigation-menu--is-expanded{
|
||||
.ibo-navigation-menu--body{
|
||||
width: $ibo-navigation-menu--body--width-expanded;
|
||||
|
||||
.ibo-navigation-menu--toggler-bar{
|
||||
&:nth-child(1){
|
||||
top: 4px;
|
||||
left: 7px;
|
||||
width: 14px;
|
||||
transform: rotateZ(-45deg);
|
||||
}
|
||||
&:nth-child(2){
|
||||
top: 8px;
|
||||
left: 7px;
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
&:nth-child(3){
|
||||
top: 12px;
|
||||
left: 7px;
|
||||
width: 14px;
|
||||
transform: rotateZ(45deg);
|
||||
}
|
||||
}
|
||||
.ibo-navigation-menu--menu-group{
|
||||
&:not(.ibo-navigation-menu--menu-group--is-active){
|
||||
&:active{
|
||||
border-radius: $ibo-navigation-menu--action--border-radius--on-active-expanded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.ibo-navigation-menu--is-active{
|
||||
.ibo-navigation-menu--drawer{
|
||||
right: calc(-1 * #{$ibo-navigation-menu--drawer--width});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-navigation-menu--body,
|
||||
.ibo-navigation-menu--drawer{
|
||||
height: $ibo-navigation-menu-height;
|
||||
}
|
||||
|
||||
/* Body */
|
||||
.ibo-navigation-menu--body{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
|
||||
height: $ibo-navigation-menu-height;
|
||||
width: $ibo-navigation-menu--body--width-collapsed;
|
||||
|
||||
background-color: $ibo-navigation-menu--body--background-color;
|
||||
transition: width 0.1s ease-in-out;
|
||||
}
|
||||
/* - Top part */
|
||||
.ibo-navigation-menu--top-part{
|
||||
padding: $ibo-navigation-menu--top-part--padding-y $ibo-navigation-menu--top-part--padding-x;
|
||||
}
|
||||
/* - Middle part */
|
||||
.ibo-navigation-menu--middle-part{
|
||||
/* Occupy as much space as possible */
|
||||
flex-grow: 1;
|
||||
/* Only the middle part should have a variable size */
|
||||
overflow-y: auto;
|
||||
padding: $ibo-navigation-menu--middle-part--padding-top $ibo-navigation-menu--middle-part--padding-x $ibo-navigation-menu--middle-part--padding-bottom;
|
||||
/* Scrollbar for Firefox and future W3C specs. */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: $ibo-navigation-menu--middle-part--scrollbar-thumb-background-color $ibo-navigation-menu--middle-part--scrollbar-track-background-color;
|
||||
}
|
||||
/* Scrollbar for Chrome/Edge/Safari */
|
||||
.ibo-navigation-menu--middle-part::-webkit-scrollbar {
|
||||
width: $ibo-navigation-menu--middle-part--scrollbar-width;
|
||||
}
|
||||
.ibo-navigation-menu--middle-part::-webkit-scrollbar-track {
|
||||
background-color: $ibo-navigation-menu--middle-part--scrollbar-track-background-color;
|
||||
}
|
||||
.ibo-navigation-menu--middle-part::-webkit-scrollbar-thumb {
|
||||
background-color: $ibo-navigation-menu--middle-part--scrollbar-thumb-background-color;
|
||||
border: $ibo-navigation-menu--middle-part--scrollbar-thumb-border;
|
||||
border-radius: $ibo-navigation-menu--middle-part--scrollbar-thumb-border-radius;
|
||||
}
|
||||
/* - Bottom part */
|
||||
.ibo-navigation-menu--bottom-part{
|
||||
height: 150px;
|
||||
background-color: $ibo-navigation-menu--bottom-part--background-color;
|
||||
}
|
||||
|
||||
.ibo-navigation-menu--toggler,
|
||||
.ibo-navigation-menu--menu-group{
|
||||
margin: calc(-1 * #{$ibo-navigation-menu--action--padding-y}) calc(-1 * #{$ibo-navigation-menu--action--padding-x});
|
||||
padding: $ibo-navigation-menu--action--padding-y $ibo-navigation-menu--action--padding-x;
|
||||
border-radius: $ibo-navigation-menu--action--border-radius;
|
||||
}
|
||||
|
||||
/* Top part */
|
||||
/* - Company logo: square */
|
||||
.ibo-navigation-menu--square-company-logo{
|
||||
display: flex;
|
||||
width: $ibo-navigation-menu--square-company-logo--width;
|
||||
margin: $ibo-navigation-menu--square-company-logo--margin-top $ibo-navigation-menu--square-company-logo--margin-x $ibo-navigation-menu--square-company-logo--margin-bottom;
|
||||
}
|
||||
/* - Toggler */
|
||||
.ibo-navigation-menu--toggler{
|
||||
display: flex;
|
||||
/* Width is define here in addition of the icon so we can fix its width whether the menu is collapsed or expanded */
|
||||
width: $ibo-navigation-menu--toggler--width;
|
||||
|
||||
&:hover,
|
||||
&:active{
|
||||
background-color: $ibo-navigation-menu--action--background-color--on-hover;
|
||||
|
||||
.ibo-navigation-menu--toggler-bar{
|
||||
background-color: $ibo-navigation-menu--action--text-color--on-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ibo-navigation-menu--toggler-icon{
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: $ibo-navigation-menu--toggler-icon--height;
|
||||
width: $ibo-navigation-menu--toggler-icon--width;
|
||||
}
|
||||
.ibo-navigation-menu--toggler-bar{
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: $ibo-navigation-menu--toggler-bar--height;
|
||||
width: $ibo-navigation-menu--toggler-bar--width;
|
||||
opacity: 1;
|
||||
transition: all 0.2s linear;
|
||||
background-color: $ibo-navigation-menu--body--text-color;
|
||||
|
||||
&:nth-child(1){
|
||||
top: 0;
|
||||
}
|
||||
&:nth-child(2){
|
||||
top: 8px;
|
||||
}
|
||||
&:nth-child(3){
|
||||
top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* - Menu groups */
|
||||
.ibo-navigation-menu--menu-group{
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
|
||||
/* To keep title on the line even when collapsed. Better visual feedback when expanding. */
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
|
||||
color: $ibo-navigation-menu--body--text-color;
|
||||
@extend %ibo-font-ral-nor-200;
|
||||
|
||||
transition-property: background-color, color, padding, margin, border-radius;
|
||||
transition-duration: 0.1s;
|
||||
transition-timing-function: linear;
|
||||
|
||||
> .ibo-navigation-menu--menu-group-icon,
|
||||
> .ibo-navigation-menu--menu-group-title{
|
||||
display: flex; /* To avoid end space due to display:inline-block */
|
||||
}
|
||||
|
||||
&:not(:last-child){
|
||||
margin-bottom: $ibo-navigation-menu--middle-part--elements-spacing;
|
||||
}
|
||||
|
||||
&:not(.ibo-navigation-menu--menu-group--is-active)
|
||||
{
|
||||
&:hover,
|
||||
&:active{
|
||||
color: $ibo-navigation-menu--action--text-color--on-hover;
|
||||
background-color: $ibo-navigation-menu--action--background-color--on-hover;
|
||||
}
|
||||
&:active{
|
||||
border-radius: $ibo-navigation-menu--action--border-radius--on-active;
|
||||
}
|
||||
}
|
||||
&.ibo-navigation-menu--menu-group--is-active{
|
||||
margin-right: calc(-2 * #{$ibo-navigation-menu--action--padding-x});
|
||||
padding-right: calc(2 - #{$ibo-navigation-menu--action--padding-x});
|
||||
color: $ibo-navigation-menu--menu-group-title--text-color--is-active;
|
||||
background-color: $ibo-navigation-menu--menu-group--background-color--is-active;
|
||||
border-radius: $ibo-navigation-menu--menu-group--border-radius--is-active;
|
||||
|
||||
.ibo-navigation-menu--menu-group-icon{
|
||||
color: $ibo-navigation-menu--menu-group-icon--text-color--is-active;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-navigation-menu--menu-group-icon{
|
||||
width: $ibo-navigation-menu--action-icon--width;
|
||||
text-align: center;
|
||||
font-size: $ibo-navigation-menu--menu-group-icon--font-size;
|
||||
|
||||
&::before{
|
||||
width: $ibo-navigation-menu--action-icon--width;
|
||||
}
|
||||
}
|
||||
.ibo-navigation-menu--menu-group-title{
|
||||
margin-left: $ibo-navigation-menu--menu-group-title--margin-left;
|
||||
text-align: left;
|
||||
@extend %ibo-text-truncated-with-ellipsis;
|
||||
}
|
||||
|
||||
/* Drawer */
|
||||
.ibo-navigation-menu--drawer{
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: $ibo-navigation-menu--drawer--width;
|
||||
padding: $ibo-navigation-menu--drawer--padding-y $ibo-navigation-menu--drawer--padding-x;
|
||||
overflow-y: auto;
|
||||
|
||||
background-color: $ibo-navigation-menu--drawer--background-color;
|
||||
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;
|
||||
}
|
||||
.ibo-navigation-menu--menu-filter-input{
|
||||
/* TODO: Refactor this into the standard field input */
|
||||
width: $ibo-navigation-menu--menu-filter-input--width;
|
||||
padding: $ibo-navigation-menu--menu-filter-input--padding-y $ibo-navigation-menu--menu-filter-input--padding-x;
|
||||
color: $ibo-navigation-menu--menu-filter-input--text-color;
|
||||
background-color: $ibo-navigation-menu--menu-filter-input--background-color;
|
||||
border: $ibo-navigation-menu--menu-filter-input--border;
|
||||
border-radius: $ibo-navigation-menu--menu-filter-input--border-radius;
|
||||
|
||||
&::placeholder{
|
||||
color: $ibo-navigation-menu--menu-filter-input--placeholder-color;
|
||||
}
|
||||
/* This rule is duplicated otherwise Chrome won't be able to parse it. */
|
||||
&:-ms-input-placeholder,
|
||||
&::-ms-input-placeholder{
|
||||
color: $ibo-navigation-menu--menu-filter-input--placeholder-color;
|
||||
}
|
||||
|
||||
padding-right: $ibo-navigation-menu--menu-filter-input--padding-right; /* Must be at least #nm-filter-hotkey width + some padding */
|
||||
}
|
||||
.ibo-navigation-menu--menu-filter-clear{
|
||||
display: none;
|
||||
}
|
||||
.ibo-navigation-menu--menu-filter-hotkey{
|
||||
position: absolute;
|
||||
top: $ibo-navigation-menu--menu-filter-input--padding-y;
|
||||
right: $ibo-navigation-menu--menu-filter-input--padding-x;
|
||||
border: $ibo-navigation-menu--menu-filter-hotkey--border;
|
||||
border-radius: $ibo-navigation-menu--menu-filter-input--border-radius;
|
||||
color: $ibo-navigation-menu--menu-filter-input--placeholder-color;
|
||||
padding: 2px 4px;
|
||||
@extend %ibo-font-ral-nor-50;
|
||||
}
|
||||
/* - Menu nodes */
|
||||
.ibo-navigation-menu--menu-nodes{
|
||||
display: none;
|
||||
|
||||
ul{
|
||||
li{
|
||||
> a,
|
||||
> span{
|
||||
display: block; /* Force to occupy parent's full width */
|
||||
margin: $ibo-navigation-menu--menu-node--margin-y $ibo-navigation-menu--menu-node--margin-x;
|
||||
padding: $ibo-navigation-menu--menu-node--padding-y $ibo-navigation-menu--menu-node--padding-x;
|
||||
border-radius: 0;
|
||||
color: $ibo-navigation-menu--menu-node--text-color;
|
||||
@extend %ibo-font-ral-nor-150;
|
||||
}
|
||||
> a{
|
||||
color: $ibo-navigation-menu--menu-node--hyperlink-color;
|
||||
|
||||
&:hover{
|
||||
background-color: $ibo-navigation-menu--menu-node--background-color;
|
||||
border-radius: $ibo-navigation-menu--menu-node--border-radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
ul{
|
||||
margin-top: $ibo-navigation-menu--menu-node--margin-top;
|
||||
padding-left: $ibo-navigation-menu--drawer--padding-x;
|
||||
}
|
||||
}
|
||||
|
||||
&.ibo-navigation-menu--menu-nodes--is-active{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.ibo-navigation-menu--menu-nodes-title{
|
||||
margin-top: $ibo-navigation-menu--menu-nodes-title--margin-top;
|
||||
margin-bottom: $ibo-navigation-menu--menu-nodes-title--margin-bottom;
|
||||
@extend %ibo-font-ral-nor-350;
|
||||
@extend %ibo-text-truncated-with-ellipsis;
|
||||
}
|
||||
27
dictionaries/ui/layouts/en.dictionary.navigation-menu.php
Normal file
27
dictionaries/ui/layouts/en.dictionary.navigation-menu.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
// Navigation menu
|
||||
Dict::Add('EN US', 'English', 'English', array(
|
||||
'UI:Layout:NavigationMenu:CompanyLogo:AltText' => 'Company logo',
|
||||
'UI:Layout:NavigationMenu:Toggler:Tooltip' => 'Expand / Collapse',
|
||||
'UI:Layout:NavigationMenu:MenuFilter:Input:Placeholder' => 'Filter...',
|
||||
'UI:Layout:NavigationMenu:MenuFilter:Input:Tooltip' => 'Type your keywords to filter menus',
|
||||
'UI:Layout:NavigationMenu:MenuFilter:Input:Hotkey' => 'Alt + M',
|
||||
));
|
||||
BIN
images/itop-logo-square.png
Normal file
BIN
images/itop-logo-square.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
143
js/layouts/navigation-menu.js
Normal file
143
js/layouts/navigation-menu.js
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
$.widget( 'itop.navigation_menu',
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
init_expanded: false,
|
||||
active_menu_group: null,
|
||||
},
|
||||
css_classes:
|
||||
{
|
||||
menu_expanded: 'ibo-navigation-menu--is-expanded',
|
||||
menu_active: 'ibo-navigation-menu--is-active',
|
||||
menu_group_active: 'ibo-navigation-menu--menu-group--is-active',
|
||||
menu_nodes_active: 'ibo-navigation-menu--menu-nodes--is-active'
|
||||
},
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
this.element.addClass('ibo-navigation-menu');
|
||||
this._bindEvents();
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
this.element.removeClass('ibo-navigation-menu');
|
||||
},
|
||||
_bindEvents: function()
|
||||
{
|
||||
var me = this;
|
||||
var oBodyElem = $('body');
|
||||
|
||||
// Click on collapse/expand toggler
|
||||
this.element.find('[data-role="ibo-navigation-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){
|
||||
me._onMenuGroupClick(oEvent, $(this))
|
||||
});
|
||||
// Mostly for outside clicks that should close elements
|
||||
oBodyElem.on('click', function(oEvent){
|
||||
me._onBodyClick(oEvent);
|
||||
});
|
||||
},
|
||||
|
||||
// Events callbacks
|
||||
_onTogglerClick: function(oEvent)
|
||||
{
|
||||
// Avoid anchor glitch
|
||||
oEvent.preventDefault();
|
||||
|
||||
this.element.toggleClass(this.css_classes.menu_expanded);
|
||||
// TODO: Save preference
|
||||
},
|
||||
_onMenuGroupClick: function(oEvent, oMenuGroupElem)
|
||||
{
|
||||
// Avoid anchor glitch
|
||||
oEvent.preventDefault();
|
||||
|
||||
var sMenuGroupId = oMenuGroupElem.attr('data-menu-group-id');
|
||||
this._openDrawer(sMenuGroupId);
|
||||
},
|
||||
_onBodyClick: function(oEvent)
|
||||
{
|
||||
if(this._checkIfClickShouldCloseDrawer(oEvent))
|
||||
{
|
||||
this._closeDrawer();
|
||||
}
|
||||
},
|
||||
|
||||
// Methods
|
||||
_checkIfClickShouldCloseDrawer: function(oEvent)
|
||||
{
|
||||
if(
|
||||
$(oEvent.target.closest('[data-role="ibo-navigation-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
|
||||
)
|
||||
{
|
||||
this._closeDrawer();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Clear the current active menu group but does NOT close the drawer
|
||||
* @private
|
||||
*/
|
||||
_clearActiveMenuGroup: function()
|
||||
{
|
||||
this.element.find('[data-role="ibo-navigation-menu--menu-group"]').removeClass(this.css_classes.menu_group_active);
|
||||
this.element.find('[data-role="ibo-navigation-menu--menu-nodes"]').removeClass(this.css_classes.menu_nodes_active);
|
||||
},
|
||||
/**
|
||||
* Open the drawer and set sMenuGroupId as the current active menu group
|
||||
* @param sMenuGroupId string
|
||||
* @private
|
||||
*/
|
||||
_openDrawer: function(sMenuGroupId)
|
||||
{
|
||||
this._clearActiveMenuGroup();
|
||||
|
||||
// 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);
|
||||
this.element.find('[data-role="ibo-navigation-menu--menu-nodes"][data-menu-group-id="'+sMenuGroupId+'"]').addClass(this.css_classes.menu_nodes_active);
|
||||
|
||||
// Set menu as active
|
||||
this.element.addClass(this.css_classes.menu_active);
|
||||
},
|
||||
/**
|
||||
* Close the drawer after clearing the active menu group
|
||||
* @private
|
||||
*/
|
||||
_closeDrawer: function()
|
||||
{
|
||||
this._clearActiveMenuGroup();
|
||||
|
||||
// Set menu as non active
|
||||
this.element.removeClass(this.css_classes.menu_active);
|
||||
}
|
||||
});
|
||||
});
|
||||
45
templates/layouts/navigation-menu/layout.html.twig
Normal file
45
templates/layouts/navigation-menu/layout.html.twig
Normal file
@@ -0,0 +1,45 @@
|
||||
<nav {% if sId is defined %}id="{{ sId }}"{% endif%} class="ibo-navigation-menu">
|
||||
<div class="ibo-navigation-menu--body">
|
||||
<div class="ibo-navigation-menu--top-part">
|
||||
<a class="ibo-navigation-menu--square-company-logo" title="{{ sAppRevisionNumber }}" href="{{ sAppIconLink }}">
|
||||
<img src="{{ 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="#">
|
||||
<span class="ibo-navigation-menu--toggler-icon">
|
||||
<span class="ibo-navigation-menu--toggler-bar"></span>
|
||||
<span class="ibo-navigation-menu--toggler-bar"></span>
|
||||
<span class="ibo-navigation-menu--toggler-bar"></span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="ibo-navigation-menu--middle-part">
|
||||
{% for aMenuGroup in aMenuGroups %}
|
||||
{% include 'layouts/navigation-menu/menu-group.html.twig' with aMenuGroup %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="ibo-navigation-menu--bottom-part">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<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 }}" />
|
||||
<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>
|
||||
<div class="ibo-navigation-menu--menu-groups">
|
||||
{% for aMenuGroup in aMenuGroups %}
|
||||
{% include 'layouts/navigation-menu/menu-nodes.html.twig' with aMenuGroup %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{# TODO: Move this to a dedicated script file #}
|
||||
<script type="text/javascript" src="js/layouts/navigation-menu.js"></script>
|
||||
<script type="text/javascript">
|
||||
setTimeout(function(){
|
||||
$('#{{ sId }}').navigation_menu();
|
||||
}, 500);
|
||||
</script>
|
||||
4
templates/layouts/navigation-menu/menu-group.html.twig
Normal file
4
templates/layouts/navigation-menu/menu-group.html.twig
Normal file
@@ -0,0 +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 {{ aMenuGroup.sIconCssClasses }}" title="{{ aMenuGroup.sTitle }}"></span>
|
||||
<span class="ibo-navigation-menu--menu-group-title">{{ aMenuGroup.sTitle }}</span>
|
||||
</a>
|
||||
15
templates/layouts/navigation-menu/menu-node.html.twig
Normal file
15
templates/layouts/navigation-menu/menu-node.html.twig
Normal file
@@ -0,0 +1,15 @@
|
||||
<li class="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>
|
||||
{% else %}
|
||||
<span class="ibo-navigation-menu--menu-node-title">{{ aMenuNode.sTitle }}</span>
|
||||
{% endif %}
|
||||
{% if aMenuNode.aChildNodes is defined and aMenuNode.aChildNodes|length > 0 %}
|
||||
<ul>
|
||||
{% for aSubMenuNode in aMenuGroup.aSubMenuNodes %}
|
||||
{% include 'layouts/navigation-menu/menu-node.html.twig' with aSubMenuNode %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
8
templates/layouts/navigation-menu/menu-nodes.html.twig
Normal file
8
templates/layouts/navigation-menu/menu-nodes.html.twig
Normal file
@@ -0,0 +1,8 @@
|
||||
<div class="ibo-navigation-menu--menu-nodes" data-role="ibo-navigation-menu--menu-nodes" data-menu-group-id="{{ aMenuGroup.sId }}">
|
||||
<h2 class="ibo-navigation-menu--menu-nodes-title">{{ aMenuGroup.sTitle }}</h2>
|
||||
<ul>
|
||||
{% for aMenuNode in aMenuGroup.aSubMenuNodes %}
|
||||
{% include 'layouts/navigation-menu/menu-node.html.twig' with aMenuNode %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
100
templates/pages/backoffice/layout.html.twig
Normal file
100
templates/pages/backoffice/layout.html.twig
Normal file
@@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ aPage.aMetadata.sLang }}">
|
||||
<head>
|
||||
<meta charset="{{ aPage.aMetadata.sCharset }}">
|
||||
{# This block can be used to add your own meta tags by extending the default template #}
|
||||
{% block iboPageExtraMetas %}
|
||||
{% endblock %}
|
||||
<base href="{{ aPage.sAbsoluteUrlAppRoot }}">
|
||||
<title>{{ aPage.sTitle }}</title>
|
||||
<link rel="shortcut icon" href="{{ aPage.sFaviconUrl|add_itop_version }}" />
|
||||
|
||||
{# Stylesheets MUST be loaded before any scripts otherwise we may face problems such as
|
||||
- Visual glitches
|
||||
- jQuery scripts spurious problems (like failing on a 'reload') #}
|
||||
{% block iboPageCssFiles %}
|
||||
{% for aCssFileData in aPage.aCssFiles %}
|
||||
{% if aCssFileData['condition'] != '' %}<!--[if {{ aCssFileData['condition'] }}]>{% endif %}
|
||||
<link type="text/css" href="{{ aCssFileData['link']|add_itop_version }}" rel="stylesheet" />
|
||||
{% if aCssFileData['condition'] != '' %}<![endif]-->{% endif %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block iboPageCssInline %}
|
||||
{# We put each styles in a dedicated style tag to prevent massive failure if 1 style is broken (eg. missing semi-colon, bracket, ...) #}
|
||||
{% for sCssInline in aPage.aCssInline %}
|
||||
<style>
|
||||
{{ sCssInline|raw }}
|
||||
</style>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block iboPageJsFiles %}
|
||||
{% for sJsFile in aPage.aJsFiles %}
|
||||
<script type="text/javascript" src="{{ sJsFile|add_itop_version }}"></script>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body data-gui-type="backoffice">
|
||||
{% include 'layouts/navigation-menu/layout.html.twig' with aLayouts.aNavigationMenu %}
|
||||
<div id="ibo-page-container">
|
||||
<nav id="ibo-top-bar" class="ibo-top-bar">
|
||||
<span>item</span><span>item</span><span>item</span><span>item</span><span>item</span><span>item</span><span>item</span><span>item</span>
|
||||
</nav>
|
||||
<main id="ibo-page-content">
|
||||
<div>item</div>
|
||||
<div>
|
||||
itemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitemitem
|
||||
</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
<div>item</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{% block iboPageJsInlineScripts %}
|
||||
<script type="text/javascript">
|
||||
{# TODO: How to do this in native JS? #}
|
||||
$(document).ready(function(){
|
||||
{% block iboPageJsInlineOnInit %}
|
||||
{% for sJsInline in aPage.aJsInlineOnInit %}
|
||||
{{ sJsInline|raw }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block iboPageJsInlineOnDomReady %}
|
||||
{% for sJsInline in aPage.aJsInlineOnDomReady %}
|
||||
{{ sJsInline|raw }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
});
|
||||
</script>
|
||||
|
||||
{% block iboPageJsInlineLive %}
|
||||
{% for sJsInline in aPage.aJsInlineLive %}
|
||||
{# We put each scripts in a dedicated script tag to prevent massive failure if 1 script is broken (eg. missing semi-colon or non closed comment) #}
|
||||
<script type="text/javascript">
|
||||
{{ sJsInline|raw }}
|
||||
</script>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user