streaming is kinda working

This commit is contained in:
Nolan Lawson 2018-02-11 19:15:21 -08:00
parent 075066ba9a
commit 2a86425c90
9 changed files with 167 additions and 27 deletions

View file

@ -16,17 +16,13 @@ async function removeDuplicates (instanceName, timelineName, updates) {
}
async function handleFreshChanges (instanceName, timelineName) {
console.log('handleFreshChanges')
let freshChanges = store.getForTimeline(instanceName, timelineName, 'freshChanges')
console.log('freshChanges', freshChanges)
if (freshChanges.updates && freshChanges.updates.length) {
let updates = freshChanges.updates.slice()
freshChanges.updates = []
store.setForTimeline(instanceName, timelineName, {freshChanges: freshChanges})
console.log('before removing duplicates, updates are ', updates.length)
updates = await removeDuplicates(instanceName, timelineName, updates)
console.log('after removing duplicates, updates are ', updates.length)
await database.insertTimelineItems(instanceName, timelineName, updates)
@ -39,7 +35,6 @@ async function handleFreshChanges (instanceName, timelineName) {
}
function handleStreamMessage (instanceName, timelineName, message) {
console.log('handleStreamMessage')
let { event, payload } = message
let key = event === 'update' ? 'updates' : 'deletes'
let freshChanges = store.getForTimeline(instanceName, timelineName, 'freshChanges') || {}
@ -54,7 +49,6 @@ function handleStreamMessage (instanceName, timelineName, message) {
export function createStream (streamingApi, instanceName, accessToken, timelineName) {
return new TimelineStream(streamingApi, accessToken, timelineName, {
onMessage (msg) {
console.log('message', msg)
if (msg.event !== 'update' && msg.event !== 'delete') {
console.error("don't know how to handle event", msg)
return
@ -73,4 +67,4 @@ export function createStream (streamingApi, instanceName, accessToken, timelineN
console.log('reconnected stream for timeline', timelineName)
}
})
}
}

View file

@ -81,3 +81,15 @@ export async function fetchTimelineItemsOnScrollToBottom () {
await fetchTimelineItemsAndPossiblyFallBack()
store.setForTimeline(instanceName, timelineName, { runningUpdate: false })
}
export async function showMoreItemsForCurrentTimeline() {
let instanceName = store.get('currentInstance')
let timelineName = store.get('currentTimeline')
let itemIdsToAdd = store.get('itemIdsToAdd')
addTimelineItemIds(instanceName, timelineName, itemIdsToAdd)
store.setForTimeline(instanceName, timelineName, {
itemIdsToAdd: [],
shouldShowHeader: false,
showHeader: false
})
}

View file

@ -0,0 +1,25 @@
<div class="more-items-header" role="dialog">
<button class="primary" type="button" on:click="onClick(event)">
Click to show {{count}} more
</button>
</div>
<style>
.more-items-header {
display: flex;
padding: 5px;
align-items: center;
justify-content:center;
}
</style>
<script>
export default {
methods: {
onClick(event) {
let onClick = this.get('onClick')
if (onClick) {
onClick(event)
}
}
}
}
</script>

View file

@ -0,0 +1,11 @@
<MoreHeader count="{{virtualProps.count}}"
onClick="{{virtualProps.onClick}}"
/>
<script>
import MoreHeader from './MoreHeader.html'
export default {
components: {
MoreHeader
}
}
</script>

View file

@ -12,9 +12,13 @@
:makeProps
items="{{$timelineItemIds}}"
on:scrollToBottom="onScrollToBottom()"
on:scrollToTop="onScrollToTop()"
shown="{{$initialized}}"
footerComponent="{{LoadingFooter}}"
showFooter="{{$initialized && $runningUpdate}}"
showHeader="{{$showHeader}}"
headerComponent="{{MoreHeaderVirtualWrapper}}"
:headerProps
realm="{{$currentInstance + '/' + timeline}}"
on:initializedVisibleItems="initialize()"
/>
@ -23,8 +27,12 @@
:makeProps
items="{{$timelineItemIds}}"
on:scrollToBottom="onScrollToBottom()"
on:scrollToTop="onScrollToTop()"
shown="{{$initialized}}"
footerComponent="{{LoadingFooter}}"
showHeader="{{$showHeader}}"
headerComponent="{{MoreHeaderVirtualWrapper}}"
:headerProps
showFooter="{{$initialized && $runningUpdate}}"
realm="{{$currentInstance + '/' + timeline}}"
on:initializedVisibleItems="initialize()"
@ -55,13 +63,16 @@
import Status from '../status/Status.html'
import PseudoVirtualList from '../pseudoVirtualList/PseudoVirtualList.html'
import LoadingFooter from './LoadingFooter.html'
import MoreHeaderVirtualWrapper from './MoreHeaderVirtualWrapper.html'
import VirtualList from '../virtualList/VirtualList.html'
import { timelines } from '../../_static/timelines'
import { database } from '../../_database/database'
import { initializeTimeline, fetchTimelineItemsOnScrollToBottom, setupTimeline } from '../../_actions/timeline'
import LoadingPage from '../LoadingPage.html'
import { focusWithCapture, blurWithCapture } from '../../_utils/events'
import { addTimelineItemIds } from '../../_actions/timeline'
import { showMoreItemsForCurrentTimeline } from '../../_actions/timeline'
import { virtualListStore } from '../virtualList/virtualListStore' // TODO: hacky, need better way to expose scrollTop
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
export default {
oncreate() {
@ -71,15 +82,7 @@
if (this.store.get('initialized')) {
this.restoreFocus()
}
let instanceName = this.store.get('currentInstance')
let timelineName = this.get('timeline')
this.observe('itemIdsToAdd', itemIdsToAdd => {
console.log('itemIdsToAdd', itemIdsToAdd)
if (itemIdsToAdd && itemIdsToAdd.length) {
addTimelineItemIds(instanceName, timelineName, itemIdsToAdd)
this.store.setForTimeline(instanceName, timelineName, {itemIdsToAdd: []})
}
})
this.setupStreaming()
},
ondestroy() {
console.log('ondestroy')
@ -89,6 +92,7 @@
StatusVirtualListItem,
NotificationVirtualListItem,
LoadingFooter,
MoreHeaderVirtualWrapper,
Status
}),
computed: {
@ -139,6 +143,12 @@
$timelines[$currentInstance] &&
$timelines[$currentInstance][timeline] &&
$timelines[$currentInstance][timeline].itemIdsToAdd) || []
},
headerProps: (itemIdsToAdd) => {
return {
count: (itemIdsToAdd && itemIdsToAdd.length) || 0,
onClick: showMoreItemsForCurrentTimeline
}
}
},
store: () => store,
@ -168,6 +178,41 @@
}
fetchTimelineItemsOnScrollToBottom()
},
onScrollToTop() {
if (this.store.get('shouldShowHeader')) {
this.store.setForCurrentTimeline({
showHeader: true,
shouldShowHeader: false
})
}
},
setupStreaming() {
let instanceName = this.store.get('currentInstance')
let timelineName = this.get('timeline')
let handleItemIdsToAdd = () => {
let itemIdsToAdd = this.get('itemIdsToAdd')
if (!itemIdsToAdd || !itemIdsToAdd.length) {
return
}
let scrollTop = virtualListStore.get('scrollTop')
let shouldShowHeader = this.store.get('shouldShowHeader')
let showHeader = this.store.get('showHeader')
//console.log('handleItemIdsToAdd', (itemIdsToAdd && itemIdsToAdd.length) || 0)
if (scrollTop === 0 && !shouldShowHeader && !showHeader) {
// if the user is scrolled to the top and we're not showing the header, then
// just insert the statuses. this is "chat room mode"
showMoreItemsForCurrentTimeline()
} else {
// user hasn't scrolled to the top, show a header instead
this.store.setForTimeline(instanceName, timelineName, {shouldShowHeader: true})
}
}
this.observe('itemIdsToAdd', itemIdsToAdd => {
if (itemIdsToAdd && itemIdsToAdd.length) {
scheduleIdleTask(handleItemIdsToAdd)
}
})
},
setupFocus() {
this.onPushState = this.onPushState.bind(this)
this.store.setForCurrentTimeline({ignoreBlurEvents: false})

View file

@ -1,5 +1,5 @@
<!-- TODO: setting height is hacky, just make this element the scroller -->
<div class="virtual-list {{shown ? '' : 'hidden'}}" style="height: {{$height}}px;">
<VirtualListHeader component="{{headerComponent}}" virtualProps="{{headerProps}}" shown="{{$showHeader}}"/>
{{#if $visibleItems}}
{{#each $visibleItems as visibleItem @key}}
<VirtualListLazyItem :component
@ -23,25 +23,32 @@
<script>
import VirtualListLazyItem from './VirtualListLazyItem'
import VirtualListFooter from './VirtualListFooter.html'
import VirtualListHeader from './VirtualListHeader.html'
import { virtualListStore } from './virtualListStore'
import throttle from 'lodash/throttle'
import { mark, stop } from '../../_utils/marks'
const DISTANCE_FROM_BOTTOM_TO_FIRE = 400
const SCROLL_TO_BOTTOM_DELAY = 1000
const SCROLL_EVENT_THROTTLE = 1000
export default {
oncreate () {
this.observe('showFooter', showFooter => {
this.store.setForRealm({showFooter: showFooter})
})
this.observe('showHeader', showHeader => {
this.store.setForRealm({showHeader: showHeader})
})
this.observe('items', (items) => {
mark('set items')
this.store.setForRealm({items: items})
stop('set items')
this.fireScrollToBottom = throttle(() => {
this.fire('scrollToBottom')
}, SCROLL_TO_BOTTOM_DELAY)
}, SCROLL_EVENT_THROTTLE)
this.fireScrollToTop = throttle(() => {
this.fire('scrollToTop')
}, SCROLL_EVENT_THROTTLE)
})
this.observe('allVisibleItemsHaveHeight', allVisibleItemsHaveHeight => {
@ -62,6 +69,12 @@
this.fireScrollToBottom()
}
})
this.observe('distanceFromTop', (distanceFromTop) => {
if (distanceFromTop === 0) {
this.fireScrollToTop()
}
})
},
data: () => ({
component: null
@ -69,12 +82,14 @@
store: () => virtualListStore,
components: {
VirtualListLazyItem,
VirtualListFooter
VirtualListFooter,
VirtualListHeader
},
computed: {
distanceFromBottom: ($scrollHeight, $scrollTop, $offsetHeight) => {
return $scrollHeight - $scrollTop - $offsetHeight
},
distanceFromTop: ($scrollTop) => $scrollTop,
// TODO: bug in svelte store, shouldn't need to do this
allVisibleItemsHaveHeight: ($allVisibleItemsHaveHeight) => $allVisibleItemsHaveHeight
}

View file

@ -0,0 +1,34 @@
<div class="virtual-list-header {{shown ? 'shown' : ''}}"
aria-hidden="{{!shown}}"
ref:node >
<:Component {component} :virtualProps />
</div>
<style>
.virtual-list-header {
position: absolute;
top: 0;
width: 100%;
opacity: 0;
pointer-events: none;
z-index: 10;
}
.virtual-list-header.shown {
opacity: 1;
pointer-events: auto;
}
</style>
<script>
import { virtualListStore } from './virtualListStore'
import { AsyncLayout } from '../../_utils/AsyncLayout'
export default {
oncreate() {
const asyncLayout = new AsyncLayout(() => '__header__')
asyncLayout.observe('__header__', this.refs.node, (rect) => {
asyncLayout.disconnect()
this.store.setForRealm({headerHeight: rect.height})
})
},
store: () => virtualListStore,
}
</script>

View file

@ -14,21 +14,23 @@ const virtualListStore = new VirtualListStore()
virtualListStore.computeForRealm('items', null)
virtualListStore.computeForRealm('showFooter', false)
virtualListStore.computeForRealm('footerHeight', 0)
virtualListStore.computeForRealm('showHeader', false)
virtualListStore.computeForRealm('headerHeight', 0)
virtualListStore.computeForRealm('scrollTop', 0)
virtualListStore.computeForRealm('scrollHeight', 0)
virtualListStore.computeForRealm('offsetHeight', 0)
virtualListStore.computeForRealm('itemHeights', {})
virtualListStore.compute('visibleItems',
['items', 'scrollTop', 'itemHeights', 'offsetHeight'],
(items, scrollTop, itemHeights, offsetHeight) => {
['items', 'scrollTop', 'itemHeights', 'offsetHeight', 'showHeader', 'headerHeight'],
(items, scrollTop, itemHeights, offsetHeight, showHeader, headerHeight) => {
mark('compute visibleItems')
if (!items) {
return null
}
let renderBuffer = VIEWPORT_RENDER_FACTOR * offsetHeight
let visibleItems = []
let totalOffset = 0
let totalOffset = showHeader ? headerHeight : 0
let len = items.length
let i = -1
while (++i < len) {
@ -57,12 +59,12 @@ virtualListStore.compute('visibleItems',
})
virtualListStore.compute('heightWithoutFooter',
['items', 'itemHeights'],
(items, itemHeights) => {
['items', 'itemHeights', 'showHeader', 'headerHeight'],
(items, itemHeights, showHeader, headerHeight) => {
if (!items) {
return 0
}
let sum = 0
let sum = showHeader ? headerHeight : 0
let i = -1
let len = items.length
while (++i < len) {

View file

@ -15,6 +15,8 @@ export function timelineComputations (store) {
computeForTimeline(store, 'lastFocusedElementSelector')
computeForTimeline(store, 'ignoreBlurEvents')
computeForTimeline(store, 'itemIdsToAdd')
computeForTimeline(store, 'showHeader')
computeForTimeline(store, 'shouldShowHeader')
store.compute('firstTimelineItemId', ['timelineItemIds'], (timelineItemIds) => timelineItemIds && timelineItemIds.length && timelineItemIds[0])
store.compute('lastTimelineItemId', ['timelineItemIds'], (timelineItemIds) => timelineItemIds && timelineItemIds.length && timelineItemIds[timelineItemIds.length - 1])