forked from cybrespace/pinafore
		
	streaming is kinda working
This commit is contained in:
		
							parent
							
								
									075066ba9a
								
							
						
					
					
						commit
						2a86425c90
					
				
					 9 changed files with 167 additions and 27 deletions
				
			
		|  | @ -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) | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| } | ||||
|  | @ -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 | ||||
|   }) | ||||
| } | ||||
							
								
								
									
										25
									
								
								routes/_components/timeline/MoreHeader.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								routes/_components/timeline/MoreHeader.html
									
										
									
									
									
										Normal 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> | ||||
							
								
								
									
										11
									
								
								routes/_components/timeline/MoreHeaderVirtualWrapper.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								routes/_components/timeline/MoreHeaderVirtualWrapper.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| <MoreHeader count="{{virtualProps.count}}" | ||||
|             onClick="{{virtualProps.onClick}}" | ||||
| /> | ||||
| <script> | ||||
|   import MoreHeader from './MoreHeader.html' | ||||
|   export default { | ||||
|     components: { | ||||
|       MoreHeader | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
|  | @ -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}) | ||||
|  |  | |||
|  | @ -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 | ||||
|     } | ||||
|  |  | |||
							
								
								
									
										34
									
								
								routes/_components/virtualList/VirtualListHeader.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								routes/_components/virtualList/VirtualListHeader.html
									
										
									
									
									
										Normal 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> | ||||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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]) | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue