My Cards and Due Cards development

* add spinner while pages are loading
* use a single publication for My Cards
* add Due Cards to the user menu
* add description to the All Users option for Due Cards
* some code clean-up
This commit is contained in:
John R. Supplee 2021-01-10 18:08:03 +02:00
parent 55b121e0d3
commit ecc3558987
10 changed files with 197 additions and 185 deletions

View file

@ -1,7 +1,6 @@
template(name="dueCardsHeaderBar")
h1
//a.back-btn(href="{{pathFor 'home'}}")
// i.fa.fa-chevron-left
i.fa.fa-calendar
| {{_ 'dueCards-title'}}
.board-header-btns.left
@ -20,31 +19,33 @@ template(name="dueCardsModalTitle")
| {{_ 'dueCards-title'}}
template(name="dueCards")
.wrapper
.due-cards-dueat-list-wrapper
each card in dueCardsList
.due-cards-card-wrapper
a.minicard-wrapper.card-title(href=card.absoluteUrl)
+minicard(card)
ul.due-cards-context-list
li.due-cards-context(title="{{_ 'board'}}")
+viewer
= card.getBoard.title
li.due-cards-context.due-cards-context-separator
= ' '
| {{_ 'context-separator'}}
= ' '
li.due-cards-context(title="{{_ 'swimlane'}}")
+viewer
= card.getSwimlane.title
li.due-cards-context
= ' '
| {{_ 'context-separator'}}
= ' '
li.due-cards-context(title="{{_ 'list'}}")
+viewer
= card.getList.title
if isPageReady.get
.wrapper
.due-cards-dueat-list-wrapper
each card in dueCardsList
.due-cards-card-wrapper
a.minicard-wrapper.card-title(href=card.absoluteUrl)
+minicard(card)
ul.due-cards-context-list
li.due-cards-context(title="{{_ 'board'}}")
+viewer
= card.getBoard.title
li.due-cards-context.due-cards-context-separator
= ' '
| {{_ 'context-separator'}}
= ' '
li.due-cards-context(title="{{_ 'swimlane'}}")
+viewer
= card.getSwimlane.title
li.due-cards-context
= ' '
| {{_ 'context-separator'}}
= ' '
li.due-cards-context(title="{{_ 'list'}}")
+viewer
= card.getList.title
else
+spinner
template(name="dueCardsViewChangePopup")
ul.pop-over-list
@ -60,5 +61,8 @@ template(name="dueCardsViewChangePopup")
a.js-due-cards-view-all
i.fa.fa-users.colorful
| {{_ 'dueCardsViewChange-choice-all'}}
span.sub-name
+viewer
| {{_ 'dueCardsViewChange-choice-all-description' }}
if $eq Utils.dueCardsView "all"
i.fa.fa-check

View file

