logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git
commit: a7387332ed3a3a26de1f818d0bf6b967ab778d4a
parent 3e8aa0a3984574d7ab191efd837dfa245e328968
Author: HJ <30-hj@users.noreply.git.pleroma.social>
Date:   Tue,  3 Jan 2023 18:57:26 +0000

Merge branch 'from/develop/tusooa/emoji-picker-lazy' into 'develop'

Remove lozad, use virtual scrolling

See merge request pleroma/pleroma-fe!1717

Diffstat:

Mpackage.json2+-
Msrc/boot/after_store.js3+++
Msrc/components/emoji_picker/emoji_picker.js117+++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/components/emoji_picker/emoji_picker.scss1+
Msrc/components/emoji_picker/emoji_picker.vue77++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/components/still-image/still-image.js3++-
Msrc/components/still-image/still-image.vue1+
Myarn.lock29++++++++++++++++++++++++-----
8 files changed, 136 insertions(+), 97 deletions(-)

diff --git a/package.json b/package.json @@ -34,7 +34,6 @@ "escape-html": "1.0.3", "js-cookie": "3.0.1", "localforage": "1.10.0", - "lozad": "1.16.0", "parse-link-header": "2.0.0", "phoenix": "1.6.2", "punycode.js": "2.1.0", @@ -46,6 +45,7 @@ "vue-i18n": "9.2.2", "vue-router": "4.1.6", "vue-template-compiler": "2.7.14", + "vue-virtual-scroller": "^2.0.0-beta.7", "vuex": "4.1.0" }, "devDependencies": { diff --git a/src/boot/after_store.js b/src/boot/after_store.js @@ -1,6 +1,8 @@ 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' @@ -397,6 +399,7 @@ const afterStoreSetup = async ({ store, i18n }) => { app.use(vClickOutside) app.use(VBodyScrollLock) + app.use(VueVirtualScroller) app.component('FAIcon', FontAwesomeIcon) app.component('FALayers', FontAwesomeLayers) diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js @@ -3,7 +3,6 @@ import Checkbox from '../checkbox/checkbox.vue' import Popover from 'src/components/popover/popover.vue' import StillImage from '../still-image/still-image.vue' import { ensureFinalFallback } from '../../i18n/languages.js' -import lozad from 'lozad' import { library } from '@fortawesome/fontawesome-svg-core' import { faBoxOpen, @@ -19,7 +18,7 @@ import { faCode, faFlag } from '@fortawesome/free-solid-svg-icons' -import { debounce, trim } from 'lodash' +import { debounce, trim, chunk } from 'lodash' library.add( faBoxOpen, @@ -82,6 +81,17 @@ const filterByKeyword = (list, keyword = '', languages, nameLocalizer) => { return orderedEmojiList.flat() } +const getOffset = (elem) => { + const style = elem.style.transform + const res = /translateY\((\d+)px\)/.exec(style) + if (!res) { return 0 } + return res[1] +} + +const toHeaderId = id => { + return id.replace(/^row-\d+-/, '') +} + const EmojiPicker = { props: { enableStickerPicker: { @@ -102,7 +112,8 @@ const EmojiPicker = { contentLoaded: false, groupRefs: {}, emojiRefs: {}, - filteredEmojiGroups: [] + filteredEmojiGroups: [], + width: 0 } }, components: { @@ -125,9 +136,6 @@ const EmojiPicker = { setGroupRef (name) { return el => { this.groupRefs[name] = el } }, - setEmojiRef (name) { - return el => { this.emojiRefs[name] = el } - }, onPopoverShown () { this.$emit('show') }, @@ -147,18 +155,21 @@ const EmojiPicker = { } 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) + onScroll (startIndex, endIndex, visibleStartIndex, visibleEndIndex) { + const target = this.$refs['emoji-groups'].$el + this.scrolledGroup(target, visibleStartIndex, visibleEndIndex) }, - scrolledGroup (target) { + scrolledGroup (target, start, end) { const top = target.scrollTop + 5 this.$nextTick(() => { - this.allEmojiGroups.forEach(group => { + this.emojiItems.slice(start, end + 1).forEach(group => { + const headerId = toHeaderId(group.id) const ref = this.groupRefs['group-' + group.id] - if (ref && ref.offsetTop <= top) { - this.activeGroup = group.id + if (!ref) { return } + const elem = ref.$el.parentElement + if (!elem) { return } + if (elem && getOffset(elem) <= top) { + this.activeGroup = headerId } }) this.scrollHeader() @@ -181,14 +192,10 @@ const EmojiPicker = { setScroll(right + margin - headerCont.clientWidth) } }, - highlight (key) { - const ref = this.groupRefs['group-' + key] - const top = ref.offsetTop + highlight (groupId) { this.setShowStickers(false) - this.activeGroup = key - this.$nextTick(() => { - this.$refs['emoji-groups'].scrollTop = top + 1 - }) + const indexInList = this.emojiItems.findIndex(k => k.id === groupId) + this.$refs['emoji-groups'].scrollToItem(indexInList) }, updateScrolledClass (target) { if (target.scrollTop <= 5) { @@ -208,43 +215,13 @@ const EmojiPicker = { filterByKeyword (list, keyword) { return filterByKeyword(list, keyword, this.languages, this.maybeLocalizedEmojiName) }, - initializeLazyLoad () { - this.destroyLazyLoad() - this.$nextTick(() => { - this.$lozad = lozad('.still-image.emoji-picker-emoji', { - load: el => { - const name = el.getAttribute('data-emoji-name') - const vn = this.emojiRefs[name] - if (!vn) { - return - } - - vn.loadLazy() - } - }) - this.$lozad.observe() - }) - }, - waitForDomAndInitializeLazyLoad () { - this.$nextTick(() => this.initializeLazyLoad()) - }, - destroyLazyLoad () { - if (this.$lozad) { - if (this.$lozad.observer) { - this.$lozad.observer.disconnect() - } - if (this.$lozad.mutationObserver) { - this.$lozad.mutationObserver.disconnect() - } - } - }, onShowing () { const oldContentLoaded = this.contentLoaded + this.recalculateItemPerRow() this.$nextTick(() => { this.$refs.search.focus() }) this.contentLoaded = true - this.waitForDomAndInitializeLazyLoad() this.filteredEmojiGroups = this.getFilteredEmojiGroups() if (!oldContentLoaded) { this.$nextTick(() => { @@ -261,6 +238,14 @@ const EmojiPicker = { emojis: this.filterByKeyword(group.emojis, trim(this.keyword)) })) .filter(group => group.emojis.length > 0) + }, + recalculateItemPerRow () { + this.$nextTick(() => { + if (!this.$refs['emoji-groups']) { + return + } + this.width = this.$refs['emoji-groups'].$el.offsetWidth + }) } }, watch: { @@ -269,14 +254,22 @@ const EmojiPicker = { this.debouncedHandleKeywordChange() }, allCustomGroups () { - this.waitForDomAndInitializeLazyLoad() this.filteredEmojiGroups = this.getFilteredEmojiGroups() } }, - destroyed () { - this.destroyLazyLoad() - }, 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 }, @@ -314,10 +307,20 @@ const EmojiPicker = { }, debouncedHandleKeywordChange () { return debounce(() => { - this.waitForDomAndInitializeLazyLoad() this.filteredEmojiGroups = this.getFilteredEmojiGroups() }, 500) }, + 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), []) + }, languages () { return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage) }, diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss @@ -74,6 +74,7 @@ $emoji-picker-emoji-size: 32px; } .emoji-groups { + height: 100%; min-height: 200px; } diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue @@ -74,45 +74,56 @@ @input="$event.target.composing = false" > </div> - <div + <DynamicScroller ref="emoji-groups" class="emoji-groups" :class="groupsScrolledClass" - @scroll="onScroll" + :min-item-size="minItemSize" + :items="emojiItems" + :emit-update="true" + @update="onScroll" + @visible="recalculateItemPerRow" > - <div - v-for="group in filteredEmojiGroups" - :key="group.id" - class="emoji-group" - > - <h6 + <template #default="{ item: group, index, active }"> + <DynamicScrollerItem :ref="setGroupRef('group-' + group.id)" - class="emoji-group-title" - > - {{ group.text }} - </h6> - <span - v-for="emoji in group.emojis" - :key="group.id + emoji.displayText" - :title="maybeLocalizedEmojiName(emoji)" - class="emoji-item" - @click.stop.prevent="onEmoji(emoji)" + :item="group" + :active="active" + :data-index="index" + :size-dependencies="[group.emojis.length]" > - <span - v-if="!emoji.imageUrl" - class="emoji-picker-emoji -unicode" - >{{ emoji.replacement }}</span> - <still-image - v-else - :ref="setEmojiRef(group.id + emoji.displayText)" - class="emoji-picker-emoji -custom" - :data-src="emoji.imageUrl" - :data-emoji-name="group.id + emoji.displayText" - /> - </span> - <span :ref="setGroupRef('group-end-' + group.id)" /> - </div> - </div> + <div + class="emoji-group" + > + <h6 + v-if="group.isFirstRow" + class="emoji-group-title" + > + {{ group.text }} + </h6> + <span + v-for="emoji in group.emojis" + :key="group.id + emoji.displayText" + :title="maybeLocalizedEmojiName(emoji)" + class="emoji-item" + @click.stop.prevent="onEmoji(emoji)" + > + <span + v-if="!emoji.imageUrl" + class="emoji-picker-emoji -unicode" + >{{ emoji.replacement }}</span> + <still-image + v-else + class="emoji-picker-emoji -custom" + loading="lazy" + :src="emoji.imageUrl" + :data-emoji-name="group.id + emoji.displayText" + /> + </span> + </div> + </DynamicScrollerItem> + </template> + </DynamicScroller> <div class="keep-open"> <Checkbox v-model="keepOpen"> {{ $t('emoji.keep_open') }} diff --git a/src/components/still-image/still-image.js b/src/components/still-image/still-image.js @@ -8,7 +8,8 @@ const StillImage = { 'alt', 'height', 'width', - 'dataSrc' + 'dataSrc', + 'loading' ], data () { return { diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue @@ -17,6 +17,7 @@ :data-src="dataSrc" :src="realSrc" :referrerpolicy="referrerpolicy" + :loading="loading" @load="onLoad" @error="onError" > diff --git a/yarn.lock b/yarn.lock @@ -6103,11 +6103,6 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" -lozad@1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/lozad/-/lozad-1.16.0.tgz#86ce732c64c69926ccdebb81c8c90bb3735948b4" - integrity sha512-JBr9WjvEFeKoyim3svo/gsQPTkgG/mOHJmDctZ/+U9H3ymUuvEkqpn8bdQMFsvTMcyRJrdJkLv0bXqGm0sP72w== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -6375,6 +6370,11 @@ minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +mitt@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mitt/-/mitt-2.1.0.tgz#f740577c23176c6205b121b2973514eade1b2230" + integrity sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg== + mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -8810,6 +8810,16 @@ vue-loader@17.0.1: hash-sum "^2.0.0" loader-utils "^2.0.0" +vue-observe-visibility@^2.0.0-alpha.1: + version "2.0.0-alpha.1" + resolved "https://registry.yarnpkg.com/vue-observe-visibility/-/vue-observe-visibility-2.0.0-alpha.1.tgz#1e4eda7b12562161d58984b7e0dea676d83bdb13" + integrity sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g== + +vue-resize@^2.0.0-alpha.1: + version "2.0.0-alpha.1" + resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz#43eeb79e74febe932b9b20c5c57e0ebc14e2df3a" + integrity sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg== + vue-router@4.1.6: version "4.1.6" resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.1.6.tgz#b70303737e12b4814578d21d68d21618469375a1" @@ -8833,6 +8843,15 @@ vue-template-compiler@2.7.14: de-indent "^1.0.2" he "^1.2.0" +vue-virtual-scroller@^2.0.0-beta.7: + version "2.0.0-beta.7" + resolved "https://registry.yarnpkg.com/vue-virtual-scroller/-/vue-virtual-scroller-2.0.0-beta.7.tgz#4ea8158638c84b2033b001a8b26c5fcb6896b271" + integrity sha512-OrouVj1i2939jaLjVfu8f5fsDlbzhAb4bOsYZYrAkpcVLylAmMoGtIL7eT3hJrdTiaKbwQpRdnv7DKf9Fn+tHg== + dependencies: + mitt "^2.1.0" + vue-observe-visibility "^2.0.0-alpha.1" + vue-resize "^2.0.0-alpha.1" + vue@3.2.45: version "3.2.45" resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.45.tgz#94a116784447eb7dbd892167784619fef379b3c8"