From 30d082e709cbeded24156436b5bc66dfba53d754 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 30 Jan 2019 16:25:23 +0100 Subject: [PATCH 1/5] Use infinite-scrolling on lists This allows to reduce the loading time of a big board. Note that there is an infinite scroll implementation in the mixins, but this doesn't fit well as the cards in the list can have arbitrary height. The idea to rely on the visibility of a spinner is based on http://www.meteorpedia.com/read/Infinite_Scrolling --- client/components/lists/list.styl | 3 + client/components/lists/listBody.jade | 12 +++- client/components/lists/listBody.js | 81 +++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/client/components/lists/list.styl b/client/components/lists/list.styl index 51ade73c4..705020836 100644 --- a/client/components/lists/list.styl +++ b/client/components/lists/list.styl @@ -211,6 +211,9 @@ max-height: 250px overflow: hidden +.sk-spinner-list + margin-top: unset !important + list-header-color(background, color...) border-bottom: 6px solid background diff --git a/client/components/lists/listBody.jade b/client/components/lists/listBody.jade index c6c9b2045..f030833ba 100644 --- a/client/components/lists/listBody.jade +++ b/client/components/lists/listBody.jade @@ -4,7 +4,7 @@ template(name="listBody") if cards.count +inlinedForm(autoclose=false position="top") +addCardForm(listId=_id position="top") - each (cards (idOrNull ../../_id)) + each (cardsWithLimit (idOrNull ../../_id)) a.minicard-wrapper.js-minicard(href=absoluteUrl class="{{#if cardIsSelected}}is-selected{{/if}}" class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") @@ -12,6 +12,16 @@ template(name="listBody") .materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection( class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") +minicard(this) + if (showSpinner (idOrNull ../../_id)) + .sk-spinner.sk-spinner-wave.sk-spinner-list( + class=currentBoard.colorClass + id="showMoreResults") + .sk-rect1 + .sk-rect2 + .sk-rect3 + .sk-rect4 + .sk-rect5 + if canSeeAddCard +inlinedForm(autoclose=false position="bottom") +addCardForm(listId=_id position="bottom") diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 1001f3bc4..501459d90 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -1,6 +1,34 @@ const subManager = new SubsManager(); +const InfiniteScrollIter = 10; BlazeComponent.extendComponent({ + onCreated() { + // for infinite scrolling + this.cardlimit = new ReactiveVar(InfiniteScrollIter); + }, + + onRendered() { + const domElement = this.find('.js-perfect-scrollbar'); + + this.$(domElement).on('scroll', () => this.updateList(domElement)); + $(window).on(`resize.${this.data().listId}`, () => this.updateList(domElement)); + + // we add a Mutation Observer to allow propagations of cardlimit + // when the spinner stays in the current view (infinite scrolling) + this.mutationObserver = new MutationObserver(() => this.updateList(domElement)); + + this.mutationObserver.observe(domElement, { + childList: true, + }); + + this.updateList(domElement); + }, + + onDestroyed() { + $(window).off(`resize.${this.data().listId}`); + this.mutationObserver.disconnect(); + }, + mixins() { return [Mixins.PerfectScrollbar]; }, @@ -60,6 +88,13 @@ BlazeComponent.extendComponent({ type: 'cardType-card', }); + // if the displayed card count is less than the total cards in the list, + // we need to increment the displayed card count to prevent the spinner + // to appear + const cardCount = this.data().cards(this.idOrNull(swimlaneId)).count(); + if (pthis.cardlimit.get() < cardCount) { + this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter); + } // In case the filter is active we need to add the newly inserted card in // the list of exceptions -- cards that are not filtered. Otherwise the @@ -119,6 +154,52 @@ BlazeComponent.extendComponent({ return undefined; }, + cardsWithLimit(swimlaneId) { + const limit = this.cardlimit.get(); + const selector = { + listId: this.currentData()._id, + archived: false, + }; + if (swimlaneId) + selector.swimlaneId = swimlaneId; + return Cards.find(Filter.mongoSelector(selector), { + sort: ['sort'], + limit, + }); + }, + + spinnerInView(container) { + const parentViewHeight = container.clientHeight; + const bottomViewPosition = container.scrollTop + parentViewHeight; + + const spinner = this.find('.sk-spinner-list'); + + const threshold = spinner.offsetTop; + + return bottomViewPosition > threshold; + }, + + showSpinner(swimlaneId) { + const list = Template.currentData(); + return list.cards(swimlaneId).count() > this.cardlimit.get(); + }, + + updateList(container) { + // first, if the spinner is not rendered, we have reached the end of + // the list of cards, so skip and disable firing the events + const target = this.find('.sk-spinner-list'); + if (!target) { + this.$(container).off('scroll'); + $(window).off(`resize.${this.data().listId}`); + return; + } + + if (this.spinnerInView(container)) { + this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter); + Ps.update(container); + } + }, + canSeeAddCard() { return !this.reachedWipLimit() && Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); }, From 66bc1f28dd4395c1c2b4434520923edfa54d4eed Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 30 Jan 2019 16:25:23 +0100 Subject: [PATCH 2/5] Use infinite-scrolling on lists This allows to reduce the loading time of a big board. Note that there is an infinite scroll implementation in the mixins, but this doesn't fit well as the cards in the list can have arbitrary height. The idea to rely on the visibility of a spinner is based on http://www.meteorpedia.com/read/Infinite_Scrolling --- client/components/lists/list.styl | 3 + client/components/lists/listBody.jade | 12 +++- client/components/lists/listBody.js | 81 +++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/client/components/lists/list.styl b/client/components/lists/list.styl index 51ade73c4..705020836 100644 --- a/client/components/lists/list.styl +++ b/client/components/lists/list.styl @@ -211,6 +211,9 @@ max-height: 250px overflow: hidden +.sk-spinner-list + margin-top: unset !important + list-header-color(background, color...) border-bottom: 6px solid background diff --git a/client/components/lists/listBody.jade b/client/components/lists/listBody.jade index c6c9b2045..f030833ba 100644 --- a/client/components/lists/listBody.jade +++ b/client/components/lists/listBody.jade @@ -4,7 +4,7 @@ template(name="listBody") if cards.count +inlinedForm(autoclose=false position="top") +addCardForm(listId=_id position="top") - each (cards (idOrNull ../../_id)) + each (cardsWithLimit (idOrNull ../../_id)) a.minicard-wrapper.js-minicard(href=absoluteUrl class="{{#if cardIsSelected}}is-selected{{/if}}" class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") @@ -12,6 +12,16 @@ template(name="listBody") .materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection( class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") +minicard(this) + if (showSpinner (idOrNull ../../_id)) + .sk-spinner.sk-spinner-wave.sk-spinner-list( + class=currentBoard.colorClass + id="showMoreResults") + .sk-rect1 + .sk-rect2 + .sk-rect3 + .sk-rect4 + .sk-rect5 + if canSeeAddCard +inlinedForm(autoclose=false position="bottom") +addCardForm(listId=_id position="bottom") diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 1001f3bc4..fdea3bae1 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -1,6 +1,34 @@ const subManager = new SubsManager(); +const InfiniteScrollIter = 10; BlazeComponent.extendComponent({ + onCreated() { + // for infinite scrolling + this.cardlimit = new ReactiveVar(InfiniteScrollIter); + }, + + onRendered() { + const domElement = this.find('.js-perfect-scrollbar'); + + this.$(domElement).on('scroll', () => this.updateList(domElement)); + $(window).on(`resize.${this.data().listId}`, () => this.updateList(domElement)); + + // we add a Mutation Observer to allow propagations of cardlimit + // when the spinner stays in the current view (infinite scrolling) + this.mutationObserver = new MutationObserver(() => this.updateList(domElement)); + + this.mutationObserver.observe(domElement, { + childList: true, + }); + + this.updateList(domElement); + }, + + onDestroyed() { + $(window).off(`resize.${this.data().listId}`); + this.mutationObserver.disconnect(); + }, + mixins() { return [Mixins.PerfectScrollbar]; }, @@ -60,6 +88,13 @@ BlazeComponent.extendComponent({ type: 'cardType-card', }); + // if the displayed card count is less than the total cards in the list, + // we need to increment the displayed card count to prevent the spinner + // to appear + const cardCount = this.data().cards(this.idOrNull(swimlaneId)).count(); + if (this.cardlimit.get() < cardCount) { + this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter); + } // In case the filter is active we need to add the newly inserted card in // the list of exceptions -- cards that are not filtered. Otherwise the @@ -119,6 +154,52 @@ BlazeComponent.extendComponent({ return undefined; }, + cardsWithLimit(swimlaneId) { + const limit = this.cardlimit.get(); + const selector = { + listId: this.currentData()._id, + archived: false, + }; + if (swimlaneId) + selector.swimlaneId = swimlaneId; + return Cards.find(Filter.mongoSelector(selector), { + sort: ['sort'], + limit, + }); + }, + + spinnerInView(container) { + const parentViewHeight = container.clientHeight; + const bottomViewPosition = container.scrollTop + parentViewHeight; + + const spinner = this.find('.sk-spinner-list'); + + const threshold = spinner.offsetTop; + + return bottomViewPosition > threshold; + }, + + showSpinner(swimlaneId) { + const list = Template.currentData(); + return list.cards(swimlaneId).count() > this.cardlimit.get(); + }, + + updateList(container) { + // first, if the spinner is not rendered, we have reached the end of + // the list of cards, so skip and disable firing the events + const target = this.find('.sk-spinner-list'); + if (!target) { + this.$(container).off('scroll'); + $(window).off(`resize.${this.data().listId}`); + return; + } + + if (this.spinnerInView(container)) { + this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter); + Ps.update(container); + } + }, + canSeeAddCard() { return !this.reachedWipLimit() && Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); }, From 7a35099fb9778d5f3656a57c74af426cfb20fba3 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Fri, 1 Feb 2019 17:03:59 +0200 Subject: [PATCH 3/5] - When writing to minicard, press Shift-Enter on minicard to go to next line below, to continue writing on same minicard 2nd line. Thanks to bentiss! --- client/components/lists/listBody.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index fdea3bae1..0f5caac56 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -260,7 +260,7 @@ BlazeComponent.extendComponent({ pressKey(evt) { // Pressing Enter should submit the card - if (evt.keyCode === 13) { + if (evt.keyCode === 13 && !evt.shiftKey) { evt.preventDefault(); const $form = $(evt.currentTarget).closest('form'); // XXX For some reason $form.submit() does not work (it's probably a bug From 8e5fc13ba45f1d8dad3c7d737ef4a13bd38b04ca Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Fri, 1 Feb 2019 17:09:05 +0200 Subject: [PATCH 4/5] Update changelog with latest changes from bentiss. --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de9ec68fa..4aa92d0a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# Upcoming Wekan release + +This release adds the following new features with Apache I-CLA, thanks to bentiss: + +- [Use infinite-scrolling on lists](https://github.com/wekan/wekan/pull/2144). + This allows to reduce the loading time of a big board. + Note that there is an infinite scroll implementation in the mixins, + but this doesn't fit well as the cards in the list can have arbitrary + height. + The idea to rely on the visibility of a spinner is based on + http://www.meteorpedia.com/read/Infinite_Scrolling +- [When writing to minicard, press Shift-Enter on minicard to go to next line + below](https://github.com/wekan/wekan/commit/7a35099fb9778d5f3656a57c74af426cfb20fba3), + to continue writing on same minicard 2nd line. + +Thanks to above GitHub users and translators for contributions. + # v2.12 2019-01-31 Wekan release This release fixes the following bugs: From 6f0e0748e1eb6cac238edfcfcfcde1e41d613b96 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Fri, 1 Feb 2019 17:15:15 +0200 Subject: [PATCH 5/5] v2.13 --- CHANGELOG.md | 4 ++-- Stackerfile.yml | 2 +- package.json | 2 +- sandstorm-pkgdef.capnp | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa92d0a5..1d101b667 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Upcoming Wekan release +# v2.13 2019-02-01 Wekan release This release adds the following new features with Apache I-CLA, thanks to bentiss: @@ -13,7 +13,7 @@ This release adds the following new features with Apache I-CLA, thanks to bentis below](https://github.com/wekan/wekan/commit/7a35099fb9778d5f3656a57c74af426cfb20fba3), to continue writing on same minicard 2nd line. -Thanks to above GitHub users and translators for contributions. +Thanks to GitHub user bentiss for contributions. # v2.12 2019-01-31 Wekan release diff --git a/Stackerfile.yml b/Stackerfile.yml index f0f61101b..523ba2b44 100644 --- a/Stackerfile.yml +++ b/Stackerfile.yml @@ -1,5 +1,5 @@ appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928 -appVersion: "v2.12.0" +appVersion: "v2.13.0" files: userUploads: - README.md diff --git a/package.json b/package.json index ff33efc17..b9936b635 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wekan", - "version": "v2.12.0", + "version": "v2.13.0", "description": "Open-Source kanban", "private": true, "scripts": { diff --git a/sandstorm-pkgdef.capnp b/sandstorm-pkgdef.capnp index d8d80938f..19e81a74e 100644 --- a/sandstorm-pkgdef.capnp +++ b/sandstorm-pkgdef.capnp @@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = ( appTitle = (defaultText = "Wekan"), # The name of the app as it is displayed to the user. - appVersion = 214, + appVersion = 215, # Increment this for every release. - appMarketingVersion = (defaultText = "2.12.0~2019-01-31"), + appMarketingVersion = (defaultText = "2.13.0~2019-02-01"), # Human-readable presentation of the app version. minUpgradableAppVersion = 0,