mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 23:40:13 +01:00
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.
This commit is contained in:
parent
6457615e6a
commit
2c0030da62
45 changed files with 883 additions and 933 deletions
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
159
client/lib/multiSelection.js
Normal file
159
client/lib/multiSelection.js
Normal file
|
|
@ -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(); }
|
||||
);
|
||||
|
|
@ -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)
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue