forked from cybrespace/pinafore
save scroll positions
This commit is contained in:
parent
208316b914
commit
fb234adb79
|
@ -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'
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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}}"
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}}"
|
||||||
|
|
Loading…
Reference in New Issue