Stackedit/src/services/optional/scrollSync.js
2017-08-03 18:08:12 +01:00

186 lines
5.1 KiB
JavaScript

import store from '../../store';
import animationSvc from '../animationSvc';
import editorSvc from '../editorSvc';
let editorScrollerElt;
let previewScrollerElt;
let previewElt;
let editorFinishTimeoutId;
let previewFinishTimeoutId;
let skipAnimation;
let isScrollEditor;
let isScrollPreview;
let isEditorMoving;
let isPreviewMoving;
let sectionDescList;
let throttleTimeoutId;
let throttleLastTime = 0;
function throttle(func, wait) {
clearTimeout(throttleTimeoutId);
const currentTime = Date.now();
const localWait = (wait + throttleLastTime) - currentTime;
if (localWait < 1) {
throttleLastTime = currentTime;
func();
} else {
throttleTimeoutId = setTimeout(() => {
throttleLastTime = Date.now();
func();
}, localWait);
}
}
const doScrollSync = () => {
const localSkipAnimation = skipAnimation || !store.getters['layout/styles'].showSidePreview;
skipAnimation = false;
if (!store.state.editor.scrollSync || !sectionDescList || sectionDescList.length === 0) {
return;
}
let editorScrollTop = editorScrollerElt.scrollTop;
if (editorScrollTop < 0) {
editorScrollTop = 0;
}
const previewScrollTop = previewScrollerElt.scrollTop;
let scrollTo;
if (isScrollEditor) {
// Scroll the preview
isScrollEditor = false;
sectionDescList.some((sectionDesc) => {
if (editorScrollTop > sectionDesc.editorDimension.endOffset) {
return false;
}
const posInSection = (editorScrollTop - sectionDesc.editorDimension.startOffset)
/ (sectionDesc.editorDimension.height || 1);
scrollTo = (sectionDesc.previewDimension.startOffset
+ (sectionDesc.previewDimension.height * posInSection));
return true;
});
scrollTo = Math.min(
scrollTo,
previewScrollerElt.scrollHeight - previewScrollerElt.offsetHeight,
);
throttle(() => {
clearTimeout(previewFinishTimeoutId);
animationSvc.animate(previewScrollerElt)
.scrollTop(scrollTo)
.duration(!localSkipAnimation && 100)
.start(() => {
previewFinishTimeoutId = setTimeout(() => {
isPreviewMoving = false;
}, 100);
}, () => {
isPreviewMoving = true;
});
}, localSkipAnimation ? 500 : 50);
} else if (!store.getters['layout/styles'].showEditor || isScrollPreview) {
// Scroll the editor
isScrollPreview = false;
sectionDescList.some((sectionDesc) => {
if (previewScrollTop > sectionDesc.previewDimension.endOffset) {
return false;
}
const posInSection = (previewScrollTop - sectionDesc.previewDimension.startOffset)
/ (sectionDesc.previewDimension.height || 1);
scrollTo = (sectionDesc.editorDimension.startOffset
+ (sectionDesc.editorDimension.height * posInSection));
return true;
});
scrollTo = Math.min(
scrollTo,
editorScrollerElt.scrollHeight - editorScrollerElt.offsetHeight,
);
throttle(() => {
clearTimeout(editorFinishTimeoutId);
animationSvc.animate(editorScrollerElt)
.scrollTop(scrollTo)
.duration(!localSkipAnimation && 100)
.start(() => {
editorFinishTimeoutId = setTimeout(() => {
isEditorMoving = false;
}, 100);
}, () => {
isEditorMoving = true;
});
}, localSkipAnimation ? 500 : 50);
}
};
let isPreviewRefreshing;
let timeoutId;
const forceScrollSync = () => {
if (!isPreviewRefreshing) {
doScrollSync();
}
};
store.watch(state => state.editor.scrollSync, forceScrollSync);
editorSvc.$on('inited', () => {
editorScrollerElt = editorSvc.editorElt.parentNode;
previewScrollerElt = editorSvc.previewElt.parentNode;
previewElt = editorSvc.previewElt;
editorScrollerElt.addEventListener('scroll', () => {
if (isEditorMoving) {
return;
}
isScrollEditor = true;
isScrollPreview = false;
doScrollSync();
});
previewScrollerElt.addEventListener('scroll', () => {
if (isPreviewMoving || isPreviewRefreshing) {
return;
}
isScrollPreview = true;
isScrollEditor = false;
doScrollSync();
});
});
editorSvc.$on('sectionList', () => {
clearTimeout(timeoutId);
isPreviewRefreshing = true;
sectionDescList = undefined;
});
editorSvc.$on('conversionCtx', () => {
// Set the preview height to prevent scrollbar from jumping
previewElt.style.height = `${previewElt.offsetHeight}px`;
});
editorSvc.$on('previewText', () => {
// Remove height property once the preview as been refreshed
previewElt.style.removeProperty('height');
// Assume the user is writing in the editor
isScrollEditor = store.getters['layout/styles'].showEditor;
// A preview scrolling event can occur if height is smaller
timeoutId = setTimeout(() => {
isPreviewRefreshing = false;
}, 100);
});
store.watch(
() => store.getters['layout/styles'],
() => {
isScrollEditor = true;
isScrollPreview = false;
skipAnimation = true;
});
store.watch(
() => store.getters['files/current'].id,
() => {
skipAnimation = true;
});
editorSvc.$on('sectionDescMeasuredList', (sectionDescMeasuredList) => {
sectionDescList = sectionDescMeasuredList;
forceScrollSync();
});