Remove PseudoVirtualList (#385)
* start on removing pseudo virtual list * rename, refactor * remove unused file * fix the tests * actually fix tests * okay actually fix tests
This commit is contained in:
		
							parent
							
								
									812fd3245f
								
							
						
					
					
						commit
						11dcaf0cf3
					
				
					 12 changed files with 130 additions and 270 deletions
				
			
		
							
								
								
									
										2
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -9227,7 +9227,7 @@
 | 
			
		|||
        "relative": "3.0.2",
 | 
			
		||||
        "require-relative": "0.8.7",
 | 
			
		||||
        "rimraf": "2.6.2",
 | 
			
		||||
        "webpack": "4.10.2",
 | 
			
		||||
        "webpack": "^4.5.0",
 | 
			
		||||
        "webpack-hot-middleware": "2.22.2"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										65
									
								
								routes/_components/list/List.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								routes/_components/list/List.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
<div class="the-list" on:initialized>
 | 
			
		||||
  {#each safeItems as item, i (item)}
 | 
			
		||||
    <ListLazyItem
 | 
			
		||||
      {component}
 | 
			
		||||
      index={i}
 | 
			
		||||
      {length}
 | 
			
		||||
      {makeProps}
 | 
			
		||||
      key={item}
 | 
			
		||||
      on:initialized="itemInitialized()"
 | 
			
		||||
    />
 | 
			
		||||
  {/each}
 | 
			
		||||
</div>
 | 
			
		||||
<style>
 | 
			
		||||
  .the-list {
 | 
			
		||||
    position: relative;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
  import ListLazyItem from './ListLazyItem.html'
 | 
			
		||||
  import { listStore } from './listStore'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    oncreate () {
 | 
			
		||||
      let { realm } = this.get()
 | 
			
		||||
      this.store.setCurrentRealm(realm)
 | 
			
		||||
    },
 | 
			
		||||
    ondestroy () {
 | 
			
		||||
      this.store.setCurrentRealm(null)
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
      itemInitialized () {
 | 
			
		||||
        let { initializedCount, length } = this.get()
 | 
			
		||||
        initializedCount++
 | 
			
		||||
        this.set({initializedCount})
 | 
			
		||||
        if (initializedCount === length) {
 | 
			
		||||
          this.initialize()
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      initialize () {
 | 
			
		||||
        let { scrollToItem } = this.get()
 | 
			
		||||
        if (scrollToItem) {
 | 
			
		||||
          let element = document.getElementById(`list-item-${scrollToItem}`)
 | 
			
		||||
          requestAnimationFrame(() => {
 | 
			
		||||
            console.log('scrolling element into view')
 | 
			
		||||
            element.scrollIntoView(true)
 | 
			
		||||
            this.fire('initialized')
 | 
			
		||||
          })
 | 
			
		||||
        } else {
 | 
			
		||||
          this.fire('initialized')
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      initializedCount: 0
 | 
			
		||||
    }),
 | 
			
		||||
    computed: {
 | 
			
		||||
      safeItems: ({ items }) => items || [],
 | 
			
		||||
      length: ({ safeItems }) => safeItems.length
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
      ListLazyItem
 | 
			
		||||
    },
 | 
			
		||||
    store: () => listStore
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										10
									
								
								routes/_components/list/ListItem.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								routes/_components/list/ListItem.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
<div
 | 
			
		||||
  id={`list-item-${key}`}
 | 
			
		||||
  aria-hidden="false"
 | 
			
		||||
>
 | 
			
		||||
  <svelte:component this={component}
 | 
			
		||||
              virtualProps={props}
 | 
			
		||||
              virtualIndex={index}
 | 
			
		||||
              virtualLength={length}
 | 
			
		||||
  />
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										33
									
								
								routes/_components/list/ListLazyItem.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								routes/_components/list/ListLazyItem.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
{#if props}
 | 
			
		||||
  <ListItem
 | 
			
		||||
    {component}
 | 
			
		||||
    {props}
 | 
			
		||||
    {key}
 | 
			
		||||
    {index}
 | 
			
		||||
    {length}
 | 
			
		||||
    on:initialized
 | 
			
		||||
  />
 | 
			
		||||
{/if}
 | 
			
		||||
<script>
 | 
			
		||||
  import ListItem from './ListItem.html'
 | 
			
		||||
  import { mark, stop } from '../../_utils/marks'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    async oncreate () {
 | 
			
		||||
      let { makeProps, key } = this.get()
 | 
			
		||||
      if (makeProps) {
 | 
			
		||||
        let props = await makeProps(key)
 | 
			
		||||
        mark('ListLazyItem set props')
 | 
			
		||||
        this.set({props: props})
 | 
			
		||||
        this.fire('initialized')
 | 
			
		||||
        stop('ListLazyItem set props')
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      props: void 0
 | 
			
		||||
    }),
 | 
			
		||||
    components: {
 | 
			
		||||
      ListItem
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										17
									
								
								routes/_components/list/listStore.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								routes/_components/list/listStore.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
import { RealmStore } from '../../_utils/RealmStore'
 | 
			
		||||
 | 
			
		||||
class ListStore extends RealmStore {
 | 
			
		||||
  constructor (state) {
 | 
			
		||||
    super(state, /* maxSize */ 10)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const listStore = new ListStore()
 | 
			
		||||
 | 
			
		||||
listStore.computeForRealm('intersectionStates', {})
 | 
			
		||||
 | 
			
		||||
if (process.browser && process.env.NODE_ENV !== 'production') {
 | 
			
		||||
  window.listStore = listStore
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { listStore }
 | 
			
		||||
| 
						 | 
				
			
			@ -1,134 +0,0 @@
 | 
			
		|||
<div class="pseudo-virtual-list" on:initialized ref:node>
 | 
			
		||||
  {#each safeItems as item, i (item)}
 | 
			
		||||
    <PseudoVirtualListLazyItem
 | 
			
		||||
      {component}
 | 
			
		||||
      index={i}
 | 
			
		||||
      length={safeItems.length}
 | 
			
		||||
      {makeProps}
 | 
			
		||||
      key={item}
 | 
			
		||||
      {intersectionObserver}
 | 
			
		||||
      isIntersecting={isIntersecting(item, $intersectionStates)}
 | 
			
		||||
      isCached={isCached(item, $intersectionStates)}
 | 
			
		||||
      height={getHeight(item, $intersectionStates)}
 | 
			
		||||
    />
 | 
			
		||||
  {/each}
 | 
			
		||||
</div>
 | 
			
		||||
<style>
 | 
			
		||||
  .pseudo-virtual-list {
 | 
			
		||||
    position: relative;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
  import PseudoVirtualListLazyItem from './PseudoVirtualListLazyItem.html'
 | 
			
		||||
  import { getRectFromEntry } from '../../_utils/getRectFromEntry'
 | 
			
		||||
  import { mark, stop } from '../../_utils/marks'
 | 
			
		||||
  import { pseudoVirtualListStore } from './pseudoVirtualListStore'
 | 
			
		||||
  import { observe } from 'svelte-extras'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    oncreate () {
 | 
			
		||||
      mark('PseudoVirtualList oncreate()')
 | 
			
		||||
      let { realm } = this.get()
 | 
			
		||||
      this.store.setCurrentRealm(realm)
 | 
			
		||||
 | 
			
		||||
      // When re-rendering, assume all items are non-intersecting until told otherwise.
 | 
			
		||||
      // We already have the heights cached.
 | 
			
		||||
      let { intersectionStates } = this.store.get()
 | 
			
		||||
      let keys = Object.keys(intersectionStates)
 | 
			
		||||
      for (let key of keys) {
 | 
			
		||||
        intersectionStates[key].isCached = true
 | 
			
		||||
      }
 | 
			
		||||
      this.store.setForRealm({intersectionStates: intersectionStates})
 | 
			
		||||
 | 
			
		||||
      let { containerQuery } = this.get()
 | 
			
		||||
      let intersectionObserver = new IntersectionObserver(this.onIntersection.bind(this), {
 | 
			
		||||
        root: document.querySelector(containerQuery),
 | 
			
		||||
        rootMargin: '300% 0px'
 | 
			
		||||
      })
 | 
			
		||||
      this.set({intersectionObserver})
 | 
			
		||||
      this.observe('allItemsHaveHeight', allItemsHaveHeight => {
 | 
			
		||||
        console.log('allItemsHaveHeight', allItemsHaveHeight)
 | 
			
		||||
        let { initialized } = this.get()
 | 
			
		||||
        if (allItemsHaveHeight && !initialized) {
 | 
			
		||||
          this.set({initialized: true})
 | 
			
		||||
          console.log('initialized PseudoVirtualList')
 | 
			
		||||
          this.fire('initialized')
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      stop('PseudoVirtualList oncreate()')
 | 
			
		||||
    },
 | 
			
		||||
    ondestroy () {
 | 
			
		||||
      let { intersectionObserver } = this.get()
 | 
			
		||||
      if (intersectionObserver) {
 | 
			
		||||
        intersectionObserver.disconnect()
 | 
			
		||||
      }
 | 
			
		||||
      this.store.setCurrentRealm(null)
 | 
			
		||||
    },
 | 
			
		||||
    helpers: {
 | 
			
		||||
      isIntersecting (key, $intersectionStates) {
 | 
			
		||||
        return !!($intersectionStates[key] && $intersectionStates[key].isIntersecting)
 | 
			
		||||
      },
 | 
			
		||||
      isCached (key, $intersectionStates) {
 | 
			
		||||
        return !!($intersectionStates[key] && $intersectionStates[key].isCached)
 | 
			
		||||
      },
 | 
			
		||||
      getHeight (key, $intersectionStates) {
 | 
			
		||||
        return $intersectionStates[key] && $intersectionStates[key].height
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
      observe,
 | 
			
		||||
      scrollToPosition (element) {
 | 
			
		||||
        let { scrolledToPosition } = this.get()
 | 
			
		||||
        if (scrolledToPosition) {
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        this.set({scrolledToPosition: true})
 | 
			
		||||
        requestAnimationFrame(() => {
 | 
			
		||||
          console.log('scrolling element into view')
 | 
			
		||||
          element.scrollIntoView(true)
 | 
			
		||||
        })
 | 
			
		||||
      },
 | 
			
		||||
      onIntersection (entries) {
 | 
			
		||||
        mark('onIntersection')
 | 
			
		||||
        let newIntersectionStates = {}
 | 
			
		||||
        let { scrollToItem } = this.get()
 | 
			
		||||
        let { intersectionStates } = this.store.get()
 | 
			
		||||
        for (let entry of entries) {
 | 
			
		||||
          let key = entry.target.getAttribute('pseudo-virtual-list-key')
 | 
			
		||||
          let rect = getRectFromEntry(entry)
 | 
			
		||||
          newIntersectionStates[key] = {
 | 
			
		||||
            isIntersecting: entry.isIntersecting,
 | 
			
		||||
            height: rect.height
 | 
			
		||||
          }
 | 
			
		||||
          if (scrollToItem === key) {
 | 
			
		||||
            this.scrollToPosition(entry.target)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        intersectionStates = Object.assign(intersectionStates, newIntersectionStates)
 | 
			
		||||
        this.store.setForRealm({intersectionStates: intersectionStates})
 | 
			
		||||
        stop('onIntersection')
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
      safeItems: ({ items }) => items || [],
 | 
			
		||||
      allItemsHaveHeight: ({ items, $intersectionStates }) => {
 | 
			
		||||
        if (!items) {
 | 
			
		||||
          return false
 | 
			
		||||
        }
 | 
			
		||||
        for (let item of items) {
 | 
			
		||||
          if (!$intersectionStates[item]) {
 | 
			
		||||
            return false
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return true
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
      PseudoVirtualListLazyItem
 | 
			
		||||
    },
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      intersectionObserver: void 0
 | 
			
		||||
    }),
 | 
			
		||||
    store: () => pseudoVirtualListStore
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,57 +0,0 @@
 | 
			
		|||
<div class="pseudo-virtual-list-item"
 | 
			
		||||
     aria-hidden={hide}
 | 
			
		||||
     pseudo-virtual-list-key={key}
 | 
			
		||||
     style="height: {shouldHide ? `${height}px` : ''};"
 | 
			
		||||
     ref:node>
 | 
			
		||||
  {#if !shouldHide}
 | 
			
		||||
    <svelte:component this={component}
 | 
			
		||||
                virtualProps={props}
 | 
			
		||||
                virtualIndex={index}
 | 
			
		||||
                virtualLength={length}
 | 
			
		||||
    />
 | 
			
		||||
  {/if}
 | 
			
		||||
</div>
 | 
			
		||||
<script>
 | 
			
		||||
  import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
 | 
			
		||||
  import { mark, stop } from '../../_utils/marks'
 | 
			
		||||
  import { observe } from 'svelte-extras'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    oncreate () {
 | 
			
		||||
      this.observe('isIntersecting', isIntersecting => {
 | 
			
		||||
        if (isIntersecting) {
 | 
			
		||||
          mark('render')
 | 
			
		||||
          this.set({hide: false})
 | 
			
		||||
          stop('render')
 | 
			
		||||
        } else {
 | 
			
		||||
          // unrender lazily; it's not a critical UI task
 | 
			
		||||
          scheduleIdleTask(() => {
 | 
			
		||||
            mark('unrender')
 | 
			
		||||
            let { isIntersecting } = this.get()
 | 
			
		||||
            if (!isIntersecting) {
 | 
			
		||||
              this.set({hide: true})
 | 
			
		||||
            }
 | 
			
		||||
            stop('unrender')
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      let { intersectionObserver } = this.get()
 | 
			
		||||
      intersectionObserver.observe(this.refs.node)
 | 
			
		||||
    },
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      hide: false
 | 
			
		||||
    }),
 | 
			
		||||
    computed: {
 | 
			
		||||
      shouldHide: ({ isIntersecting, isCached, hide }) => {
 | 
			
		||||
        if (isCached) {
 | 
			
		||||
          return true // if it's cached, always unrender immediately until proven it's intersecting
 | 
			
		||||
        }
 | 
			
		||||
        return !isIntersecting && hide
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
      observe
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,35 +0,0 @@
 | 
			
		|||
{#if props}
 | 
			
		||||
  <PseudoVirtualListItem {component}
 | 
			
		||||
                         {props}
 | 
			
		||||
                         {key}
 | 
			
		||||
                         {index}
 | 
			
		||||
                         {length}
 | 
			
		||||
                         {intersectionObserver}
 | 
			
		||||
                         {isIntersecting}
 | 
			
		||||
                         {isCached}
 | 
			
		||||
                         {height}
 | 
			
		||||
                         on:scrollToPosition
 | 
			
		||||
  />
 | 
			
		||||
{/if}
 | 
			
		||||
<script>
 | 
			
		||||
  import PseudoVirtualListItem from './PseudoVirtualListItem.html'
 | 
			
		||||
  import { mark, stop } from '../../_utils/marks'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    async oncreate () {
 | 
			
		||||
      let { makeProps, key } = this.get()
 | 
			
		||||
      if (makeProps) {
 | 
			
		||||
        let props = await makeProps(key)
 | 
			
		||||
        mark('PseudoVirtualListLazyItem set props')
 | 
			
		||||
        this.set({props: props})
 | 
			
		||||
        stop('PseudoVirtualListLazyItem set props')
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      props: void 0
 | 
			
		||||
    }),
 | 
			
		||||
    components: {
 | 
			
		||||
      PseudoVirtualListItem
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,17 +0,0 @@
 | 
			
		|||
import { RealmStore } from '../../_utils/RealmStore'
 | 
			
		||||
 | 
			
		||||
class PseudoVirtualListStore extends RealmStore {
 | 
			
		||||
  constructor (state) {
 | 
			
		||||
    super(state, /* maxSize */ 10)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const pseudoVirtualListStore = new PseudoVirtualListStore()
 | 
			
		||||
 | 
			
		||||
pseudoVirtualListStore.computeForRealm('intersectionStates', {})
 | 
			
		||||
 | 
			
		||||
if (process.browser && process.env.NODE_ENV !== 'production') {
 | 
			
		||||
  window.pseudoVirtualListStore = pseudoVirtualListStore
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { pseudoVirtualListStore }
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +36,7 @@
 | 
			
		|||
  import MoreHeaderVirtualWrapper from './MoreHeaderVirtualWrapper.html'
 | 
			
		||||
  import {
 | 
			
		||||
    importVirtualList,
 | 
			
		||||
    importPseudoVirtualList,
 | 
			
		||||
    importList,
 | 
			
		||||
    importStatusVirtualListItem,
 | 
			
		||||
    importNotificationVirtualListItem
 | 
			
		||||
  } from '../../_utils/asyncModules'
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +85,7 @@
 | 
			
		|||
      componentsPromise: ({ timelineType }) => {
 | 
			
		||||
        return Promise.all([
 | 
			
		||||
          timelineType === 'status'
 | 
			
		||||
            ? importPseudoVirtualList()
 | 
			
		||||
            ? importList()
 | 
			
		||||
            : importVirtualList(),
 | 
			
		||||
          timelineType === 'notifications'
 | 
			
		||||
            ? importNotificationVirtualListItem()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,8 +26,8 @@ export const importVirtualList = () => import(
 | 
			
		|||
  /* webpackChunkName: 'VirtualList.html' */ '../_components/virtualList/VirtualList.html'
 | 
			
		||||
  ).then(mod => mod.default)
 | 
			
		||||
 | 
			
		||||
export const importPseudoVirtualList = () => import(
 | 
			
		||||
  /* webpackChunkName: 'PseudoVirtualList.html' */ '../_components/pseudoVirtualList/PseudoVirtualList.html'
 | 
			
		||||
export const importList = () => import(
 | 
			
		||||
  /* webpackChunkName: 'List.html' */ '../_components/list/List.html'
 | 
			
		||||
  ).then(mod => mod.default)
 | 
			
		||||
 | 
			
		||||
export const importStatusVirtualListItem = () => import(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,22 +0,0 @@
 | 
			
		|||
// Get the bounding client rect from an IntersectionObserver entry.
 | 
			
		||||
// This is to work around a bug in Chrome: https://crbug.com/737228
 | 
			
		||||
 | 
			
		||||
let hasBoundingRectBug
 | 
			
		||||
 | 
			
		||||
function rectsAreEqual (rectA, rectB) {
 | 
			
		||||
  return rectA.height === rectB.height &&
 | 
			
		||||
  rectA.top === rectB.top &&
 | 
			
		||||
  rectA.width === rectB.width &&
 | 
			
		||||
  rectA.bottom === rectB.bottom &&
 | 
			
		||||
  rectA.left === rectB.left &&
 | 
			
		||||
  rectA.right === rectB.right
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getRectFromEntry (entry) {
 | 
			
		||||
  if (typeof hasBoundingRectBug !== 'boolean') {
 | 
			
		||||
    const boundingRect = entry.target.getBoundingClientRect()
 | 
			
		||||
    const observerRect = entry.boundingClientRect
 | 
			
		||||
    hasBoundingRectBug = !rectsAreEqual(boundingRect, observerRect)
 | 
			
		||||
  }
 | 
			
		||||
  return hasBoundingRectBug ? entry.target.getBoundingClientRect() : entry.boundingClientRect
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue