diff options
author | Maxime Quandalle <maxime@quandalle.com> | 2015-05-29 23:35:30 +0200 |
---|---|---|
committer | Maxime Quandalle <maxime@quandalle.com> | 2015-05-30 03:50:14 +0200 |
commit | 2c0030da62b9a1e59a55e3429fe514bbd51e1ee3 (patch) | |
tree | b2834702806e59cb05ea02e2c377266eb17d6c8f /client/lib | |
parent | 6457615e6ac6717d2175be9483388d4d70ea1c4a (diff) | |
download | wekan-2c0030da62b9a1e59a55e3429fe514bbd51e1ee3.tar.gz wekan-2c0030da62b9a1e59a55e3429fe514bbd51e1ee3.tar.bz2 wekan-2c0030da62b9a1e59a55e3429fe514bbd51e1ee3.zip |
Implement multi-selection
The UI and the internal APIs are still rough around the edges but the
feature is basically working. You can now select multiple cards and
move them together or (un|)assign them a label.
Diffstat (limited to 'client/lib')
-rw-r--r-- | client/lib/filter.js | 11 | ||||
-rw-r--r-- | client/lib/keyboard.js | 13 | ||||
-rw-r--r-- | client/lib/multiSelection.js | 159 | ||||
-rw-r--r-- | client/lib/popup.js | 4 |
4 files changed, 182 insertions, 5 deletions
diff --git a/client/lib/filter.js b/client/lib/filter.js index d96fa89c..359b65d3 100644 --- a/client/lib/filter.js +++ b/client/lib/filter.js @@ -91,7 +91,7 @@ Filter = { }); }, - getMongoSelector: function() { + _getMongoSelector: function() { var self = this; if (! self.isActive()) @@ -110,6 +110,14 @@ Filter = { return {$or: [filterSelector, exceptionsSelector]}; }, + mongoSelector: function(additionalSelector) { + var filterSelector = this._getMongoSelector(); + if (_.isUndefined(additionalSelector)) + return filterSelector; + else + return {$and: [filterSelector, additionalSelector]}; + }, + reset: function() { var self = this; _.forEach(self._fields, function(fieldName) { @@ -123,6 +131,7 @@ Filter = { if (this.isActive()) { this._exceptions.push(_id); this._exceptionsDep.changed(); + Tracker.flush(); } }, diff --git a/client/lib/keyboard.js b/client/lib/keyboard.js index 0fbdbfd5..8b105c28 100644 --- a/client/lib/keyboard.js +++ b/client/lib/keyboard.js @@ -47,11 +47,16 @@ EscapeActions = { 'textcomplete', 'popup', 'inlinedForm', + 'multiselection-disable', 'sidebarView', - 'detailedPane' + 'detailsPane', + 'multiselection-reset' ], - register: function(label, condition, action) { + register: function(label, action, condition) { + if (_.isUndefined(condition)) + condition = function() { return true; }; + // XXX Rewrite this with ES6: .push({ priority, condition, action }) var priority = this.hierarchy.indexOf(label); if (priority === -1) { @@ -87,6 +92,10 @@ EscapeActions = { if (!! currentAction.condition()) currentAction.action(); } + }, + + executeAll: function() { + return this.executeLowerThan(); } }; diff --git a/client/lib/multiSelection.js b/client/lib/multiSelection.js new file mode 100644 index 00000000..53c16da0 --- /dev/null +++ b/client/lib/multiSelection.js @@ -0,0 +1,159 @@ + +var getCardsBetween = function(idA, idB) { + + var pluckId = function(doc) { + return doc._id; + }; + + var getListsStrictlyBetween = function(id1, id2) { + return Lists.find({ + $and: [ + { sort: { $gt: Lists.findOne(id1).sort } }, + { sort: { $lt: Lists.findOne(id2).sort } } + ], + archived: false + }).map(pluckId); + }; + + var cards = _.sortBy([Cards.findOne(idA), Cards.findOne(idB)], function(c) { + return c.sort; + }); + + var selector; + if (cards[0].listId === cards[1].listId) { + selector = { + listId: cards[0].listId, + sort: { + $gte: cards[0].sort, + $lte: cards[1].sort + }, + archived: false + }; + } else { + selector = { + $or: [{ + listId: cards[0].listId, + sort: { $lte: cards[0].sort } + }, { + listId: { + $in: getListsStrictlyBetween(cards[0].listId, cards[1].listId) + } + }, { + listId: cards[1].listId, + sort: { $gte: cards[1].sort } + }], + archived: false + }; + } + + return Cards.find(Filter.mongoSelector(selector)).map(pluckId); +}; + +MultiSelection = { + sidebarView: 'multiselection', + + _selectedCards: new ReactiveVar([]), + + _isActive: new ReactiveVar(false), + + startRangeCardId: null, + + reset: function() { + this._selectedCards.set([]); + }, + + getMongoSelector: function() { + return Filter.mongoSelector({ + _id: { $in: this._selectedCards.get() } + }); + }, + + isActive: function() { + return this._isActive.get(); + }, + + isEmpty: function() { + return this._selectedCards.get().length === 0; + }, + + activate: function() { + if (! this.isActive()) { + EscapeActions.executeLowerThan('detailsPane'); + this._isActive.set(true); + Sidebar.setView(this.sidebarView); + Tracker.flush(); + } + }, + + disable: function() { + if (this.isActive()) { + this._isActive.set(false); + if (Sidebar && Sidebar.getView() === this.sidebarView) { + Sidebar.setView(); + } + } + }, + + add: function(cardIds) { + return this.toogle(cardIds, { add: true, remove: false }); + }, + + remove: function(cardIds) { + return this.toogle(cardIds, { add: false, remove: true }); + }, + + toogleRange: function(cardId) { + var selectedCards = this._selectedCards.get(); + var startRange; + this.reset(); + if (! this.isActive() || selectedCards.length === 0) { + this.toogle(cardId); + } else { + startRange = selectedCards[selectedCards.length - 1]; + this.toogle(getCardsBetween(startRange, cardId)); + } + }, + + toogle: function(cardIds, options) { + var self = this; + cardIds = _.isString(cardIds) ? [cardIds] : cardIds; + options = _.extend({ + add: true, + remove: true + }, options || {}); + + if (! self.isActive()) { + self.reset(); + self.activate(); + } + + var selectedCards = self._selectedCards.get(); + + _.each(cardIds, function(cardId) { + var indexOfCard = selectedCards.indexOf(cardId); + + if (options.remove && indexOfCard > -1) + selectedCards.splice(indexOfCard, 1); + + else if (options.add) + selectedCards.push(cardId); + }); + + self._selectedCards.set(selectedCards); + }, + + isSelected: function(cardId) { + return this._selectedCards.get().indexOf(cardId) > -1; + } +}; + +Blaze.registerHelper('MultiSelection', MultiSelection); + +EscapeActions.register('multiselection-disable', + function() { MultiSelection.disable(); }, + function() { return MultiSelection.isActive(); } +); + +EscapeActions.register('multiselection-reset', + function() { MultiSelection.reset(); } +); diff --git a/client/lib/popup.js b/client/lib/popup.js index 6298ba81..46c137e8 100644 --- a/client/lib/popup.js +++ b/client/lib/popup.js @@ -205,6 +205,6 @@ $(document).on('click', function(evt) { // Press escape to close the popup. var bindPopup = function(f) { return _.bind(f, Popup); }; EscapeActions.register('popup', - bindPopup(Popup.isOpen), - bindPopup(Popup.close) + bindPopup(Popup.close), + bindPopup(Popup.isOpen) ); |