From ad443af5cd031deba35de31d1c8b18e547b41bdd Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sun, 28 Jan 2018 21:57:35 -0800 Subject: [PATCH] start on scroll to status in thread --- routes/_actions/timeline.js | 6 +- routes/_components/timeline/Timeline.html | 3 +- .../_components/virtualList/VirtualList.html | 12 ++-- .../virtualList/VirtualListContainer.html | 15 ++++- .../virtualList/virtualListStore.js | 65 ++++++++++++++++--- routes/_store/timelineComputations.js | 1 + 6 files changed, 85 insertions(+), 17 deletions(-) diff --git a/routes/_actions/timeline.js b/routes/_actions/timeline.js index e7885534..7d6eae7c 100644 --- a/routes/_actions/timeline.js +++ b/routes/_actions/timeline.js @@ -36,7 +36,11 @@ async function addStatuses(instanceName, timelineName, newStatuses) { let newStatusIds = newStatuses.map(status => status.id) let oldStatusIds = store.getForTimeline(instanceName, timelineName, 'statusIds') || [] let merged = mergeArrays(oldStatusIds, newStatusIds) - store.setForTimeline(instanceName, timelineName, { statusIds: merged }) + store.setForTimeline(instanceName, timelineName, { + statusIds: merged, + // if it's a status (context) list, we need to scroll to the status in question + scrollToStatusId: timelineName.startsWith('status/') && timelineName.split('/').slice(-1)[0] + }) stop('addStatuses') } diff --git a/routes/_components/timeline/Timeline.html b/routes/_components/timeline/Timeline.html index 803dd3fa..94f6ab48 100644 --- a/routes/_components/timeline/Timeline.html +++ b/routes/_components/timeline/Timeline.html @@ -2,11 +2,12 @@ diff --git a/routes/_components/virtualList/VirtualList.html b/routes/_components/virtualList/VirtualList.html index 4e6dd320..d26ed072 100644 --- a/routes/_components/virtualList/VirtualList.html +++ b/routes/_components/virtualList/VirtualList.html @@ -30,6 +30,9 @@ export default { oncreate () { + this.fireScrollToBottom = throttle(() => { + this.fire('scrollToBottom') + }, SCROLL_TO_BOTTOM_DELAY) this.observe('showFooter', showFooter => { this.store.setForRealm({showFooter: showFooter}) }) @@ -37,11 +40,12 @@ mark('set items') this.store.setForRealm({items: items}) stop('set items') - this.fireScrollToBottom = throttle(() => { - this.fire('scrollToBottom') - }, SCROLL_TO_BOTTOM_DELAY) }) - + this.observe('scrollToItem', (scrollToItem) => { + mark('scrollToItem') + this.store.setForRealm({scrollToItem: scrollToItem}) + stop('scrollToItem') + }) this.observe('allVisibleItemsHaveHeight', allVisibleItemsHaveHeight => { if (allVisibleItemsHaveHeight) { this.fire('initializedVisibleItems') diff --git a/routes/_components/virtualList/VirtualListContainer.html b/routes/_components/virtualList/VirtualListContainer.html index d67fc24b..e6f770c1 100644 --- a/routes/_components/virtualList/VirtualListContainer.html +++ b/routes/_components/virtualList/VirtualListContainer.html @@ -38,6 +38,18 @@ scrollHeight: node.scrollHeight, offsetHeight: node.offsetHeight }) + this.observe('scrollToItemOffset', scrollToItemOffset => { + console.log('scrollToItemOffset', scrollToItemOffset) + if (!this.get('initializedScrollTop') && scrollToItemOffset && node) { + this.set({'initializedScrollTop': true}) + requestAnimationFrame(() => { + mark('set scrollToItemOffset') + console.log('forcing scroll top to ', scrollToItemOffset) + node.scrollTop = scrollToItemOffset + stop('set scrollToItemOffset') + }) + } + }) } stop('onCreate VirtualListContainer') }, @@ -91,7 +103,8 @@ }, computed: { // TODO: bug in svelte/store – the observer in oncreate() never get removed without this hack - allVisibleItemsHaveHeight: ($allVisibleItemsHaveHeight) => $allVisibleItemsHaveHeight + allVisibleItemsHaveHeight: ($allVisibleItemsHaveHeight) => $allVisibleItemsHaveHeight, + scrollToItemOffset: ($scrollToItemOffset) => $scrollToItemOffset } }; \ No newline at end of file diff --git a/routes/_components/virtualList/virtualListStore.js b/routes/_components/virtualList/virtualListStore.js index 834f4471..4d84c4f3 100644 --- a/routes/_components/virtualList/virtualListStore.js +++ b/routes/_components/virtualList/virtualListStore.js @@ -73,9 +73,13 @@ virtualListStore.compute('offsetHeight', ['currentRealm', 'realms'], (currentRea return realms[currentRealm] && realms[currentRealm].offsetHeight || 0 }) +virtualListStore.compute('scrollToItem', ['currentRealm', 'realms'], (currentRealm, realms) => { + return realms[currentRealm] && realms[currentRealm].scrollToItem +}) + virtualListStore.compute('visibleItems', - ['items', 'scrollTop', 'itemHeights', 'offsetHeight'], - (items, scrollTop, itemHeights, offsetHeight) => { + ['items', 'scrollTop', 'itemHeights', 'offsetHeight', 'itemsLeftToCalculateHeight'], + (items, scrollTop, itemHeights, offsetHeight, itemsLeftToCalculateHeight) => { mark('compute visibleItems') let renderBuffer = VIEWPORT_RENDER_FACTOR * offsetHeight let visibleItems = [] @@ -87,14 +91,16 @@ virtualListStore.compute('visibleItems', let height = itemHeights[key] || 0 let currentOffset = totalOffset totalOffset += height - let isBelowViewport = (currentOffset < scrollTop) - if (isBelowViewport) { - if (scrollTop - renderBuffer > currentOffset) { - continue // below the area we want to render - } - } else { - if (currentOffset > (scrollTop + height + renderBuffer)) { - break // above the area we want to render + if (!itemsLeftToCalculateHeight) { + let isBelowViewport = (currentOffset < scrollTop) + if (!isBelowViewport) { + if (scrollTop - renderBuffer > currentOffset) { + continue // below the area we want to render + } + } else { + if (currentOffset > (scrollTop + height + renderBuffer)) { + break // above the area we want to render + } } } visibleItems.push({ @@ -142,6 +148,45 @@ virtualListStore.compute('allVisibleItemsHaveHeight', return true }) +// if we need to initialize the scroll at a particular item, then +// we effectively have to calculate all visible item heights +// TODO: technically not, we only need to calculate the items above it... or even estimate +virtualListStore.compute('mustCalculateAllItemHeights', + ['scrollToItem'], + (scrollToItem) => !!scrollToItem + ) + +virtualListStore.compute('itemsLeftToCalculateHeight', + ['mustCalculateAllItemHeights', 'itemHeights', 'items'], + (mustCalculateAllItemHeights, itemHeights, items) => { + if (!mustCalculateAllItemHeights) { + return false + } + for (let item of items) { + if (!itemHeights[item]) { + return true + } + } + return false +}) + +virtualListStore.compute('scrollToItemOffset', + ['mustCalculateAllItemHeights', 'itemsLeftToCalculateHeight', 'scrollToItem', 'items', 'itemHeights'], + (mustCalculateAllItemHeights, itemsLeftToCalculateHeight, scrollToItem, items, itemHeights) => { + if (!mustCalculateAllItemHeights || itemsLeftToCalculateHeight) { + return null + } + let offset = 0 + for (let item of items) { + if (item === scrollToItem) { + break + } + offset += itemHeights[item] + } + return offset +}) + + if (process.browser && process.env.NODE_ENV !== 'production') { window.virtualListStore = virtualListStore } diff --git a/routes/_store/timelineComputations.js b/routes/_store/timelineComputations.js index bad286a8..530185e2 100644 --- a/routes/_store/timelineComputations.js +++ b/routes/_store/timelineComputations.js @@ -8,4 +8,5 @@ export function timelineComputations(store) { store.compute('runningUpdate', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.runningUpdate) store.compute('initialized', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.initialized) store.compute('lastStatusId', ['statusIds'], (statusIds) => statusIds.length && statusIds[statusIds.length - 1]) + store.compute('scrollToStatusId', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.scrollToStatusId) } \ No newline at end of file