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:
commit
0f743af271
11 changed files with 101 additions and 17 deletions
|
@ -35,7 +35,9 @@ const conversation = {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
highlight: null,
|
highlight: null,
|
||||||
expanded: false
|
expanded: false,
|
||||||
|
// Approximate minimum height of a status, gets overwritten with real one
|
||||||
|
virtualHeight: '120px'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
|
@ -44,7 +46,8 @@ const conversation = {
|
||||||
'isPage',
|
'isPage',
|
||||||
'pinnedStatusIdsObject',
|
'pinnedStatusIdsObject',
|
||||||
'inProfile',
|
'inProfile',
|
||||||
'profileUserId'
|
'profileUserId',
|
||||||
|
'virtualHidden'
|
||||||
],
|
],
|
||||||
created () {
|
created () {
|
||||||
if (this.isPage) {
|
if (this.isPage) {
|
||||||
|
@ -102,6 +105,9 @@ const conversation = {
|
||||||
},
|
},
|
||||||
isExpanded () {
|
isExpanded () {
|
||||||
return this.expanded || this.isPage
|
return this.expanded || this.isPage
|
||||||
|
},
|
||||||
|
hiddenStyle () {
|
||||||
|
return this.virtualHidden ? { height: this.virtualHeight } : {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
@ -121,6 +127,9 @@ const conversation = {
|
||||||
if (value) {
|
if (value) {
|
||||||
this.fetchConversation()
|
this.fetchConversation()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
virtualHidden (value) {
|
||||||
|
this.virtualHeight = `${this.$el.clientHeight}px`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
:style="hiddenStyle"
|
||||||
class="timeline panel-default"
|
class="timeline panel-default"
|
||||||
:class="[isExpanded ? 'panel' : 'panel-disabled']"
|
:class="[isExpanded ? 'panel' : 'panel-disabled']"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isExpanded"
|
v-if="isExpanded && !virtualHidden"
|
||||||
class="panel-heading conversation-heading"
|
class="panel-heading conversation-heading"
|
||||||
>
|
>
|
||||||
<span class="title"> {{ $t('timeline.conversation') }} </span>
|
<span class="title"> {{ $t('timeline.conversation') }} </span>
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
:replies="getReplies(status.id)"
|
:replies="getReplies(status.id)"
|
||||||
:in-profile="inProfile"
|
:in-profile="inProfile"
|
||||||
:profile-user-id="profileUserId"
|
:profile-user-id="profileUserId"
|
||||||
|
:virtual-hidden="virtualHidden"
|
||||||
class="status-fadein panel-body"
|
class="status-fadein panel-body"
|
||||||
@goto="setHighlight"
|
@goto="setHighlight"
|
||||||
@toggleExpanded="toggleExpanded"
|
@toggleExpanded="toggleExpanded"
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
<li>
|
<li>
|
||||||
<Checkbox v-model="useStreamingApi">
|
<Checkbox v-model="useStreamingApi">
|
||||||
{{ $t('settings.useStreamingApi') }}
|
{{ $t('settings.useStreamingApi') }}
|
||||||
<br/>
|
<br>
|
||||||
<small>
|
<small>
|
||||||
{{ $t('settings.useStreamingApiWarning') }}
|
{{ $t('settings.useStreamingApiWarning') }}
|
||||||
</small>
|
</small>
|
||||||
|
@ -92,6 +92,11 @@
|
||||||
{{ $t('settings.reply_link_preview') }}
|
{{ $t('settings.reply_link_preview') }}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="virtualScrolling">
|
||||||
|
{{ $t('settings.virtual_scrolling') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,8 @@ const Status = {
|
||||||
'inlineExpanded',
|
'inlineExpanded',
|
||||||
'showPinned',
|
'showPinned',
|
||||||
'inProfile',
|
'inProfile',
|
||||||
'profileUserId'
|
'profileUserId',
|
||||||
|
'virtualHidden'
|
||||||
],
|
],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
@ -121,7 +122,7 @@ const Status = {
|
||||||
return this.mergedConfig.hideFilteredStatuses
|
return this.mergedConfig.hideFilteredStatuses
|
||||||
},
|
},
|
||||||
hideStatus () {
|
hideStatus () {
|
||||||
return (this.hideReply || this.deleted) || (this.muted && this.hideFilteredStatuses)
|
return (this.hideReply || this.deleted) || (this.muted && this.hideFilteredStatuses) || this.virtualHidden
|
||||||
},
|
},
|
||||||
isFocused () {
|
isFocused () {
|
||||||
// retweet or root of an expanded conversation
|
// retweet or root of an expanded conversation
|
||||||
|
|
|
@ -18,14 +18,16 @@ const StillImage = {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onLoad () {
|
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
|
const canvas = this.$refs.canvas
|
||||||
if (!canvas) return
|
if (!canvas) return
|
||||||
const width = this.$refs.src.naturalWidth
|
const width = image.naturalWidth
|
||||||
const height = this.$refs.src.naturalHeight
|
const height = image.naturalHeight
|
||||||
canvas.width = width
|
canvas.width = width
|
||||||
canvas.height = height
|
canvas.height = height
|
||||||
canvas.getContext('2d').drawImage(this.$refs.src, 0, 0, width, height)
|
canvas.getContext('2d').drawImage(image, 0, 0, width, height)
|
||||||
},
|
},
|
||||||
onError () {
|
onError () {
|
||||||
this.imageLoadError && this.imageLoadError()
|
this.imageLoadError && this.imageLoadError()
|
||||||
|
|
|
@ -32,7 +32,8 @@ const Timeline = {
|
||||||
return {
|
return {
|
||||||
paused: false,
|
paused: false,
|
||||||
unfocused: false,
|
unfocused: false,
|
||||||
bottomedOut: false
|
bottomedOut: false,
|
||||||
|
virtualScrollIndex: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -68,6 +69,16 @@ const Timeline = {
|
||||||
},
|
},
|
||||||
pinnedStatusIdsObject () {
|
pinnedStatusIdsObject () {
|
||||||
return keyBy(this.pinnedStatusIds)
|
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: {
|
components: {
|
||||||
|
@ -79,7 +90,7 @@ const Timeline = {
|
||||||
const credentials = store.state.users.currentUser.credentials
|
const credentials = store.state.users.currentUser.credentials
|
||||||
const showImmediately = this.timeline.visibleStatuses.length === 0
|
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 }
|
if (store.state.api.fetchers[this.timelineName]) { return false }
|
||||||
|
|
||||||
|
@ -100,7 +111,7 @@ const Timeline = {
|
||||||
window.addEventListener('keydown', this.handleShortKey)
|
window.addEventListener('keydown', this.handleShortKey)
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed () {
|
||||||
window.removeEventListener('scroll', this.scrollLoad)
|
window.removeEventListener('scroll', this.handleScroll)
|
||||||
window.removeEventListener('keydown', this.handleShortKey)
|
window.removeEventListener('keydown', this.handleShortKey)
|
||||||
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
|
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
|
||||||
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
|
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
|
||||||
|
@ -142,6 +153,48 @@ const Timeline = {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, 1000, this),
|
}, 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) {
|
scrollLoad (e) {
|
||||||
const bodyBRect = document.body.getBoundingClientRect()
|
const bodyBRect = document.body.getBoundingClientRect()
|
||||||
const height = Math.max(bodyBRect.height, -(bodyBRect.y))
|
const height = Math.max(bodyBRect.height, -(bodyBRect.y))
|
||||||
|
@ -152,6 +205,10 @@ const Timeline = {
|
||||||
this.fetchOlderStatuses()
|
this.fetchOlderStatuses()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
handleScroll: throttle(function (e) {
|
||||||
|
this.determineVisibleStatuses()
|
||||||
|
this.scrollLoad(e)
|
||||||
|
}, 100),
|
||||||
handleVisibilityChange () {
|
handleVisibilityChange () {
|
||||||
this.unfocused = document.hidden
|
this.unfocused = document.hidden
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="classes.body">
|
<div :class="classes.body">
|
||||||
<div class="timeline">
|
<div
|
||||||
|
ref="timeline"
|
||||||
|
class="timeline"
|
||||||
|
>
|
||||||
<template v-for="statusId in pinnedStatusIds">
|
<template v-for="statusId in pinnedStatusIds">
|
||||||
<conversation
|
<conversation
|
||||||
v-if="timeline.statusesObject[statusId]"
|
v-if="timeline.statusesObject[statusId]"
|
||||||
|
@ -56,6 +59,7 @@
|
||||||
:collapsable="true"
|
:collapsable="true"
|
||||||
:in-profile="inProfile"
|
:in-profile="inProfile"
|
||||||
:profile-user-id="userId"
|
:profile-user-id="userId"
|
||||||
|
:virtual-hidden="virtualScrollingEnabled && !statusesToDisplay.includes(status.id)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -384,6 +384,7 @@
|
||||||
"false": "no",
|
"false": "no",
|
||||||
"true": "yes"
|
"true": "yes"
|
||||||
},
|
},
|
||||||
|
"virtual_scrolling": "Optimize timeline rendering",
|
||||||
"fun": "Fun",
|
"fun": "Fun",
|
||||||
"greentext": "Meme arrows",
|
"greentext": "Meme arrows",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
|
|
|
@ -228,7 +228,8 @@
|
||||||
"values": {
|
"values": {
|
||||||
"false": "pois päältä",
|
"false": "pois päältä",
|
||||||
"true": "päällä"
|
"true": "päällä"
|
||||||
}
|
},
|
||||||
|
"virtual_scrolling": "Optimoi aikajanan suorituskykyä"
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"day": "{0} päivä",
|
"day": "{0} päivä",
|
||||||
|
|
|
@ -49,7 +49,8 @@ export const defaultState = {
|
||||||
useContainFit: false,
|
useContainFit: false,
|
||||||
greentext: undefined, // instance default
|
greentext: undefined, // instance default
|
||||||
hidePostStats: undefined, // instance default
|
hidePostStats: undefined, // instance default
|
||||||
hideUserStats: undefined // instance default
|
hideUserStats: undefined, // instance default
|
||||||
|
virtualScrolling: undefined // instance default
|
||||||
}
|
}
|
||||||
|
|
||||||
// caching the instance default properties
|
// caching the instance default properties
|
||||||
|
|
|
@ -34,6 +34,7 @@ const defaultState = {
|
||||||
showFeaturesPanel: true,
|
showFeaturesPanel: true,
|
||||||
minimalScopesMode: false,
|
minimalScopesMode: false,
|
||||||
greentext: false,
|
greentext: false,
|
||||||
|
virtualScrolling: true,
|
||||||
|
|
||||||
// Nasty stuff
|
// Nasty stuff
|
||||||
pleromaBackend: true,
|
pleromaBackend: true,
|
||||||
|
|
Loading…
Add table
Reference in a new issue