save scroll positions

This commit is contained in:
Nolan Lawson 2018-01-24 09:47:31 -08:00
parent 208316b914
commit fb234adb79
10 changed files with 108 additions and 18 deletions

View file

@ -1,10 +1,18 @@
<Nav :page :dynamicPage :dynamicHref :dynamicIcon :dynamicLabel/> <Nav :page :dynamicPage :dynamicHref :dynamicIcon :dynamicLabel/>
<VirtualListContainer> {{#if virtual}}
<VirtualListContainer storeKey="{{virtualStoreKey}}">
<main> <main>
<slot></slot> <slot></slot>
</main> </main>
</VirtualListContainer> </VirtualListContainer>
{{else}}
<div class="container">
<main>
<slot></slot>
</main>
</div>
{{/if}}
<script> <script>
import Nav from './Nav.html'; import Nav from './Nav.html';
import VirtualListContainer from './VirtualListContainer.html' import VirtualListContainer from './VirtualListContainer.html'

View file

@ -7,6 +7,8 @@
shown="{{initialized}}" shown="{{initialized}}"
footerComponent="{{LoadingFooter}}" footerComponent="{{LoadingFooter}}"
showFooter="{{initialized && runningUpdate}}" showFooter="{{initialized && runningUpdate}}"
storeKey="{{timeline}}"
initialized="{{initialized}}"
/> />
</div> </div>
<style> <style>
@ -27,18 +29,32 @@
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import { database } from '../_utils/database/database' import { database } from '../_utils/database/database'
const cachedTimelines = {}
if (process.browser && process.env.NODE_ENV !== 'production') {
window.cachedTimelines = cachedTimelines
}
const FETCH_LIMIT = 20 const FETCH_LIMIT = 20
export default { export default {
async oncreate() { async oncreate() {
let statuses = await this.fetchStatusesAndPossiblyFallBack() let timeline = this.get('timeline')
this.addStatuses(statuses) let cachedStatusIds = cachedTimelines[timeline]
if (cachedStatusIds) {
this.set({statusIds: cachedStatusIds})
} else {
this.addStatuses(await this.fetchStatusesAndPossiblyFallBack())
}
requestAnimationFrame(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
requestAnimationFrame((() => {
this.set({initialized: true}) this.set({initialized: true})
this.fire('initialized') this.fire('initialized')
}))
}) })
})
},
ondestroy() {
cachedTimelines[this.get('timeline')] = this.get('statusIds')
}, },
data: () => ({ data: () => ({
StatusListItem: StatusListItem, StatusListItem: StatusListItem,

View file

@ -13,17 +13,44 @@
const SCROLL_EVENT_DELAY = 300 const SCROLL_EVENT_DELAY = 300
const cachedVirtualStores = {}
if (process.browser && process.env.NODE_ENV !== 'production') {
window.cachedVirtualStores = cachedVirtualStores
}
export default { export default {
oncreate() { oncreate() {
mark('onCreate VirtualListContainer') mark('onCreate VirtualListContainer')
let node = this.refs.node let node = this.refs.node
let storeKey = this.get('storeKey')
let cachedStore
if (storeKey && (cachedStore = cachedVirtualStores[storeKey])) {
this.store.set({ this.store.set({
scrollTop: node.scrollTop, scrollTop: cachedStore.state.scrollTop,
scrollHeight: cachedStore.state.scrollHeight,
offsetHeight: cachedStore.state.offsetHeight
})
this.rehydrateScrollTop(cachedStore)
this.store.set(cachedStore.state)
} else {
this.store.set({
scrollTop: 0,
scrollHeight: node.scrollHeight, scrollHeight: node.scrollHeight,
offsetHeight: node.offsetHeight offsetHeight: node.offsetHeight
}) })
}
stop('onCreate VirtualListContainer') stop('onCreate VirtualListContainer')
}, },
ondestroy() {
let storeKey = this.get('storeKey')
if (storeKey) {
cachedVirtualStores[storeKey] = {
state: this.store.cloneState(),
height: this.store.get('height')
}
}
},
store: () => virtualListStore, store: () => virtualListStore,
events: { events: {
scroll(node, callback) { scroll(node, callback) {
@ -70,6 +97,25 @@
console.log('is fullscreen? ', isFullscreen()) console.log('is fullscreen? ', isFullscreen())
this.set({ fullscreen: isFullscreen() }) this.set({ fullscreen: isFullscreen() })
stop('onFullscreenChange') stop('onFullscreenChange')
},
rehydrateScrollTop(cachedStore) {
let cachedScrollTop = cachedStore.state.scrollTop || 0
let cachedHeight = cachedStore.height
if (cachedScrollTop === 0) {
return
}
let initializedScrollTop = false
let node = this.refs.node
this.store.observe('height', height => {
if (!initializedScrollTop && height === cachedHeight && node) {
initializedScrollTop = true
requestAnimationFrame(() => {
mark('set scrollTop')
node.scrollTop = cachedScrollTop
stop('set scrollTop')
})
}
})
} }
} }
}; };

View file

@ -3,12 +3,28 @@ import { mark, stop } from '../_utils/marks'
const VIEWPORT_RENDER_FACTOR = 4 const VIEWPORT_RENDER_FACTOR = 4
const cloneKeys = [
'items',
'itemHeights',
'scrollTop',
'scrollHeight',
'offsetHeight'
]
class VirtualListStore extends Store { class VirtualListStore extends Store {
constructor(state) { constructor(state) {
super(state) super(state)
this._batches = {} this._batches = {}
} }
cloneState() {
let res = {}
for (let key of cloneKeys) {
res[key] = this.get(key)
}
return res
}
batchUpdate(key, subKey, value) { batchUpdate(key, subKey, value) {
let batch = this._batches[key] let batch = this._batches[key]
if (!batch) { if (!batch) {

View file

@ -3,6 +3,8 @@
</:Head> </:Head>
<Layout page='tags' <Layout page='tags'
virtual="true"
virtualStoreKey='account/{{params.accountId}}'
dynamicPage="{{profileName}}" dynamicPage="{{profileName}}"
dynamicHref="/accounts/{{params.accountId}}" dynamicHref="/accounts/{{params.accountId}}"
dynamicLabel="{{shortProfileName}}" dynamicLabel="{{shortProfileName}}"

View file

@ -2,7 +2,7 @@
<title>Pinafore Federated</title> <title>Pinafore Federated</title>
</:Head> </:Head>
<Layout page='federated'> <Layout page='federated' virtual="true" virtualStoreKey="federated">
{{#if $isUserLoggedIn}} {{#if $isUserLoggedIn}}
<LazyTimeline timeline='federated' /> <LazyTimeline timeline='federated' />
{{else}} {{else}}

View file

@ -2,7 +2,7 @@
<title>Pinafore Home</title> <title>Pinafore Home</title>
</:Head> </:Head>
<Layout page='home'> <Layout page='home' virtual="true" virtualStoreKey="home">
{{#if $isUserLoggedIn}} {{#if $isUserLoggedIn}}
<LazyTimeline timeline='home' /> <LazyTimeline timeline='home' />
{{else}} {{else}}

View file

@ -2,7 +2,7 @@
<title>Pinafore Local</title> <title>Pinafore Local</title>
</:Head> </:Head>
<Layout page='local'> <Layout page='local' virtual="true" virtualStoreKey="local">
{{#if $isUserLoggedIn}} {{#if $isUserLoggedIn}}
<LazyTimeline timeline='local' /> <LazyTimeline timeline='local' />
{{else}} {{else}}

View file

@ -2,7 +2,7 @@
<title>Pinafore Notifications</title> <title>Pinafore Notifications</title>
</:Head> </:Head>
<Layout page='notifications'> <Layout page='notifications' virtual="true" virtualStoreKey="federated">
<HiddenFromSSR> <HiddenFromSSR>
<FreeTextLayout> <FreeTextLayout>
<h1>Notifications</h1> <h1>Notifications</h1>

View file

@ -3,6 +3,8 @@
</:Head> </:Head>
<Layout page='tags' <Layout page='tags'
virtual="true"
virtualStoreKey='tag/{{params.tagName}}'
dynamicPage="{{params.tagName}}" dynamicPage="{{params.tagName}}"
dynamicHref="/tags/{{params.tagName}}" dynamicHref="/tags/{{params.tagName}}"
dynamicLabel="{{'#' + params.tagName}}" dynamicLabel="{{'#' + params.tagName}}"