diff --git a/routes/_actions/timeline.js b/routes/_actions/timeline.js index e788553..9f01023 100644 --- a/routes/_actions/timeline.js +++ b/routes/_actions/timeline.js @@ -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') } diff --git a/routes/_components/pseudoVirtualList/PseudoVirtualList.html b/routes/_components/pseudoVirtualList/PseudoVirtualList.html new file mode 100644 index 0000000..796ca0a --- /dev/null +++ b/routes/_components/pseudoVirtualList/PseudoVirtualList.html @@ -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> \ No newline at end of file diff --git a/routes/_components/pseudoVirtualList/PseudoVirtualListItem.html b/routes/_components/pseudoVirtualList/PseudoVirtualListItem.html new file mode 100644 index 0000000..3295787 --- /dev/null +++ b/routes/_components/pseudoVirtualList/PseudoVirtualListItem.html @@ -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> diff --git a/routes/_components/pseudoVirtualList/PseudoVirtualListLazyItem.html b/routes/_components/pseudoVirtualList/PseudoVirtualListLazyItem.html new file mode 100644 index 0000000..eddce33 --- /dev/null +++ b/routes/_components/pseudoVirtualList/PseudoVirtualListLazyItem.html @@ -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> \ No newline at end of file diff --git a/routes/_components/timeline/StatusListItem.html b/routes/_components/timeline/StatusVirtualListItem.html similarity index 100% rename from routes/_components/timeline/StatusListItem.html rename to routes/_components/timeline/StatusVirtualListItem.html diff --git a/routes/_components/timeline/Timeline.html b/routes/_components/timeline/Timeline.html index 803dd3f..98b26e7 100644 --- a/routes/_components/timeline/Timeline.html +++ b/routes/_components/timeline/Timeline.html @@ -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() { diff --git a/routes/statuses/[statusId].html b/routes/statuses/[statusId].html index 7fc0e31..b53be51 100644 --- a/routes/statuses/[statusId].html +++ b/routes/statuses/[statusId].html @@ -3,8 +3,6 @@ </:Head> <Layout page='statuses' - virtual="true" - virtualRealm='status/{{params.statusId}}' dynamicPage="Status" dynamicHref="/statuses/{{params.statusId}}" dynamicLabel="Status"