diff --git a/app/components/draggable-dropzone.js b/app/components/draggable-dropzone.js index 3eacd71..e708e90 100644 --- a/app/components/draggable-dropzone.js +++ b/app/components/draggable-dropzone.js @@ -1,13 +1,16 @@ import Ember from 'ember'; +import Sortable from '../mixins/sortable'; var { set } = Ember; -export default Ember.Component.extend({ +export default Ember.Component.extend(Sortable, { tagName: 'div', classNames: [ 'draggableDropzone plots' ], classNameBindings: [ 'dragClass' ], dragClass: 'deactivated', + placeholder_sort: 'plot-placeholder', + dragLeave(event) { event.preventDefault(); set(this, 'dragClass', 'deactivated'); diff --git a/app/components/plot-abstract.js b/app/components/plot-abstract.js new file mode 100644 index 0000000..779c646 --- /dev/null +++ b/app/components/plot-abstract.js @@ -0,0 +1,33 @@ +import Ember from 'ember'; +import Resizable from '../mixins/resizable'; + +export default Ember.Component.extend(Resizable, { + attributeBindings: [ 'style' ], + + plot: null, + + disabled_resize: false, + autoHide_resize: false, + + style: function() { + return 'width: ' + this.get('plot.width') + 'px; height: ' + this.get('plot.height') + 'px;'; + }.property('plot'), + + stop_resize(event, ui) { + var width = ui.size.width; + var height = ui.size.height; + + this.set('plot.width', width); + this.set('plot.height', height); + }, + + _updateUI: function() { + if (this.get('editing') === true) { + this.set('disabled_resize', false); + this.set('autoHide_resize', false); + } else { + this.set('disabled_resize', true); + this.set('autoHide_resize', true); + } + }.observes('editing').on('init') +}); diff --git a/app/components/plot-container.js b/app/components/plot-container.js index a9c99bb..da80664 100644 --- a/app/components/plot-container.js +++ b/app/components/plot-container.js @@ -1,6 +1,9 @@ import Ember from 'ember'; export default Ember.Component.extend({ + classNames: [ 'plot' ], + editing: false, + isTable: function() { var type = this.get('plot.type'); return type === 'table'; diff --git a/app/components/plot-table.js b/app/components/plot-table.js index d8a74a2..584fd9c 100644 --- a/app/components/plot-table.js +++ b/app/components/plot-table.js @@ -1,20 +1,10 @@ import Ember from 'ember'; -import Resizable from '../mixins/resizable'; +import PlotAbstract from './plot-abstract'; -export default Ember.Component.extend(Resizable, { +export default PlotAbstract.extend({ tagName: 'div', - attributeBindings: [ 'style' ], classNames: [ 'plotContainer', 'plotTable' ], - plot: null, - editing: false, - - style: function() { - return 'width: ' + this.get('plot.width') + 'px; height: ' + this.get('plot.height') + 'px;'; - }.property('plot'), - - stop_resize(event, ui) { - this.set('plot.width', this.$().width()); - this.set('plot.height', this.$().height()); - } + minWidth_resize: 200, + minHeight_resize: 60 }); diff --git a/app/components/plot-value.js b/app/components/plot-value.js index bb27fb4..5992e34 100644 --- a/app/components/plot-value.js +++ b/app/components/plot-value.js @@ -1,23 +1,10 @@ import Ember from 'ember'; -import Resizable from '../mixins/resizable'; +import PlotAbstract from './plot-abstract'; -export default Ember.Component.extend(Resizable, { +export default PlotAbstract.extend({ tagName: 'div', - attributeBindings: [ 'style' ], classNames: [ 'plotContainer', 'plotValue' ], - plot: null, - editing: false, - minWidth_resize: 50, - minHeight_resize: 20, - - style: function() { - return 'width: ' + this.get('plot.width') + 'px; height: ' + this.get('plot.height') + 'px;'; - }.property('plot'), - - stop_resize(event, ui) { - this.set('plot.width', this.$().width()); - this.set('plot.height', this.$().height()); - } + minHeight_resize: 20 }); diff --git a/app/controllers/visualization/edit.js b/app/controllers/visualization/edit.js index 7ca0e6f..38e9f0c 100644 --- a/app/controllers/visualization/edit.js +++ b/app/controllers/visualization/edit.js @@ -33,6 +33,25 @@ export default Ember.Controller.extend({ } else { console.error('Unknown plot type: ' + name); } + }, + + saveEdit() { + // save changes to store + var plots = this.get('model.plots'); + plots.forEach(function(plot) { + plot.save(); + }); + + // go back to index + var id = this.get('model.id'); + this.transitionToRoute('/visualization/' + id); + }, + + cancelEdit() { + // TODO: revert changes + + let id = this.get('model.id'); + this.transitionToRoute('/visualization/' + id); } } }); diff --git a/app/mixins/draggable.js b/app/mixins/draggable.js index 0fb10eb..1e89d90 100644 --- a/app/mixins/draggable.js +++ b/app/mixins/draggable.js @@ -61,13 +61,15 @@ export default Ember.Mixin.create({ _gatherDragEvents(options) { // register callbacks for each event var uiDragEvents = this.get('uiDragEvents') || []; + var _this = this; + uiDragEvents.forEach(function(event) { - var callback = this[event]; + var callback = _this[event]; if (callback) { options[event.split('_')[0]] = function(event, ui) { - callback.call(this, event, ui); + callback.call(_this, event, ui); }; } - }, this); + }); } }); diff --git a/app/mixins/droppable.js b/app/mixins/droppable.js index e3c2b7f..eb1c57a 100644 --- a/app/mixins/droppable.js +++ b/app/mixins/droppable.js @@ -60,13 +60,15 @@ export default Ember.Mixin.create({ _gatherDropEvents(options) { // register callbacks for each event var uiDropEvents = this.get('uiDropEvents') || []; + var _this = this; + uiDropEvents.forEach(function(event) { - var callback = this[event]; + var callback = _this[event]; if (callback) { options[event.split('_')[0]] = function(event, ui) { - callback.call(this, event, ui); + callback.call(_this, event, ui); }; } - }, this); + }); } }); diff --git a/app/mixins/resizable.js b/app/mixins/resizable.js index 83fa97e..7a102d1 100644 --- a/app/mixins/resizable.js +++ b/app/mixins/resizable.js @@ -1,9 +1,9 @@ import Ember from 'ember'; export default Ember.Mixin.create({ - uiResizeOptions: [ 'disable_resize', 'alsoResize_resize', 'animate_resize', + uiResizeOptions: [ 'disabled_resize', 'alsoResize_resize', 'animate_resize', 'animateDuration_resize', 'animateEasing_resize', 'aspectRatio_resize', - 'autoHide_resize', 'cancel_resize', 'containment_resize', 'delay_resize', + 'autoHide_resize', 'cancel_resize', 'containment_resize', 'delay_resize', 'distance_resize', 'ghost_resize', 'grid_resize', 'handles_resize', 'helper_resize', 'maxHeight_resize', 'maxWidth_resize', 'minHeight_resize', 'minWidth_resize' ], uiResizeEvents: [ 'create_resize', 'start_resize', 'resize_resize', 'stop_resize' ], @@ -11,12 +11,12 @@ export default Ember.Mixin.create({ didInsertElement() { this._super(); + // get available options and events var options = this._gatherResizeOptions(); - this._gatherResizeEvents(options); + // create a new jQuery UI widget var ui = Ember.$.ui['resizable'](options, this.get('element')); - this.set('ui', ui); }, @@ -24,6 +24,7 @@ export default Ember.Mixin.create({ var ui = this.get('ui'); if (ui) { + // remove all observers for jQuery UI widget var observers = this._observers; for (var prop in observers) { if (observers.hasOwnProperty(prop)) { @@ -31,16 +32,20 @@ export default Ember.Mixin.create({ } } - ui._destroy(); + ui.destroy(); } }, _gatherResizeOptions() { - var uiResizeOptions = this.get('uiResizeOptions'), options = {}; + // parse all options and add observers for them + var uiResizeOptions = this.get('uiResizeOptions') || []; + var options = {}; uiResizeOptions.forEach(function(key) { + // save the resize option without the postfix options[key.split('_')[0]] = this.get(key); + // create an observer for this option var observer = function() { var value = this.get(key); console.log(key + ': ' + value); @@ -49,6 +54,7 @@ export default Ember.Mixin.create({ this.addObserver(key, observer); + // save observer to remove it later on this._observers = this._observers || {}; this._observers[key] = observer; }, this); @@ -57,13 +63,15 @@ export default Ember.Mixin.create({ }, _gatherResizeEvents(options) { - var uiResizeEvents = this.get('uiResizeEvents') || [], self = this; - uiResizeEvents.forEach(function(event) { - var callback = self[event]; + // register callbacks for each event + var uiResizeEvents = this.get('uiResizeEvents') || []; + var _this = this; + uiResizeEvents.forEach(function(event) { + var callback = _this[event]; if (callback) { options[event.split('_')[0]] = function(event, ui) { - callback.call(self, event, ui); + callback.call(_this, event, ui); }; } }); diff --git a/app/mixins/sortable.js b/app/mixins/sortable.js new file mode 100644 index 0000000..603dfde --- /dev/null +++ b/app/mixins/sortable.js @@ -0,0 +1,82 @@ +import Ember from 'ember'; + +export default Ember.Mixin.create({ + uiSortOptions: [ 'appendTo_sort', 'axis_sort', 'cancel_sort', 'connectWith_sort', + 'containment_sort', 'cursor_sort', 'cursorAt_sort', 'delay_sort', 'disabled_sort', + 'distance_sort', 'dropOnEmpty_sort', 'forceHelperSize_sort', 'forcePlaceholderSize_sort', + 'grid_sort', 'handle_sort', 'helper_sort', 'items_sort', 'opacity_sort', + 'placeholder_sort', 'revert_sort', 'scroll_sort', 'scrollSensitivity_sort', + 'scrollSpeed_sort', 'tolerance_sort', 'zIndex_sort' ], + uiSortEvents: [ 'activate_sort', 'beforeStop_sort', 'change_sort', 'create_sort', + 'deactivate_sort', 'out_sort', 'over_sort', 'receive_sort', 'remove_sort', + 'sort_sort', 'start_sort', 'stop_sort', 'update_sort' ], + + didInsertElement() { + this._super(); + + // get available options and events + var options = this._gatherSortOptions(); + this._gatherSortEvents(options); + + // create a new jQuery UI widget + var ui = Ember.$.ui['sortable'](options, this.get('element')); + this.set('ui', ui); + }, + + willDestroyElement() { + var ui = this.get('ui'); + + if (ui) { + // remove all observers for jQuery UI widget + var observers = this._observers; + for (var prop in observers) { + if (observers.hasOwnProperty(prop)) { + this.removeObserver(prop, observers[prop]); + } + } + + ui.destroy(); + } + }, + + _gatherSortOptions() { + // parse all options and add observers for them + var uiSortOptions = this.get('uiSortOptions') || []; + var options = {}; + + uiSortOptions.forEach(function(key) { + // save the sort option without the postfix + options[key.split('_')[0]] = this.get(key); + + // create an observer for this option + var observer = function() { + var value = this.get(key); + console.log(key + ': ' + value); + this.get('ui').option(key.split('_')[0], value); + }; + + this.addObserver(key, observer); + + // save observer to remove it later on + this._observers = this._observers || {}; + this._observers[key] = observer; + }, this); + + return options; + }, + + _gatherSortEvents(options) { + // register callbacks for each event + var uiSortEvents = this.get('uiSortEvents') || []; + var _this = this; + + uiSortEvents.forEach(function(event) { + var callback = _this[event]; + if (callback) { + options[event.split('_')[0]] = function(event, ui) { + callback.call(_this, event, ui); + }; + } + }); + } +}); diff --git a/app/styles/app.css b/app/styles/app.css index 9487c40..82ea5c9 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -117,8 +117,8 @@ footer { overflow: auto; } -.plot-toolbox { - margin-top: 20px; +.plots > div { + float: left; } .plot-add-row { @@ -126,9 +126,22 @@ footer { font-weight: 800; text-align: center; +} - margin: 0; - margin-top: -25px; +.plot-add-row > div { + float: none; +} + +.plot-toolbox { + margin-top: 20px; +} + +.plot-placeholder { + border: 1px dotted black; + width: 100px; + height: 100px; + + margin: 10px; } .draggableDropzone { @@ -166,7 +179,7 @@ footer { margin: 10px; padding: 5px 10px; - float: left; + display: inline-block; } .plotValue { diff --git a/app/templates/components/plot-abstract.hbs b/app/templates/components/plot-abstract.hbs new file mode 100644 index 0000000..889d9ee --- /dev/null +++ b/app/templates/components/plot-abstract.hbs @@ -0,0 +1 @@ +{{yield}} diff --git a/app/templates/visualization/edit.hbs b/app/templates/visualization/edit.hbs index 2ced225..f3ffa5d 100644 --- a/app/templates/visualization/edit.hbs +++ b/app/templates/visualization/edit.hbs @@ -15,17 +15,19 @@ {{/draggable-item}} -{{#draggable-dropzone dropped='addPlot'}} - {{#each model.plots as |plot|}} - {{#plot-container plot=plot editing=true}}{{/plot-container}} - {{/each}} -{{/draggable-dropzone}} +
+ {{#draggable-dropzone dropped='addPlot'}} + {{#each model.plots as |plot|}} + {{#plot-container plot=plot editing=true}}{{/plot-container}} + {{/each}} + {{/draggable-dropzone}} +
{{#draggable-dropzone dropped='addRowWithPlot'}}
+
{{/draggable-dropzone}} -

+

diff --git a/tests/integration/components/plot-abstract-test.js b/tests/integration/components/plot-abstract-test.js new file mode 100644 index 0000000..392a7fb --- /dev/null +++ b/tests/integration/components/plot-abstract-test.js @@ -0,0 +1,24 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('plot-abstract', 'Integration | Component | plot abstract', { + integration: true +}); + +test('it renders', function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... }); + + this.render(hbs`{{plot-abstract}}`); + + assert.equal(this.$().text().trim(), ''); + + // Template block usage: + this.render(hbs` + {{#plot-abstract}} + template block text + {{/plot-abstract}} + `); + + assert.equal(this.$().text().trim(), 'template block text'); +}); diff --git a/tests/unit/mixins/sortable-test.js b/tests/unit/mixins/sortable-test.js new file mode 100644 index 0000000..2efc2c2 --- /dev/null +++ b/tests/unit/mixins/sortable-test.js @@ -0,0 +1,12 @@ +import Ember from 'ember'; +import SortableMixin from 'villasweb-frontend/mixins/sortable'; +import { module, test } from 'qunit'; + +module('Unit | Mixin | sortable'); + +// Replace this with your real tests. +test('it works', function(assert) { + let SortableObject = Ember.Object.extend(SortableMixin); + let subject = SortableObject.create(); + assert.ok(subject); +}); diff --git a/todo.md b/todo.md index 4b9a455..b0982cf 100644 --- a/todo.md +++ b/todo.md @@ -6,7 +6,7 @@ - Websocket node is working in develop branch - Add API host to config/environment.js - Empty visualization after delete - + - Go into edit mode if visualization is empty websocketserverip/config.json websocketserverip/nodes.json