diff --git a/application/dashboard.class.inc.php b/application/dashboard.class.inc.php index 2932cf79c..3a0428054 100644 --- a/application/dashboard.class.inc.php +++ b/application/dashboard.class.inc.php @@ -17,6 +17,9 @@ * You should have received a copy of the GNU Affero General Public License */ +use Combodo\iTop\Application\UI\Component\Button\ButtonFactory; +use Combodo\iTop\Application\UI\Component\Toolbar\Toolbar; + require_once(APPROOT.'application/dashboardlayout.class.inc.php'); require_once(APPROOT.'application/dashlet.class.inc.php'); require_once(APPROOT.'core/modelreflection.class.inc.php'); @@ -529,34 +532,37 @@ EOF */ public function Render($oPage, $bEditMode = false, $aExtraParams = array(), $bCanEdit = true) { - if (!array_key_exists('dashboard_div_id', $aExtraParams)) - { + if (!array_key_exists('dashboard_div_id', $aExtraParams)) { $aExtraParams['dashboard_div_id'] = utils::Sanitize($this->GetId(), '', 'element_identifier'); } $sTitleForHTML = utils::HtmlEntities(Dict::S($this->sTitle)); - $oPage->add(<< -
{$sTitleForHTML}
- -HTML - ); + + $sHtml = "
{$sTitleForHTML}
"; + if ($oPage instanceof iTopWebPage) { + $oTopBar = $oPage->GetTopBarLayout(); + $oToolbar = new Toolbar(); + $oTopBar->SetToolbar($oToolbar); + $oToolbar->AddHtml($sHtml); + } else { + $oPage->add_script(<<sLayoutClass(); - foreach($this->aCells as $iCellIdx => $aDashlets) - { - foreach($aDashlets as $oDashlet) - { + foreach ($this->aCells as $iCellIdx => $aDashlets) { + foreach ($aDashlets as $oDashlet) { $aDashletCoordinates = $oLayout->GetDashletCoordinates($iCellIdx); $this->PrepareDashletForRendering($oDashlet, $aDashletCoordinates, $aExtraParams); } } $oLayout->Render($oPage, $this->aCells, $bEditMode, $aExtraParams); - if (!$bEditMode) - { + if (!$bEditMode) { $oPage->add_linked_script('../js/dashlet.js'); $oPage->add_linked_script('../js/dashboard.js'); } @@ -987,39 +993,38 @@ EOF } /** - * @param \iTopWebPage $oPage + * @param WebPage $oPage * @param array $aAjaxParams + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \MySQLException */ - protected function RenderSelector($oPage, $aAjaxParams = array()) + protected function RenderSelector(WebPage $oPage, $aAjaxParams = array()) { $sId = $this->GetId(); $sDivId = utils::Sanitize($sId, '', 'element_identifier'); $sExtraParams = json_encode($aAjaxParams); - $sSelectorHtml = '
'; - if ($this->HasCustomDashboard()) - { + $sSelectorHtml = '
'; + if ($this->HasCustomDashboard()) { $bStandardSelected = appUserPreferences::GetPref('display_original_dashboard_'.$sId, false); $sStandard = Dict::S('UI:Toggle:StandardDashboard'); $sSelectorHtml .= '
'.$sStandard.'
'; $sSelectorHtml .= ''; $sCustom = Dict::S('UI:Toggle:CustomDashboard'); $sSelectorHtml .= '
'.$sCustom.'
'; - } $sSelectorHtml .= '
'; - $sSelectorHtml = addslashes($sSelectorHtml); $sFile = addslashes($this->GetDefinitionFile()); $sReloadURL = $this->GetReloadURL(); - $oPage->add_ready_script( -<<GetTopBarLayout()->GetToolbar(); + $oToolbar->AddHtml($sSelectorHtml); - $oPage->add_script( -<<add_script( + <<add_script(<<add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.iframe-transport.js'); $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.fileupload.js'); - $sEditMenu = "
    • "; - + $sId = utils::Sanitize($this->GetId(), '', 'element_identifier'); + + $sMenuTogglerId = "ibo-dashboard-menu-toggler-{$sId}"; + $sPopoverMenuId = "ibo-dashboard-menu-popover-{$sId}"; + $sName = 'UI:Dashboard:Actions'; + $oToolbar = $oPage->GetTopBarLayout()->GetToolbar(); + $oActionButton = ButtonFactory::MakeLinkNeutral('', '', 'fas fa-ellipsis-v', $sName, '', $sMenuTogglerId); + $oActionButton->AddCSSClasses("ibo-top-bar--toolbar-dashboard-menu-toggler"); + + $oToolbar->AddSubBlock($oActionButton); + $aActions = array(); $sFile = addslashes($this->sDefinitionFile); $sJSExtraParams = json_encode($aExtraParams); $bCanEdit = true; - if ($this->HasCustomDashboard()) - { + if ($this->HasCustomDashboard()) { $bCanEdit = !appUserPreferences::GetPref('display_original_dashboard_'.$this->GetId(), false); } - if ($bCanEdit) - { + if ($bCanEdit) { $oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:Edit'), "return EditDashboard('{$this->sId}', '$sFile', $sJSExtraParams)"); $aActions[$oEdit->GetUID()] = $oEdit->GetMenuItem(); } - if ($this->bCustomized) - { + if ($this->bCustomized) { $oRevert = new JSPopupMenuItem('UI:Dashboard:RevertConfirm', Dict::S('UI:Dashboard:Revert'), - "if (confirm('".addslashes(Dict::S('UI:Dashboard:RevertConfirm'))."')) return RevertDashboard('{$this->sId}', $sJSExtraParams); else return false"); + "if (confirm('".addslashes(Dict::S('UI:Dashboard:RevertConfirm'))."')) return RevertDashboard('{$this->sId}', $sJSExtraParams); else return false"); $aActions[$oRevert->GetUID()] = $oRevert->GetMenuItem(); } + utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_DASHBOARD_ACTIONS, $this, $aActions); - $sEditMenu .= $oPage->RenderPopupMenuItems($aActions); - $sEditMenu = addslashes($sEditMenu); - $sReloadURL = $this->GetReloadURL(); - $oPage->add_ready_script( -<<ul').popupmenu(); + + $oToolbar->AddSubBlock($oPage->GetPopoverMenu($sPopoverMenuId, $aActions)); + $oActionButton->AddCSSClasses('ibo-action-button') + ->SetJsCode(<<GetReloadURL(); $oPage->add_script( -<<GetDBSearch($aExtraParams); $aClassAliases = $oFilter->GetSelectedClasses(); - } - catch (Exception $e) - { + } catch (Exception $e) { //on error, return default value return null; } diff --git a/css/backoffice/layout/_top-bar.scss b/css/backoffice/layout/_top-bar.scss index 63e6756e5..2500e60d1 100644 --- a/css/backoffice/layout/_top-bar.scss +++ b/css/backoffice/layout/_top-bar.scss @@ -44,16 +44,106 @@ $ibo-top-bar--quick-actions--margin-right: $ibo-top-bar--elements-spacing !defau background-color: var(--ibo-top-bar--background-color); @extend %ibo-elevation-100; - .ibo-breadcrumbs{ + .ibo-breadcrumbs { flex-grow: 1; /* Occupy as much width as possible */ overflow-x: hidden; /* Avoid glitches when too many items */ } } -.ibo-top-bar--quick-actions{ + +.ibo-top-bar--quick-actions { @extend %ibo-full-height-content; margin-right: var(--ibo-top-bar--quick-actions--margin-right); - .ibo-global-search{ + .ibo-global-search { } -} \ No newline at end of file +} + +.ibo-top-bar--toolbar { + @extend %ibo-full-height-content; +} + +.ibo-top-bar--toolbar-dashboard-title { + @extend %ibo-full-height-content; + @extend %ibo-font-ral-med-250; + display: flex; + align-items: center; +} + +.ibo-top-bar--toolbar-dashboard-menu-toggler { + @extend %ibo-full-height-content; + display: flex; + align-items: center; +} + +.ibo-top-bar--toolbar-dashboard-selector { + @extend %ibo-full-height-content; + display: flex; + align-items: center; + + .selector-label { + display: inline-block; + margin-left: 10px; + margin-right: 10px; + vertical-align: super; + } +} + +// Round Toggle +/* The switch - the box around the slider */ +.switch { + position: relative; + display: inline-block; + width: 36px; + height: 20px; + vertical-align: baseline; +} + +/* Hide default HTML checkbox */ +.switch input { + display: none; +} + +/* The slider */ +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: $ibo-color-secondary-600; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 15px; + width: 15px; + left: 3px; + bottom: 3px; + background-color: $ibo-color-secondary-300; + transition: .4s; +} + +input:checked + .slider { + background-color: $ibo-color-primary-600; +} + +input:focus + .slider { + box-shadow: 0 0 1px $ibo-color-primary-600; +} + +input:checked + .slider:before { + transform: translateX(14.5px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 20px; +} + +.slider.round:before { + border-radius: 7px; +} diff --git a/sources/application/UI/Layout/TopBar/TopBar.php b/sources/application/UI/Layout/TopBar/TopBar.php index 89edb641b..5159a544e 100644 --- a/sources/application/UI/Layout/TopBar/TopBar.php +++ b/sources/application/UI/Layout/TopBar/TopBar.php @@ -23,6 +23,7 @@ namespace Combodo\iTop\Application\UI\Layout\TopBar; use Combodo\iTop\Application\UI\Component\Breadcrumbs\Breadcrumbs; use Combodo\iTop\Application\UI\Component\GlobalSearch\GlobalSearch; use Combodo\iTop\Application\UI\Component\QuickCreate\QuickCreate; +use Combodo\iTop\Application\UI\Component\Toolbar\Toolbar; use Combodo\iTop\Application\UI\UIBlock; /** @@ -45,6 +46,8 @@ class TopBar extends UIBlock protected $oGlobalSearch; /** @var Breadcrumbs|null $oBreadcrumbs */ protected $oBreadcrumbs; + /** @var Toolbar|null */ + protected $oToolbar; /** * TopBar constructor. @@ -163,6 +166,35 @@ class TopBar extends UIBlock return ($this->oBreadcrumbs !== null); } + /** + * @return Toolbar|null + */ + public function GetToolbar(): ?Toolbar + { + return $this->oToolbar; + } + + /** + * @param Toolbar|null $oToolbar + * + * @return TopBar + */ + public function SetToolbar(?Toolbar $oToolbar): TopBar + { + $this->oToolbar = $oToolbar; + return $this; + } + + /** + * Return true if the breadcrumb has been set + * + * @return bool + */ + public function HasToolbar() + { + return ($this->oToolbar !== null); + } + /** * @inheritDoc */ @@ -170,12 +202,10 @@ class TopBar extends UIBlock { $aSubBlocks = []; - $aSubBlocksNames = ['QuickCreate', 'GlobalSearch', 'Breadcrumbs']; - foreach($aSubBlocksNames as $sSubBlockName) - { + $aSubBlocksNames = ['QuickCreate', 'GlobalSearch', 'Breadcrumbs', 'Toolbar']; + foreach ($aSubBlocksNames as $sSubBlockName) { $sHasMethodName = 'Has'.$sSubBlockName; - if(true === call_user_func_array([$this, $sHasMethodName], [])) - { + if (true === call_user_func_array([$this, $sHasMethodName], [])) { $sPropertyName = 'o'.$sSubBlockName; $aSubBlocks[$this->$sPropertyName->GetId()] = $this->$sPropertyName; } diff --git a/sources/application/WebPage/iTopWebPage.php b/sources/application/WebPage/iTopWebPage.php index 709d6bce6..84764bc64 100644 --- a/sources/application/WebPage/iTopWebPage.php +++ b/sources/application/WebPage/iTopWebPage.php @@ -19,12 +19,14 @@ use Combodo\iTop\Application\TwigBase\Twig\TwigHelper; +use Combodo\iTop\Application\UI\Component\Breadcrumbs\Breadcrumbs; use Combodo\iTop\Application\UI\Component\Panel\PanelFactory; use Combodo\iTop\Application\UI\iUIBlock; use Combodo\iTop\Application\UI\Layout\iUIContentBlock; use Combodo\iTop\Application\UI\Layout\NavigationMenu\NavigationMenuFactory; use Combodo\iTop\Application\UI\Layout\PageContent\PageContent; use Combodo\iTop\Application\UI\Layout\PageContent\PageContentFactory; +use Combodo\iTop\Application\UI\Layout\TopBar\TopBar; use Combodo\iTop\Application\UI\Layout\TopBar\TopBarFactory; use Combodo\iTop\Application\UI\UIBlock; use Combodo\iTop\Renderer\BlockRenderer; @@ -49,6 +51,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage /** @var \TabManager */ protected $m_oTabs; + protected $oTopBarLayout; protected $bBreadCrumbEnabled; protected $sBreadCrumbEntryId; protected $sBreadCrumbEntryLabel; @@ -78,16 +81,15 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker'); - if ((count($_POST) == 0) || (array_key_exists('loginop', $_POST))) - { + if ((count($_POST) == 0) || (array_key_exists('loginop', $_POST))) { // Create a breadcrumb entry for the current page, but get its title as late as possible (page title could be changed later) $this->bBreadCrumbEnabled = true; - } - else - { + } else { $this->bBreadCrumbEnabled = false; } + $this->SetTopBarLayout(TopBarFactory::MakeStandard($this->GetBreadCrumbsNewEntry())); + utils::InitArchiveMode(); $this->m_aMessages = array(); @@ -535,6 +537,8 @@ JS $this->sBreadCrumbEntryUrl = $sUrl; $this->sBreadCrumbEntryIcon = $sIcon; $this->sBreadCrumbEntryIconType = $sIconType; + + $this->GetTopBarLayout()->SetBreadcrumbs(new Breadcrumbs($this->GetBreadCrumbsNewEntry(), Breadcrumbs::BLOCK_CODE)); } /** @@ -632,20 +636,6 @@ JS return NavigationMenuFactory::MakeStandard(); } - /** - * Return the top bar layout (global search, breadcrumbs, ...) - * - * @internal - * @return \Combodo\iTop\Application\UI\Layout\TopBar\TopBar - * @throws \CoreException - * @throws \CoreUnexpectedValue - * @since 3.0.0 - */ - protected function GetTopBarLayout() - { - return TopBarFactory::MakeStandard($this->GetBreadCrumbsNewEntry()); - } - /** * Set the content layout (main content, [side content,] manually added content, ...) * This function is public as the developer needs to be able to set how the content will be displayed. @@ -1393,4 +1383,25 @@ EOF $this->m_aInitScript[] = $sScript; } } + + /** + * @return TopBar + */ + public function GetTopBarLayout(): TopBar + { + return $this->oTopBarLayout; + } + + /** + * @param TopBar $oTopBarLayout + * + * @return iTopWebPage + */ + public function SetTopBarLayout(TopBar $oTopBarLayout): iTopWebPage + { + $this->oTopBarLayout = $oTopBarLayout; + return $this; + } + + } diff --git a/templates/layouts/top-bar/layout.html.twig b/templates/layouts/top-bar/layout.html.twig index 48021d813..f7e11b8c4 100644 --- a/templates/layouts/top-bar/layout.html.twig +++ b/templates/layouts/top-bar/layout.html.twig @@ -10,4 +10,9 @@ {% if oUIBlock.HasBreadcrumbs() %} {{ render_block(oUIBlock.GetBreadcrumbs(), {aPage: aPage}) }} {% endif %} + {% if oUIBlock.HasToolBar() %} +
      + {{ render_block(oUIBlock.GetToolBar(), {aPage: aPage}) }} +
      + {% endif %} \ No newline at end of file