implement PseudoVirtualList for thread timelines

This commit is contained in:
Nolan Lawson 2018-01-29 19:22:28 -08:00
parent 37661f4c6a
commit 6f69bf89c5
7 changed files with 159 additions and 19 deletions

View file

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

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

View file

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

View file

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

View file

@ -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() {

View file

@ -3,8 +3,6 @@
</:Head>
<Layout page='statuses'
virtual="true"
virtualRealm='status/{{params.statusId}}'
dynamicPage="Status"
dynamicHref="/statuses/{{params.statusId}}"
dynamicLabel="Status"