Commit 2fa7100c by Torkel Ödegaard

ux(dashboard): lots of tweaks and polish to row menu, added remove X to add…

ux(dashboard): lots of tweaks and polish to row menu, added remove X to add panel and row option views, #6442
parent 616e9ce5
...@@ -68,6 +68,7 @@ ...@@ -68,6 +68,7 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"eventemitter3": "^1.2.0", "eventemitter3": "^1.2.0",
"gaze": "^1.1.2",
"grunt-jscs": "~1.5.x", "grunt-jscs": "~1.5.x",
"grunt-sass-lint": "^0.2.0", "grunt-sass-lint": "^0.2.0",
"grunt-sync": "^0.4.1", "grunt-sync": "^0.4.1",
......
...@@ -66,6 +66,7 @@ export class KeybindingSrv { ...@@ -66,6 +66,7 @@ export class KeybindingSrv {
Mousetrap.bind(keyArg, evt => { Mousetrap.bind(keyArg, evt => {
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
evt.returnValue = false;
return this.$rootScope.$apply(fn.bind(this)); return this.$rootScope.$apply(fn.bind(this));
}); });
} }
...@@ -80,31 +81,32 @@ export class KeybindingSrv { ...@@ -80,31 +81,32 @@ export class KeybindingSrv {
// dashboard.toggleEditMode(); // dashboard.toggleEditMode();
// }); // });
this.bind('ctrl+o', () => { this.bind('mod+o', () => {
dashboard.sharedCrosshair = !dashboard.sharedCrosshair; dashboard.sharedCrosshair = !dashboard.sharedCrosshair;
scope.broadcastRefresh(); scope.broadcastRefresh();
}); });
this.bind('ctrl+h', () => { this.bind('mod+h', () => {
dashboard.hideControls = !dashboard.hideControls; dashboard.hideControls = !dashboard.hideControls;
}); });
this.bind(['ctrl+s', 'command+s'], () => { this.bind('mod+s', e => {
scope.appEvent('save-dashboard'); scope.appEvent('save-dashboard');
}); });
this.bind('ctrl+z', () => {
this.bind('t z', () => {
scope.appEvent('zoom-out'); scope.appEvent('zoom-out');
}); });
this.bind('left', () => { this.bind('t left', () => {
scope.appEvent('shift-time-backward'); scope.appEvent('shift-time-backward');
}); });
this.bind('right', () => { this.bind('t right', () => {
scope.appEvent('shift-time-forward'); scope.appEvent('shift-time-forward');
}); });
this.bind('ctrl+i', () => { this.bind('mod+i', () => {
scope.appEvent('quick-snapshot'); scope.appEvent('quick-snapshot');
}); });
...@@ -133,7 +135,7 @@ export class KeybindingSrv { ...@@ -133,7 +135,7 @@ export class KeybindingSrv {
}); });
// delete panel // delete panel
this.bind('r', () => { this.bind('p r', () => {
if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) { if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) {
var panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId); var panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId);
panelInfo.row.removePanel(panelInfo.panel); panelInfo.row.removePanel(panelInfo.panel);
...@@ -141,8 +143,26 @@ export class KeybindingSrv { ...@@ -141,8 +143,26 @@ export class KeybindingSrv {
} }
}); });
// delete panel // delete row
this.bind('s', () => { this.bind('r r', () => {
if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) {
var panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId);
dashboard.removeRow(panelInfo.row);
dashboard.meta.focusPanelId = 0;
}
});
// collapse row
this.bind('r c', () => {
if (dashboard.meta.focusPanelId) {
var panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId);
panelInfo.row.toggleCollapse();
dashboard.meta.focusPanelId = 0;
}
});
// share panel
this.bind('p s', () => {
if (dashboard.meta.focusPanelId) { if (dashboard.meta.focusPanelId) {
var shareScope = scope.$new(); var shareScope = scope.$new();
var panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId); var panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId);
......
...@@ -23,20 +23,22 @@ export class UtilSrv { ...@@ -23,20 +23,22 @@ export class UtilSrv {
this.modalScope.dismiss(); this.modalScope.dismiss();
} }
if (options.model || !options.scope) {
options.scope = this.modalScope = this.$rootScope.$new();
options.scope.model = options.model;
}
this.modalScope = options.scope; this.modalScope = options.scope;
if (options.model) {
this.modalScope = this.$rootScope.$new();
this.modalScope.model = options.model;
} else if (!this.modalScope) {
this.modalScope = this.$rootScope.$new();
}
var modal = this.$modal({ var modal = this.$modal({
modalClass: options.modalClass, modalClass: options.modalClass,
template: options.src, template: options.src,
templateHtml: options.templateHtml, templateHtml: options.templateHtml,
persist: false, persist: false,
show: false, show: false,
scope: options.scope, scope: this.modalScope,
keyboard: false, keyboard: false,
backdrop: options.backdrop backdrop: options.backdrop
}); });
......
<div class="dash-row-add-panel"> <div class="dash-row-dropview">
<a class="dash-row-dropview-close pointer" ng-click="ctrl.rowCtrl.closeDropView();">
<i class="fa fa-remove"></i>
</a>
<div class="gf-form-inline dash-row-add-panel-form"> <div class="gf-form-inline dash-row-add-panel-form">
<div class="gf-form"> <div class="gf-form">
...@@ -12,7 +15,7 @@ ...@@ -12,7 +15,7 @@
ng-repeat="panel in ctrl.panelHits" ng-repeat="panel in ctrl.panelHits"
ng-class="{active: $index === ctrl.activeIndex}" ng-class="{active: $index === ctrl.activeIndex}"
ng-click="ctrl.addPanel(panel)" ng-click="ctrl.addPanel(panel)"
ui-draggable="ctrl.dashboard.editMode" ui-draggable="true"
drag="panel.id" drag="panel.id"
title="{{panel.name}}"> title="{{panel.name}}">
<img class="add-panel-item-img" ng-src="{{panel.info.logos.small}}"></img> <img class="add-panel-item-img" ng-src="{{panel.info.logos.small}}"></img>
......
<div class="dash-row-options"> <div class="dash-row-dropview">
<div class="gf-form section"> <a class="dash-row-dropview-close pointer" ng-click="ctrl.rowCtrl.closeDropView();">
<div class="gf-form-inline"> <i class="fa fa-remove"></i>
</a>
<div>
<div class="section">
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-6">Row Title</span> <span class="gf-form-label width-6">Title</span>
<input type="text" class="gf-form-input max-width-14" ng-model='ctrl.row.title'></input> <input type="text" class="gf-form-input max-width-14" ng-model='ctrl.row.title'></input>
</div> </div>
<div class="gf-form"> <div class="gf-form-inline">
<label class="gf-form-label width-6">Size</label> <div class="gf-form">
<div class="gf-form-select-wrapper"> <label class="gf-form-label width-6">Size</label>
<select class="input-small gf-form-input" ng-model="ctrl.row.titleSize" ng-options="f for f in ctrl.fontSizes"></select> <div class="gf-form-select-wrapper">
<select class="input-small gf-form-input" ng-model="ctrl.row.titleSize" ng-options="f for f in ctrl.fontSizes"></select>
</div>
</div> </div>
<gf-form-switch class="gf-form" label="Show" checked="ctrl.row.showTitle">
</gf-form-switch>
</div> </div>
<gf-form-switch class="gf-form" label="Show" checked="ctrl.row.showTitle">
</gf-form-switch>
</div> </div>
<div class="gf-form-inline"> <div class="section">
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-6">Height</span> <span class="gf-form-label width-7">Height</span>
<input type="text" class="gf-form-input max-width-14" ng-model='ctrl.row.height'></input> <input type="text" class="gf-form-input max-width-14" ng-model='ctrl.row.height'></input>
</div> </div>
<div class="gf-form">
<span class="gf-form-label width-7">Repeat for</span>
<dash-repeat-option model="ctrl.row"></dash-repeat-option>
</div>
</div> </div>
</div> </div>
<div class="gf-form section">
<div class="gf-form">
<span class="gf-form-label">Repeat Row</span>
<dash-repeat-option model="ctrl.row"></dash-repeat-option>
</div>
</div>
<div class="clearfix"></div>
</div> </div>
<div class="dash-row-menu-container" data-click-hide>
<ul class="dash-row-menu" role="menu">
<li>
<a ng-click="ctrl.onMenuAddPanel()">
<i class="fa fa-plus"></i> Add Panel
</a>
</li>
<li>
<a ng-click="ctrl.onMenuRowOptions()">
<i class="fa fa-cog"></i> Row Options
</a>
</li>
<li>
<a ng-click="ctrl.onMenuDeleteRow()">
<i class="fa fa-arrow-up"></i> Move Up
</a>
</li>
<li>
<a ng-click="ctrl.onMenuDeleteRow()">
<i class="fa fa-arrow-down"></i> Move Down
</a>
</li>
<li>
<a ng-click="ctrl.onMenuDeleteRow()">
<i class="fa fa-trash"></i> Remove row
</a>
</li>
</ul>
<div class="dash-row-menu-grip">
<i class="fa fa-ellipsis-v"></i>
</div>
</div>
<div class="dash-row-header" ng-if="ctrl.row.showTitle || ctrl.row.collapse"> <div class="dash-row-header" ng-if="ctrl.row.showTitle || ctrl.row.collapse">
<a class="dash-row-header-title" ng-click="ctrl.toggleCollapse()"> <a class="dash-row-header-title" ng-click="ctrl.toggleCollapse()">
<span class="dash-row-collapse-toggle pointer"> <span class="dash-row-collapse-toggle pointer">
...@@ -50,19 +17,57 @@ ...@@ -50,19 +17,57 @@
</div> </div>
<div class="panels-wrapper" ng-if="!ctrl.row.collapse"> <div class="panels-wrapper" ng-if="!ctrl.row.collapse">
<div ng-repeat="panel in ctrl.row.panels track by panel.id" class="panel" ui-draggable="!ctrl.dashboard.meta.fullscreen" drag="panel.id" ui-on-drop="ctrl.onDrop($data, panel)" drag-handle-class="drag-handle" panel-width> <div class="dash-row-menu-container" data-click-hide>
<plugin-component type="panel" class="panel-margin"> <ul class="dash-row-menu" role="menu">
</plugin-component> <li>
</div> <a ng-click="ctrl.toggleCollapse()">
<i class="fa fa-minus"></i> Collapse
</a>
</li>
<li ng-show="ctrl.dashboard.meta.canEdit">
<a ng-click="ctrl.onMenuAddPanel()">
<i class="fa fa-plus"></i> Add Panel
</a>
</li>
<li ng-show="ctrl.dashboard.meta.canEdit">
<a ng-click="ctrl.onMenuRowOptions()">
<i class="fa fa-cog"></i> Row Options
</a>
</li>
<li ng-show="ctrl.dashboard.meta.canEdit">
<a ng-click="ctrl.moveRow(-1)">
<i class="fa fa-arrow-up"></i> Move Up
</a>
</li>
<li ng-show="ctrl.dashboard.meta.canEdit">
<a ng-click="ctrl.moveRow(1)">
<i class="fa fa-arrow-down"></i> Move Down
</a>
</li>
<li ng-show="ctrl.dashboard.meta.canEdit">
<a ng-click="ctrl.onMenuDeleteRow()">
<i class="fa fa-trash"></i> Remove
</a>
</li>
</ul>
<div class="dash-row-menu-grip">
<i class="fa fa-ellipsis-v"></i>
</div>
</div>
<div panel-drop-zone class="panel panel-drop-zone" ui-on-drop="ctrl.onDrop($data)" data-drop="true"> <div ng-repeat="panel in ctrl.row.panels track by panel.id" class="panel" ui-draggable="!ctrl.dashboard.meta.fullscreen" drag="panel.id" ui-on-drop="ctrl.onDrop($data, panel)" drag-handle-class="drag-handle" panel-width>
<div class="panel-margin"> <plugin-component type="panel" class="panel-margin">
<div class="panel-container"> </plugin-component>
<div class="panel-drop-zone-text"></div> </div>
</div>
</div> <div panel-drop-zone class="panel panel-drop-zone" ui-on-drop="ctrl.onDrop($data)" data-drop="true">
</div> <div class="panel-margin">
<div class="panel-container">
<div class="panel-drop-zone-text"></div>
</div>
</div>
</div>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
...@@ -127,6 +127,7 @@ coreModule.directive('dashRow', function($rootScope) { ...@@ -127,6 +127,7 @@ coreModule.directive('dashRow', function($rootScope) {
}, },
link: function(scope, element) { link: function(scope, element) {
scope.$watchGroup(['ctrl.row.collapse', 'ctrl.row.height'], function() { scope.$watchGroup(['ctrl.row.collapse', 'ctrl.row.height'], function() {
element.toggleClass('dash-row--collapse', scope.ctrl.row.collapse);
element.find('.panels-wrapper').css({minHeight: scope.ctrl.row.collapse ? '5px' : scope.ctrl.row.height}); element.find('.panels-wrapper').css({minHeight: scope.ctrl.row.collapse ? '5px' : scope.ctrl.row.height});
}); });
......
...@@ -11,6 +11,7 @@ export class DashboardRow { ...@@ -11,6 +11,7 @@ export class DashboardRow {
events: Emitter; events: Emitter;
span: number; span: number;
height: number; height: number;
collapse: boolean;
defaults = { defaults = {
title: 'Dashboard Row', title: 'Dashboard Row',
...@@ -22,6 +23,7 @@ export class DashboardRow { ...@@ -22,6 +23,7 @@ export class DashboardRow {
repeat: null, repeat: null,
repeatRowId: null, repeatRowId: null,
repeatIteration: null, repeatIteration: null,
collapse: false,
}; };
constructor(private model) { constructor(private model) {
...@@ -79,7 +81,6 @@ export class DashboardRow { ...@@ -79,7 +81,6 @@ export class DashboardRow {
} }
removePanel(panel, ask?) { removePanel(panel, ask?) {
console.log('remove panel');
if (ask !== false) { if (ask !== false) {
appEvents.emit('confirm-modal', { appEvents.emit('confirm-modal', {
title: 'Remove Panel', title: 'Remove Panel',
...@@ -113,5 +114,9 @@ export class DashboardRow { ...@@ -113,5 +114,9 @@ export class DashboardRow {
this.showTitle = source.showTitle; this.showTitle = source.showTitle;
this.titleSize = source.titleSize; this.titleSize = source.titleSize;
} }
toggleCollapse() {
this.collapse = !this.collapse;
}
} }
...@@ -32,6 +32,10 @@ ...@@ -32,6 +32,10 @@
.page-container { .page-container {
padding: ($spacer * 1) ($spacer * 2); padding: ($spacer * 1) ($spacer * 2);
} }
.dash-row-menu-container {
display: none;
}
} }
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
......
...@@ -4,6 +4,14 @@ ...@@ -4,6 +4,14 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
&--collapse {
.dash-row-header {
background: $panel-bg;
border: $panel-border;
margin-bottom: $panel-margin*2;
}
}
} }
.dash-row-header { .dash-row-header {
...@@ -46,19 +54,21 @@ ...@@ -46,19 +54,21 @@
position: relative; position: relative;
} }
.dash-row-options { .dash-row-dropview {
position: relative;
background: $panel-bg; background: $panel-bg;
border: 1px solid $dash-row-border-color; border: 1px solid $dash-row-border-color;
margin: 0 $panel-margin $panel-margin*2 $panel-margin; margin: 0 $panel-margin $panel-margin*2 $panel-margin;
padding: $panel-margin*2; padding: $panel-margin*2;
display: flex;
} }
.dash-row-add-panel { .dash-row-dropview-close {
background: $panel-bg; position: absolute;
border: 1px solid $dash-row-border-color; right: -15px;
margin: 0 $panel-margin $panel-margin*2 $panel-margin; top: -12px;
padding: $panel-margin*2; width: 20px;
display: flex; height: 20px;
} }
.add-panel-panels-scroll { .add-panel-panels-scroll {
...@@ -80,7 +90,7 @@ ...@@ -80,7 +90,7 @@
.add-panel-item { .add-panel-item {
background: $input-label-bg; background: $input-label-bg;
border: $panel-border; border: $panel-border;
padding: $spacer; padding: $spacer/3 $spacer;
min-width: 9rem; min-width: 9rem;
max-width: 9rem; max-width: 9rem;
text-align: center; text-align: center;
...@@ -89,7 +99,7 @@ ...@@ -89,7 +99,7 @@
&.active, &.active,
&:hover { &:hover {
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 5px rgba(82,168,236,10.8) box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 1px rgba(82,168,236,5.8)
} }
} }
...@@ -109,6 +119,8 @@ $dash-row-menu-animation-speed: 0.20s; ...@@ -109,6 +119,8 @@ $dash-row-menu-animation-speed: 0.20s;
.dash-row-menu-container { .dash-row-menu-container {
position: absolute; position: absolute;
top: 0px; top: 0px;
width: 138px;
height: 100%;
transform: translate(-131px, 0); transform: translate(-131px, 0);
transition: 0.1s ease-out 0.4s; transition: 0.1s ease-out 0.4s;
z-index: 100; z-index: 100;
...@@ -128,8 +140,6 @@ $dash-row-menu-animation-speed: 0.20s; ...@@ -128,8 +140,6 @@ $dash-row-menu-animation-speed: 0.20s;
} }
.dash-row-menu { .dash-row-menu {
border-top: $panel-border;
border-bottom: $panel-border;
list-style: none; list-style: none;
flex-grow: 1; flex-grow: 1;
box-shadow: $search-shadow; box-shadow: $search-shadow;
......
...@@ -179,7 +179,7 @@ ...@@ -179,7 +179,7 @@
top: 38%; top: 38%;
right: 6px; right: 6px;
font-size: 14px; font-size: 14px;
color: $text-color-weak; color: $text-color-faint;
} }
.sidemenu-org-avatar, .sidemenu-org-avatar,
......
...@@ -35,6 +35,9 @@ ...@@ -35,6 +35,9 @@
.dash-row-menu { .dash-row-menu {
display: none; display: none;
} }
.panel-drop-zone {
visibility: hidden;
}
} }
div.flot-text { div.flot-text {
......
module.exports = function(config, grunt) { module.exports = function(config, grunt) {
'use strict'; 'use strict';
grunt.event.on('watch', function(action, filepath) { var gaze = require('gaze');
var newPath; var path = require('path');
var firstRun = true;
var done;
var lastTime;
grunt.log.writeln('File Changed: ' + filepath); grunt.registerTask('watch', function() {
if (/(\.html)|(\.json)$/.test(filepath)) { done = this.async();
newPath = filepath.replace(/^public/, 'public_gen'); lastTime = new Date().getTime();
grunt.log.writeln('Copying to ' + newPath);
grunt.file.copy(filepath, newPath); if (firstRun === false) {
grunt.log.writeln('Watch resuming');
return;
} }
if (/(\.js)$/.test(filepath)) { gaze(config.srcDir + '/**/*', function(err, watcher) {
newPath = filepath.replace(/^public/, 'public_gen');
grunt.log.writeln('Copying to ' + newPath);
grunt.file.copy(filepath, newPath);
grunt.task.run('jshint'); console.log('Gaze watchers setup');
grunt.task.run('jscs');
}
if (/(\.scss)$/.test(filepath)) { watcher.on('all', function(evtName, filepath) {
grunt.task.run('clean:css'); filepath = path.relative(process.cwd(), filepath);
grunt.task.run('css');
}
if (/(\.ts)$/.test(filepath)) { // ignore multiple changes at once
newPath = filepath.replace(/^public/, 'public_gen'); var now = new Date().getTime();
grunt.log.writeln('Copying to ' + newPath); if (now - lastTime < 100) {
grunt.file.copy(filepath, newPath); return;
}
lastTime = now;
// copy ts file also used by source maps var newPath;
//changes changed file source to that of the changed file grunt.log.writeln('File Changed: ', filepath);
grunt.config('typescript.build.src', filepath);
grunt.config('tslint.source.files.src', filepath);
grunt.task.run('typescript:build'); if (/(\.html)|(\.json)$/.test(filepath)) {
grunt.task.run('tslint'); newPath = filepath.replace(/^public/, 'public_gen');
} grunt.log.writeln('Copying to ' + newPath);
}); grunt.file.copy(filepath, newPath);
}
if (/(\.js)$/.test(filepath)) {
newPath = filepath.replace(/^public/, 'public_gen');
grunt.log.writeln('Copying to ' + newPath);
grunt.file.copy(filepath, newPath);
return { grunt.task.run('jshint');
copy_to_gen: { grunt.task.run('jscs');
files: ['<%= srcDir %>/**/*'], }
tasks: [],
options: { if (/(\.scss)$/.test(filepath)) {
spawn: false grunt.task.run('clean:css');
} grunt.task.run('css');
}, }
};
if (/(\.ts)$/.test(filepath)) {
newPath = filepath.replace(/^public/, 'public_gen');
grunt.log.writeln('Copying to ' + newPath);
grunt.file.copy(filepath, newPath);
// copy ts file also used by source maps
//changes changed file source to that of the changed file
grunt.config('typescript.build.src', filepath);
grunt.config('tslint.source.files.src', filepath);
grunt.task.run('typescript:build');
grunt.task.run('tslint');
}
done();
firstRun = false;
grunt.task.run('watch');
});
});
});
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment