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:
Nolan Lawson 2018-06-09 22:55:58 -07:00 committed by GitHub
parent 812fd3245f
commit 11dcaf0cf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 130 additions and 270 deletions

2
package-lock.json generated
View File

@ -9227,7 +9227,7 @@
"relative": "3.0.2", "relative": "3.0.2",
"require-relative": "0.8.7", "require-relative": "0.8.7",
"rimraf": "2.6.2", "rimraf": "2.6.2",
"webpack": "4.10.2", "webpack": "^4.5.0",
"webpack-hot-middleware": "2.22.2" "webpack-hot-middleware": "2.22.2"
}, },
"dependencies": { "dependencies": {

View 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>

View File

@ -0,0 +1,10 @@
<div
id={`list-item-${key}`}
aria-hidden="false"
>
<svelte:component this={component}
virtualProps={props}
virtualIndex={index}
virtualLength={length}
/>
</div>

View 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>

View 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 }

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 }

View File

@ -36,7 +36,7 @@
import MoreHeaderVirtualWrapper from './MoreHeaderVirtualWrapper.html' import MoreHeaderVirtualWrapper from './MoreHeaderVirtualWrapper.html'
import { import {
importVirtualList, importVirtualList,
importPseudoVirtualList, importList,
importStatusVirtualListItem, importStatusVirtualListItem,
importNotificationVirtualListItem importNotificationVirtualListItem
} from '../../_utils/asyncModules' } from '../../_utils/asyncModules'
@ -85,7 +85,7 @@
componentsPromise: ({ timelineType }) => { componentsPromise: ({ timelineType }) => {
return Promise.all([ return Promise.all([
timelineType === 'status' timelineType === 'status'
? importPseudoVirtualList() ? importList()
: importVirtualList(), : importVirtualList(),
timelineType === 'notifications' timelineType === 'notifications'
? importNotificationVirtualListItem() ? importNotificationVirtualListItem()

View File

@ -26,8 +26,8 @@ export const importVirtualList = () => import(
/* webpackChunkName: 'VirtualList.html' */ '../_components/virtualList/VirtualList.html' /* webpackChunkName: 'VirtualList.html' */ '../_components/virtualList/VirtualList.html'
).then(mod => mod.default) ).then(mod => mod.default)
export const importPseudoVirtualList = () => import( export const importList = () => import(
/* webpackChunkName: 'PseudoVirtualList.html' */ '../_components/pseudoVirtualList/PseudoVirtualList.html' /* webpackChunkName: 'List.html' */ '../_components/list/List.html'
).then(mod => mod.default) ).then(mod => mod.default)
export const importStatusVirtualListItem = () => import( export const importStatusVirtualListItem = () => import(

View File

@ -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
}