+
diff --git a/src/plugins/kibana/public/visualize/editor/agg_group.js b/src/plugins/kibana/public/visualize/editor/agg_group.js
index 1718d80c1b580..6bc2341d7df1d 100644
--- a/src/plugins/kibana/public/visualize/editor/agg_group.js
+++ b/src/plugins/kibana/public/visualize/editor/agg_group.js
@@ -41,6 +41,9 @@ uiModules
if (count < schema.max) return true;
});
});
+
+ $scope.$on('agg-drag-start', e => $scope.dragging = true);
+ $scope.$on('agg-drag-end', e => $scope.dragging = false);
}
};
diff --git a/src/plugins/kibana/public/visualize/editor/draggable_container.js b/src/plugins/kibana/public/visualize/editor/draggable_container.js
new file mode 100644
index 0000000000000..c4d7ba76ea2b8
--- /dev/null
+++ b/src/plugins/kibana/public/visualize/editor/draggable_container.js
@@ -0,0 +1,88 @@
+import _ from 'lodash';
+import $ from 'jquery';
+import dragula from 'dragula';
+import uiModules from 'ui/modules';
+
+uiModules
+.get('app/visualize')
+.directive('draggableContainer', function () {
+
+ return {
+ restrict: 'A',
+ scope: true,
+ controllerAs: 'draggableContainerCtrl',
+ controller($scope, $attrs, $parse) {
+ this.getList = () => $parse($attrs.draggableContainer)($scope);
+ },
+ link($scope, $el, attr) {
+ const drake = dragula({
+ containers: $el.toArray(),
+ moves(el, source, handle) {
+ const itemScope = $(el).scope();
+ if (!('draggableItemCtrl' in itemScope)) {
+ return; // only [draggable-item] is draggable
+ }
+ return itemScope.draggableItemCtrl.moves(handle);
+ }
+ });
+
+ const drakeEvents = [
+ 'cancel',
+ 'cloned',
+ 'drag',
+ 'dragend',
+ 'drop',
+ 'out',
+ 'over',
+ 'remove',
+ 'shadow'
+ ];
+ const prettifiedDrakeEvents = {
+ drag: 'start',
+ dragend: 'end'
+ };
+
+ drakeEvents.forEach(type => {
+ drake.on(type, (el, ...args) => forwardEvent(type, el, ...args));
+ });
+ drake.on('drag', markDragging(true));
+ drake.on('dragend', markDragging(false));
+ drake.on('drop', drop);
+ $scope.$on('$destroy', drake.destroy);
+ $scope.drake = drake;
+
+ function markDragging(isDragging) {
+ return el => {
+ const scope = $(el).scope();
+ scope.isDragging = isDragging;
+ scope.$apply();
+ };
+ }
+
+ function forwardEvent(type, el, ...args) {
+ const name = `drag-${prettifiedDrakeEvents[type] || type}`;
+ const scope = $(el).scope();
+ scope.$broadcast(name, el, ...args);
+ }
+
+ function drop(el, target, source, sibling) {
+ const list = $scope.draggableContainerCtrl.getList();
+ const itemScope = $(el).scope();
+ const item = itemScope.draggableItemCtrl.getItem();
+ const toIndex = getSiblingItemIndex(list, sibling);
+ _.move(list, item, toIndex);
+ }
+
+ function getSiblingItemIndex(list, sibling) {
+ if (!sibling) { // means the item was dropped at the end of the list
+ return list.length - 1;
+ }
+ const siblingScope = $(sibling).scope();
+ const siblingItem = siblingScope.draggableItemCtrl.getItem();
+ const siblingIndex = list.indexOf(siblingItem);
+ return siblingIndex;
+ }
+ }
+ };
+
+});
diff --git a/src/plugins/kibana/public/visualize/editor/draggable_handle.js b/src/plugins/kibana/public/visualize/editor/draggable_handle.js
new file mode 100644
index 0000000000000..7e62160b8e91a
--- /dev/null
+++ b/src/plugins/kibana/public/visualize/editor/draggable_handle.js
@@ -0,0 +1,14 @@
+import uiModules from 'ui/modules';
+
+uiModules
+.get('app/visualize')
+.directive('draggableHandle', function () {
+ return {
+ restrict: 'A',
+ require: '^draggableItem',
+ link($scope, $el, attr, ctrl) {
+ ctrl.registerHandle($el);
+ $el.addClass('gu-handle');
+ }
+ };
+});
diff --git a/src/plugins/kibana/public/visualize/editor/draggable_item.js b/src/plugins/kibana/public/visualize/editor/draggable_item.js
new file mode 100644
index 0000000000000..e949caf5a89fd
--- /dev/null
+++ b/src/plugins/kibana/public/visualize/editor/draggable_item.js
@@ -0,0 +1,29 @@
+import $ from 'jquery';
+import uiModules from 'ui/modules';
+
+uiModules
+.get('app/visualize')
+.directive('draggableItem', function () {
+ return {
+ restrict: 'A',
+ require: '^draggableContainer',
+ scope: true,
+ controllerAs: 'draggableItemCtrl',
+ controller($scope, $attrs, $parse) {
+ const dragHandles = $();
+
+ this.getItem = () => $parse($attrs.draggableItem)($scope);
+ this.registerHandle = $el => {
+ dragHandles.push(...$el);
+ };
+ this.moves = handle => {
+ const $handle = $(handle);
+ const $anywhereInParentChain = $handle.parents().addBack();
+ const movable = dragHandles.is($anywhereInParentChain);
+ return movable;
+ };
+ },
+ link($scope, $el, attr) {
+ }
+ };
+});
diff --git a/src/plugins/kibana/public/visualize/editor/editor.js b/src/plugins/kibana/public/visualize/editor/editor.js
index 2b9341f86a886..5543ecd6dd647 100644
--- a/src/plugins/kibana/public/visualize/editor/editor.js
+++ b/src/plugins/kibana/public/visualize/editor/editor.js
@@ -119,8 +119,8 @@ uiModules
if (!angular.equals($state.vis, savedVisState)) {
Promise.try(function () {
- vis.setState($state.vis);
editableVis.setState($state.vis);
+ vis.setState(editableVis.getEnabledState());
})
.catch(courier.redirectWhenMissing({
'index-pattern-field': '/visualize'
@@ -149,9 +149,9 @@ uiModules
$scope.stageEditableVis = transferVisState(editableVis, vis, true);
$scope.resetEditableVis = transferVisState(vis, editableVis);
$scope.$watch(function () {
- return editableVis.getState();
+ return editableVis.getEnabledState();
}, function (newState) {
- editableVis.dirty = !angular.equals(newState, vis.getState());
+ editableVis.dirty = !angular.equals(newState, vis.getEnabledState());
$scope.responseValueAggs = null;
try {
@@ -289,14 +289,16 @@ uiModules
}
};
- function transferVisState(fromVis, toVis, fetch) {
+ function transferVisState(fromVis, toVis, stage) {
return function () {
- toVis.setState(fromVis.getState());
+ const view = fromVis.getEnabledState();
+ const full = fromVis.getState();
+ toVis.setState(view);
editableVis.dirty = false;
- $state.vis = vis.getState();
+ $state.vis = full;
$state.save();
- if (fetch) $scope.fetch();
+ if (stage) $scope.fetch();
};
}
diff --git a/src/plugins/kibana/public/visualize/index.js b/src/plugins/kibana/public/visualize/index.js
index 185abd4310e99..88f684f59ccb9 100644
--- a/src/plugins/kibana/public/visualize/index.js
+++ b/src/plugins/kibana/public/visualize/index.js
@@ -11,6 +11,9 @@ import 'plugins/kibana/visualize/editor/agg_params';
import 'plugins/kibana/visualize/editor/nesting_indicator';
import 'plugins/kibana/visualize/editor/sidebar';
import 'plugins/kibana/visualize/editor/vis_options';
+import 'plugins/kibana/visualize/editor/draggable_container';
+import 'plugins/kibana/visualize/editor/draggable_item';
+import 'plugins/kibana/visualize/editor/draggable_handle';
import 'plugins/kibana/visualize/saved_visualizations/_saved_vis';
import 'plugins/kibana/visualize/saved_visualizations/saved_visualizations';
import uiRoutes from 'ui/routes';
diff --git a/src/ui/public/dragula/gu-dragula.less b/src/ui/public/dragula/gu-dragula.less
new file mode 100644
index 0000000000000..c51c873601806
--- /dev/null
+++ b/src/ui/public/dragula/gu-dragula.less
@@ -0,0 +1,13 @@
+.gu-handle {
+ cursor: move;
+ cursor: grab;
+ cursor: -moz-grab;
+ cursor: -webkit-grab;
+}
+
+.gu-mirror,
+.gu-mirror .gu-handle {
+ cursor: grabbing;
+ cursor: -moz-grabbing;
+ cursor: -webkit-grabbing;
+}
diff --git a/src/ui/public/styles/base.less b/src/ui/public/styles/base.less
index e9d47e40671c8..bc5d2eb534cc4 100644
--- a/src/ui/public/styles/base.less
+++ b/src/ui/public/styles/base.less
@@ -607,3 +607,5 @@ fieldset {
}
}
}
+
+@import (reference) "~dragula/dist/dragula.css";
diff --git a/src/ui/public/vis/__tests__/_vis.js b/src/ui/public/vis/__tests__/_vis.js
index d750bc5226fbf..ad4aa0ad837e5 100644
--- a/src/ui/public/vis/__tests__/_vis.js
+++ b/src/ui/public/vis/__tests__/_vis.js
@@ -58,7 +58,7 @@ describe('Vis Class', function () {
describe('getState()', function () {
it('should get a state that represents the... er... state', function () {
- let state = vis.getState();
+ let state = vis.getEnabledState();
expect(state).to.have.property('type', 'pie');
expect(state).to.have.property('params');
diff --git a/src/ui/public/vis/agg_config.js b/src/ui/public/vis/agg_config.js
index ab6c649bce58b..c1250db719b98 100644
--- a/src/ui/public/vis/agg_config.js
+++ b/src/ui/public/vis/agg_config.js
@@ -9,6 +9,7 @@ export default function AggConfigFactory(Private, fieldTypeFilter) {
self.id = String(opts.id || AggConfig.nextId(vis.aggs));
self.vis = vis;
self._opts = opts = (opts || {});
+ self.enabled = typeof opts.enabled === 'boolean' ? opts.enabled : true;
// setters
self.type = opts.type;
@@ -232,6 +233,7 @@ export default function AggConfigFactory(Private, fieldTypeFilter) {
return {
id: self.id,
+ enabled: self.enabled,
type: self.type && self.type.name,
schema: self.schema && self.schema.name,
params: outParams
diff --git a/src/ui/public/vis/vis.js b/src/ui/public/vis/vis.js
index dbf29fe416429..6525b0c35b3fd 100644
--- a/src/ui/public/vis/vis.js
+++ b/src/ui/public/vis/vis.js
@@ -44,7 +44,7 @@ export default function VisFactory(Notifier, Private) {
oldConfigs.forEach(function (oldConfig) {
let agg = {
schema: schema.name,
- type: oldConfig.agg,
+ type: oldConfig.agg
};
let aggType = aggTypes.byName[agg.type];
@@ -81,18 +81,27 @@ export default function VisFactory(Notifier, Private) {
this.aggs = new AggConfigs(this, state.aggs);
};
- Vis.prototype.getState = function () {
+ Vis.prototype.getStateInternal = function (includeDisabled) {
return {
title: this.title,
type: this.type.name,
params: this.params,
- aggs: this.aggs.map(function (agg) {
- return agg.toJSON();
- }).filter(Boolean),
+ aggs: this.aggs
+ .filter(agg => includeDisabled || agg.enabled)
+ .map(agg => agg.toJSON())
+ .filter(Boolean),
listeners: this.listeners
};
};
+ Vis.prototype.getEnabledState = function () {
+ return this.getStateInternal(false);
+ };
+
+ Vis.prototype.getState = function () {
+ return this.getStateInternal(true);
+ };
+
Vis.prototype.createEditableVis = function () {
return this._editableVis || (this._editableVis = this.clone());
};
diff --git a/src/ui/public/visualize/visualize.js b/src/ui/public/visualize/visualize.js
index 86c9a5beae6be..45aed2c04c57e 100644
--- a/src/ui/public/visualize/visualize.js
+++ b/src/ui/public/visualize/visualize.js
@@ -29,8 +29,7 @@ uiModules
},
template: visualizeTemplate,
link: function ($scope, $el, attr) {
- let chart; // set in "vis" watcher
- let minVisChartHeight = 180;
+ const minVisChartHeight = 180;
if (_.isUndefined($scope.showSpyPanel)) {
$scope.showSpyPanel = true;