implement PseudoVirtualList for thread timelines
This commit is contained in:
		
							parent
							
								
									37661f4c6a
								
							
						
					
					
						commit
						6f69bf89c5
					
				
					 7 changed files with 159 additions and 19 deletions
				
			
		|  | @ -78,11 +78,11 @@ export async function setupTimeline() { | |||
|   if (statusStream) { | ||||
|     statusStream.close() | ||||
|   } | ||||
|   statusStream = new StatusStream(instanceInfo.urls.streaming_api, accessToken, timelineName, { | ||||
|   /*statusStream = new StatusStream(instanceInfo.urls.streaming_api, accessToken, timelineName, { | ||||
|     onMessage(message) { | ||||
|       console.log('message', message) | ||||
|     } | ||||
|   }) | ||||
|   })*/ | ||||
|   stop('addStatuses') | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										65
									
								
								routes/_components/pseudoVirtualList/PseudoVirtualList.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								routes/_components/pseudoVirtualList/PseudoVirtualList.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | |||
| <div class="pseudo-virtual-list {{shown ? '' : 'hidden'}}" on:initializedVisibleItems> | ||||
|   {{#each wrappedItems as wrappedItem, i @item}} | ||||
|     <PseudoVirtualListLazyItem | ||||
|       component="{{component}}" | ||||
|       index="{{i}}" | ||||
|       length="{{items.length}}" | ||||
|       makeProps="{{makeProps}}" | ||||
|       key="{{wrappedItem.item}}" | ||||
|       scrollToThisItem="{{wrappedItem.item === scrollToItem}}" | ||||
|       on:scrollToPosition="onScrollToPosition(event)" | ||||
|       on:renderedListItem="onRenderedListItem()" | ||||
|     /> | ||||
|   {{/each}} | ||||
| </div> | ||||
| <style> | ||||
|   .pseudo-virtual-list { | ||||
|     position: relative; | ||||
|     transition: opacity 0.25s linear; | ||||
|   } | ||||
| </style> | ||||
| <script> | ||||
| 
 | ||||
|   import PseudoVirtualListLazyItem from './PseudoVirtualListLazyItem.html' | ||||
| 
 | ||||
|   export default { | ||||
|     oncreate() { | ||||
|       this.observe('numRenderedListItems', numRenderedListItems => { | ||||
|         let items = this.get('items') | ||||
|         if (items.length && items.length === numRenderedListItems && !this.get('initialized')) { | ||||
|           this.set({initialized: true}) | ||||
|           console.log('fire initialized') | ||||
|           this.fire('initializedVisibleItems') | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     methods: { | ||||
|       onScrollToPosition(rect) { | ||||
|         console.log('onScrollToPosition', rect) | ||||
|         // TODO: there should be some cleaner way to grab the container element | ||||
|         let container = document.querySelector('.container') | ||||
|         if (!container) { | ||||
|           return | ||||
|         } | ||||
|         let containerRect = container.getBoundingClientRect() | ||||
|         let scrollTop = rect.top - containerRect.top | ||||
|         if (scrollTop !== 0) { | ||||
|           requestAnimationFrame(() => { | ||||
|             console.log('setting scrollTop to ', scrollTop) | ||||
|             container.scrollTop = scrollTop | ||||
|           }) | ||||
|         } | ||||
|       }, | ||||
|       onRenderedListItem() { | ||||
|         let numRenderedListItems = this.get('numRenderedListItems') || 0 | ||||
|         this.set({numRenderedListItems: numRenderedListItems + 1}) | ||||
|       } | ||||
|     }, | ||||
|     computed: { | ||||
|       wrappedItems: (items) => items.map(item => ({item: item})) | ||||
|     }, | ||||
|     components: { | ||||
|       PseudoVirtualListLazyItem | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
|  | @ -0,0 +1,32 @@ | |||
| <div class="pseudo-virtual-list-item" pseudo-virtual-list-key="{{index}}" ref:node> | ||||
|   <:Component {component} | ||||
|               virtualProps="{{props}}" | ||||
|               virtualIndex="{{index}}" | ||||
|               virtualLength="{length}}" | ||||
|               on:renderedListItem | ||||
|               on:scrollToPosition | ||||
|   /> | ||||
| </div> | ||||
| <script> | ||||
| 
 | ||||
|   import { AsyncLayout } from '../../_utils/AsyncLayout' | ||||
| 
 | ||||
|   export default { | ||||
|     oncreate() { | ||||
|       this.fire('renderedListItem') | ||||
|       if (this.get('scrollToThisItem')) { | ||||
|         if (this.get('firedScrollToPosition')) { | ||||
|           return | ||||
|         } | ||||
|         this.set({firedScrollToPosition: true}) | ||||
|         let node = this.refs.node | ||||
|         let asyncLayout = new AsyncLayout(node => node.getAttribute('pseudo-virtual-list-key')) | ||||
|         let key = this.get('index') | ||||
|         asyncLayout.observe(key, this.refs.node, (rect) => { | ||||
|           asyncLayout.disconnect() | ||||
|           this.fire('scrollToPosition', rect) | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
|  | @ -0,0 +1,24 @@ | |||
| {{#if props}} | ||||
|   <PseudoVirtualListItem :component | ||||
|                          :props | ||||
|                          :key | ||||
|                          :index | ||||
|                          :scrollToThisItem | ||||
|                          on:renderedListItem | ||||
|                          on:scrollToPosition | ||||
|   /> | ||||
| {{/if}} | ||||
| <script> | ||||
|   import PseudoVirtualListItem from './PseudoVirtualListItem.html' | ||||
|   export default { | ||||
|     async oncreate() { | ||||
|       let makeProps = this.get('makeProps') | ||||
|       let key = this.get('key') | ||||
|       let props = await makeProps(key) | ||||
|       this.set({ props: props }) | ||||
|     }, | ||||
|     components: { | ||||
|       PseudoVirtualListItem | ||||
|     } | ||||
|   } | ||||
| </script> | ||||
|  | @ -1,14 +1,26 @@ | |||
| <div class="timeline" role="feed" aria-label="{{label}}"> | ||||
|   <VirtualList component="{{StatusListItem}}" | ||||
|                :makeProps | ||||
|                items="{{$statusIds}}" | ||||
|                on:scrollToBottom="onScrollToBottom()" | ||||
|                shown="{{$initialized}}" | ||||
|                footerComponent="{{LoadingFooter}}" | ||||
|                showFooter="{{$initialized && $runningUpdate}}" | ||||
|                realm="{{$currentInstance + '/' + timeline}}" | ||||
|                on:initializedVisibleItems="initialize()" | ||||
|   /> | ||||
|   {{#if virtual}} | ||||
|     <VirtualList component="{{StatusVirtualListItem}}" | ||||
|                  :makeProps | ||||
|                  items="{{$statusIds}}" | ||||
|                  on:scrollToBottom="onScrollToBottom()" | ||||
|                  shown="{{$initialized}}" | ||||
|                  footerComponent="{{LoadingFooter}}" | ||||
|                  showFooter="{{$initialized && $runningUpdate}}" | ||||
|                  realm="{{$currentInstance + '/' + timeline}}" | ||||
|                  on:initializedVisibleItems="initialize()" | ||||
|     /> | ||||
|   {{else}} | ||||
|     <!-- if this is a status thread, it's easier to just render the | ||||
|          whole thing rather than use a virtual list --> | ||||
|     <PseudoVirtualList component="{{StatusVirtualListItem}}" | ||||
|                        :makeProps | ||||
|                        items="{{$statusIds}}" | ||||
|                        shown="{{$initialized}}" | ||||
|                        on:initializedVisibleItems="initialize()" | ||||
|                        scrollToItem="{{timelineValue}}" | ||||
|     /> | ||||
|   {{/if}} | ||||
| </div> | ||||
| <style> | ||||
|   .timeline { | ||||
|  | @ -17,7 +29,9 @@ | |||
| </style> | ||||
| <script> | ||||
|   import { store } from '../../_store/store' | ||||
|   import StatusListItem from './StatusListItem.html' | ||||
|   import StatusVirtualListItem from './StatusVirtualListItem.html' | ||||
|   import Status from '../status/Status.html' | ||||
|   import PseudoVirtualList from '../pseudoVirtualList/PseudoVirtualList.html' | ||||
|   import LoadingFooter from './LoadingFooter.html' | ||||
|   import VirtualList from '../virtualList/VirtualList.html' | ||||
|   import { timelines } from '../../_static/timelines' | ||||
|  | @ -26,11 +40,13 @@ | |||
| 
 | ||||
|   export default { | ||||
|     async oncreate() { | ||||
|       console.log('timeline oncreate()') | ||||
|       setupTimeline() | ||||
|     }, | ||||
|     data: () => ({ | ||||
|       StatusListItem: StatusListItem, | ||||
|       LoadingFooter: LoadingFooter | ||||
|       StatusVirtualListItem, | ||||
|       LoadingFooter, | ||||
|       Status | ||||
|     }), | ||||
|     computed: { | ||||
|       makeProps: ($currentInstance, timelineType) => async (statusId) => ({ | ||||
|  | @ -56,17 +72,22 @@ | |||
|       }, | ||||
|       timelineValue: (timeline) => { | ||||
|         return timeline.split('/').slice(-1)[0] | ||||
|       } | ||||
|       }, | ||||
|       // for threads, it's simpler to just render all items due to need to scroll to the right item | ||||
|       // TODO: this can be optimized to use a virtual list | ||||
|       virtual: (timelineType) => timelineType !=='status' | ||||
|     }, | ||||
|     store: () => store, | ||||
|     components: { | ||||
|       VirtualList | ||||
|       VirtualList, | ||||
|       PseudoVirtualList | ||||
|     }, | ||||
|     methods: { | ||||
|       initialize() { | ||||
|         if (this.store.get('initialized') || !this.store.get('statusIds') || !this.store.get('statusIds').length) { | ||||
|           return | ||||
|         } | ||||
|         console.log('timeline initialize()') | ||||
|         initializeTimeline() | ||||
|       }, | ||||
|       onScrollToBottom() { | ||||
|  |  | |||
|  | @ -3,8 +3,6 @@ | |||
| </:Head> | ||||
| 
 | ||||
| <Layout page='statuses' | ||||
|         virtual="true" | ||||
|         virtualRealm='status/{{params.statusId}}' | ||||
|         dynamicPage="Status" | ||||
|         dynamicHref="/statuses/{{params.statusId}}" | ||||
|         dynamicLabel="Status" | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue