Commit 6783350d authored by Phil Hughes's avatar Phil Hughes

Created pre-renderer and scroller sync

The pre-renderer pre-renders the diff files one by one to catch
the sizes of the diff file.
This is important for when we want to scroll to a set index
as without this the virtual scroller will try to calculate the sizes of each
one before the specified index which can lock the page.

The scroll sync component is used to sync up virtual scroller
scroll to index with whatever diff file the user wants to be viewing.

There will still be some lock-ups and maybe some slowness at certain
points, but in order to get over them hurdles we need to improve
the individual diff file components.
parent 212effa0
......@@ -53,7 +53,9 @@ import DiffFile from './diff_file.vue';
import HiddenFilesWarning from './hidden_files_warning.vue';
import MergeConflictWarning from './merge_conflict_warning.vue';
import NoChanges from './no_changes.vue';
import PreRenderer from './pre_renderer.vue';
import TreeList from './tree_list.vue';
import VirtualScrollerScrollSync from './virtual_scroller_scroll_sync';
export default {
name: 'DiffsApp',
......@@ -72,6 +74,8 @@ export default {
GlSprintf,
DynamicScroller,
DynamicScrollerItem,
PreRenderer,
VirtualScrollerScrollSync,
},
alerts: {
ALERT_OVERFLOW_HIDDEN,
......@@ -167,6 +171,7 @@ export default {
return {
treeWidth,
diffFilesLength: 0,
virtualScrollCurrentIndex: -1,
};
},
computed: {
......@@ -503,7 +508,6 @@ export default {
const targetIndex = this.currentDiffIndex + step;
if (targetIndex >= 0 && targetIndex < this.diffFiles.length) {
this.scrollToFile(this.diffFiles[targetIndex].file_path);
this.scrollVirtualScrollerToFileHash(this.diffFiles[targetIndex].file_hash);
}
},
setTreeDisplay() {
......@@ -518,12 +522,15 @@ export default {
return this.setShowTreeList({ showTreeList, saving: false });
},
scrollVirtualScrollerToFileHash(hash) {
async scrollVirtualScrollerToFileHash(hash) {
const index = this.diffFiles.findIndex((f) => f.file_hash === hash);
if (index !== -1) {
this.$refs.virtualScroller.scrollToItem(index);
window.scrollBy(0, -154);
this.virtualScrollCurrentIndex = index;
await this.$nextTick();
this.virtualScrollCurrentIndex = -1;
}
},
},
......@@ -610,6 +617,24 @@ export default {
/>
</dynamic-scroller-item>
</template>
<template #before>
<pre-renderer :max-length="diffFilesLength">
<template #default="{ item, index, active }">
<dynamic-scroller-item :item="item" :active="active">
<diff-file
:file="item"
:reviewed="fileReviews[item.id]"
:is-first-file="index === 0"
:is-last-file="index === diffFilesLength - 1"
:help-page-path="helpPagePath"
:can-current-user-fork="canCurrentUserFork"
:view-diffs-file-by-file="viewDiffsFileByFile"
/>
</dynamic-scroller-item>
</template>
</pre-renderer>
<virtual-scroller-scroll-sync :index="virtualScrollCurrentIndex" />
</template>
</dynamic-scroller>
<template v-else>
<diff-file
......
<script>
export default {
inject: ['vscrollParent'],
props: {
maxLength: {
type: Number,
required: true,
},
},
data() {
return {
nextIndex: -1,
nextItem: null,
startedRender: false,
width: 0,
};
},
mounted() {
this.width = this.$el.parentNode.offsetWidth;
this.$_itemsWithSizeWatcher = this.$watch('vscrollParent.itemsWithSize', async () => {
await this.$nextTick();
const nextItem = this.findNextToRender();
if (nextItem) {
this.startedRender = true;
this.nextItem = nextItem;
} else if (this.startedRender) {
this.nextItem = null;
if (this.maxLength === this.vscrollParent.itemsWithSize.length) {
this.$_itemsWithSizeWatcher();
}
}
});
},
beforeDestroy() {
this.$_itemsWithSizeWatcher();
},
methods: {
findNextToRender() {
return this.vscrollParent.itemsWithSize.find(({ size }, index) => {
const isNext = size === 0;
if (isNext) {
this.nextIndex = index;
}
return isNext;
});
},
},
};
</script>
<template>
<div v-if="nextItem" :style="{ width: `${width}px` }" class="gl-absolute diff-file-offscreen">
<slot
v-bind="{ item: nextItem.item, index: nextIndex, active: true, itemWithSize: nextItem }"
></slot>
</div>
</template>
<style scoped>
.diff-file-offscreen {
top: -200%;
left: -200%;
}
</style>
export default {
inject: ['vscrollParent'],
props: {
index: {
type: Number,
required: true,
},
},
watch: {
index: {
handler() {
if (this.index < 0) return;
if (this.vscrollParent.itemsWithSize[this.index].size) {
this.scrollToIndex();
} else {
this.$_itemsWithSizeWatcher = this.$watch('vscrollParent.itemsWithSize', async () => {
await this.$nextTick();
if (this.vscrollParent.itemsWithSize[this.index].size) {
this.$_itemsWithSizeWatcher();
this.scrollToIndex();
}
});
}
},
immediate: true,
},
},
beforeDestroy() {
if (this.$_itemsWithSizeWatcher) this.$_itemsWithSizeWatcher();
},
methods: {
scrollToIndex() {
this.vscrollParent.scrollToItem(this.index);
},
},
render(h) {
return h(null);
},
};
......@@ -189,7 +189,7 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => {
.catch(() => commit(types.SET_RETRIEVING_BATCHES, false));
return getBatch()
.then(() => handleLocationHash())
.then(() => !window.gon?.features?.diffsVirtualScrolling && handleLocationHash())
.then(() => {
eventHub.$emit(
'scrollToFileHash',
......@@ -532,12 +532,17 @@ export const scrollToFile = ({ state, commit }, path) => {
if (!state.treeEntries[path]) return;
const { fileHash } = state.treeEntries[path];
document.location.hash = fileHash;
commit(types.VIEW_DIFF_FILE, fileHash);
if (window.gon?.features?.diffsVirtualScrolling) {
eventHub.$emit('scrollToFileHash', fileHash);
setTimeout(() => {
document.location.hash = fileHash;
});
} else {
document.location.hash = fileHash;
}
};
......
......@@ -79,10 +79,6 @@ export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventNa
// automatically adjust scroll position for hash urls taking the height of the navbar into account
// https://github.com/twitter/bootstrap/issues/1768
export const handleLocationHash = () => {
if (window.gon?.features?.diffsVirtualScrolling && document.querySelector('#diffs.active')) {
return;
}
let hash = getLocationHash();
if (!hash) return;
......
......@@ -195,14 +195,15 @@ export default {
observeSize () {
if (!this.vscrollResizeObserver) return
this.vscrollResizeObserver.observe(this.$el.parentNode)
this.$el.parentNode.addEventListener('resize', this.onResize)
this.$_parentNode = this.$el.parentNode;
this.vscrollResizeObserver.observe(this.$_parentNode)
this.$_parentNode.addEventListener('resize', this.onResize)
},
unobserveSize () {
if (!this.vscrollResizeObserver) return
this.vscrollResizeObserver.unobserve(this.$el.parentNode)
this.$el.parentNode.removeEventListener('resize', this.onResize)
this.vscrollResizeObserver.unobserve(this.$_parentNode)
this.$_parentNode.removeEventListener('resize', this.onResize)
},
onResize (event) {
......
......@@ -572,8 +572,14 @@ export default {
},
scrollToItem (index) {
this.$_scrollDirty = true
const { viewport, scrollDirection, scrollDistance } = this.scrollToPosition(index)
viewport[scrollDirection] = scrollDistance
setTimeout(() => {
this.$_scrollDirty = false
this.updateVisibleItems(false, true)
})
},
scrollToPosition (index) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment