forked from cybrespace/pinafore
		
	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,5 +1,6 @@
 | 
			
		|||
<div class="timeline" role="feed" aria-label="{{label}}">
 | 
			
		||||
  <VirtualList component="{{StatusListItem}}"
 | 
			
		||||
  {{#if virtual}}
 | 
			
		||||
    <VirtualList component="{{StatusVirtualListItem}}"
 | 
			
		||||
                 :makeProps
 | 
			
		||||
                 items="{{$statusIds}}"
 | 
			
		||||
                 on:scrollToBottom="onScrollToBottom()"
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +10,17 @@
 | 
			
		|||
                 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