summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGrigorii K. Shartsev <me@shgk.me>2023-06-27 13:23:47 +0200
committerGrigorii K. Shartsev <me@shgk.me>2023-06-27 13:23:47 +0200
commit6bb2d5cc5a5b1013917c3aea522424f2d984ef2a (patch)
tree9825ce9106981fb0d04ce764b65edeb25a5daf88
parentced01aead0308d4a5d56a240f25a3f172b6aac38 (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.json60
-rw-r--r--package.json1
-rw-r--r--src/components/ConversationIcon.vue7
-rw-r--r--src/components/LeftSidebar/LeftSidebar.vue82
-rw-r--r--src/services/localFeatureFlagsService.js1
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,
}
/**