Browse Source

Fixing tabs for Grafana 5 - #10082 (#10103)

* ux: Make new tabs responsive #10082

* ux: Add possibility to manipulate url in angular router outside of angular - and use it in the responsive navigation #10082
Johannes Schill 8 years ago
parent
commit
e8807f4bce

+ 2 - 4
public/app/core/angular_wrappers.ts

@@ -1,12 +1,10 @@
 import { react2AngularDirective } from 'app/core/utils/react2angular';
 import { PasswordStrength } from './components/PasswordStrength';
-import PageHeader from './components/PageHeader';
+import PageHeader from './components/PageHeader/PageHeader';
 import EmptyListCTA from './components/EmptyListCTA/EmptyListCTA';
 
 export function registerAngularDirectives() {
-
   react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
-  react2AngularDirective('pageHeader', PageHeader, ['model', "noTabs"]);
+  react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']);
   react2AngularDirective('emptyListCta', EmptyListCTA, ['model']);
-
 }

+ 41 - 4
public/app/core/components/PageHeader.tsx → public/app/core/components/PageHeader/PageHeader.tsx

@@ -1,6 +1,7 @@
 import React from 'react';
-import { NavModel, NavModelItem } from '../nav_model_srv';
+import { NavModel, NavModelItem } from '../../nav_model_srv';
 import classNames from 'classnames';
+import appEvents from 'app/core/app_events';
 
 export interface IProps {
   model: NavModel;
@@ -26,8 +27,44 @@ function TabItem(tab: NavModelItem) {
   );
 }
 
