diff options
author | Grigorii K. Shartsev <me@shgk.me> | 2023-06-27 13:23:47 +0200 |
---|---|---|
committer | Grigorii K. Shartsev <me@shgk.me> | 2023-06-27 13:23:47 +0200 |
commit | 6bb2d5cc5a5b1013917c3aea522424f2d984ef2a (patch) | |
tree | 9825ce9106981fb0d04ce764b65edeb25a5daf88 | |
parent | ced01aead0308d4a5d56a240f25a3f172b6aac38 (diff) |
feat(feature-flags): add virtual scrolling for left sidebar flag
Signed-off-by: Grigorii K. Shartsev <me@shgk.me>
-rw-r--r-- | package-lock.json | 60 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | src/components/ConversationIcon.vue | 7 | ||||
-rw-r--r-- | src/components/LeftSidebar/LeftSidebar.vue | 82 | ||||
-rw-r--r-- | src/services/localFeatureFlagsService.js | 1 |
5 files changed, 127 insertions, 24 deletions
diff --git a/package-lock.json b/package-lock.json index 8444e8ef1..3968631d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "vue-resize": "^1.0.1", "vue-router": "^3.6.5", "vue-shortkey": "^3.1.7", + "vue-virtual-scroller": "^1.1.2", "vue2-leaflet": "^2.7.1", "vuex": "^3.6.2", "webdav": "^4.11.2", @@ -14955,6 +14956,11 @@ "dev": true, "peer": true }, + "node_modules/scrollparent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", + "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==" + }, "node_modules/sdp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz", @@ -17428,6 +17434,32 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "node_modules/vue-virtual-scroller": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vue-virtual-scroller/-/vue-virtual-scroller-1.1.2.tgz", + "integrity": "sha512-SkUyc7QHCJFB5h1Fya7LxVizlVzOZZuFVipBGHYoTK8dwLs08bIz/tclvRApYhksaJIm/nn51inzO2UjpGJPMQ==", + "dependencies": { + "scrollparent": "^2.0.1", + "vue-observe-visibility": "^0.4.4", + "vue-resize": "^0.4.5" + }, + "peerDependencies": { + "vue": "^2.6.11" + } + }, + "node_modules/vue-virtual-scroller/node_modules/vue-observe-visibility": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vue-observe-visibility/-/vue-observe-visibility-0.4.6.tgz", + "integrity": "sha512-xo0CEVdkjSjhJoDdLSvoZoQrw/H2BlzB5jrCBKGZNXN2zdZgMuZ9BKrxXDjNP2AxlcCoKc8OahI3F3r3JGLv2Q==" + }, + "node_modules/vue-virtual-scroller/node_modules/vue-resize": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-0.4.5.tgz", + "integrity": "sha512-bhP7MlgJQ8TIkZJXAfDf78uJO+mEI3CaLABLjv0WNzr4CcGRGPIAItyWYnP6LsPA4Oq0WE+suidNs6dgpO4RHg==", + "peerDependencies": { + "vue": "^2.3.0" + } + }, "node_modules/vue2-datepicker": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/vue2-datepicker/-/vue2-datepicker-3.11.0.tgz", @@ -29621,6 +29653,11 @@ } } }, + "scrollparent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", + "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==" + }, "sdp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz", @@ -31525,6 +31562,29 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "vue-virtual-scroller": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vue-virtual-scroller/-/vue-virtual-scroller-1.1.2.tgz", + "integrity": "sha512-SkUyc7QHCJFB5h1Fya7LxVizlVzOZZuFVipBGHYoTK8dwLs08bIz/tclvRApYhksaJIm/nn51inzO2UjpGJPMQ==", + "requires": { + "scrollparent": "^2.0.1", + "vue-observe-visibility": "^0.4.4", + "vue-resize": "^0.4.5" + }, + "dependencies": { + "vue-observe-visibility": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vue-observe-visibility/-/vue-observe-visibility-0.4.6.tgz", + "integrity": "sha512-xo0CEVdkjSjhJoDdLSvoZoQrw/H2BlzB5jrCBKGZNXN2zdZgMuZ9BKrxXDjNP2AxlcCoKc8OahI3F3r3JGLv2Q==" + }, + "vue-resize": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-0.4.5.tgz", + "integrity": "sha512-bhP7MlgJQ8TIkZJXAfDf78uJO+mEI3CaLABLjv0WNzr4CcGRGPIAItyWYnP6LsPA4Oq0WE+suidNs6dgpO4RHg==", + "requires": {} + } + } + }, "vue2-datepicker": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/vue2-datepicker/-/vue2-datepicker-3.11.0.tgz", diff --git a/package.json b/package.json index f55698c73..b9653735b 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "vue-resize": "^1.0.1", "vue-router": "^3.6.5", "vue-shortkey": "^3.1.7", + "vue-virtual-scroller": "^1.1.2", "vue2-leaflet": "^2.7.1", "vuex": "^3.6.2", "webdav": "^4.11.2", diff --git a/src/components/ConversationIcon.vue b/src/components/ConversationIcon.vue index b09f20ea4..86caa9f02 100644 --- a/src/components/ConversationIcon.vue +++ b/src/components/ConversationIcon.vue @@ -31,6 +31,13 @@ <div v-if="iconClass" class="avatar icon" :class="iconClass" /> + <!-- img is used here instead of NcAvatar to explicitly set key required to avoid glitching in virtual scrolling --> + <img v-else-if="FEATURE_FLAGS.CONVERSATIONS_LIST__VIRTUAL_SCROLLING && !isOneToOne" + :key="avatarUrl" + :src="avatarUrl" + :width="size" + :height="size" + style="border-radius: 100%"> <NcAvatar v-else-if="!isOneToOne" :url="avatarUrl" :size="size" /> diff --git a/src/components/LeftSidebar/LeftSidebar.vue b/src/components/LeftSidebar/LeftSidebar.vue index 4194209fb..822e86fec 100644 --- a/src/components/LeftSidebar/LeftSidebar.vue +++ b/src/components/LeftSidebar/LeftSidebar.vue @@ -34,8 +34,9 @@ </div> <template #list> <li ref="container" class="left-sidebar__list"> - <ul ref="scroller" + <ul :ref="FEATURE_FLAGS.CONVERSATIONS_LIST__VIRTUAL_SCROLLING ? null : 'scroller'" class="scroller" + :style="FEATURE_FLAGS.CONVERSATIONS_LIST__VIRTUAL_SCROLLING && 'height: 100%'" @scroll="debounceHandleScroll"> <NcAppNavigationCaption :class="{'hidden-visually': !isSearching}" :title="t('spreed', 'Conversations')" /> @@ -45,10 +46,25 @@ <!-- Conversations List --> <template v-if="!isSearching"> - <Conversation v-for="item of conversationsList" - :key="item.id" - :ref="`conversation-${item.token}`" - :item="item" /> + <RecycleScroller v-if="FEATURE_FLAGS.CONVERSATIONS_LIST__VIRTUAL_SCROLLING" + ref="scroller" + style="height: 100%" + list-tag="li" + item-tag="ul" + :items="conversationsList" + :item-size="64" + key-field="token" + @scroll="debounceHandleScroll"> + <template #default="{ item }"> + <Conversation :ref="`conversation-${item.token}`" :item="item" /> + </template> + </RecycleScroller> + <template v-else> + <Conversation v-for="item of conversationsList" + :key="item.id" + :ref="`conversation-${item.token}`" + :item="item" /> + </template> </template> <!-- Search results --> @@ -142,6 +158,7 @@ <script> import debounce from 'debounce' +import { RecycleScroller } from 'vue-virtual-scroller' import { showError } from '@nextcloud/dialogs' import { emit } from '@nextcloud/event-bus' @@ -167,8 +184,11 @@ import { searchListedConversations, } from '../../services/conversationsService.js' import { EventBus } from '../../services/EventBus.js' +import { FEATURE_FLAGS } from '../../services/localFeatureFlagsService.js' import CancelableRequest from '../../utils/cancelableRequest.js' +import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' + export default { name: 'LeftSidebar', @@ -184,6 +204,7 @@ export default { LoadingPlaceholder, NcListItem, ConversationIcon, + RecycleScroller, }, mixins: [ @@ -551,26 +572,39 @@ export default { scrollToConversation(token) { this.$nextTick(() => { - // In Vue 2 ref on v-for is always an array and its order is not guaranteed to match the order of v-for source - // See https://github.com/vuejs/vue/issues/4952#issuecomment-280661367 - // Fixed in Vue 3 - // Temp solution - use unique ref name for each v-for element. The value is still array but with one element - // TODO: Vue3: remove [0] here or use object for template refs - const conversation = this.$refs[`conversation-${token}`]?.[0].$el - if (!conversation) { - return - } + if (FEATURE_FLAGS.CONVERSATIONS_LIST__VIRTUAL_SCROLLING) { + (async () => { + // FIXME + await this.$nextTick() + await this.$nextTick() + await this.$nextTick() + const index = this.conversationsList.findIndex((conversation) => conversation.token === token) + if (index !== null) { + this.$refs.scroller.scrollToPosition(index * 64) + } + })() + } else { + // In Vue 2 ref on v-for is always an array and its order is not guaranteed to match the order of v-for source + // See https://github.com/vuejs/vue/issues/4952#issuecomment-280661367 + // Fixed in Vue 3 + // Temp solution - use unique ref name for each v-for element. The value is still array but with one element + // TODO: Vue3: remove [0] here or use object for template refs + const conversation = this.$refs[`conversation-${token}`]?.[0].$el + if (!conversation) { + return + } - if (this.elementIsBelowViewpoint(this.$refs.container, conversation)) { - this.$refs.container.scrollTo({ - top: conversation.offsetTop + conversation.offsetHeight * 2 - this.$refs.container.clientHeight, - behavior: 'smooth', - }) - } else if (this.elementIsAboveViewpoint(this.$refs.container, conversation)) { - this.$refs.container.scrollTo({ - top: conversation.offsetTop - conversation.offsetHeight, - behavior: 'smooth', - }) + if (this.elementIsBelowViewpoint(this.$refs.container, conversation)) { + this.$refs.container.scrollTo({ + top: conversation.offsetTop + conversation.offsetHeight * 2 - this.$refs.container.clientHeight, + behavior: 'smooth', + }) + } else if (this.elementIsAboveViewpoint(this.$refs.container, conversation)) { + this.$refs.container.scrollTo({ + top: conversation.offsetTop - conversation.offsetHeight, + behavior: 'smooth', + }) + } } }) }, diff --git a/src/services/localFeatureFlagsService.js b/src/services/localFeatureFlagsService.js index 30480855f..ad741525a 100644 --- a/src/services/localFeatureFlagsService.js +++ b/src/services/localFeatureFlagsService.js @@ -25,6 +25,7 @@ export const FEATURE_FLAGS = { CONVERSATIONS_LIST__SOFT_CONVERSATIONS_UPDATE: false, CONVERSATIONS_LIST__REVERT_USER_STATUS_SYNC: false, CONVERSATIONS_LIST__HIDDEN_AVATARS: false, + CONVERSATIONS_LIST__VIRTUAL_SCROLLING: false, } /** |