diff --git a/package.json b/package.json index 030219fe587..ce861a25f7b 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "angular-route": "^1.6.6", "angular-sanitize": "^1.6.6", "babel-polyfill": "^6.26.0", + "baron": "^3.0.3", "brace": "^0.10.0", "classnames": "^2.2.5", "clipboard": "^1.7.1", @@ -151,7 +152,6 @@ "moment": "^2.18.1", "mousetrap": "^1.6.0", "mousetrap-global-bind": "^1.1.0", - "perfect-scrollbar": "^1.2.0", "prop-types": "^15.6.0", "react": "^16.2.0", "react-dom": "^16.2.0", diff --git a/public/app/core/components/ScrollBar/ScrollBar.tsx b/public/app/core/components/ScrollBar/ScrollBar.tsx index 7d9e015df94..a358dc1926a 100644 --- a/public/app/core/components/ScrollBar/ScrollBar.tsx +++ b/public/app/core/components/ScrollBar/ScrollBar.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import PerfectScrollbar from 'perfect-scrollbar'; +import baron from 'baron'; export interface Props { children: any; @@ -8,31 +8,36 @@ export interface Props { export default class ScrollBar extends React.Component { private container: any; - private ps: PerfectScrollbar; + private scrollbar: baron; constructor(props) { super(props); } componentDidMount() { - this.ps = new PerfectScrollbar(this.container, { - wheelPropagation: true, + this.scrollbar = baron({ + root: this.container.parentElement, + scroller: this.container, + bar: '.baron__bar', + barOnCls: '_scrollbar', + scrollingCls: '_scrolling', + track: '.baron__track', }); } componentDidUpdate() { - this.ps.update(); + this.scrollbar.update(); } componentWillUnmount() { - this.ps.destroy(); + this.scrollbar.dispose(); } // methods can be invoked by outside setScrollTop(top) { if (this.container) { this.container.scrollTop = top; - this.ps.update(); + this.scrollbar.update(); return true; } @@ -42,7 +47,7 @@ export default class ScrollBar extends React.Component { setScrollLeft(left) { if (this.container) { this.container.scrollLeft = left; - this.ps.update(); + this.scrollbar.update(); return true; } @@ -55,8 +60,14 @@ export default class ScrollBar extends React.Component { render() { return ( -
- {this.props.children} +
+
+ {this.props.children} +
+ +
+
+
); } diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index 798a40cb1bf..1e3f0cb9119 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -167,6 +167,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop if (sidemenuHidden) { sidemenuHidden = false; body.addClass('sidemenu-open'); + appEvents.emit('toggle-inactive-mode'); $timeout(function() { $rootScope.$broadcast('render'); }, 100); diff --git a/public/app/core/components/scroll/page_scroll.ts b/public/app/core/components/scroll/page_scroll.ts new file mode 100644 index 00000000000..e6db344a4d6 --- /dev/null +++ b/public/app/core/components/scroll/page_scroll.ts @@ -0,0 +1,41 @@ +import coreModule from 'app/core/core_module'; +import appEvents from 'app/core/app_events'; + +export function pageScrollbar() { + return { + restrict: 'A', + link: function(scope, elem, attrs) { + let lastPos = 0; + + appEvents.on( + 'dash-scroll', + evt => { + if (evt.restore) { + elem[0].scrollTop = lastPos; + return; + } + + lastPos = elem[0].scrollTop; + + if (evt.animate) { + elem.animate({ scrollTop: evt.pos }, 500); + } else { + elem[0].scrollTop = evt.pos; + } + }, + scope + ); + + scope.$on('$routeChangeSuccess', () => { + lastPos = 0; + elem[0].scrollTop = 0; + elem[0].focus(); + }); + + elem[0].tabIndex = -1; + elem[0].focus(); + }, + }; +} + +coreModule.directive('pageScrollbar', pageScrollbar); diff --git a/public/app/core/components/scroll/scroll.ts b/public/app/core/components/scroll/scroll.ts index fbf5fd6cd37..3f9865e6dce 100644 --- a/public/app/core/components/scroll/scroll.ts +++ b/public/app/core/components/scroll/scroll.ts @@ -1,15 +1,44 @@ -import PerfectScrollbar from 'perfect-scrollbar'; +import $ from 'jquery'; +import baron from 'baron'; import coreModule from 'app/core/core_module'; import appEvents from 'app/core/app_events'; +const scrollBarHTML = ` +
+
+
+`; + +const scrollRootClass = 'baron baron__root'; +const scrollerClass = 'baron__scroller'; + export function geminiScrollbar() { return { restrict: 'A', link: function(scope, elem, attrs) { - let scrollbar = new PerfectScrollbar(elem[0], { - wheelPropagation: true, - wheelSpeed: 3, - }); + let scrollRoot = elem.parent(); + let scroller = elem; + + if (attrs.grafanaScrollbar && attrs.grafanaScrollbar === 'scrollonroot') { + scrollRoot = scroller; + } + + scrollRoot.addClass(scrollRootClass); + $(scrollBarHTML).appendTo(scrollRoot); + elem.addClass(scrollerClass); + + let scrollParams = { + root: scrollRoot[0], + scroller: scroller[0], + bar: '.baron__bar', + barOnCls: '_scrollbar', + scrollingCls: '_scrolling', + track: '.baron__track', + direction: 'v', + }; + + let scrollbar = baron(scrollParams); + let lastPos = 0; appEvents.on( @@ -31,13 +60,24 @@ export function geminiScrollbar() { scope ); + // force updating dashboard width + appEvents.on('toggle-sidemenu', forceUpdate, scope); + appEvents.on('toggle-sidemenu-hidden', forceUpdate, scope); + appEvents.on('toggle-view-mode', forceUpdate, scope); + appEvents.on('toggle-kiosk-mode', forceUpdate, scope); + appEvents.on('toggle-inactive-mode', forceUpdate, scope); + + function forceUpdate() { + scrollbar.scroll(); + } + scope.$on('$routeChangeSuccess', () => { lastPos = 0; elem[0].scrollTop = 0; }); scope.$on('$destroy', () => { - scrollbar.destroy(); + scrollbar.dispose(); }); }, }; diff --git a/public/app/core/components/search/search.html b/public/app/core/components/search/search.html index acaf0730a6b..afb9e723cad 100644 --- a/public/app/core/components/search/search.html +++ b/public/app/core/components/search/search.html @@ -19,6 +19,7 @@
+
No dashboards matching your query were found.
+
diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 353d8762a9a..fb7021fe883 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -47,6 +47,7 @@ import { NavModelSrv, NavModel } from './nav_model_srv'; import { userPicker } from './components/user_picker'; import { teamPicker } from './components/team_picker'; import { geminiScrollbar } from './components/scroll/scroll'; +import { pageScrollbar } from './components/scroll/page_scroll'; import { gfPageDirective } from './components/gf_page'; import { orgSwitcher } from './components/org_switcher'; import { profiler } from './profiler'; @@ -85,6 +86,7 @@ export { userPicker, teamPicker, geminiScrollbar, + pageScrollbar, gfPageDirective, orgSwitcher, manageDashboardsDirective, diff --git a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx index aeb840c317a..98d1657f4bd 100644 --- a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx +++ b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx @@ -103,7 +103,7 @@ export class AddPanelPanel extends React.Component +
diff --git a/public/app/features/dashboard/view_state_srv.ts b/public/app/features/dashboard/view_state_srv.ts index 576b8b6fce8..fa471b89989 100644 --- a/public/app/features/dashboard/view_state_srv.ts +++ b/public/app/features/dashboard/view_state_srv.ts @@ -196,9 +196,10 @@ export class DashboardViewState { this.oldTimeRange = ctrl.range; this.fullscreenPanel = panelScope; + // Firefox doesn't return scrollTop postion properly if 'dash-scroll' is emitted after setViewMode() + this.$scope.appEvent('dash-scroll', { animate: false, pos: 0 }); this.dashboard.setViewMode(ctrl.panel, true, ctrl.editMode); this.$scope.appEvent('panel-fullscreen-enter', { panelId: ctrl.panel.id }); - this.$scope.appEvent('dash-scroll', { animate: false, pos: 0 }); } registerPanel(panelScope) { diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index dec7868a553..e549ca262d3 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -1,6 +1,7 @@ import angular from 'angular'; +import $ from 'jquery'; import Drop from 'tether-drop'; -import PerfectScrollbar from 'perfect-scrollbar'; +import baron from 'baron'; var module = angular.module('grafana.directives'); @@ -86,6 +87,9 @@ module.directive('grafanaPanel', function($rootScope, $document, $timeout) { function panelHeightUpdated() { panelContent.css({ height: ctrl.height + 'px' }); + } + + function resizeScrollableContent() { if (panelScrollbar) { panelScrollbar.update(); } @@ -100,9 +104,30 @@ module.directive('grafanaPanel', function($rootScope, $document, $timeout) { // update scrollbar after mounting ctrl.events.on('component-did-mount', () => { if (ctrl.__proto__.constructor.scrollable) { - panelScrollbar = new PerfectScrollbar(panelContent[0], { - wheelPropagation: true, + const scrollRootClass = 'baron baron__root baron__clipper panel-content--scrollable'; + const scrollerClass = 'baron__scroller'; + const scrollBarHTML = ` +
+
+
+ `; + + let scrollRoot = panelContent; + let scroller = panelContent.find(':first').find(':first'); + + scrollRoot.addClass(scrollRootClass); + $(scrollBarHTML).appendTo(scrollRoot); + scroller.addClass(scrollerClass); + + panelScrollbar = baron({ + root: scrollRoot[0], + scroller: scroller[0], + bar: '.baron__bar', + barOnCls: '_scrollbar', + scrollingCls: '_scrolling', }); + + panelScrollbar.scroll(); } }); @@ -110,6 +135,7 @@ module.directive('grafanaPanel', function($rootScope, $document, $timeout) { ctrl.calculatePanelHeight(); panelHeightUpdated(); $timeout(() => { + resizeScrollableContent(); ctrl.render(); }); }); @@ -199,7 +225,7 @@ module.directive('grafanaPanel', function($rootScope, $document, $timeout) { } if (panelScrollbar) { - panelScrollbar.update(); + panelScrollbar.dispose(); } }); }, diff --git a/public/app/features/templating/query_variable.ts b/public/app/features/templating/query_variable.ts index b87167ad646..54bd7bb660c 100644 --- a/public/app/features/templating/query_variable.ts +++ b/public/app/features/templating/query_variable.ts @@ -198,7 +198,9 @@ export class QueryVariable implements Variable { } }); } else if (sortType === 3) { - options = _.sortBy(options, opt => { return _.toLower(opt.text); }); + options = _.sortBy(options, opt => { + return _.toLower(opt.text); + }); } if (reverseSort) { diff --git a/public/app/partials/dashboard.html b/public/app/partials/dashboard.html index 210275d2200..9506587c515 100644 --- a/public/app/partials/dashboard.html +++ b/public/app/partials/dashboard.html @@ -1,18 +1,18 @@
-
- - +
+ + -
- - +
+ + - - -
-
+ + +
+
diff --git a/public/app/plugins/panel/dashlist/module.html b/public/app/plugins/panel/dashlist/module.html index 8fa3e7ef71f..fdba0c79f35 100644 --- a/public/app/plugins/panel/dashlist/module.html +++ b/public/app/plugins/panel/dashlist/module.html @@ -1,17 +1,19 @@ -
-
-
- {{group.header}} -
-
- - - {{dash.title}} - - - - - +
+
+
+
+ {{group.header}} +
+
diff --git a/public/app/plugins/panel/graph/legend.ts b/public/app/plugins/panel/graph/legend.ts index 4dfeb75ff55..b668555b6a6 100644 --- a/public/app/plugins/panel/graph/legend.ts +++ b/public/app/plugins/panel/graph/legend.ts @@ -1,7 +1,7 @@ import angular from 'angular'; import _ from 'lodash'; import $ from 'jquery'; -import PerfectScrollbar from 'perfect-scrollbar'; +import baron from 'baron'; var module = angular.module('grafana.directives'); @@ -16,11 +16,10 @@ module.directive('graphLegend', function(popoverSrv, $timeout) { var i; var legendScrollbar; const legendRightDefaultWidth = 10; + let legendElem = elem.parent(); scope.$on('$destroy', function() { - if (legendScrollbar) { - legendScrollbar.destroy(); - } + destroyScrollbar(); }); ctrl.events.on('render-legend', () => { @@ -112,7 +111,7 @@ module.directive('graphLegend', function(popoverSrv, $timeout) { } function render() { - let legendWidth = elem.width(); + let legendWidth = legendElem.width(); if (!ctrl.panel.legend.show) { elem.empty(); firstRender = true; @@ -134,8 +133,8 @@ module.directive('graphLegend', function(popoverSrv, $timeout) { // Set width so it works with IE11 var width: any = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth + 'px' : ''; var ieWidth: any = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth - 1 + 'px' : ''; - elem.css('min-width', width); - elem.css('width', ieWidth); + legendElem.css('min-width', width); + legendElem.css('width', ieWidth); elem.toggleClass('graph-legend-table', panel.legend.alignAsTable === true); @@ -241,8 +240,10 @@ module.directive('graphLegend', function(popoverSrv, $timeout) { tbodyElem.append(tableHeaderElem); tbodyElem.append(seriesElements); elem.append(tbodyElem); + tbodyElem.wrap('
'); } else { - elem.append(seriesElements); + elem.append('
'); + elem.find('.graph-legend-scroll').append(seriesElements); } if (!panel.legend.rightSide || (panel.legend.rightSide && legendWidth !== legendRightDefaultWidth)) { @@ -253,23 +254,45 @@ module.directive('graphLegend', function(popoverSrv, $timeout) { } function addScrollbar() { - const scrollbarOptions = { - // Number of pixels the content height can surpass the container height without enabling the scroll bar. - scrollYMarginOffset: 2, - suppressScrollX: true, - wheelPropagation: true, + const scrollRootClass = 'baron baron__root'; + const scrollerClass = 'baron__scroller'; + const scrollBarHTML = ` +
+
+
+ `; + + let scrollRoot = elem; + let scroller = elem.find('.graph-legend-scroll'); + + // clear existing scroll bar track to prevent duplication + scrollRoot.find('.baron__track').remove(); + + scrollRoot.addClass(scrollRootClass); + $(scrollBarHTML).appendTo(scrollRoot); + scroller.addClass(scrollerClass); + + let scrollbarParams = { + root: scrollRoot[0], + scroller: scroller[0], + bar: '.baron__bar', + track: '.baron__track', + barOnCls: '_scrollbar', + scrollingCls: '_scrolling', }; if (!legendScrollbar) { - legendScrollbar = new PerfectScrollbar(elem[0], scrollbarOptions); + legendScrollbar = baron(scrollbarParams); } else { - legendScrollbar.update(); + destroyScrollbar(); + legendScrollbar = baron(scrollbarParams); } + legendScrollbar.scroll(); } function destroyScrollbar() { if (legendScrollbar) { - legendScrollbar.destroy(); + legendScrollbar.dispose(); legendScrollbar = undefined; } } diff --git a/public/app/plugins/panel/graph/template.ts b/public/app/plugins/panel/graph/template.ts index 0b9eb8227df..c897327fe1a 100644 --- a/public/app/plugins/panel/graph/template.ts +++ b/public/app/plugins/panel/graph/template.ts @@ -3,7 +3,9 @@ var template = `
-
+
+
+
`; diff --git a/public/sass/components/_panel_add_panel.scss b/public/sass/components/_panel_add_panel.scss index 51754a54d92..4a17438ffeb 100644 --- a/public/sass/components/_panel_add_panel.scss +++ b/public/sass/components/_panel_add_panel.scss @@ -1,5 +1,13 @@ +.add-panel-container { + height: 100%; +} + .add-panel { height: 100%; + + .baron__root { + height: calc(100% - 43px); + } } .add-panel__header { @@ -39,7 +47,6 @@ flex-direction: row; flex-wrap: wrap; overflow: auto; - height: calc(100% - 43px); align-content: flex-start; justify-content: space-around; position: relative; diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss index e15cd576367..72f3ca3dbbe 100644 --- a/public/sass/components/_panel_graph.scss +++ b/public/sass/components/_panel_graph.scss @@ -49,6 +49,7 @@ } .graph-legend { + display: flex; flex: 0 1 auto; max-height: 30%; margin: 0; @@ -56,11 +57,27 @@ padding-top: 6px; position: relative; + // fix for Firefox (white stripe on the right of scrollbar) + width: calc(100% - 1px); + .popover-content { padding: 0; } } +.graph-legend-content { + position: relative; + + // fix for Firefox (white stripe on the right of scrollbar) + width: calc(100% - 1px); +} + +.graph-legend-scroll { + position: relative; + overflow: auto !important; + padding: 1px; +} + .graph-legend-icon { position: relative; padding-right: 4px; @@ -115,8 +132,20 @@ // fix for phantomjs .body--phantomjs { .graph-panel--legend-right { + .graph-legend { + display: inline-block; + } + + .graph-panel__chart { + display: flex; + } + .graph-legend-table { display: table; + + .graph-legend-scroll { + display: table; + } } } } @@ -124,9 +153,9 @@ .graph-legend-table { tbody { display: block; + position: relative; overflow-y: auto; overflow-x: hidden; - height: 100%; padding-bottom: 1px; padding-right: 5px; padding-left: 5px; diff --git a/public/sass/components/_scrollbar.scss b/public/sass/components/_scrollbar.scss index 42818e786f6..78173b73f47 100644 --- a/public/sass/components/_scrollbar.scss +++ b/public/sass/components/_scrollbar.scss @@ -9,6 +9,11 @@ -ms-touch-action: auto; } +// ._scrollbar { +// overflow-x: hidden !important; +// overflow-y: auto; +// } + /* * Scrollbar rail styles */ @@ -101,7 +106,7 @@ opacity: 0.9; } -// Srollbars +// Scrollbars // ::-webkit-scrollbar { @@ -172,3 +177,120 @@ border-top: 1px solid $scrollbarBorder; border-left: 1px solid $scrollbarBorder; } + +// Baron styles + +.baron { + // display: inline-block; // this brakes phantomjs rendering (width becomes 0) + overflow: hidden; +} + +// Fix for side menu on mobile devices +.main-view.baron { + width: unset; +} + +.baron__clipper { + position: relative; + overflow: hidden; +} + +.baron__scroller { + overflow-y: scroll; + -ms-overflow-style: none; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin: 0; + border: 0; + padding: 0; + width: 100%; + height: 100%; + -webkit-overflow-scrolling: touch; + /* remove line to customize scrollbar in iOs */ +} + +.baron__scroller::-webkit-scrollbar { + width: 0; + height: 0; +} + +.baron__track { + display: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; +} + +.baron._scrollbar .baron__track { + display: block; +} + +.baron__free { + position: absolute; + top: 0; + bottom: 0; + right: 0; +} + +.baron__bar { + display: none; + position: absolute; + right: 0; + z-index: 1; + // width: 10px; + background: #999; + + // height: 15px; + width: 15px; + transition: background-color 0.2s linear, opacity 0.2s linear; + opacity: 0; +} + +.baron._scrollbar .baron__bar { + display: block; + + @include gradient-vertical($scrollbarBackground, $scrollbarBackground2); + border-radius: 6px; + width: 6px; + /* there must be 'right' for ps__thumb-y */ + right: 0px; + /* please don't change 'position' */ + position: absolute; + + // background-color: transparent; + // opacity: 0.6; + + &:hover, + &:focus { + // background-color: transparent; + opacity: 0.9; + } +} + +.panel-hover-highlight .baron__track .baron__bar { + opacity: 0.6; +} + +.baron._scrolling > .baron__track .baron__bar { + opacity: 0.9; +} + +// fix for phantomjs +.body--phantomjs .baron__track .baron__bar { + opacity: 0 !important; +} + +.baron__control { + display: none; +} + +.baron.panel-content--scrollable { + // Width needs to be set to prevent content width issues + // Set to less than 100% for fixing Firefox issue (white stripe on the right of scrollbar) + width: calc(100% - 2px); + + .baron__scroller { + padding-top: 1px; + } +} diff --git a/public/sass/components/_search.scss b/public/sass/components/_search.scss index 99033b90ff1..8338a5d72ae 100644 --- a/public/sass/components/_search.scss +++ b/public/sass/components/_search.scss @@ -102,14 +102,21 @@ } } +.search-results-scroller { + display: flex; + position: relative; +} + .search-results-container { - height: 100%; display: block; padding: $spacer; position: relative; flex-grow: 10; margin-bottom: 1rem; + // Fix for search scroller in mobile view + height: unset; + .label-tag { margin-left: 6px; font-size: 11px; diff --git a/public/sass/layout/_page.scss b/public/sass/layout/_page.scss index 03941a47408..c80d461541e 100644 --- a/public/sass/layout/_page.scss +++ b/public/sass/layout/_page.scss @@ -28,12 +28,20 @@ width: 100%; overflow: auto; height: 100%; + -webkit-overflow-scrolling: touch; &--dashboard { height: calc(100% - 56px); } } +// fix for phantomjs +.body--phantomjs { + .scroll-canvas { + overflow: hidden; + } +} + .page-body { padding-top: $spacer*2; min-height: 500px; diff --git a/public/views/index.template.html b/public/views/index.template.html index 2d408f70f8c..79da1d7179c 100644 --- a/public/views/index.template.html +++ b/public/views/index.template.html @@ -16,7 +16,7 @@ - + @@ -40,7 +40,7 @@
-
+