grid: progress on new grid, resize & saving layouts works

This commit is contained in:
Torkel Ödegaard 2017-10-10 14:20:53 +02:00
parent 207773e07e
commit 10a3504309
12 changed files with 152 additions and 539 deletions

View File

@ -1,11 +1,62 @@
import {Emitter} from 'app/core/core';
export interface PanelModel {
id: number;
export interface GridPos {
x: number;
y: number;
width: number;
height: number;
type: string;
title: string;
w: number;
h: number;
}
const notPersistedProperties: {[str: string]: boolean} = {
"model": true,
"events": true,
};
export class PanelModel {
id: number;
gridPos: GridPos;
type: string;
title: string;
events: Emitter;
constructor(private model) {
// copy properties from persisted model
for (var property in model) {
this[property] = model[property];
}
this.events = new Emitter();
}
getSaveModel() {
this.model = {};
for (var property in this) {
if (notPersistedProperties[property] || !this.hasOwnProperty(property)) {
console.log('PanelModel.getSaveModel() skiping property', property);
continue;
}
this.model[property] = this[property];
}
return this.model;
}
updateGridPos(newPos: GridPos) {
let sizeChanged = false;
if (this.gridPos.w !== newPos.w || this.gridPos.h !== newPos.h) {
sizeChanged = true;
}
this.gridPos.x = newPos.x;
this.gridPos.y = newPos.y;
this.gridPos.w = newPos.w;
this.gridPos.h = newPos.h;
if (sizeChanged) {
console.log('PanelModel sizeChanged event and render events fired');
this.events.emit('panel-size-changed');
}
}
}

View File

@ -121,7 +121,8 @@ export class DashboardCtrl implements PanelContainer {
}
panelPossitionUpdated(panel: PanelModel) {
console.log('panel pos updated', panel);
//console.log('panel pos updated', panel);
//this.$rootScope.$broadcast('render');
}
timezoneChanged() {

View File

@ -4,6 +4,7 @@ import ReactGridLayout from 'react-grid-layout';
import {CELL_HEIGHT, CELL_VMARGIN} from '../model';
import {DashboardPanel} from './DashboardPanel';
import {PanelContainer} from './PanelContainer';
import {PanelModel} from '../PanelModel';
import sizeMe from 'react-sizeme';
const COLUMN_COUNT = 12;
@ -22,6 +23,8 @@ function GridWrapper({size, layout, onLayoutChange, children}) {
isDraggable={true}
isResizable={true}
measureBeforeMount={false}
containerPadding={[0, 0]}
useCSSTransforms={true}
margin={[CELL_VMARGIN, CELL_VMARGIN]}
cols={COLUMN_COUNT}
rowHeight={CELL_HEIGHT}
@ -42,6 +45,7 @@ export interface DashboardGridProps {
export class DashboardGrid extends React.Component<DashboardGridProps, any> {
gridToPanelMap: any;
panelContainer: PanelContainer;
panelMap: {[id: string]: PanelModel};
constructor(props) {
super(props);
@ -52,22 +56,34 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
buildLayout() {
const layout = [];
const panels = this.panelContainer.getPanels();
this.panelMap = {};
for (let panel of panels) {
let stringId = panel.id.toString();
this.panelMap[stringId] = panel;
if (!panel.gridPos) {
console.log('panel without gridpos');
continue;
}
layout.push({
i: panel.id.toString(),
x: panel.x,
y: panel.y,
w: panel.width,
h: panel.height,
i: stringId,
x: panel.gridPos.x,
y: panel.gridPos.y,
w: panel.gridPos.w,
h: panel.gridPos.h,
});
}
console.log('layout', layout);
return layout;
}
onLayoutChange() {}
onLayoutChange(newLayout) {
for (const newPos of newLayout) {
this.panelMap[newPos.i].updateGridPos(newPos);
}
}
renderPanels() {
const panels = this.panelContainer.getPanels();
@ -76,10 +92,7 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
for (let panel of panels) {
panelElements.push(
<div key={panel.id.toString()} className="panel">
<DashboardPanel
panel={panel}
getPanelContainer={this.props.getPanelContainer}
/>
<DashboardPanel panel={panel} getPanelContainer={this.props.getPanelContainer} />
</div>,
);
}

View File

@ -1,211 +0,0 @@
// ///<reference path="../../../headers/common.d.ts" />
//
// import coreModule from 'app/core/core_module';
// import {CELL_HEIGHT, CELL_VMARGIN} from '../model';
//
// import 'jquery-ui';
// import 'gridstack/dist/jquery.jQueryUI';
// import 'gridstack';
//
// const template = `
// <div class="grid-stack">
// <dash-grid-item ng-repeat="panel in ctrl.dashboard.panels track by panel.id"
// class="grid-stack-item"
// grid-ctrl="ctrl"
// panel="panel">
// <plugin-component type="panel" class="grid-stack-item-content">
// </plugin-component>
// </dash-grid-item>
// </div>
// `;
//
// var rowIndex = 0;
//
// export class GridCtrl {
// options: any;
// dashboard: any;
// panels: any;
// gridstack: any;
// gridElem: any;
// isInitialized: boolean;
// isDestroyed: boolean;
// index: number;
// changeRenderPromise: any;
//
// #<{(|* @ngInject |)}>#
// constructor(private $scope, private $element, private $timeout) {
// console.log(this.dashboard);
// this.index = rowIndex;
// rowIndex += 1;
// }
//
// init() {
// this.gridElem = this.$element.find('.grid-stack');
//
// this.gridstack = this.gridElem.gridstack({
// animate: true,
// cellHeight: CELL_HEIGHT,
// verticalMargin: CELL_VMARGIN,
// acceptWidgets: '.grid-stack-item',
// handle: '.grid-drag-handle'
// }).data('gridstack');
//
// this.isInitialized = true;
//
// this.gridElem.on('added', (e, items) => {
// for (let item of items) {
// this.onGridStackItemAdded(item);
// }
// });
//
// this.gridElem.on('removed', (e, items) => {
// for (let item of items) {
// this.onGridStackItemRemoved(item);
// }
// });
//
// this.gridElem.on('change', (e, items) => {
// this.$timeout(() => this.onGridStackItemsChanged(items), 50);
// });
// }
//
// onGridStackItemAdded(item) {
// console.log('row: ' + this.index + ' item added', item);
// }
//
// onGridStackItemRemoved(item) {
// console.log('row: ' + this.index + ' item removed', item.id, item);
// }
//
// onGridStackItemsChanged(items) {
// console.log('onGridStackItemsChanged');
//
// for (let item of items) {
// // find panel
// var panel = this.dashboard.getPanelById(parseInt(item.id));
//
// if (!panel) {
// console.log('item change but no panel found for item', item);
// continue;
// }
//
// // update panel model position
// panel.x = item.x;
// panel.y = item.y;
// panel.width = item.width;
// panel.height = item.height;
//
// console.log('updating panel: ' + panel.id + ' x: ' + panel.x + ' y: ' + panel.y);
// }
//
// this.dashboard.panels.sort(function (a, b) {
// let aScore = a.x + (a.y * 12);
// let bScore = b.x + (b.y * 12);
// if (aScore < bScore) { return -1; }
// if (aScore > bScore) { return 1; }
// return 0;
// });
//
// if (this.changeRenderPromise) {
// this.$timeout.cancel(this.changeRenderPromise);
// }
//
// this.changeRenderPromise = this.$timeout(() => {
// console.log('broadcasting render');
// this.$scope.$broadcast('render');
// });
// }
//
// destroy() {
// this.gridstack.destroy();
// this.gridstack = null;
// this.isDestroyed = true;
// }
// }
//
// #<{(|* @ngInject *|)}>#
// export function dashGrid($timeout) {
// return {
// restrict: 'E',
// template: template,
// controller: GridCtrl,
// bindToController: true,
// controllerAs: 'ctrl',
// scope: {
// dashboard: "=",
// },
// link: function(scope, elem, attrs, ctrl) {
// $timeout(function() {
// ctrl.init();
// });
//
// scope.$on('$destroy', () => {
// ctrl.destroy();
// });
// }
// };
// }
//
// #<{(|* @ngInject *|)}>#
// export function dashGridItem($timeout, $rootScope) {
// return {
// restrict: "E",
// scope: {
// panel: '=',
// gridCtrl: '='
// },
// link: function (scope, element, attrs) {
// let gridCtrl = scope.gridCtrl;
// let panel = scope.panel;
// let gridStackNode = null;
//
// element.attr({
// 'data-gs-id': panel.id,
// 'data-gs-x': panel.x,
// 'data-gs-y': panel.y,
// 'data-gs-width': panel.width,
// 'data-gs-height': panel.height,
// 'data-gs-no-resize': panel.type === 'row',
// });
//
// $rootScope.onAppEvent('panel-fullscreen-exit', (evt, payload) => {
// if (panel.id !== payload.panelId) {
// return;
// }
// gridCtrl.gridstack.locked(element, false);
// element.removeClass('panel-fullscreen');
// }, scope);
//
// $rootScope.onAppEvent('panel-fullscreen-enter', (evt, payload) => {
// if (panel.id !== payload.panelId) {
// return;
// }
// element.addClass('panel-fullscreen');
// }, scope);
//
// scope.$on('$destroy', () => {
// console.log('grid-item scope $destroy');
// if (gridCtrl.isDestroyed) {
// return;
// }
//
// if (gridStackNode) {
// console.log('grid-item scope $destroy removeWidget');
// gridStackNode._grid.removeWidget(element);
// }
// });
//
// if (gridCtrl.isInitialized) {
// gridCtrl.gridstack.makeWidget(element);
// gridStackNode = element.data('_gridstack_node');
// } else {
// setTimeout(function() {
// gridStackNode = element.data('_gridstack_node');
// }, 500);
// }
// }
// };
// }
//
// coreModule.directive('dashGrid', dashGrid);
// coreModule.directive('dashGridItem', dashGridItem);

View File

@ -1,232 +0,0 @@
Skip to content
This repository
Search
Pull requests
Issues
Marketplace
Gist
@torkelo
Sign out
Unwatch 946
Unstar 17,021
Fork 2,862 grafana/grafana
Code Issues 1,079 Pull requests 46 Projects 1 Wiki Settings Insights
Branch: gridstack Find file Copy pathgrafana/public/app/core/components/dashgrid/dashgrid.ts
a6bbcb8 on Jun 13
@torkelo torkelo ux: gridstack poc
1 contributor
RawBlameHistory
213 lines (181 sloc) 5.45 KB
///<reference path="../../../headers/common.d.ts" />
import $ from 'jquery';
import coreModule from '../../core_module';
import 'jquery-ui';
import 'gridstack';
import 'gridstack.jquery-ui';
const template = `
<div gridstack gridstack-handler="ctrl.gridstack" class="grid-stack"
options="ctrl.options"
on-change="ctrl.onChange(event,items)"
on-drag-start="ctrl.onDragStart(event,ui)"
on-drag-stop="ctrl.onDragStop(event, ui)"
on-resize-start="ctrl.onResizeStart(event, ui)"
on-resize-stop="ctrl.onResizeStop(event, ui)">
<div gridstack-item ng-repeat="panel in ctrl.panels"
class="grid-stack-item"
gs-item-id="panel.id"
gs-item-x="panel.x"
gs-item-y="panel.y"
gs-item-width="panel.width"
gs-item-height="panel.height"
gs-item-autopos="1"
on-item-added="ctrl.onItemAdded(item)"
on-item-removed="ctrl.onItemRemoved(item)">
<plugin-component type="panel" class="panel-margin grid-stack-item-content">
</plugin-component>
</div>
</div>
`;
export class DashGridCtrl {
options: any;
/** @ngInject */
constructor(private $rootScope) {
this.options = {
animate: true,
};
}
onResizeStop() {
this.$rootScope.$broadcast('render');
}
}
export function dashGrid($timeout) {
return {
restrict: 'E',
template: template,
controller: DashGridCtrl,
bindToController: true,
controllerAs: 'ctrl',
scope: {
dashboard: "="
},
link: function(scope, elem, attrs, ctrl) {
ctrl.panels = [];
ctrl.dashboard.forEachPanel((panel, panelIndex, row, rowIndex) => {
panel.width = 4;
panel.height = 4;
panel.x = panelIndex * 4;
panel.y = rowIndex * 4;
ctrl.panels.push(panel);
});
}
};
}
/** @ngInject */
coreModule.controller('GridstackController', ['$scope', function($scope) {
var gridstack = null;
this.init = function(element, options) {
gridstack = element.gridstack(options).data('gridstack');
return gridstack;
};
this.removeItem = function(element) {
if (gridstack) {
return gridstack.removeWidget(element, false);
}
return null;
};
this.addItem = function(element) {
if (gridstack) {
gridstack.makeWidget(element);
return element;
}
return null;
};
}]);
/** @ngInject */
coreModule.directive('gridstack', ['$timeout', function($timeout) {
return {
restrict: "A",
controller: 'GridstackController',
scope: {
onChange: '&',
onDragStart: '&',
onDragStop: '&',
onResizeStart: '&',
onResizeStop: '&',
gridstackHandler: '=',
options: '='
},
link: function (scope, element, attrs, controller, ngModel) {
var gridstack = controller.init(element, scope.options);
scope.gridstackHandler = gridstack;
element.on('change', function (e, items) {
$timeout(function() {
scope.$apply();
scope.onChange({event: e, items: items});
});
});
element.on('dragstart', function(e, ui) {
scope.onDragStart({event: e, ui: ui});
});
element.on('dragstop', function(e, ui) {
$timeout(function() {
scope.$apply();
scope.onDragStop({event: e, ui: ui});
});
});
element.on('resizestart', function(e, ui) {
scope.onResizeStart({event: e, ui: ui});
});
element.on('resizestop', function(e, ui) {
$timeout(function() {
scope.$apply();
scope.onResizeStop({event: e, ui: ui});
});
});
}
};
}]);
/** @ngInject */
coreModule.directive('gridstackItem', ['$timeout', function($timeout) {
return {
restrict: "A",
controller: 'GridstackController',
require: '^gridstack',
scope: {
gridstackItem: '=',
onItemAdded: '&',
onItemRemoved: '&',
gsItemId: '=',
gsItemX: '=',
gsItemY: '=',
gsItemWidth: '=',
gsItemHeight: '=',
gsItemAutopos: '='
},
link: function (scope, element, attrs, controller) {
$(element).attr('data-gs-id', scope.gsItemId);
$(element).attr('data-gs-x', scope.gsItemX);
$(element).attr('data-gs-y', scope.gsItemY);
$(element).attr('data-gs-width', scope.gsItemWidth);
$(element).attr('data-gs-height', scope.gsItemHeight);
$(element).attr('data-gs-auto-position', scope.gsItemAutopos);
var widget = controller.addItem(element);
var item = element.data('_gridstack_node');
$timeout(function() {
scope.onItemAdded({item: item});
});
scope.$watch(function () { return $(element).attr('data-gs-id'); }, function (val) {
scope.gsItemId = val;
});
scope.$watch(function(){ return $(element).attr('data-gs-x'); }, function(val) {
scope.gsItemX = val;
});
scope.$watch(function(){ return $(element).attr('data-gs-y'); }, function(val) {
scope.gsItemY = val;
});
scope.$watch(function(){ return $(element).attr('data-gs-width'); }, function(val) {
scope.gsItemWidth = val;
});
scope.$watch(function(){ return $(element).attr('data-gs-height'); }, function(val) {
scope.gsItemHeight = val;
});
element.bind('$destroy', function() {
var item = element.data('_gridstack_node');
scope.onItemRemoved({item: item});
controller.removeItem(element);
});
}
};
}]);
coreModule.directive('dashGrid', dashGrid);
Contact GitHub API Training Shop Blog About
© 2017 GitHub, Inc. Terms Privacy Security Status Help

View File

@ -69,8 +69,7 @@ export class DashboardModel {
this.links = data.links || [];
this.gnetId = data.gnetId || null;
this.folderId = data.folderId || null;
this.panels = data.panels || [];
this.rows = [];
this.panels = _.map(data.panels || [], panelData => new PanelModel(panelData));
this.addBuiltInAnnotationQuery();
this.initMeta(meta);
@ -123,34 +122,32 @@ export class DashboardModel {
// temp remove stuff
var events = this.events;
var meta = this.meta;
var rows = this.rows;
var variables = this.templating.list;
var panels = this.panels;
delete this.events;
delete this.meta;
delete this.panels;
// prepare save model
this.rows = _.map(rows, row => row.getSaveModel());
this.templating.list = _.map(variables, variable => variable.getSaveModel ? variable.getSaveModel() : variable);
this.panels = _.map(panels, panel => panel.getSaveModel());
// make clone
var copy = $.extend(true, {}, this);
// sort clone
copy = sortByKeys(copy);
console.log(copy.panels);
// restore properties
this.events = events;
this.meta = meta;
this.rows = rows;
this.templating.list = variables;
this.panels = panels;
return copy;
}
addEmptyRow() {
this.rows.push(new DashboardRow({isNew: true}));
}
private ensureListExist(data) {
if (!data) { data = {}; }
if (!data.list) { data.list = []; }
@ -327,7 +324,7 @@ export class DashboardModel {
var i, j, k;
var oldVersion = this.schemaVersion;
var panelUpgrades = [];
this.schemaVersion = 15;
this.schemaVersion = 16;
if (oldVersion === this.schemaVersion) {
return;
@ -636,7 +633,7 @@ export class DashboardModel {
this.graphTooltip = old.sharedCrosshair ? 1 : 0;
}
if (oldVersion < 15) {
if (oldVersion < 16) {
this.upgradeToGridLayout(old);
}
@ -644,60 +641,54 @@ export class DashboardModel {
return;
}
for (i = 0; i < this.rows.length; i++) {
var row = this.rows[i];
for (j = 0; j < row.panels.length; j++) {
for (k = 0; k < panelUpgrades.length; k++) {
panelUpgrades[k].call(this, row.panels[j]);
}
for (j = 0; j < this.panels.length; j++) {
for (k = 0; k < panelUpgrades.length; k++) {
panelUpgrades[k].call(this, this.panels[j]);
}
}
}
upgradeToGridLayout(old) {
let yPos = 0;
let rowIds = 1000;
//let rowIds = 1000;
for (let row of old.rows) {
let xPos = 0;
let height: any = row.height;
if (this.meta.keepRows) {
this.panels.push({
id: rowIds++,
type: 'row',
title: row.title,
x: 0,
y: yPos,
height: 1,
width: 12
});
yPos += 1;
}
// if (this.meta.keepRows) {
// this.panels.push({
// id: rowIds++,
// type: 'row',
// title: row.title,
// x: 0,
// y: yPos,
// height: 1,
// width: 12
// });
//
// yPos += 1;
// }
if (_.isString(height)) {
height = parseInt(height.replace('px', ''), 10);
}
height = Math.ceil(height / CELL_HEIGHT);
const rowGridHeight = Math.ceil(height / CELL_HEIGHT);
for (let panel of row.panels) {
// should wrap to next row?
if (xPos + panel.span >= 12) {
yPos += height;
yPos += rowGridHeight;
}
panel.x = xPos;
panel.y = yPos;
panel.width = panel.span;
panel.height = height;
panel.gridPos = { x: xPos, y: yPos, w: panel.span, h: rowGridHeight };
delete panel.span;
xPos += panel.width;
xPos += rowGridHeight;
this.panels.push(panel);
this.panels.push(new PanelModel(panel));
}
yPos += height;

View File

@ -55,11 +55,9 @@ export class SaveDashboardAsModalCtrl {
// remove alerts if source dashboard is already persisted
// do not want to create alert dupes
if (dashboard.id > 0) {
this.clone.rows.forEach(row => {
row.panels.forEach(panel => {
delete panel.thresholds;
delete panel.alert;
});
this.clone.panels.forEach(panel => {
delete panel.thresholds;
delete panel.alert;
});
}

View File

@ -37,7 +37,7 @@ export class PanelCtrl {
this.$scope = $scope;
this.$timeout = $injector.get('$timeout');
this.editorTabIndex = 0;
this.events = new Emitter();
this.events = this.panel.events;
this.timing = {};
var plugin = config.panels[this.panel.type];
@ -47,21 +47,14 @@ export class PanelCtrl {
}
$scope.$on("refresh", () => this.refresh());
$scope.$on("render", () => this.render());
$scope.$on("$destroy", () => {
this.events.emit('panel-teardown');
this.events.removeAllListeners();
});
// we should do something interesting
// with newly added panels
if (this.panel.isNew) {
delete this.panel.isNew;
}
}
init() {
this.calculatePanelHeight();
this.events.on('panel-size-changed', this.onSizeChanged.bind(this));
this.publishAppEvent('panel-initialized', {scope: this.$scope});
this.events.emit('panel-initialized');
}
@ -71,7 +64,7 @@ export class PanelCtrl {
}
refresh() {
this.events.emit('refresh', null);
this.events.emit('refresh', null);
}
publishAppEvent(evtName, evt) {
@ -170,23 +163,24 @@ export class PanelCtrl {
var fullscreenHeight = Math.floor(docHeight * 0.8);
this.containerHeight = this.editMode ? editHeight : fullscreenHeight;
} else {
this.containerHeight = this.panel.height * CELL_HEIGHT + ((this.panel.height-1) * CELL_VMARGIN);
this.containerHeight = this.panel.gridPos.h * CELL_HEIGHT + ((this.panel.gridPos.h-1) * CELL_VMARGIN);
}
this.height = this.containerHeight - (PANEL_BORDER + PANEL_PADDING + (this.panel.title ? TITLE_HEIGHT : EMPTY_TITLE_HEIGHT));
}
render(payload?) {
// ignore if other panel is in fullscreen mode
if (this.otherPanelInFullscreenMode()) {
return;
}
this.calculatePanelHeight();
this.timing.renderStart = new Date().getTime();
this.events.emit('render', payload);
}
private onSizeChanged() {
this.calculatePanelHeight();
this.$timeout(() => {
this.render();
});
}
duplicate() {
this.dashboard.duplicatePanel(this.panel);
this.$timeout(() => {

View File

@ -98,6 +98,7 @@ module.directive('grafanaPanel', function($rootScope, $document) {
}
ctrl.events.on('render', () => {
console.log('panelDirective::render!');
if (lastHeight !== ctrl.containerHeight) {
panelContainer.css({minHeight: ctrl.containerHeight});
lastHeight = ctrl.containerHeight;

View File

@ -88,7 +88,7 @@ function panelHeader($compile) {
let menuScope;
elem.click(function(evt) {
const targetClass = evt.target.className;
//const targetClass = evt.target.className;
// remove existing scope
if (menuScope) {
@ -100,10 +100,10 @@ function panelHeader($compile) {
menuElem.html(menuHtml);
$compile(menuElem)(menuScope);
if (targetClass === 'panel-title-text' || targetClass === 'panel-title') {
evt.stopPropagation();
elem.find('[data-toggle=dropdown]').dropdown('toggle');
}
// if (targetClass === 'panel-title-text' || targetClass === 'panel-title') {
// evt.stopPropagation();
// elem.find('[data-toggle=dropdown]').dropdown('toggle');
// }
});
}
};

View File

@ -54,6 +54,7 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
});
ctrl.events.on('render', function(renderData) {
console.log('graph render');
data = renderData || data;
if (!data) {
return;

View File

@ -13,7 +13,6 @@
{
"content": "<div class=\"text-center dashboard-header\">\n <span>Home Dashboard</span>\n</div>",
"editable": true,
"height": 2,
"id": 1,
"links": [],
"mode": "html",
@ -21,14 +20,16 @@
"title": "",
"transparent": true,
"type": "text",
"width": 12,
"x": 0,
"y": 0
"gridPos": {
"w": 12,
"h": 2,
"x": 0,
"y": 0
}
},
{
"folderId": 0,
"headings": true,
"height": 17,
"id": 3,
"limit": 4,
"links": [],
@ -40,22 +41,27 @@
"title": "",
"transparent": false,
"type": "dashlist",
"width": 7,
"x": 0,
"y": 6
"gridPos": {
"w": 7,
"h": 17,
"x": 0,
"y": 6
}
},
{
"editable": true,
"error": false,
"height": 17,
"id": 4,
"links": [],
"title": "",
"transparent": false,
"type": "pluginlist",
"width": 5,
"x": 7,
"y": 6
"gridPos": {
"w": 5,
"h": 17,
"x": 7,
"y": 6
}
}
],
"rows": [],