Skip to content

Commit

Permalink
Dashboard auto-saving
Browse files Browse the repository at this point in the history
  • Loading branch information
ranbena committed Mar 28, 2019
1 parent fe4a7b6 commit dd92550
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 110 deletions.
63 changes: 2 additions & 61 deletions client/app/components/dashboards/gridstack/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function gridstack($parse, dashboardGridOptions) {
scope: {
editing: '=',
batchUpdate: '=', // set by directive - for using in wrapper components
onLayoutChanged: '=',
isOneColumnMode: '=',
},
controller() {
Expand Down Expand Up @@ -121,67 +122,6 @@ function gridstack($parse, dashboardGridOptions) {
});
};

this.batchUpdateWidgets = (items) => {
// This method is used to update multiple widgets with a single
// reflow (for example, restore positions when dashboard editing cancelled).
// "dirty" part of code: updating grid and DOM nodes directly.
// layout reflow is triggered by `batchUpdate`/`commit` calls
this.update((grid) => {
_.each(grid.grid.nodes, (node) => {
const item = items[node.id];
if (item) {
if (_.isNumber(item.col)) {
node.x = parseFloat(item.col);
node.el.attr('data-gs-x', node.x);
node._dirty = true;
}

if (_.isNumber(item.row)) {
node.y = parseFloat(item.row);
node.el.attr('data-gs-y', node.y);
node._dirty = true;
}

if (_.isNumber(item.sizeX)) {
node.width = parseFloat(item.sizeX);
node.el.attr('data-gs-width', node.width);
node._dirty = true;
}

if (_.isNumber(item.sizeY)) {
node.height = parseFloat(item.sizeY);
node.el.attr('data-gs-height', node.height);
node._dirty = true;
}

if (_.isNumber(item.minSizeX)) {
node.minWidth = parseFloat(item.minSizeX);
node.el.attr('data-gs-min-width', node.minWidth);
node._dirty = true;
}

if (_.isNumber(item.maxSizeX)) {
node.maxWidth = parseFloat(item.maxSizeX);
node.el.attr('data-gs-max-width', node.maxWidth);
node._dirty = true;
}

if (_.isNumber(item.minSizeY)) {
node.minHeight = parseFloat(item.minSizeY);
node.el.attr('data-gs-min-height', node.minHeight);
node._dirty = true;
}

if (_.isNumber(item.maxSizeY)) {
node.maxHeight = parseFloat(item.maxSizeY);
node.el.attr('data-gs-max-height', node.maxHeight);
node._dirty = true;
}
}
});
});
};

this.removeWidget = ($element) => {
const grid = this.grid();
if (grid) {
Expand Down Expand Up @@ -300,6 +240,7 @@ function gridstack($parse, dashboardGridOptions) {
$(node.el).trigger('gridstack.changed', node);
}
});
$scope.onLayoutChanged();
changedNodes = {};
});

Expand Down
20 changes: 9 additions & 11 deletions client/app/pages/dashboards/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@ <h3>
</div>
<div class="col-xs-4 col-sm-5 col-lg-5 text-right dashboard__control p-r-0">
<span ng-if="!$ctrl.dashboard.is_archived && !public" class="hidden-print">
<div class="btn-group">
<button type="button" class="btn btn-primary btn-sm"
ng-disabled="$ctrl.isGridDisabled"
ng-click="$ctrl.editLayout(false, true)" ng-if="$ctrl.layoutEditing">
<i class="zmdi zmdi-check"></i> Apply Changes
</button>
<div ng-if="$ctrl.layoutEditing">
<span class="save-status" data-dirty="{{ $ctrl.isLayoutDirty && !$ctrl.saveInProgress }}">
Saved
</span>

<button type="button" class="btn btn-default btn-sm"
<button type="button" class="btn btn-primary btn-sm"
ng-disabled="$ctrl.isGridDisabled"
ng-click="$ctrl.editLayout(false, false)" ng-if="$ctrl.layoutEditing">
<i class="zmdi zmdi-close"></i> Cancel
ng-click="$ctrl.editLayout(false)">
<i class="fa fa-check"></i> Done Editing
</button>
</div>

Expand Down Expand Up @@ -91,8 +89,8 @@ <h3>
</div>

