diff --git a/src/core_plugins/kibana/public/assets/play-circle.svg b/src/core_plugins/kibana/public/assets/play-circle.svg new file mode 100644 index 00000000000000..d668d52587b846 --- /dev/null +++ b/src/core_plugins/kibana/public/assets/play-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/core_plugins/kibana/public/dashboard/directives/grid.js b/src/core_plugins/kibana/public/dashboard/directives/grid.js index 99945519a7f235..afff8d497bc387 100644 --- a/src/core_plugins/kibana/public/dashboard/directives/grid.js +++ b/src/core_plugins/kibana/public/dashboard/directives/grid.js @@ -100,6 +100,7 @@ app.directive('dashboardGrid', function ($compile, Notifier) { safeLayout(); $window.on('resize', safeLayout); $scope.$on('ready:vis', safeLayout); + $scope.$on('globalNav:update', safeLayout); } // return the panel object for an element. diff --git a/src/ui/public/chrome/chrome.html b/src/ui/public/chrome/chrome.html deleted file mode 100644 index 42b78e7ee7f397..00000000000000 --- a/src/ui/public/chrome/chrome.html +++ /dev/null @@ -1,45 +0,0 @@ -
- - - -
-
- - -
-
-
-
diff --git a/src/ui/public/chrome/chrome.js b/src/ui/public/chrome/chrome.js index 6e7a2b8d6f4bc4..5819426d9c833b 100644 --- a/src/ui/public/chrome/chrome.js +++ b/src/ui/public/chrome/chrome.js @@ -10,8 +10,10 @@ import 'ui/timefilter'; import 'ui/notify'; import 'ui/private'; import 'ui/promises'; +import 'ui/storage'; import 'ui/directives/kbn_src'; import 'ui/watch_multi'; +import './services'; let chrome = {}; let internals = _.defaults( diff --git a/src/ui/public/chrome/directives/app_switcher/app_switcher.html b/src/ui/public/chrome/directives/app_switcher/app_switcher.html deleted file mode 100644 index 7d8291bc26ae51..00000000000000 --- a/src/ui/public/chrome/directives/app_switcher/app_switcher.html +++ /dev/null @@ -1,11 +0,0 @@ - - diff --git a/src/ui/public/chrome/directives/app_switcher/app_switcher.less b/src/ui/public/chrome/directives/app_switcher/app_switcher.less deleted file mode 100644 index 0d22b23c0aa0a0..00000000000000 --- a/src/ui/public/chrome/directives/app_switcher/app_switcher.less +++ /dev/null @@ -1,72 +0,0 @@ -@import (reference) "~ui/styles/mixins"; -@import (reference) "~ui/styles/variables"; - -body { overflow-x: hidden; } - -.app-links-wrapper { - width: @as-open-width; - position: fixed; - left: 0; - top: 0; - bottom: 0; - z-index: 0; - background-color: @app-links-wrapper-background; - overflow: hidden; - transition: width @transition-time; - transition-delay: @transition-delay; - - &:hover { - .app-title { - display: inline-block; - } - + .app-wrapper { - transform: translateX(@as-open-width - @as-closed-width); - } - } - - .logo-small, - .logo { - height: 70px; - width: @as-open-width; - list-style-type: none; - &.kibana { - background-image: url("~ui/images/kibana.svg"); - background-position: 6px 10px; - background-size: 140px 50px; - background-repeat: no-repeat; - background-color: #e8488b; - } - } - - .bottom-apps { - position: absolute; - bottom: 0; - } -} - -.app-wrapper { - .real-flex-parent(); - position: absolute; - transition: transform @transition-time; - transition-delay: @transition-delay; - left: @as-closed-width; - top: 0; - right: 0; - bottom: 0; - z-index: 1; - margin: 0 auto; - background-color: #fff; - - &.hidden-chrome { - left: 0; - } - - .navbar-right { - margin-right: 0; - } -} - - .app-wrapper-panel { - .flex-parent(@shrink: 0); - box-shadow: -4px 0px 3px rgba(0,0,0,0.2); - } diff --git a/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.js b/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.js deleted file mode 100644 index 11adda2828a78a..00000000000000 --- a/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.js +++ /dev/null @@ -1,35 +0,0 @@ - -import appSwitcherLinkTemplate from './app_switcher_link.html'; -import './app_switcher_link.less'; -import uiModules from 'ui/modules'; - -const module = uiModules.get('kibana'); - -module.directive('appSwitcherLink', chrome => { - return { - restrict: 'E', - replace: true, - scope: { - isActive: '=appSwitcherLinkIsActive', - isDisabled: '=appSwitcherLinkIsDisabled', - tooltip: '=appSwitcherLinkTooltip', - onClick: '&appSwitcherLinkOnClick', - href: '=appSwitcherLinkHref', - kbnRoute: '=appSwitcherLinkKbnRoute', - icon: '=appSwitcherLinkIcon', - title: '=appSwitcherLinkTitle' - }, - template: appSwitcherLinkTemplate, - link: scope => { - scope.getHref = () => { - if (scope.href) { - return scope.href; - } - - if (scope.kbnRoute) { - return chrome.addBasePath(scope.kbnRoute); - } - }; - } - }; -}); diff --git a/src/ui/public/chrome/directives/app_switcher/__tests__/app_switcher.js b/src/ui/public/chrome/directives/global_nav/app_switcher/__tests__/app_switcher.js similarity index 98% rename from src/ui/public/chrome/directives/app_switcher/__tests__/app_switcher.js rename to src/ui/public/chrome/directives/global_nav/app_switcher/__tests__/app_switcher.js index 46cb6fcaf9dd63..3f0dac3370d89e 100644 --- a/src/ui/public/chrome/directives/app_switcher/__tests__/app_switcher.js +++ b/src/ui/public/chrome/directives/global_nav/app_switcher/__tests__/app_switcher.js @@ -26,7 +26,7 @@ describe('appSwitcher directive', function () { env = { $scope: $rootScope, - $el: $compile($(''))($rootScope), + $el: $compile($(''))($rootScope), currentHref: href, location: domLocation }; diff --git a/src/ui/public/chrome/directives/global_nav/app_switcher/app_switcher.html b/src/ui/public/chrome/directives/global_nav/app_switcher/app_switcher.html new file mode 100644 index 00000000000000..5cfd1b424b5260 --- /dev/null +++ b/src/ui/public/chrome/directives/global_nav/app_switcher/app_switcher.html @@ -0,0 +1,11 @@ + + diff --git a/src/ui/public/chrome/directives/app_switcher/app_switcher.js b/src/ui/public/chrome/directives/global_nav/app_switcher/app_switcher.js similarity index 84% rename from src/ui/public/chrome/directives/app_switcher/app_switcher.js rename to src/ui/public/chrome/directives/global_nav/app_switcher/app_switcher.js index 3181f88d62cdce..202882920f9b90 100644 --- a/src/ui/public/chrome/directives/app_switcher/app_switcher.js +++ b/src/ui/public/chrome/directives/global_nav/app_switcher/app_switcher.js @@ -1,7 +1,6 @@ import DomLocationProvider from 'ui/dom_location'; import { parse } from 'url'; import { bindKey } from 'lodash'; -import './app_switcher.less'; import uiModules from 'ui/modules'; import appSwitcherTemplate from './app_switcher.html'; @@ -55,11 +54,12 @@ uiModules .directive('appSwitcher', function () { return { restrict: 'E', + scope: { + chrome: '=', + }, template: appSwitcherTemplate, controllerAs: 'switcher', - controller($scope, appSwitcherEnsureNavigation) { - // since we render this in an isolate scope we can't "require: ^chrome", but - // rather than remove all helpfull checks we can just check here. + controller($scope, appSwitcherEnsureNavigation, globalNavState) { if (!$scope.chrome || !$scope.chrome.getNavLinks) { throw new TypeError('appSwitcher directive requires the "chrome" config-object'); } @@ -72,6 +72,15 @@ uiModules // links don't cause full-navigation events in certain scenarios // so we force them when needed this.ensureNavigation = appSwitcherEnsureNavigation; + + this.getTooltip = link => { + // If the sidebar is open then we don't need to show the title because + // it will already be visible. + if (globalNavState.isOpen()) { + return link.tooltip; + } + return link.tooltip ? link.title + ' - ' + link.tooltip : link.title; + }; } }; }); diff --git a/src/ui/public/chrome/directives/global_nav/global_nav.html b/src/ui/public/chrome/directives/global_nav/global_nav.html new file mode 100644 index 00000000000000..d9751a211dcb89 --- /dev/null +++ b/src/ui/public/chrome/directives/global_nav/global_nav.html @@ -0,0 +1,46 @@ + diff --git a/src/ui/public/chrome/directives/global_nav/global_nav.js b/src/ui/public/chrome/directives/global_nav/global_nav.js new file mode 100644 index 00000000000000..b688982cba5116 --- /dev/null +++ b/src/ui/public/chrome/directives/global_nav/global_nav.js @@ -0,0 +1,51 @@ + +import './app_switcher'; +import './global_nav_link'; + +import globalNavTemplate from './global_nav.html'; +import './global_nav.less'; +import uiModules from 'ui/modules'; + +const module = uiModules.get('kibana'); + +module.directive('globalNav', globalNavState => { + return { + restrict: 'E', + replace: true, + scope: { + chrome: '=', + isVisible: '=', + logoBrand: '=', + smallLogoBrand: '=', + appTitle: '=', + }, + template: globalNavTemplate, + link: scope => { + // App switcher functionality. + function updateGlobalNav() { + const isOpen = globalNavState.isOpen(); + scope.isGlobalNavOpen = isOpen; + scope.globalNavToggleButton = { + classes: isOpen ? 'global-nav-link--close' : undefined, + title: isOpen ? 'Collapse' : 'Expand', + tooltipContent: isOpen ? 'Collapse side bar' : 'Expand side bar', + }; + + // Notify visualizations, e.g. the dashboard, that they should re-render. + scope.$root.$broadcast('globalNav:update'); + } + + updateGlobalNav(); + + scope.$root.$on('globalNavState:change', () => { + updateGlobalNav(); + }); + + scope.toggleGlobalNav = event => { + event.preventDefault(); + globalNavState.setOpen(!globalNavState.isOpen()); + }; + + } + }; +}); diff --git a/src/ui/public/chrome/directives/global_nav/global_nav.less b/src/ui/public/chrome/directives/global_nav/global_nav.less new file mode 100644 index 00000000000000..8d38a8c26d7158 --- /dev/null +++ b/src/ui/public/chrome/directives/global_nav/global_nav.less @@ -0,0 +1,44 @@ + +@import (reference) "~ui/styles/variables"; + +.global-nav { + width: @as-closed-width; + position: fixed; + left: 0; + top: 0; + bottom: 0; + z-index: 0; + background-color: @app-links-wrapper-background; + overflow: hidden; + + &.is-global-nav-open { + width: @as-open-width; + + .app-title { + display: inline-block; + } + + + .app-wrapper { + left: @as-open-width; + } + } + + .logo-small, + .logo { + height: 70px; + width: @as-open-width; + list-style-type: none; + &.kibana { + background-image: url("~ui/images/kibana.svg"); + background-position: 6px 10px; + background-size: 140px 50px; + background-repeat: no-repeat; + background-color: #e8488b; + } + } +} + + .gloal-nav__bottom-links { + position: absolute; + bottom: 0; + } diff --git a/src/ui/public/chrome/directives/app_switcher_link/__tests__/app-switcher-link.js b/src/ui/public/chrome/directives/global_nav/global_nav_link/__tests__/global_nav_link.js similarity index 57% rename from src/ui/public/chrome/directives/app_switcher_link/__tests__/app-switcher-link.js rename to src/ui/public/chrome/directives/global_nav/global_nav_link/__tests__/global_nav_link.js index 25768f2072893b..54254736724e4a 100644 --- a/src/ui/public/chrome/directives/app_switcher_link/__tests__/app-switcher-link.js +++ b/src/ui/public/chrome/directives/global_nav/global_nav_link/__tests__/global_nav_link.js @@ -2,9 +2,9 @@ import sinon from 'auto-release-sinon'; import ngMock from 'ng_mock'; import expect from 'expect.js'; -import '../app_switcher_link'; +import '../global_nav_link'; -describe('appSwitcherLink directive', () => { +describe('globalNavLink directive', () => { let scope; let $compile; @@ -19,15 +19,15 @@ describe('appSwitcherLink directive', () => { function create(attrs) { const template = ` - `; @@ -42,96 +42,96 @@ describe('appSwitcherLink directive', () => { describe('interface', () => { - describe('appSwitcherLinkIsActive attribute', () => { + describe('isActive attribute', () => { it(`doesn't apply the active class when false`, () => { const element = create({ - appSwitcherLinkIsActive: false, + isActive: false, }); expect(element.hasClass('active')).to.be(false); }); it('applies the active class when true', () => { const element = create({ - appSwitcherLinkIsActive: true, + isActive: true, }); expect(element.hasClass('active')).to.be(true); }); }); - describe('appSwitcherLinkIsDisabled attribute', () => { - it(`doesn't apply the is-app-switcher-link-disabled class when false`, () => { + describe('isDisabled attribute', () => { + it(`doesn't apply the is-global-nav-link-disabled class when false`, () => { const element = create({ - appSwitcherLinkIsDisabled: false, + isDisabled: false, }); - expect(element.hasClass('is-app-switcher-link-disabled')).to.be(false); + expect(element.hasClass('is-global-nav-link-disabled')).to.be(false); }); - it('applies the is-app-switcher-link-disabled class when true', () => { + it('applies the is-global-nav-link-disabled class when true', () => { const element = create({ - appSwitcherLinkIsDisabled: true, + isDisabled: true, }); - expect(element.hasClass('is-app-switcher-link-disabled')).to.be(true); + expect(element.hasClass('is-global-nav-link-disabled')).to.be(true); }); }); - describe('appSwitcherLinkTooltip attribute', () => { + describe('tooltipContent attribute', () => { it('is applied to the tooltip directive', () => { const attrs = { - appSwitcherLinkTooltip: 'hello i am a tooltip', + tooltipContent: 'hello i am a tooltip', }; const element = create(attrs); - expect(element.attr('tooltip')).to.be(attrs.appSwitcherLinkTooltip); + expect(element.attr('tooltip')).to.be(attrs.tooltipContent); }); }); - describe('appSwitcherLinkOnClick attribute', () => { + describe('onClick attribute', () => { it('is called when the link is clicked', () => { const attrs = { - appSwitcherLinkOnClick: sinon.spy(), + onClick: sinon.spy(), }; const element = create(attrs); element.find('[data-test-subj=appLink]').click(); - sinon.assert.called(attrs.appSwitcherLinkOnClick); + sinon.assert.called(attrs.onClick); }); }); - describe('appSwitcherLinkHref attribute', () => { + describe('href attribute', () => { it('is applied to the link', () => { const attrs = { - appSwitcherLinkHref: 'link to a website', + href: 'link to a website', }; const element = create(attrs); const link = element.find('[data-test-subj=appLink]'); - expect(link.attr('href')).to.be(attrs.appSwitcherLinkHref); + expect(link.attr('href')).to.be(attrs.href); }); }); - describe('appSwitcherLinkKbnRoute attribute', () => { + describe('kbnRoute attribute', () => { it(`is applied to the link when href isn't defined`, () => { const attrs = { - appSwitcherLinkKbnRoute: '#test', + kbnRoute: '#test', }; const element = create(attrs); const link = element.find('[data-test-subj=appLink]'); - expect(link.attr('href')).to.be(attrs.appSwitcherLinkKbnRoute); + expect(link.attr('href')).to.be(attrs.kbnRoute); }); it(`isn't applied to the link when href is defined`, () => { const attrs = { - appSwitcherLinkHref: 'link to a website', - appSwitcherLinkKbnRoute: '#test', + href: 'link to a website', + kbnRoute: '#test', }; const element = create(attrs); const link = element.find('[data-test-subj=appLink]'); - expect(link.attr('href')).not.to.be(attrs.appSwitcherLinkKbnRoute); + expect(link.attr('href')).not.to.be(attrs.kbnRoute); }); }); - describe('appSwitcherLinkIcon attribute', () => { + describe('icon attribute', () => { describe('when present', () => { it('displays the img element', () => { const attrs = { - appSwitcherLinkIcon: 'icon url', + icon: 'icon url', }; const element = create(attrs); const img = element.find('img'); @@ -140,7 +140,7 @@ describe('appSwitcherLink directive', () => { it('hides the placeholder', () => { const attrs = { - appSwitcherLinkIcon: 'icon url', + icon: 'icon url', }; const element = create(attrs); const placeholder = element.find('[data-test-subj=appLinkIconPlaceholder]'); @@ -149,18 +149,18 @@ describe('appSwitcherLink directive', () => { it(`is set as the img src`, () => { const attrs = { - appSwitcherLinkIcon: 'icon url', + icon: 'icon url', }; const element = create(attrs); const img = element.find('img'); - expect(img.attr('src')).to.contain(encodeURI(attrs.appSwitcherLinkIcon)); + expect(img.attr('src')).to.contain(encodeURI(attrs.icon)); }); }); describe('when not present', () => { it('hides the img element', () => { const attrs = { - appSwitcherLinkIcon: undefined, + icon: undefined, }; const element = create(attrs); const img = element.find('img'); @@ -169,7 +169,7 @@ describe('appSwitcherLink directive', () => { it('displays the placeholder', () => { const attrs = { - appSwitcherLinkIcon: undefined, + icon: undefined, }; const element = create(attrs); const placeholder = element.find('[data-test-subj=appLinkIconPlaceholder]'); @@ -178,33 +178,33 @@ describe('appSwitcherLink directive', () => { it(`uses the title's first letter as the placeholder`, () => { const attrs = { - appSwitcherLinkIcon: undefined, - appSwitcherLinkTitle: 'Xyz', + icon: undefined, + title: 'Xyz', }; const element = create(attrs); const placeholder = element.find('[data-test-subj=appLinkIconPlaceholder]'); - expect(placeholder.text()).to.contain(attrs.appSwitcherLinkTitle[0]); + expect(placeholder.text()).to.contain(attrs.title[0]); }); }); }); - describe('appSwitcherLinkTitle attribute', () => { + describe('title attribute', () => { it('is displayed', () => { const attrs = { - appSwitcherLinkTitle: 'demo title', + title: 'demo title', }; const element = create(attrs); - const title = element.find('.app-switcher-link__title'); - expect(title.text().trim()).to.be(attrs.appSwitcherLinkTitle); + const title = element.find('.global-nav-link__title'); + expect(title.text().trim()).to.be(attrs.title); }); it('is set as a title attribute on the anchor tag', () => { const attrs = { - appSwitcherLinkTitle: 'demo title', + title: 'demo title', }; const element = create(attrs); const link = element.find('[data-test-subj=appLink]'); - expect(link.attr('title')).to.be(attrs.appSwitcherLinkTitle); + expect(link.attr('title')).to.be(attrs.title); }); }); }); diff --git a/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.html b/src/ui/public/chrome/directives/global_nav/global_nav_link/global_nav_link.html similarity index 54% rename from src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.html rename to src/ui/public/chrome/directives/global_nav/global_nav_link/global_nav_link.html index 6a7843d0430cf6..9dbc657a3eceba 100644 --- a/src/ui/public/chrome/directives/app_switcher_link/app_switcher_link.html +++ b/src/ui/public/chrome/directives/global_nav/global_nav_link/global_nav_link.html @@ -1,35 +1,35 @@