@ -1,3 +1,5 @@
const subManager = new SubsManager();
BlazeComponent.extendComponent({
dueCardsView() {
// eslint-disable-next-line no-console
@ -40,8 +42,20 @@ BlazeComponent.extendComponent({
BlazeComponent.extendComponent({
onCreated() {
this.isPageReady = new ReactiveVar(false);
this.autorun(() => {
const handle = subManager.subscribe(
'dueCards',
Utils.dueCardsView() === 'all',
);
Tracker.nonreactive(() => {
Tracker.autorun(() => {
this.isPageReady.set(handle.ready());
});
});
});
Meteor.subscribe('setting');
Meteor.subscribe('dueCards', Utils.dueCardsView() === 'all');
},
dueCardsView() {

View file

@ -2,6 +2,7 @@ template(name="myCardsHeaderBar")
h1
//a.back-btn(href="{{pathFor 'home'}}")
// i.fa.fa-chevron-left
i.fa.fa-list
| {{_ 'my-cards'}}
.board-header-btns.left
@ -20,51 +21,53 @@ template(name="myCardsModalTitle")
| {{_ 'my-cards'}}
template(name="myCards")
.wrapper
if $eq myCardsSort 'board'
each board in myBoards
.my-cards-board-wrapper
.my-cards-board-title
+viewer
= board.title
each swimlane in board.mySwimlanes
.my-cards-swimlane-title(class="{{#if swimlane.colorClass}}{{ swimlane.colorClass }}{{else}}swimlane-default-color{{/if}}")
if isPageReady.get
.wrapper
if $eq myCardsSort 'board'
each board in myCardsList
.my-cards-board-wrapper
.my-cards-board-title
+viewer
= swimlane.title
each list in swimlane.myLists
.my-cards-list-wrapper
.my-cards-list-title(class=list.colorClass)
= board.title
each swimlane in board.mySwimlanes
.my-cards-swimlane-title(class="{{#if swimlane.colorClass}}{{ swimlane.colorClass }}{{else}}swimlane-default-color{{/if}}")
+viewer
= swimlane.title
each list in swimlane.myLists
.my-cards-list-wrapper
.my-cards-list-title(class=list.colorClass)
+viewer
= list.title
each card in list.myCards
.my-cards-card-wrapper
a.minicard-wrapper(href=card.absoluteUrl)
+minicard(card)
else
.my-cards-dueat-list-wrapper
each card in myDueCardsList
.my-cards-card-wrapper
a.minicard-wrapper.card-title(href=card.absoluteUrl)
+minicard(card)
ul.my-cards-context-list
li.my-cards-context(title="{{_ 'board'}}")
+viewer
= list.title
each card in list.myCards
.my-cards-card-wrapper
a.minicard-wrapper(href=card.absoluteUrl)
+minicard(card)
else
.my-cards-dueat-list-wrapper
each card in myCardsList
.my-cards-card-wrapper
a.minicard-wrapper.card-title(href=card.absoluteUrl)
+minicard(card)
ul.my-cards-context-list
li.my-cards-context(title="{{_ 'board'}}")
+viewer
= card.getBoard.title
li.my-cards-context.my-cards-context-separator
= ' '
| {{_ 'context-separator'}}
= ' '
li.my-cards-context(title="{{_ 'swimlane'}}")
+viewer
= card.getSwimlane.title
li.my-cards-context
= ' '
| {{_ 'context-separator'}}
= ' '
li.my-cards-context(title="{{_ 'list'}}")
+viewer
= card.getList.title
= card.getBoard.title
li.my-cards-context.my-cards-context-separator
= ' '
| {{_ 'context-separator'}}
= ' '
li.my-cards-context(title="{{_ 'swimlane'}}")
+viewer
= card.getSwimlane.title
li.my-cards-context
= ' '
| {{_ 'context-separator'}}
= ' '
li.my-cards-context(title="{{_ 'list'}}")
+viewer
= card.getList.title
else
+spinner
template(name="myCardsSortChangePopup")
ul.pop-over-list

View file

@ -1,3 +1,5 @@
const subManager = new SubsManager();
BlazeComponent.extendComponent({
myCardsSort() {
// eslint-disable-next-line no-console
@ -42,10 +44,17 @@ BlazeComponent.extendComponent({
BlazeComponent.extendComponent({
onCreated() {
this.isPageReady = new ReactiveVar(false);
this.autorun(() => {
const handle = subManager.subscribe('myCards');
Tracker.nonreactive(() => {
Tracker.autorun(() => {
this.isPageReady.set(handle.ready());
});
});
});
Meteor.subscribe('setting');
Meteor.subscribe('myCards');
Meteor.subscribe('mySwimlanes');
Meteor.subscribe('myLists');
},
myCardsSort() {
@ -58,7 +67,7 @@ BlazeComponent.extendComponent({
return this.myCardsSort() === 'board';
},
myBoards() {
myCardsList() {
const userId = Meteor.userId();
const boards = [];
let board = null;
@ -173,7 +182,7 @@ BlazeComponent.extendComponent({
return boards;
},
myCardsList() {
myDueCardsList() {
const userId = Meteor.userId();
const cursor = Cards.find(

View file

@ -17,6 +17,10 @@ template(name="memberMenuPopup")
a.js-my-cards(href="{{pathFor 'my-cards'}}")
i.fa.fa-list
| {{_ 'my-cards'}}
li
a.js-due-cards(href="{{pathFor 'due-cards'}}")
i.fa.fa-calendar
| {{_ 'dueCards-title'}}
li
a(href="{{pathFor 'home'}}")
span.fa.fa-home

View file

@ -28,6 +28,9 @@ Template.memberMenuPopup.events({
'click .js-my-cards'() {
Popup.close();
},
'click .js-due-cards'() {
Popup.close();
},
'click .js-open-archived-board'() {
Modal.open('archivedBoards');
},

View file

@ -116,8 +116,6 @@ FlowRouter.route('/shortcuts', {
FlowRouter.route('/my-cards', {
name: 'my-cards',
action() {
const myCardsTemplate = 'myCards';
Filter.reset();
// EscapeActions.executeAll();
EscapeActions.executeUpTo('popup-close');
@ -125,15 +123,9 @@ FlowRouter.route('/my-cards', {
Utils.manageCustomUI();
Utils.manageMatomo();
// if (previousPath) {
// Modal.open(myCardsTemplate, {
// header: 'myCardsModalTitle',
// onCloseGoTo: previousPath,
// });
// } else {
BlazeLayout.render('defaultLayout', {
headerBar: 'myCardsHeaderBar',
content: myCardsTemplate,
content: 'myCards',
});
// }
},
@ -142,8 +134,6 @@ FlowRouter.route('/my-cards', {
FlowRouter.route('/due-cards', {
name: 'due-cards',
action() {
const dueCardsTemplate = 'dueCards';
Filter.reset();
// EscapeActions.executeAll();
EscapeActions.executeUpTo('popup-close');
@ -151,15 +141,9 @@ FlowRouter.route('/due-cards', {
Utils.manageCustomUI();
Utils.manageMatomo();
// if (previousPath) {
// Modal.open(dueCardsTemplate, {
// header: 'dueCardsModalTitle',
// onCloseGoTo: previousPath,
// });
// } else {
BlazeLayout.render('defaultLayout', {
headerBar: 'dueCardsHeaderBar',
content: dueCardsTemplate,
content: 'dueCards',
});
// }
},

View file

@ -860,5 +860,6 @@
"dueCards-title": "Due Cards",
"dueCardsViewChange-title": "Due Cards View",
"dueCardsViewChange-choice-me": "Me",
"dueCardsViewChange-choice-all": "All Users"
"dueCardsViewChange-choice-all": "All Users",
"dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission."
}

View file

@ -43,71 +43,6 @@ Meteor.publish('boards', function() {
);
});
Meteor.publish('mySwimlanes', function() {
const userId = this.userId;
const swimlanes = [];
Cards.find({
archived: false,
$or: [{ members: userId }, { assignees: userId }],
}).forEach(card => {
swimlanes.push(card.swimlaneId);
});
return Swimlanes.find(
{
// archived: false,
_id: { $in: swimlanes },
},
{
fields: {
_id: 1,
title: 1,
boardId: 1,
type: 1,
color: 1,
sort: 1,
},
// sort: {
// sort: ['boardId', 'listId', 'sort'],
// },
},
);
});
Meteor.publish('myLists', function() {
const userId = this.userId;
const lists = [];
Cards.find({
archived: false,
$or: [{ members: userId }, { assignees: userId }],
}).forEach(card => {
lists.push(card.listId);
});
return Lists.find(
{
// archived: false,
_id: { $in: lists },
},
{
fields: {
_id: 1,
boardId: 1,
swimlaneId: 1,
title: 1,
color: 1,
type: 1,
sort: 1,
},
// sort: {
// sort: ['boardId', 'listId', 'sort'],
// },
},
);
});
Meteor.publish('archivedBoards', function() {
const userId = this.userId;
if (!Match.test(userId, String)) return [];

View file

@ -4,33 +4,76 @@ Meteor.publish('card', cardId => {
});
Meteor.publish('myCards', function() {
const userId = this.userId;
const userId = Meteor.userId();
return Cards.find(
{
archived: false,
$or: [{ members: userId }, { assignees: userId }],
const archivedBoards = [];
Boards.find({ archived: true }).forEach(board => {
archivedBoards.push(board._id);
});
const archivedSwimlanes = [];
Swimlanes.find({ archived: true }).forEach(swimlane => {
archivedSwimlanes.push(swimlane._id);
});
const archivedLists = [];
Lists.find({ archived: true }).forEach(list => {
archivedLists.push(list._id);
});
selector = {
archived: false,
boardId: { $nin: archivedBoards },
swimlaneId: { $nin: archivedSwimlanes },
listId: { $nin: archivedLists },
$or: [{ members: userId }, { assignees: userId }],
};
const cards = Cards.find(selector, {
fields: {
_id: 1,
archived: 1,
boardId: 1,
swimlaneId: 1,
listId: 1,
title: 1,
type: 1,
sort: 1,
members: 1,
assignees: 1,
colors: 1,
dueAt: 1,
},
{
fields: {
_id: 1,
archived: 1,
boardId: 1,
swimlaneId: 1,
listId: 1,
title: 1,
type: 1,
sort: 1,
members: 1,
assignees: 1,
colors: 1,
dueAt: 1,
},
// sort: {
// sort: ['boardId', 'listId', 'sort'],
// },
},
);
});
const boards = [];
const swimlanes = [];
const lists = [];
const users = [];
cards.forEach(card => {
if (card.boardId) boards.push(card.boardId);
if (card.swimlaneId) swimlanes.push(card.swimlaneId);
if (card.listId) lists.push(card.listId);
if (card.members) {
card.members.forEach(userId => {
users.push(userId);
});
}
if (card.assignees) {
card.assignees.forEach(userId => {
users.push(userId);
});
}
});
return [
cards,
Boards.find({ _id: { $in: boards } }),
Swimlanes.find({ _id: { $in: swimlanes } }),
Lists.find({ _id: { $in: lists } }),
Users.find({ _id: { $in: users } }),
];
});
Meteor.publish('dueCards', function(allUsers = false) {
@ -105,11 +148,22 @@ Meteor.publish('dueCards', function(allUsers = false) {
const boards = [];
const swimlanes = [];
const lists = [];
const users = [];
cards.forEach(card => {
if (card.boardId) boards.push(card.boardId);
if (card.swimlaneId) swimlanes.push(card.swimlaneId);
if (card.listId) lists.push(card.listId);
if (card.members) {
card.members.forEach(userId => {
users.push(userId);
});
}
if (card.assignees) {
card.assignees.forEach(userId => {
users.push(userId);
});
}
});
return [
@ -117,5 +171,6 @@ Meteor.publish('dueCards', function(allUsers = false) {
Boards.find({ _id: { $in: boards } }),
Swimlanes.find({ _id: { $in: swimlanes } }),
Lists.find({ _id: { $in: lists } }),
Users.find({ _id: { $in: users } }),
];
});