-function Tabs({main}: {main: NavModelItem}) {
-  return <ul className="gf-tabs">{main.children.map(TabItem)}</ul>;
+function SelectOption(navItem: NavModelItem) {
+  if (navItem.hideFromTabs) { // TODO: Rename hideFromTabs => hideFromNav
+    return (null);
+  }
+
+  return (
+    <option key={navItem.url} value={navItem.url}>
+      {navItem.text}
+    </option>
+  );
+}
+
+function Navigation({main}: {main: NavModelItem}) {
+  return (<nav>
+    <SelectNav customCss="page-header__select_nav" main={main} />
+    <Tabs customCss="page-header__tabs" main={main} />
+  </nav>);
+}
+
+function SelectNav({main, customCss}: {main: NavModelItem, customCss: string}) {
+  const defaultSelectedItem = main.children.find(navItem => {
+    return navItem.active === true;
+  });
+
+  const gotoUrl = evt => {
+    var element = evt.target;
+    var url = element.options[element.selectedIndex].value;
+    appEvents.emit('location-change', {href: url});
+  };
+
+  return (<select
+    className={`gf-select-nav ${customCss}`}
+    defaultValue={defaultSelectedItem.url}
+    onChange={gotoUrl}>{main.children.map(SelectOption)}</select>);
+}
+
+function Tabs({main, customCss}: {main: NavModelItem, customCss: string}) {
+  return <ul className={`gf-tabs ${customCss}`}>{main.children.map(TabItem)}</ul>;
 }
 
 export default class PageHeader extends React.Component<IProps, any> {
@@ -63,7 +100,7 @@ export default class PageHeader extends React.Component<IProps, any> {
         <div className="page-container">
           <div className="page-header">
             {this.renderHeaderTitle(this.props.model.main)}
-            {this.props.model.main.children && <Tabs main={this.props.model.main} />}
+            {this.props.model.main.children && <Navigation main={this.props.model.main} />}
           </div>
         </div>
       </div>

+ 2 - 1
public/app/core/components/grafana_app.ts

@@ -12,7 +12,7 @@ import Drop from 'tether-drop';
 export class GrafanaCtrl {
 
   /** @ngInject */
-  constructor($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv) {
+  constructor($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv, globalEventSrv) {
 
     $scope.init = function() {
       $scope.contextSrv = contextSrv;
@@ -23,6 +23,7 @@ export class GrafanaCtrl {
       profiler.init(config, $rootScope);
       alertSrv.init();
       utilSrv.init();
+      globalEventSrv.init();
 
       $scope.dashAlerts = alertSrv;
     };

+ 1 - 0
public/app/core/services/all.js

@@ -8,5 +8,6 @@ define([
   './segment_srv',
   './backend_srv',
   './dynamic_directive_srv',
+  './global_event_srv'
 ],
 function () {});

+ 21 - 0
public/app/core/services/global_event_srv.ts

@@ -0,0 +1,21 @@
+import coreModule from 'app/core/core_module';
+import appEvents from 'app/core/app_events';
+
+// This service is for registering global events.
+// Good for communication react > angular and vice verse
+export class GlobalEventSrv {
+
+  /** @ngInject */
+  constructor(private $location, private $timeout) {
+  }
+
+  init() {
+    appEvents.on('location-change', payload => {
+        this.$timeout(() => { // A hack to use timeout when we're changing things (in this case the url) from outside of Angular.
+            this.$location.path(payload.href);
+        });
+    });
+  }
+}
+
+coreModule.service('globalEventSrv', GlobalEventSrv);

+ 0 - 3
public/app/core/utils/react2angular.ts

@@ -1,10 +1,7 @@
 import coreModule from 'app/core/core_module';
 
 export function react2AngularDirective(name: string, component: any, options: any) {
-
   coreModule.directive(name, ['reactDirective', reactDirective => {
     return reactDirective(component, options);
   }]);
-
 }
-

+ 13 - 1
public/app/features/plugins/ds_list_ctrl.ts

@@ -1,6 +1,7 @@
 ///<reference path="../../headers/common.d.ts" />
 
 import coreModule from '../../core/core_module';
+import {appEvents} from 'app/core/core';
 
 export class DataSourcesCtrl {
   datasources: any;
@@ -11,13 +12,24 @@ export class DataSourcesCtrl {
     private $scope,
     private backendSrv,
     private datasourceSrv,
+    private $location,
     private navModelSrv) {
 
     this.navModel = this.navModelSrv.getNav('cfg', 'datasources', 0);
-
+    this.navigateToUrl = this.navigateToUrl.bind(this);
     backendSrv.get('/api/datasources').then(result => {
       this.datasources = result;
     });
+
+    appEvents.on('location-change', payload => {
+      this.navigateToUrl(payload.href);
+    });
+  }
+
+  navigateToUrl(url) {
+    // debugger;
+    this.$location.path(url);
+    this.$location.replace();
   }
 
   removeDataSourceConfirmed(ds) {

+ 1 - 1
public/sass/_variables.scss

@@ -235,5 +235,5 @@ $dashboard-padding: $panel-margin * 2;
 $panel-padding: 0px 10px 5px 10px;
 
 // tabs
-$tabs-padding: 9px 15px 9px;
+$tabs-padding: 10px 15px 9px;
 

+ 15 - 0
public/sass/components/_page_header.scss

@@ -72,6 +72,21 @@
   text-transform: uppercase;
 }
 
+.page-header__select_nav {
+  margin-bottom: 10px;
+
+  @include media-breakpoint-up(lg) {
+    display: none;
+  }
+}
+
+.page-header__tabs {
+  display: none;
+  @include media-breakpoint-up(lg) {
+    display: block;
+  }
+}
+
 .page-breadcrumbs {
   display: flex;
   padding: 10px 0;

+ 13 - 1
public/sass/components/_tabs.scss

@@ -16,7 +16,7 @@
   position: relative;
   display: block;
   border: solid transparent;
-  border-width: 2px 1px 1px;
+  border-width: 0 1px 1px;
   border-radius: 3px 3px 0 0;
 
   i {
@@ -34,6 +34,18 @@
     border-color: $orange $tab-border-color transparent;
     background: $page-bg;
     color: $link-color;
+    overflow: hidden;
+
+    &::before {
+      display: block;
+      content: ' ';
+      position: absolute;
+      left: 0;
+      right: 0;
+      height: 2px;
+      top: 0;
+      background-image: linear-gradient(to right, #ffd500 0%, #ff4400 99%, #ff4400 100%);
+    }
   }
 }