<div style="padding-bottom: 5px;" ng-if="$ctrl.dashboard.widgets.length > 0">
<div gridstack editing="$ctrl.layoutEditing && !$ctrl.saveInProgress" batch-update="$ctrl.updateGridItems"
is-one-column-mode="$ctrl.isGridDisabled" class="dashboard-wrapper"
<div gridstack editing="$ctrl.layoutEditing" batch-update="$ctrl.updateGridItems"
is-one-column-mode="$ctrl.isGridDisabled" class="dashboard-wrapper" on-layout-changed="$ctrl.onLayoutChanged"
ng-class="{'preview-mode': !$ctrl.layoutEditing, 'editing-mode': $ctrl.layoutEditing}">
<div class="dashboard-widget-wrapper"
ng-repeat="widget in $ctrl.dashboard.widgets track by widget.id"
Expand Down
66 changes: 28 additions & 38 deletions client/app/pages/dashboards/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,34 +48,35 @@ function DashboardCtrl(
) {
this.saveInProgress = false;

const saveDashboardLayout = (widgets) => {
const saveDashboardLayout = () => {
if (!this.dashboard.canEdit()) {
return;
}

// calc diff, bail if none
const changedWidgets = getWidgetsWithChangedPositions(this.dashboard.widgets);
if (!changedWidgets.length) {
this.isLayoutDirty = false;
$scope.$applyAsync();
return;
}

this.saveInProgress = true;
const showMessages = true;
return $q
.all(_.map(widgets, widget => widget.save()))
.all(_.map(changedWidgets, widget => widget.save()))
.then(() => {
if (showMessages) {
notification.success('Changes saved.');
}
// Update original widgets positions
_.each(widgets, (widget) => {
_.extend(widget.$originalPosition, widget.options.position);
});
this.isLayoutDirty = false;
})
.catch(() => {
if (showMessages) {
notification.error('Error saving changes.');
}
notification.error('Error saving changes.');
})
.finally(() => {
this.saveInProgress = false;
});
};

const saveDashboardLayoutDebounced = _.debounce(saveDashboardLayout, 1000);

this.layoutEditing = false;
this.isFullscreen = false;
this.refreshRate = null;
Expand All @@ -84,6 +85,7 @@ function DashboardCtrl(
this.showPermissionsControl = clientConfig.showPermissionsControl;
this.globalParameters = [];
this.isDashboardOwner = false;
this.isLayoutDirty = false;

this.refreshRates = clientConfig.dashboardRefreshIntervals.map(interval => ({
name: durationHumanize(interval),
Expand Down Expand Up @@ -242,28 +244,17 @@ function DashboardCtrl(
});
};

this.editLayout = (enableEditing, applyChanges) => {
if (!this.isGridDisabled) {
if (!enableEditing) {
if (applyChanges) {
const changedWidgets = getWidgetsWithChangedPositions(this.dashboard.widgets);
saveDashboardLayout(changedWidgets);
} else {
// Revert changes
const items = {};
_.each(this.dashboard.widgets, (widget) => {
_.extend(widget.options.position, widget.$originalPosition);
items[widget.id] = widget.options.position;
});
this.dashboard.widgets = Dashboard.prepareWidgetsForDashboard(this.dashboard.widgets);
if (this.updateGridItems) {
this.updateGridItems(items);
}
}
}

this.layoutEditing = enableEditing;
this.onLayoutChanged = () => {
// prevent unnecessary save when gridstack is loaded
if (!this.layoutEditing) {
return;
}
this.isLayoutDirty = true;
saveDashboardLayoutDebounced();
};

this.editLayout = (enableEditing) => {
this.layoutEditing = enableEditing;
};

this.loadTags = () => getTags('api/dashboards/tags').then(tags => _.map(tags, t => t.name));
Expand Down Expand Up @@ -405,12 +396,11 @@ function DashboardCtrl(
this.removeWidget = (widgetId) => {
this.dashboard.widgets = this.dashboard.widgets.filter(w => w.id !== undefined && w.id !== widgetId);
this.extractGlobalParameters();
$scope.$applyAsync();

if (!this.layoutEditing) {
// We need to wait a bit while `angular` updates widgets, and only then save new layout
$timeout(() => {
const changedWidgets = getWidgetsWithChangedPositions(this.dashboard.widgets);
saveDashboardLayout(changedWidgets);
}, 50);
$timeout(saveDashboardLayout, 50);
}
};

Expand Down
17 changes: 17 additions & 0 deletions client/app/pages/dashboards/dashboard.less
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,23 @@
}
}

.dashboard__control {
.save-status {
vertical-align: middle;
margin-right: 7px;
font-size: 12px;
position: relative;

&[data-dirty="true"] {
opacity: 0.6;

&::after {
content: "*";
}
}
}
}


// Mobile fixes
@media (max-width: 767px) {
Expand Down
6 changes: 6 additions & 0 deletions client/app/services/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ function WidgetFactory($http, $location, Query, Visualization, dashboardGridOpti
this.options.position.autoHeight = true;
}

this.updateOriginalPosition();
}

updateOriginalPosition() {
// Save original position (create a shallow copy)
this.$originalPosition = extend({}, this.options.position);
}
Expand Down Expand Up @@ -161,6 +165,8 @@ function WidgetFactory($http, $location, Query, Visualization, dashboardGridOpti
this[k] = v;
});

this.updateOriginalPosition();

return this;
});
}
Expand Down

0 comments on commit dd92550

Please sign in to comment.