From 2a86425c90bc3925ffc972f5deedce91e533ac98 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sun, 11 Feb 2018 19:15:21 -0800 Subject: [PATCH] streaming is kinda working --- routes/_actions/streaming.js | 8 +-- routes/_actions/timeline.js | 12 ++++ routes/_components/timeline/MoreHeader.html | 25 +++++++ .../timeline/MoreHeaderVirtualWrapper.html | 11 ++++ routes/_components/timeline/Timeline.html | 65 ++++++++++++++++--- .../_components/virtualList/VirtualList.html | 23 +++++-- .../virtualList/VirtualListHeader.html | 34 ++++++++++ .../virtualList/virtualListStore.js | 14 ++-- routes/_store/timelineComputations.js | 2 + 9 files changed, 167 insertions(+), 27 deletions(-) create mode 100644 routes/_components/timeline/MoreHeader.html create mode 100644 routes/_components/timeline/MoreHeaderVirtualWrapper.html create mode 100644 routes/_components/virtualList/VirtualListHeader.html diff --git a/routes/_actions/streaming.js b/routes/_actions/streaming.js index 5fc7b2e..ba86917 100644 --- a/routes/_actions/streaming.js +++ b/routes/_actions/streaming.js @@ -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) } }) -} +} \ No newline at end of file diff --git a/routes/_actions/timeline.js b/routes/_actions/timeline.js index a17596f..e5a0762 100644 --- a/routes/_actions/timeline.js +++ b/routes/_actions/timeline.js @@ -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 + }) +} \ No newline at end of file diff --git a/routes/_components/timeline/MoreHeader.html b/routes/_components/timeline/MoreHeader.html new file mode 100644 index 0000000..849db49 --- /dev/null +++ b/routes/_components/timeline/MoreHeader.html @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/routes/_components/timeline/MoreHeaderVirtualWrapper.html b/routes/_components/timeline/MoreHeaderVirtualWrapper.html new file mode 100644 index 0000000..9043c44 --- /dev/null +++ b/routes/_components/timeline/MoreHeaderVirtualWrapper.html @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/routes/_components/timeline/Timeline.html b/routes/_components/timeline/Timeline.html index b4d7281..db478be 100644 --- a/routes/_components/timeline/Timeline.html +++ b/routes/_components/timeline/Timeline.html @@ -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}) diff --git a/routes/_components/virtualList/VirtualList.html b/routes/_components/virtualList/VirtualList.html index 6691456..81b21ee 100644 --- a/routes/_components/virtualList/VirtualList.html +++ b/routes/_components/virtualList/VirtualList.html @@ -1,5 +1,5 @@ -
+ {{#if $visibleItems}} {{#each $visibleItems as visibleItem @key}} 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 } diff --git a/routes/_components/virtualList/VirtualListHeader.html b/routes/_components/virtualList/VirtualListHeader.html new file mode 100644 index 0000000..37ae79e --- /dev/null +++ b/routes/_components/virtualList/VirtualListHeader.html @@ -0,0 +1,34 @@ +
+ <:Component {component} :virtualProps /> +
+ + \ No newline at end of file diff --git a/routes/_components/virtualList/virtualListStore.js b/routes/_components/virtualList/virtualListStore.js index bba41a6..23bcf3d 100644 --- a/routes/_components/virtualList/virtualListStore.js +++ b/routes/_components/virtualList/virtualListStore.js @@ -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) { diff --git a/routes/_store/timelineComputations.js b/routes/_store/timelineComputations.js index 0e7bbe8..0eb37a6 100644 --- a/routes/_store/timelineComputations.js +++ b/routes/_store/timelineComputations.js @@ -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])