diff --git a/package.json b/package.json index c7cc8a83644eb..39176751e2c7e 100644 --- a/package.json +++ b/package.json @@ -199,6 +199,7 @@ "calculate-size": "1.1.1", "classnames": "2.2.6", "clipboard": "2.0.4", + "css-element-queries": "^1.1.1", "d3": "4.13.0", "d3-scale-chromatic": "1.3.3", "eventemitter3": "2.0.3", diff --git a/public/app/features/dashboard/services/ChangeTracker.ts b/public/app/features/dashboard/services/ChangeTracker.ts index 7fe365cd8e004..f5f125e6cf432 100644 --- a/public/app/features/dashboard/services/ChangeTracker.ts +++ b/public/app/features/dashboard/services/ChangeTracker.ts @@ -110,6 +110,7 @@ export class ChangeTracker { // ignore iteration property delete dash.iteration; + let hasDynamicHeight = false; dash.panels = _.filter(dash.panels, panel => { if (panel.repeatPanelId) { return false; @@ -124,6 +125,15 @@ export class ChangeTracker { delete panel.legend.sortDesc; } + // ignore all y parameters after a dynamic height + if (panel.dynamicHeight) { + hasDynamicHeight = true; + delete panel.gridPos.h; + } + if (hasDynamicHeight) { + delete panel.gridPos.y; + } + return true; }); diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index cf110cdc705b6..b1898c40f1854 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -14,7 +14,7 @@ import { calculateInnerPanelHeight, } from 'app/features/dashboard/utils/panel'; -import { GRID_COLUMN_COUNT } from 'app/core/constants'; +import { GRID_COLUMN_COUNT, GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants'; export class PanelCtrl { panel: any; @@ -63,6 +63,25 @@ export class PanelCtrl { profiler.renderingCompleted(); } + dynamicHeightChanged(height: number): boolean { + const min = this.panel.dynamicHeightMIN || 50; + if (height < min) { + height = min; + } + if (this.panel.dynamicHeightMAX && height > this.panel.dynamicHeightMAX) { + height = this.panel.dynamicHeightMAX; + } + const h = Math.ceil((height + 5) / (GRID_CELL_HEIGHT + GRID_CELL_VMARGIN)) + 1; + if (h !== this.panel.gridPos.h) { + //console.log('Dynamic Height Changed', height, 'new:', h, 'old', this.panel.gridPos.h); + this.panel.gridPos.h = h; + this.events.emit('panel-size-changed'); + this.dashboard.events.emit('row-expanded'); // triggers grid re-layout + return true; + } + return false; + } + refresh() { this.panel.refresh(); } diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index 8cfc627b424b6..6fe45d666616c 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -2,6 +2,7 @@ import angular from 'angular'; import $ from 'jquery'; import Drop from 'tether-drop'; import baron from 'baron'; +import ResizeSensor from 'css-element-queries/src/ResizeSensor.js'; const module = angular.module('grafana.directives'); @@ -40,6 +41,8 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => { const ctrl = scope.ctrl; let infoDrop; let panelScrollbar; + let panelInnerContent; + let panelInnerContentHeight = -1; // the reason for handling these classes this way is for performance // limit the watchers on panels etc @@ -58,6 +61,16 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => { ctrl.dashboard.setPanelFocus(0); } + function checkInnerContentHeight() { + if (ctrl.panel.dynamicHeight && panelInnerContent) { + const v = panelInnerContent.outerHeight(true); + if (v !== panelInnerContentHeight) { + panelInnerContentHeight = v; + ctrl.dynamicHeightChanged(panelInnerContentHeight); + } + } + } + function resizeScrollableContent() { if (panelScrollbar) { panelScrollbar.update(); @@ -82,7 +95,21 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => { `; const scrollRoot = panelContent; - const scroller = panelContent.find(':first').find(':first'); + let scroller = panelContent.find(':first').find(':first'); + + // Add a div under the scroller and watch for changes + if (ctrl.panel.dynamicHeight) { + $(scroller).wrap('
'); + scroller = panelContent.find(':first'); + + panelInnerContent = $(scroller).find(':first'); + panelInnerContent.removeClass('panel-height-helper'); + panelInnerContent.css('margin-right', '20px'); + // panelInnerContent.css('border', '2px solid #F0F'); + + // tslint:disable-next-line + new ResizeSensor(panelInnerContent, checkInnerContentHeight); + } scrollRoot.addClass(scrollRootClass); $(scrollBarHTML).appendTo(scrollRoot); diff --git a/public/app/features/panel/partials/general_tab.html b/public/app/features/panel/partials/general_tab.html index ceae445f3ed88..2573129ff72b9 100644 --- a/public/app/features/panel/partials/general_tab.html +++ b/public/app/features/panel/partials/general_tab.html @@ -52,3 +52,21 @@ + + +
+
Height
+
+ +
+ Minimum + +
+
+ Maximum + +
+
+
+ + diff --git a/yarn.lock b/yarn.lock index 1710b7e87102a..06dd13cde7c71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5366,6 +5366,11 @@ css-declaration-sorter@^4.0.1: postcss "^7.0.1" timsort "^0.3.0" +css-element-queries@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/css-element-queries/-/css-element-queries-1.1.1.tgz#129e104ecb277aa6337b7302cbb6d511c2d1633a" + integrity sha512-/PX6Bkk77ShgbOx/mpawHdEvS3PGgy1mmMktcztDPndWdMJxcorcQiivrs+nEljqtBpvNEhAmQky9tQR6FSm8Q== + css-loader@2.1.1, css-loader@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-2.1.1.tgz#d8254f72e412bb2238bb44dd674ffbef497333ea"