replace sanity button with loading on scroll

This commit is contained in:
Henry Jameson 2019-11-08 21:25:13 +02:00
parent a38d16273d
commit 9338c81f4a
2 changed files with 44 additions and 54 deletions

View file

@ -1,9 +1,12 @@
import { set } from 'vue' import { set } from 'vue'
import Checkbox from '../checkbox/checkbox.vue' import Checkbox from '../checkbox/checkbox.vue'
const LOAD_EMOJI_BY = 50 // At widest, approximately 20 emoji are visible in a row,
const LOAD_EMOJI_INTERVAL = 100 // loading 3 rows, could be overkill for narrow picker
const LOAD_EMOJI_SANE_AMOUNT = 500 const LOAD_EMOJI_BY = 60
// When to start loading new batch emoji, in pixels
const LOAD_EMOJI_MARGIN = 64
const filterByKeyword = (list, keyword = '') => { const filterByKeyword = (list, keyword = '') => {
return list.filter(x => x.displayText.includes(keyword)) return list.filter(x => x.displayText.includes(keyword))
@ -34,10 +37,22 @@ const EmojiPicker = {
Checkbox Checkbox
}, },
methods: { methods: {
onStickerUploaded (e) {
this.$emit('sticker-uploaded', e)
},
onStickerUploadFailed (e) {
this.$emit('sticker-upload-failed', e)
},
onEmoji (emoji) { onEmoji (emoji) {
const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen }) 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)
},
highlight (key) { highlight (key) {
const ref = this.$refs['group-' + key] const ref = this.$refs['group-' + key]
const top = ref[0].offsetTop const top = ref[0].offsetTop
@ -47,9 +62,7 @@ const EmojiPicker = {
this.$refs['emoji-groups'].scrollTop = top + 1 this.$refs['emoji-groups'].scrollTop = top + 1
}) })
}, },
scrolledGroup (e) { updateScrolledClass (target) {
const target = (e && e.target) || this.$refs['emoji-groups']
const top = target.scrollTop + 5
if (target.scrollTop <= 5) { if (target.scrollTop <= 5) {
this.groupsScrolledClass = 'scrolled-top' this.groupsScrolledClass = 'scrolled-top'
} else if (target.scrollTop >= target.scrollTopMax - 5) { } else if (target.scrollTop >= target.scrollTopMax - 5) {
@ -57,6 +70,26 @@ const EmojiPicker = {
} else { } else {
this.groupsScrolledClass = 'scrolled-middle' this.groupsScrolledClass = 'scrolled-middle'
} }
},
triggerLoadMore (target) {
const ref = this.$refs['group-end-custom'][0]
const bottom = ref.offsetTop + ref.offsetHeight
const scrollerBottom = target.scrollTop + target.clientHeight
const scrollerTop = target.scrollTop
// Loads more emoji when they come into view
const approachingBottom = bottom - scrollerBottom < LOAD_EMOJI_MARGIN
// Always load when at the very top in case there's no scroll space yet
const atTop = scrollerTop < 5
// Don't load when looking at unicode category
const bottomAboveViewport = bottom < scrollerTop
if (!bottomAboveViewport && (approachingBottom || atTop)) {
this.loadEmoji()
}
},
scrolledGroup (target) {
const top = target.scrollTop + 5
this.$nextTick(() => { this.$nextTick(() => {
this.emojisView.forEach(group => { this.emojisView.forEach(group => {
const ref = this.$refs['group-' + group.id] const ref = this.$refs['group-' + group.id]
@ -66,56 +99,34 @@ const EmojiPicker = {
}) })
}) })
}, },
loadEmojiInsane () {
this.customEmojiLoadAllConfirmed = true
this.continueEmojiLoad()
},
loadEmoji () { loadEmoji () {
const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length
const saneLoaded = this.customEmojiBuffer.length >= LOAD_EMOJI_SANE_AMOUNT &&
!this.customEmojiLoadAllConfirmed
if (allLoaded || saneLoaded) { if (allLoaded) {
return return
} }
this.customEmojiBufferSlice += LOAD_EMOJI_BY this.customEmojiBufferSlice += LOAD_EMOJI_BY
this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL)
}, },
startEmojiLoad (forceUpdate = false) { startEmojiLoad (forceUpdate = false) {
const bufferSize = this.customEmojiBuffer.length const bufferSize = this.customEmojiBuffer.length
const bufferPrefilledSane = bufferSize === LOAD_EMOJI_SANE_AMOUNT && !this.customEmojiLoadAllConfirmed
const bufferPrefilledAll = bufferSize === this.filteredEmoji.length const bufferPrefilledAll = bufferSize === this.filteredEmoji.length
if ((bufferPrefilledSane || bufferPrefilledAll) && !forceUpdate) { if (bufferPrefilledAll && !forceUpdate) {
return return
} }
if (this.customEmojiTimeout) {
window.clearTimeout(this.customEmojiTimeout)
}
this.customEmojiBufferSlice = LOAD_EMOJI_BY this.customEmojiBufferSlice = LOAD_EMOJI_BY
this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL)
},
continueEmojiLoad () {
this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL)
}, },
toggleStickers () { toggleStickers () {
this.showingStickers = !this.showingStickers this.showingStickers = !this.showingStickers
}, },
setShowStickers (value) { setShowStickers (value) {
this.showingStickers = value this.showingStickers = value
},
onStickerUploaded (e) {
this.$emit('sticker-uploaded', e)
},
onStickerUploadFailed (e) {
this.$emit('sticker-upload-failed', e)
} }
}, },
watch: { watch: {
keyword () { keyword () {
this.customEmojiLoadAllConfirmed = false this.customEmojiLoadAllConfirmed = false
this.scrolledGroup() this.onScroll()
this.startEmojiLoad(true) this.startEmojiLoad(true)
} }
}, },
@ -129,10 +140,6 @@ const EmojiPicker = {
} }
return 0 return 0
}, },
saneAmount () {
// for UI
return LOAD_EMOJI_SANE_AMOUNT
},
filteredEmoji () { filteredEmoji () {
return filterByKeyword( return filterByKeyword(
this.$store.state.instance.customEmoji || [], this.$store.state.instance.customEmoji || [],
@ -142,10 +149,6 @@ const EmojiPicker = {
customEmojiBuffer () { customEmojiBuffer () {
return this.filteredEmoji.slice(0, this.customEmojiBufferSlice) return this.filteredEmoji.slice(0, this.customEmojiBufferSlice)
}, },
askForSanity () {
return this.customEmojiBuffer.length >= LOAD_EMOJI_SANE_AMOUNT &&
!this.customEmojiLoadAllConfirmed
},
emojis () { emojis () {
const standardEmojis = this.$store.state.instance.emoji || [] const standardEmojis = this.$store.state.instance.emoji || []
const customEmojis = this.customEmojiBuffer const customEmojis = this.customEmojiBuffer

View file

@ -47,7 +47,7 @@
ref="emoji-groups" ref="emoji-groups"
class="emoji-groups" class="emoji-groups"
:class="groupsScrolledClass" :class="groupsScrolledClass"
@scroll="scrolledGroup" @scroll="onScroll"
> >
<div <div
v-for="group in emojisView" v-for="group in emojisView"
@ -73,6 +73,7 @@
:src="emoji.imageUrl" :src="emoji.imageUrl"
> >
</span> </span>
<span :ref="'group-end-' + group.id"/>
</div> </div>
</div> </div>
<div class="keep-open"> <div class="keep-open">
@ -80,20 +81,6 @@
{{ $t('emoji.keep_open') }} {{ $t('emoji.keep_open') }}
</Checkbox> </Checkbox>
</div> </div>
<div
v-if="askForSanity"
class="too-many-emoji"
>
<div class="alert warning hint">
{{ $t('emoji.load_all_hint', { saneAmount } ) }}
</div>
<button
class="btn btn-default"
@click.prevent="loadEmojiInsane"
>
{{ $t('emoji.load_all', { emojiAmount: filteredEmoji.length } ) }}
</button>
</div>
</div> </div>
<div <div
v-if="showingStickers" v-if="showingStickers"