Merge remote-tracking branch 'upstream/feat/custom-virtual-scrolling' into shigusegubu

* upstream/feat/custom-virtual-scrolling:
  whoops
  fix data property being called the wrong name in conversation
  fix warnings and console errors
  fix minor bugs
  make virtual scrolling optional in case people want to be able to ctrl-f all page
  rename hidden stuff to virtualHidden, remove log
  add custom solution for virtual scrolling to ease ram and cpu use when scrolling for a long time
This commit is contained in:
Henry Jameson 2020-01-27 23:39:41 +02:00
commit 0f743af271
11 changed files with 101 additions and 17 deletions

View file

@ -35,7 +35,9 @@ const conversation = {
data () {
return {
highlight: null,
expanded: false
expanded: false,
// Approximate minimum height of a status, gets overwritten with real one
virtualHeight: '120px'
}
},
props: [
@ -44,7 +46,8 @@ const conversation = {
'isPage',
'pinnedStatusIdsObject',
'inProfile',
'profileUserId'
'profileUserId',
'virtualHidden'
],
created () {
if (this.isPage) {
@ -102,6 +105,9 @@ const conversation = {
},
isExpanded () {
return this.expanded || this.isPage
},
hiddenStyle () {
return this.virtualHidden ? { height: this.virtualHeight } : {}
}
},
components: {
@ -121,6 +127,9 @@ const conversation = {
if (value) {
this.fetchConversation()
}
},
virtualHidden (value) {
this.virtualHeight = `${this.$el.clientHeight}px`
}
},
methods: {

View file

@ -1,10 +1,11 @@
<template>
<div
:style="hiddenStyle"
class="timeline panel-default"
:class="[isExpanded ? 'panel' : 'panel-disabled']"
>
<div
v-if="isExpanded"
v-if="isExpanded && !virtualHidden"
class="panel-heading conversation-heading"
>
<span class="title"> {{ $t('timeline.conversation') }} </span>
@ -28,6 +29,7 @@
:replies="getReplies(status.id)"
:in-profile="inProfile"
:profile-user-id="profileUserId"
:virtual-hidden="virtualHidden"
class="status-fadein panel-body"
@goto="setHighlight"
@toggleExpanded="toggleExpanded"

View file

@ -76,9 +76,9 @@
<li>
<Checkbox v-model="useStreamingApi">
{{ $t('settings.useStreamingApi') }}
<br/>
<br>
<small>
{{ $t('settings.useStreamingApiWarning') }}
{{ $t('settings.useStreamingApiWarning') }}
</small>
</Checkbox>
</li>
@ -92,6 +92,11 @@
{{ $t('settings.reply_link_preview') }}
</Checkbox>
</li>
<li>
<Checkbox v-model="virtualScrolling">
{{ $t('settings.virtual_scrolling') }}
</Checkbox>
</li>
</ul>
</div>

View file

@ -34,7 +34,8 @@ const Status = {
'inlineExpanded',
'showPinned',
'inProfile',
'profileUserId'
'profileUserId',
'virtualHidden'
],
data () {
return {
@ -121,7 +122,7 @@ const Status = {
return this.mergedConfig.hideFilteredStatuses
},
hideStatus () {
return (this.hideReply || this.deleted) || (this.muted && this.hideFilteredStatuses)
return (this.hideReply || this.deleted) || (this.muted && this.hideFilteredStatuses) || this.virtualHidden
},
isFocused () {
// retweet or root of an expanded conversation

View file

@ -18,14 +18,16 @@ const StillImage = {
},
methods: {
onLoad () {
this.imageLoadHandler && this.imageLoadHandler(this.$refs.src)
const image = this.$refs.src
if (!image) return
this.imageLoadHandler && this.imageLoadHandler(image)
const canvas = this.$refs.canvas
if (!canvas) return
const width = this.$refs.src.naturalWidth
const height = this.$refs.src.naturalHeight
const width = image.naturalWidth
const height = image.naturalHeight
canvas.width = width
canvas.height = height
canvas.getContext('2d').drawImage(this.$refs.src, 0, 0, width, height)
canvas.getContext('2d').drawImage(image, 0, 0, width, height)
},
onError () {
this.imageLoadError && this.imageLoadError()

View file

@ -32,7 +32,8 @@ const Timeline = {
return {
paused: false,
unfocused: false,
bottomedOut: false
bottomedOut: false,
virtualScrollIndex: 0
}
},
computed: {
@ -68,6 +69,16 @@ const Timeline = {
},
pinnedStatusIdsObject () {
return keyBy(this.pinnedStatusIds)
},
statusesToDisplay () {
const amount = this.timeline.visibleStatuses.length
const statusesPerSide = Math.ceil(Math.max(10, window.innerHeight / 100))
const min = Math.max(0, this.virtualScrollIndex - statusesPerSide)
const max = Math.min(amount, this.virtualScrollIndex + statusesPerSide)
return this.timeline.visibleStatuses.slice(min, max).map(_ => _.id)
},
virtualScrollingEnabled () {
return this.$store.getters.mergedConfig.virtualScrolling
}
},
components: {
@ -79,7 +90,7 @@ const Timeline = {
const credentials = store.state.users.currentUser.credentials
const showImmediately = this.timeline.visibleStatuses.length === 0
window.addEventListener('scroll', this.scrollLoad)
window.addEventListener('scroll', this.handleScroll)
if (store.state.api.fetchers[this.timelineName]) { return false }
@ -100,7 +111,7 @@ const Timeline = {
window.addEventListener('keydown', this.handleShortKey)
},
destroyed () {
window.removeEventListener('scroll', this.scrollLoad)
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('keydown', this.handleShortKey)
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
@ -142,6 +153,48 @@ const Timeline = {
}
})
}, 1000, this),
determineVisibleStatuses () {
if (!this.$refs.timeline) return
const statuses = this.$refs.timeline.children
if (statuses.length === 0) return
const bodyBRect = document.body.getBoundingClientRect()
const height = Math.max(bodyBRect.height, -(bodyBRect.y))
const centerOfScreen = window.pageYOffset + (window.innerHeight * 0.5)
// Start from approximating the index of some visible status by using the
// the center of the screen on the timeline.
let approxIndex = Math.floor(statuses.length * (centerOfScreen / height))
let err = statuses[approxIndex].getBoundingClientRect().y
// if we have a previous scroll index that can be used, test if it's
// closer than the previous approximation, use it if so
if (
this.virtualScrollIndex < statuses.length &&
Math.abs(err) > statuses[this.virtualScrollIndex].getBoundingClientRect().y
) {
approxIndex = this.virtualScrollIndex
err = statuses[approxIndex].getBoundingClientRect().y
}
// if the status is too far from viewport, check the next/previous ones if
// they happen to be better
while (err < -100 && approxIndex < statuses.length - 1) {
approxIndex++
err = statuses[approxIndex].getBoundingClientRect().y
}
while (err > window.innerHeight + 100 && approxIndex > 0) {
approxIndex--
err = statuses[approxIndex].getBoundingClientRect().y
}
// this status is now the center point for virtual scrolling and visible
// statuses will be nearby statuses before and after it
this.virtualScrollIndex = approxIndex
},
scrollLoad (e) {
const bodyBRect = document.body.getBoundingClientRect()
const height = Math.max(bodyBRect.height, -(bodyBRect.y))
@ -152,6 +205,10 @@ const Timeline = {
this.fetchOlderStatuses()
}
},
handleScroll: throttle(function (e) {
this.determineVisibleStatuses()
this.scrollLoad(e)
}, 100),
handleVisibilityChange () {
this.unfocused = document.hidden
}

View file

@ -34,7 +34,10 @@
</div>
</div>
<div :class="classes.body">
<div class="timeline">
<div
ref="timeline"
class="timeline"
>
<template v-for="statusId in pinnedStatusIds">
<conversation
v-if="timeline.statusesObject[statusId]"
@ -56,6 +59,7 @@
:collapsable="true"
:in-profile="inProfile"
:profile-user-id="userId"
:virtual-hidden="virtualScrollingEnabled && !statusesToDisplay.includes(status.id)"
/>
</template>
</div>

View file

@ -384,6 +384,7 @@
"false": "no",
"true": "yes"
},
"virtual_scrolling": "Optimize timeline rendering",
"fun": "Fun",
"greentext": "Meme arrows",
"notifications": "Notifications",

View file

@ -228,7 +228,8 @@
"values": {
"false": "pois päältä",
"true": "päällä"
}
},
"virtual_scrolling": "Optimoi aikajanan suorituskykyä"
},
"time": {
"day": "{0} päivä",

View file

@ -49,7 +49,8 @@ export const defaultState = {
useContainFit: false,
greentext: undefined, // instance default
hidePostStats: undefined, // instance default
hideUserStats: undefined // instance default
hideUserStats: undefined, // instance default
virtualScrolling: undefined // instance default
}
// caching the instance default properties

View file

@ -34,6 +34,7 @@ const defaultState = {
showFeaturesPanel: true,
minimalScopesMode: false,
greentext: false,
virtualScrolling: true,
// Nasty stuff
pleromaBackend: true,