Add virtual scrolling to emoji picker
Some checks reported errors
continuous-integration/drone/push Build was killed
Some checks reported errors
continuous-integration/drone/push Build was killed
there is a graphical glitch when swapping from unicode but it works! Signed-off-by: Sam Therapy <sam@samtherapy.net>
This commit is contained in:
parent
b731d9e471
commit
9d7f25a91d
6 changed files with 154 additions and 38 deletions
|
@ -44,6 +44,7 @@
|
|||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "4.0.14",
|
||||
"vue-template-compiler": "2.6.11",
|
||||
"vue-virtual-scroller": "^2.0.0-beta.7",
|
||||
"vuex": "4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -2,6 +2,9 @@ import Cookies from 'js-cookie'
|
|||
import { createApp } from 'vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import vClickOutside from 'click-outside-vue3'
|
||||
import VueVirtualScroller from 'vue-virtual-scroller'
|
||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||
|
||||
|
||||
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
|
||||
import { config } from '@fortawesome/fontawesome-svg-core';
|
||||
|
@ -428,6 +431,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||
app.use(vClickOutside)
|
||||
app.use(VBodyScrollLock)
|
||||
app.use(DomNodeToComponent)
|
||||
app.use(VueVirtualScroller)
|
||||
|
||||
app.component('FAIcon', FontAwesomeIcon)
|
||||
app.component('FALayers', FontAwesomeLayers)
|
||||
|
|
|
@ -38,7 +38,11 @@ const EmojiPicker = {
|
|||
keepOpen: false,
|
||||
customEmojiBufferSlice: LOAD_EMOJI_BY,
|
||||
customEmojiTimeout: null,
|
||||
customEmojiLoadAllConfirmed: false
|
||||
customEmojiLoadAllConfirmed: false,
|
||||
groupRefs: {},
|
||||
emojiRefs: {},
|
||||
filteredEmojiGroups: [],
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -56,16 +60,18 @@ const EmojiPicker = {
|
|||
const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
|
||||
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
|
||||
},
|
||||
onScroll (e) {
|
||||
const target = (e && e.target) || this.$refs['emoji-groups']
|
||||
this.updateScrolledClass(target)
|
||||
this.scrolledGroup(target)
|
||||
this.triggerLoadMore(target)
|
||||
onScroll(startIndex, endIndex, visibleStartIndex, visibleEndIndex) {
|
||||
const target = this.$refs[ 'emoji-groups' ].$el
|
||||
this.scrolledGroup(target, visibleStartIndex, visibleEndIndex)
|
||||
},
|
||||
onWheel (e) {
|
||||
e.preventDefault()
|
||||
this.$refs['emoji-tabs'].scrollBy(e.deltaY, 0)
|
||||
},
|
||||
setGroupRef(name) {
|
||||
return el => { this.groupRefs[ name ] = el }
|
||||
},
|
||||
|
||||
highlight (key) {
|
||||
this.setShowStickers(false)
|
||||
this.activeGroup = key
|
||||
|
@ -98,17 +104,46 @@ const EmojiPicker = {
|
|||
this.loadEmoji()
|
||||
}
|
||||
},
|
||||
scrolledGroup (target) {
|
||||
scrolledGroup(target, start, end) {
|
||||
const top = target.scrollTop + 5
|
||||
this.$nextTick(() => {
|
||||
this.emojisView.forEach(group => {
|
||||
const ref = this.$refs['group-' + group.id]
|
||||
if (ref.offsetTop <= top) {
|
||||
this.activeGroup = group.id
|
||||
this.emojiItems.slice(start, end + 1).forEach(group => {
|
||||
const headerId = toHeaderId(group.id)
|
||||
const ref = this.groupRefs[ 'group-' + group.id ]
|
||||
if (!ref) { return }
|
||||
const elem = ref.$el.parentElement
|
||||
if (!elem) { return }
|
||||
if (elem && getOffset(elem) <= top) {
|
||||
this.activeGroup = headerId
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
onShowing() {
|
||||
const oldContentLoaded = this.contentLoaded
|
||||
this.recalculateItemPerRow()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.search.focus()
|
||||
})
|
||||
this.contentLoaded = true
|
||||
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
||||
if (!oldContentLoaded) {
|
||||
this.$nextTick(() => {
|
||||
if (this.defaultGroup) {
|
||||
this.highlight(this.defaultGroup)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
getFilteredEmojiGroups() {
|
||||
return this.allEmojiGroups
|
||||
.map(group => ({
|
||||
...group,
|
||||
emojis: this.filterByKeyword(group.emojis, trim(this.keyword))
|
||||
}))
|
||||
.filter(group => group.emojis.length > 0)
|
||||
},
|
||||
|
||||
loadEmoji () {
|
||||
const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length
|
||||
|
||||
|
@ -154,6 +189,18 @@ const EmojiPicker = {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
minItemSize() {
|
||||
return this.emojiHeight
|
||||
},
|
||||
emojiHeight() {
|
||||
return 32 + 4
|
||||
},
|
||||
emojiWidth() {
|
||||
return 32 + 4
|
||||
},
|
||||
itemPerRow() {
|
||||
return this.width ? Math.floor(this.width / this.emojiWidth - 1) : 6
|
||||
},
|
||||
activeGroupView () {
|
||||
return this.showingStickers ? '' : this.activeGroup
|
||||
},
|
||||
|
@ -195,6 +242,17 @@ const EmojiPicker = {
|
|||
}
|
||||
].concat(emojiPacks)
|
||||
},
|
||||
emojiItems () {
|
||||
return this.filteredEmojiGroups.map(group =>
|
||||
chunk(group.emojis, this.itemPerRow)
|
||||
.map((items, index) => ({
|
||||
...group,
|
||||
id: index === 0 ? group.id : `row-${index}-${group.id}`,
|
||||
emojis: items,
|
||||
isFirstRow: index === 0
|
||||
})))
|
||||
.reduce((a, c) => a.concat(c), [])
|
||||
},
|
||||
sortedEmoji () {
|
||||
const customEmojis = this.$store.state.instance.customEmoji || []
|
||||
const sortedEmojiGroups = new Map()
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
>
|
||||
<span
|
||||
v-for="group in emojis"
|
||||
:ref="setGroupRef('group-' + group.id)"
|
||||
:key="group.id"
|
||||
class="emoji-tabs-item"
|
||||
:class="{
|
||||
|
@ -51,39 +52,52 @@
|
|||
@input="$event.target.composing = false"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
<DynamicScroller
|
||||
ref="emoji-groups"
|
||||
class="emoji-groups"
|
||||
:class="groupsScrolledClass"
|
||||
:items="emojisView"
|
||||
:min-item-size="minItemSize"
|
||||
:emit-update="true"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<div
|
||||
v-for="group in emojisView"
|
||||
:key="group.id"
|
||||
class="emoji-group"
|
||||
>
|
||||
<h6
|
||||
:ref="'group-' + group.id"
|
||||
class="emoji-group-title"
|
||||
<template #default="{ item: group, index, active }">
|
||||
<DynamicScrollerItem
|
||||
:ref="setGroupRef('group-' + group.id)"
|
||||
:item="group"
|
||||
:active="active"
|
||||
:data-index="index"
|
||||
:size-dependencies="[group.emojis.length]"
|
||||
>
|
||||
{{ group.text }}
|
||||
</h6>
|
||||
<span
|
||||
v-for="emoji in group.emojis"
|
||||
:key="group.id + emoji.displayText"
|
||||
:title="emoji.displayText"
|
||||
class="emoji-item"
|
||||
@click.stop.prevent="onEmoji(emoji)"
|
||||
>
|
||||
<span v-if="!emoji.imageUrl">{{ emoji.replacement }}</span>
|
||||
<img
|
||||
v-else
|
||||
:src="emoji.imageUrl"
|
||||
<div
|
||||
class="emoji-group"
|
||||
>
|
||||
</span>
|
||||
<span :ref="'group-end-' + group.id" />
|
||||
</div>
|
||||
</div>
|
||||
<h6
|
||||
class="emoji-group-title"
|
||||
>
|
||||
{{ group.text }}
|
||||
</h6>
|
||||
<span
|
||||
v-for="emoji in group.emojis"
|
||||
:key="group.id + emoji.displayText"
|
||||
:title="emoji.displayText"
|
||||
class="emoji-item"
|
||||
@click.stop.prevent="onEmoji(emoji)"
|
||||
>
|
||||
<span
|
||||
v-if="!emoji.imageUrl"
|
||||
>{{ emoji.replacement }}</span>
|
||||
<img
|
||||
v-else
|
||||
:src="emoji.imageUrl"
|
||||
loading="lazy"
|
||||
>
|
||||
</span>
|
||||
<span :ref="'group-end-' + group.id" />
|
||||
</div>
|
||||
</DynamicScrollerItem>
|
||||
</template>
|
||||
</DynamicScroller>
|
||||
<div class="keep-open">
|
||||
<Checkbox v-model="keepOpen">
|
||||
{{ $t('emoji.keep_open') }}
|
||||
|
|
|
@ -196,7 +196,7 @@
|
|||
|
||||
<div
|
||||
class="language-selector"
|
||||
>
|
||||
>
|
||||
<Select
|
||||
id="post-language"
|
||||
v-model="newStatus.language"
|
||||
|
|
39
yarn.lock
39
yarn.lock
|
@ -8368,6 +8368,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mitt@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "mitt@npm:2.1.0"
|
||||
checksum: a8ed2f212b41be554c4abde8bf599394d1aa2f2ece30ff505aa367d98d186a03b37a8b0fdd01560e06f475d1b92fb90444f2db5607f30b9e1f60b099ebaa5c77
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mkdirp@npm:0.5.1":
|
||||
version: 0.5.1
|
||||
resolution: "mkdirp@npm:0.5.1"
|
||||
|
@ -9293,6 +9300,7 @@ __metadata:
|
|||
vue-router: "npm:4.0.14"
|
||||
vue-style-loader: "npm:^4.1.2"
|
||||
vue-template-compiler: "npm:2.6.11"
|
||||
vue-virtual-scroller: "npm:^2.0.0-beta.7"
|
||||
vuex: "npm:4.0.2"
|
||||
webpack: "npm:^5.75.0"
|
||||
webpack-dev-middleware: "npm:^5.3.3"
|
||||
|
@ -11824,6 +11832,24 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vue-observe-visibility@npm:^2.0.0-alpha.1":
|
||||
version: 2.0.0-alpha.1
|
||||
resolution: "vue-observe-visibility@npm:2.0.0-alpha.1"
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
checksum: 793924571c5ac5ea5c6079d76d8c73d3c3d007de3322a9ca8c97fc5cd578ed423cd74c1bcbbadcde7533ae94ea3e356a8c90b35c91567f4cc13e3748f0a31ae3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vue-resize@npm:^2.0.0-alpha.1":
|
||||
version: 2.0.0-alpha.1
|
||||
resolution: "vue-resize@npm:2.0.0-alpha.1"
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
checksum: 4476ae81ddb3c88c549a5e1b0b2216b9a70b941ff5737208dfbe1a8225e9b1a12aaf24dfc903aa0761a02c7929b9bc170ffc993b32b4bdcf0424490c613cf343
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vue-router@npm:4.0.14":
|
||||
version: 4.0.14
|
||||
resolution: "vue-router@npm:4.0.14"
|
||||
|
@ -11855,6 +11881,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vue-virtual-scroller@npm:^2.0.0-beta.7":
|
||||
version: 2.0.0-beta.7
|
||||
resolution: "vue-virtual-scroller@npm:2.0.0-beta.7"
|
||||
dependencies:
|
||||
mitt: "npm:^2.1.0"
|
||||
vue-observe-visibility: "npm:^2.0.0-alpha.1"
|
||||
vue-resize: "npm:^2.0.0-alpha.1"
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
checksum: e502a18177f3b6c18aa7a52a7153b5c910395fc89a1aaeb61d21f8fa7ee5ffdc9a641fd9072abce1362c214b463031e82e461a42a6ec394550a8151857e0ab16
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vue@npm:^3.2.31":
|
||||
version: 3.2.37
|
||||
resolution: "vue@npm:3.2.37"
|
||||
|
|
Loading…
Reference in a new issue