mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 15:30:13 +01:00
This bug was introduced with the introduction of fast-render in
41b23f8. With fast-render data is available instantly after the page
logging, but calls to `Meteor.userId()` still return `null` as the
user isn't authenticated on the DDP channel yet (previously the data
was loaded on DDP after user authentication). Which mean that we know
need to reactively activate Drag and Drop on user log in.
I'm not sure why I was not able to reproduce this bug outside of
Sandstorm.
Fixes #453
120 lines
4.4 KiB
JavaScript
120 lines
4.4 KiB
JavaScript
const { calculateIndex } = Utils;
|
|
|
|
BlazeComponent.extendComponent({
|
|
template() {
|
|
return 'list';
|
|
},
|
|
|
|
// Proxy
|
|
openForm(options) {
|
|
this.childComponents('listBody')[0].openForm(options);
|
|
},
|
|
|
|
onCreated() {
|
|
this.newCardFormIsVisible = new ReactiveVar(true);
|
|
},
|
|
|
|
// The jquery UI sortable library is the best solution I've found so far. I
|
|
// tried sortable and dragula but they were not powerful enough four our use
|
|
// case. I also considered writing/forking a drag-and-drop + sortable library
|
|
// but it's probably too much work.
|
|
// By calling asking the sortable library to cancel its move on the `stop`
|
|
// callback, we basically solve all issues related to reactive updates. A
|
|
// comment below provides further details.
|
|
onRendered() {
|
|
const boardComponent = this.parentComponent();
|
|
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
|
const $cards = this.$('.js-minicards');
|
|
$cards.sortable({
|
|
connectWith: '.js-minicards',
|
|
tolerance: 'pointer',
|
|
appendTo: 'body',
|
|
helper(evt, item) {
|
|
const helper = item.clone();
|
|
if (MultiSelection.isActive()) {
|
|
const andNOthers = $cards.find('.js-minicard.is-checked').length - 1;
|
|
if (andNOthers > 0) {
|
|
helper.append($(Blaze.toHTML(HTML.DIV(
|
|
{ 'class': 'and-n-other' },
|
|
TAPi18n.__('and-n-other-card', { count: andNOthers })
|
|
))));
|
|
}
|
|
}
|
|
return helper;
|
|
},
|
|
distance: 7,
|
|
items: itemsSelector,
|
|
scroll: false,
|
|
placeholder: 'minicard-wrapper placeholder',
|
|
start(evt, ui) {
|
|
ui.placeholder.height(ui.helper.height());
|
|
EscapeActions.executeUpTo('popup');
|
|
boardComponent.setIsDragging(true);
|
|
},
|
|
stop(evt, ui) {
|
|
// To attribute the new index number, we need to get the DOM element
|
|
// of the previous and the following card -- if any.
|
|
const prevCardDom = ui.item.prev('.js-minicard').get(0);
|
|
const nextCardDom = ui.item.next('.js-minicard').get(0);
|
|
const nCards = MultiSelection.isActive() ? MultiSelection.count() : 1;
|
|
const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards);
|
|
const listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
|
|
|
|
// Normally the jquery-ui sortable library moves the dragged DOM element
|
|
// to its new position, which disrupts Blaze reactive updates mechanism
|
|
// (especially when we move the last card of a list, or when multiple
|
|
// users move some cards at the same time). To prevent these UX glitches
|
|
// we ask sortable to gracefully cancel the move, and to put back the
|
|
// DOM in its initial state. The card move is then handled reactively by
|
|
// Blaze with the below query.
|
|
$cards.sortable('cancel');
|
|
|
|
if (MultiSelection.isActive()) {
|
|
Cards.find(MultiSelection.getMongoSelector()).forEach((card, i) => {
|
|
card.move(listId, sortIndex.base + i * sortIndex.increment);
|
|
});
|
|
} else {
|
|
const cardDomElement = ui.item.get(0);
|
|
const card = Blaze.getData(cardDomElement);
|
|
card.move(listId, sortIndex.base);
|
|
}
|
|
boardComponent.setIsDragging(false);
|
|
},
|
|
});
|
|
|
|
function userIsMember() {
|
|
return Meteor.user() && Meteor.user().isBoardMember();
|
|
}
|
|
|
|
// Disable drag-dropping if the current user is not a board member
|
|
this.autorun(() => {
|
|
$cards.sortable('option', 'disabled', !userIsMember());
|
|
});
|
|
|
|
// We want to re-run this function any time a card is added.
|
|
this.autorun(() => {
|
|
const currentBoardId = Tracker.nonreactive(() => {
|
|
return Session.get('currentBoard');
|
|
});
|
|
Cards.find({ boardId: currentBoardId }).fetch();
|
|
Tracker.afterFlush(() => {
|
|
$cards.find(itemsSelector).droppable({
|
|
hoverClass: 'draggable-hover-card',
|
|
accept: '.js-member,.js-label',
|
|
drop(event, ui) {
|
|
const cardId = Blaze.getData(this)._id;
|
|
const card = Cards.findOne(cardId);
|
|
|
|
if (ui.draggable.hasClass('js-member')) {
|
|
const memberId = Blaze.getData(ui.draggable.get(0)).userId;
|
|
card.assignMember(memberId);
|
|
} else {
|
|
const labelId = Blaze.getData(ui.draggable.get(0))._id;
|
|
card.addLabel(labelId);
|
|
}
|
|
},
|
|
});
|
|
});
|
|
});
|
|
},
|
|
}).register('list');
|