move all timeline data to centralized store

This commit is contained in:
Nolan Lawson 2018-01-27 10:46:52 -08:00
parent 9782cb400f
commit 5f627bf9f1
5 changed files with 62 additions and 39 deletions

View file

@ -1,7 +1,7 @@
<Nav :page :dynamicPage :dynamicHref :dynamicIcon :dynamicLabel/> <Nav :page :dynamicPage :dynamicHref :dynamicIcon :dynamicLabel/>
{{#if virtual}} {{#if virtual}}
<VirtualListContainer realm="{{virtualRealm}}"> <VirtualListContainer realm="{{$currentInstance + '/' + virtualRealm}}">
<main> <main>
<slot></slot> <slot></slot>
</main> </main>
@ -16,11 +16,13 @@
<script> <script>
import Nav from './Nav.html'; import Nav from './Nav.html';
import VirtualListContainer from './virtualList/VirtualListContainer.html' import VirtualListContainer from './virtualList/VirtualListContainer.html'
import { store } from '../_utils/store'
export default { export default {
components: { components: {
VirtualListContainer, VirtualListContainer,
Nav Nav
} },
store: () => store
} }
</script> </script>

View file

@ -1,5 +1,5 @@
<div class="lazy-timeline"> <div class="lazy-timeline">
{{#if loading}} {{#if !$initialized}}
<div transition:fade> <div transition:fade>
<div class="loading-page"> <div class="loading-page">
<LoadingSpinner /> <LoadingSpinner />
@ -8,7 +8,7 @@
{{/if}} {{/if}}
{{#await promise}} {{#await promise}}
{{then constructor}} {{then constructor}}
<:Component {constructor} :timeline on:initialized="set({'loading': false})"/> <:Component {constructor} :timeline />
{{catch error}} {{catch error}}
<div>Component failed to load. Please try refreshing! {{error}}</div> <div>Component failed to load. Please try refreshing! {{error}}</div>
{{/await}} {{/await}}
@ -34,11 +34,18 @@
import { importTimeline } from '../_utils/asyncModules' import { importTimeline } from '../_utils/asyncModules'
import LoadingSpinner from './LoadingSpinner.html' import LoadingSpinner from './LoadingSpinner.html'
import { fade } from 'svelte-transitions' import { fade } from 'svelte-transitions'
import { store } from '../_utils/store'
export default { export default {
oncreate() {
let instanceName = this.store.get('currentInstance')
let timeline = this.get('timeline')
this.store.set({currentTimeline: timeline})
this.store.setForTimeline(instanceName, timeline, {runningUpdate: false})
},
store: () => store,
data: () => ({ data: () => ({
promise: importTimeline(), promise: importTimeline()
loading: true
}), }),
components: { components: {
LoadingSpinner LoadingSpinner

View file

@ -1,13 +1,13 @@
<:Window bind:online /> <:Window bind:online />
<div class="timeline" role="feed" aria-label="{{label}}" on:initialized> <div class="timeline" role="feed" aria-label="{{label}}">
<VirtualList component="{{StatusListItem}}" <VirtualList component="{{StatusListItem}}"
:makeProps :makeProps
items="{{statusIds}}" items="{{$statusIds}}"
on:scrollToBottom="onScrollToBottom()" on:scrollToBottom="onScrollToBottom()"
shown="{{initialized}}" shown="{{$initialized}}"
footerComponent="{{LoadingFooter}}" footerComponent="{{LoadingFooter}}"
showFooter="{{initialized && runningUpdate}}" showFooter="{{$initialized && $runningUpdate}}"
realm="{{timeline}}" realm="{{$currentInstance + '/' + timeline}}"
on:initializedVisibleItems="initialize()" on:initializedVisibleItems="initialize()"
/> />
</div> </div>
@ -31,12 +31,6 @@
import { database } from '../_utils/database/database' import { database } from '../_utils/database/database'
import { StatusStream } from '../_utils/mastodon/StatusStream' import { StatusStream } from '../_utils/mastodon/StatusStream'
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 {
@ -44,10 +38,7 @@
let timeline = this.get('timeline') let timeline = this.get('timeline')
let instanceName = this.store.get('currentInstance') let instanceName = this.store.get('currentInstance')
let accessToken = this.store.get('accessToken') let accessToken = this.store.get('accessToken')
let cachedStatusIds = cachedTimelines[timeline] if (!this.store.get('statusIds').length) {
if (cachedStatusIds) {
this.set({statusIds: cachedStatusIds})
} else {
this.addStatuses(await this.fetchStatusesAndPossiblyFallBack()) this.addStatuses(await this.fetchStatusesAndPossiblyFallBack())
} }
/* no await */ getInstanceInfo(instanceName).then(instanceInfo => database.setInstanceInfo(instanceName, instanceInfo)) /* no await */ getInstanceInfo(instanceName).then(instanceInfo => database.setInstanceInfo(instanceName, instanceInfo))
@ -62,18 +53,13 @@
if (this._statusStream) { if (this._statusStream) {
this._statusStream.close() this._statusStream.close()
} }
cachedTimelines[this.get('timeline')] = this.get('statusIds')
}, },
data: () => ({ data: () => ({
StatusListItem: StatusListItem, StatusListItem: StatusListItem,
LoadingFooter: LoadingFooter, LoadingFooter: LoadingFooter
statusIds: [],
runningUpdate: false,
initialized: false
}), }),
computed: { computed: {
makeProps: ($currentInstance) => (statusId) => database.getStatus($currentInstance, statusId), makeProps: ($currentInstance) => (statusId) => database.getStatus($currentInstance, statusId),
lastStatusId: (statusIds) => statusIds.length && statusIds[statusIds.length - 1],
label: (timeline, $currentInstance) => { label: (timeline, $currentInstance) => {
if (timelines[timeline]) { if (timelines[timeline]) {
`${timelines[timeline].label} timeline for ${$currentInstance}` `${timelines[timeline].label} timeline for ${$currentInstance}`
@ -94,27 +80,30 @@
splice: splice, splice: splice,
push: push, push: push,
initialize() { initialize() {
if (this.get('initialized') || !this.get('statusIds') || !this.get('statusIds').length) { if (this.store.get('initialized') || !this.store.get('statusIds') || !this.store.get('statusIds').length) {
return return
} }
let instanceName = this.store.get('currentInstance')
let timeline = this.get('timeline')
requestAnimationFrame(() => { requestAnimationFrame(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
this.set({initialized: true}) this.store.setForTimeline(instanceName, timeline, {initialized: true})
this.fire('initialized')
}) })
}) })
}, },
async onScrollToBottom() { async onScrollToBottom() {
if (!this.get('initialized')) { if (!this.store.get('initialized')) {
return return
} }
if (this.get('runningUpdate')) { if (this.store.get('runningUpdate')) {
return return
} }
mark('onScrollToBottom') mark('onScrollToBottom')
this.set({ runningUpdate: true }) let timeline = this.get('timeline')
let instanceName = this.store.get('currentInstance')
this.store.setForTimeline(instanceName, timeline, { runningUpdate: true })
let newStatuses = await this.fetchStatusesAndPossiblyFallBack() let newStatuses = await this.fetchStatusesAndPossiblyFallBack()
this.set({ runningUpdate: false }) this.store.setForTimeline(instanceName, timeline, { runningUpdate: false })
this.addStatuses(newStatuses) this.addStatuses(newStatuses)
stop('onScrollToBottom') stop('onScrollToBottom')
}, },
@ -122,21 +111,21 @@
console.log('addStatuses()') console.log('addStatuses()')
let instanceName = this.store.get('currentInstance') let instanceName = this.store.get('currentInstance')
let timeline = this.get('timeline') let timeline = this.get('timeline')
let statusIds = this.get('statusIds') let statusIds = this.store.get('statusIds')
if (!statusIds) { if (!statusIds) {
return return
} }
/* no await */ database.insertStatuses(instanceName, timeline, newStatuses) /* no await */ database.insertStatuses(instanceName, timeline, newStatuses)
let newStatusIds = newStatuses.map(status => status.id) let newStatusIds = newStatuses.map(status => status.id)
let merged = mergeStatuses(statusIds, newStatusIds) let merged = mergeStatuses(statusIds, newStatusIds)
this.set({ statusIds: merged }) this.store.setForTimeline(instanceName, timeline, { statusIds: merged })
}, },
async fetchStatusesAndPossiblyFallBack() { async fetchStatusesAndPossiblyFallBack() {
let online = this.get('online') let online = this.get('online')
let instanceName = this.store.get('currentInstance') let instanceName = this.store.get('currentInstance')
let instanceData = this.store.get('currentInstanceData') let instanceData = this.store.get('currentInstanceData')
let timeline = this.get('timeline') let timeline = this.get('timeline')
let lastStatusId = this.get('lastStatusId') let lastStatusId = this.store.get('lastStatusId')
let statuses let statuses
if (!online) { if (!online) {
statuses = await database.getTimeline(instanceName, timeline, lastStatusId, FETCH_LIMIT) statuses = await database.getTimeline(instanceName, timeline, lastStatusId, FETCH_LIMIT)

View file

@ -14,6 +14,9 @@ class AsyncLayout {
} }
observe(key, node, callback) { observe(key, node, callback) {
if (!node) {
return
}
if (this._intersectionObserver) { if (this._intersectionObserver) {
this._onIntersectionCallbacks[key] = (entry) => { this._onIntersectionCallbacks[key] = (entry) => {
callback(getRectFromEntry(entry)) callback(getRectFromEntry(entry))
@ -27,6 +30,9 @@ class AsyncLayout {
if (key in this._onIntersectionCallbacks) { if (key in this._onIntersectionCallbacks) {
return return
} }
if (!node) {
return
}
if (this._intersectionObserver) { if (this._intersectionObserver) {
this._intersectionObserver.unobserve(node) this._intersectionObserver.unobserve(node)
} }

View file

@ -11,7 +11,7 @@ const LOCAL_STORAGE_KEYS = new Set([
]) ])
const LS = process.browser && localStorage const LS = process.browser && localStorage
class LocalStorageStore extends Store { class PinaforeStore extends Store {
constructor(state) { constructor(state) {
super(state) super(state)
@ -44,9 +44,18 @@ class LocalStorageStore extends Store {
this.keysToStore = {} this.keysToStore = {}
} }
} }
setForTimeline(instanceName, timelineName, obj) {
console.log('setForTimeline')
let timelines = this.get('timelines') || {}
let timelineData = timelines[instanceName] || {}
timelineData[timelineName] = Object.assign(timelineData[timelineName] || {}, obj)
timelines[instanceName] = timelineData
this.set({timelines: timelines})
}
} }
const store = new LocalStorageStore({ const store = new PinaforeStore({
instanceNameInSearch: '', instanceNameInSearch: '',
currentRegisteredInstance: null, currentRegisteredInstance: null,
currentRegisteredInstanceName: '', currentRegisteredInstanceName: '',
@ -98,6 +107,16 @@ store.compute(
} }
) )
store.compute('currentTimelineData', ['currentInstance', 'currentTimeline', 'timelines'],
(currentInstance, currentTimeline, timelines) => {
return ((timelines && timelines[currentInstance]) || {})[currentTimeline] || {}
})
store.compute('statusIds', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.statusIds || [])
store.compute('runningUpdate', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.runningUpdate)
store.compute('initialized', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.initialized)
store.compute('lastStatusId', ['statusIds'], (statusIds) => statusIds.length && statusIds[statusIds.length - 1])
if (process.browser && process.env.NODE_ENV !== 'production') { if (process.browser && process.env.NODE_ENV !== 'production') {
window.store = store // for debugging window.store = store // for debugging
} }