From 0750695e2f804f276f935b72220c5bbb685ed261 Mon Sep 17 00:00:00 2001 From: Tilman Vatteroth Date: Sat, 17 Oct 2020 20:22:08 +0200 Subject: [PATCH] extract scroll code from renderer into hook Signed-off-by: Tilman Vatteroth --- .../scrolling-document-render-pane.tsx | 82 ++----------------- .../scroll/hooks/use-scroll-to-line-mark.ts | 43 ++++++++++ .../editor/scroll/hooks/use-user-scroll.ts | 41 ++++++++++ 3 files changed, 91 insertions(+), 75 deletions(-) create mode 100644 src/components/editor/scroll/hooks/use-scroll-to-line-mark.ts create mode 100644 src/components/editor/scroll/hooks/use-user-scroll.ts diff --git a/src/components/editor/document-renderer-pane/scrolling-document-render-pane.tsx b/src/components/editor/document-renderer-pane/scrolling-document-render-pane.tsx index df8b8e047..ea6cddd91 100644 --- a/src/components/editor/document-renderer-pane/scrolling-document-render-pane.tsx +++ b/src/components/editor/document-renderer-pane/scrolling-document-render-pane.tsx @@ -1,7 +1,8 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react' +import React, { useMemo, useRef, useState } from 'react' import { LineMarkerPosition } from '../../markdown-renderer/types' -import { ScrollProps, ScrollState } from '../scroll/scroll-props' -import { findLineMarks } from '../scroll/utils' +import { useScrollToLineMark } from '../scroll/hooks/use-scroll-to-line-mark' +import { useUserScroll } from '../scroll/hooks/use-user-scroll' +import { ScrollProps } from '../scroll/scroll-props' import { DocumentRenderPane, DocumentRenderPaneProps } from './document-render-pane' export const ScrollingDocumentRenderPane: React.FC = ({ @@ -14,81 +15,12 @@ export const ScrollingDocumentRenderPane: React.FC { - const lastScrollPosition = useRef() const renderer = useRef(null) const [lineMarks, setLineMarks] = useState() - const scrollTo = useCallback((targetPosition: number): void => { - if (!renderer.current || targetPosition === lastScrollPosition.current) { - return - } - lastScrollPosition.current = targetPosition - renderer.current.scrollTo({ - top: targetPosition - }) - }, [renderer]) - - useEffect(() => { - if (!renderer.current || !lineMarks || lineMarks.length === 0 || !scrollState) { - return - } - if (scrollState.firstLineInView < lineMarks[0].line) { - scrollTo(0) - return - } - if (scrollState.firstLineInView > lineMarks[lineMarks.length - 1].line) { - scrollTo(renderer.current.offsetHeight) - return - } - const { lastMarkBefore, firstMarkAfter } = findLineMarks(lineMarks, scrollState.firstLineInView) - const positionBefore = lastMarkBefore ? lastMarkBefore.position : lineMarks[0].position - const positionAfter = firstMarkAfter ? firstMarkAfter.position : renderer.current.offsetHeight - const lastMarkBeforeLine = lastMarkBefore ? lastMarkBefore.line : 1 - const firstMarkAfterLine = firstMarkAfter ? firstMarkAfter.line : content.split('\n').length - const lineCount = firstMarkAfterLine - lastMarkBeforeLine - const blockHeight = positionAfter - positionBefore - const lineHeight = blockHeight / lineCount - const position = positionBefore + (scrollState.firstLineInView - lastMarkBeforeLine) * lineHeight + scrollState.scrolledPercentage / 100 * lineHeight - const correctedPosition = Math.floor(position) - scrollTo(correctedPosition) - }, [content, lineMarks, scrollState, scrollTo]) - - const userScroll = useCallback(() => { - if (!renderer.current || !lineMarks || lineMarks.length === 0 || !onScroll) { - return - } - - const scrollTop = renderer.current.scrollTop - - const lineMarksBeforeScrollTop = lineMarks.filter(lineMark => lineMark.position <= scrollTop) - if (lineMarksBeforeScrollTop.length === 0) { - return - } - - const lineMarksAfterScrollTop = lineMarks.filter(lineMark => lineMark.position > scrollTop) - if (lineMarksAfterScrollTop.length === 0) { - return - } - - const beforeLineMark = lineMarksBeforeScrollTop - .reduce((prevLineMark, currentLineMark) => - prevLineMark.line >= currentLineMark.line ? prevLineMark : currentLineMark) - - const afterLineMark = lineMarksAfterScrollTop - .reduce((prevLineMark, currentLineMark) => - prevLineMark.line < currentLineMark.line ? prevLineMark : currentLineMark) - - const componentHeight = afterLineMark.position - beforeLineMark.position - const distanceToBefore = scrollTop - beforeLineMark.position - const percentageRaw = (distanceToBefore / componentHeight) - const lineCount = afterLineMark.line - beforeLineMark.line - const line = Math.floor(lineCount * percentageRaw + beforeLineMark.line) - const lineHeight = componentHeight / lineCount - const innerScrolling = Math.floor((distanceToBefore % lineHeight) / lineHeight * 100) - - const newScrollState: ScrollState = { firstLineInView: line, scrolledPercentage: innerScrolling } - onScroll(newScrollState) - }, [lineMarks, onScroll]) + const contentLineCount = useMemo(() => content.split('\n').length, [content]) + useScrollToLineMark(scrollState, lineMarks, contentLineCount, renderer) + const userScroll = useUserScroll(lineMarks, renderer, onScroll) return ( ): void => { + const lastScrollPosition = useRef() + + const scrollTo = useCallback((targetPosition: number): void => { + if (!renderer.current || targetPosition === lastScrollPosition.current) { + return + } + lastScrollPosition.current = targetPosition + renderer.current.scrollTo({ + top: targetPosition + }) + }, [renderer]) + + useEffect(() => { + if (!renderer.current || !lineMarks || lineMarks.length === 0 || !scrollState) { + return + } + if (scrollState.firstLineInView < lineMarks[0].line) { + scrollTo(0) + return + } + if (scrollState.firstLineInView > lineMarks[lineMarks.length - 1].line) { + scrollTo(renderer.current.offsetHeight) + return + } + const { lastMarkBefore, firstMarkAfter } = findLineMarks(lineMarks, scrollState.firstLineInView) + const positionBefore = lastMarkBefore ? lastMarkBefore.position : lineMarks[0].position + const positionAfter = firstMarkAfter ? firstMarkAfter.position : renderer.current.offsetHeight + const lastMarkBeforeLine = lastMarkBefore ? lastMarkBefore.line : 1 + const firstMarkAfterLine = firstMarkAfter ? firstMarkAfter.line : contentLineCount + const linesBetweenMarkers = firstMarkAfterLine - lastMarkBeforeLine + const blockHeight = positionAfter - positionBefore + const lineHeight = blockHeight / linesBetweenMarkers + const position = positionBefore + (scrollState.firstLineInView - lastMarkBeforeLine) * lineHeight + scrollState.scrolledPercentage / 100 * lineHeight + const correctedPosition = Math.floor(position) + scrollTo(correctedPosition) + }, [contentLineCount, lineMarks, renderer, scrollState, scrollTo]) +} diff --git a/src/components/editor/scroll/hooks/use-user-scroll.ts b/src/components/editor/scroll/hooks/use-user-scroll.ts new file mode 100644 index 000000000..cef8858fe --- /dev/null +++ b/src/components/editor/scroll/hooks/use-user-scroll.ts @@ -0,0 +1,41 @@ +import { RefObject, useCallback } from 'react' +import { LineMarkerPosition } from '../../../markdown-renderer/types' +import { ScrollState } from '../scroll-props' + +export const useUserScroll = (lineMarks: LineMarkerPosition[] | undefined, renderer: RefObject, onScroll: ((newScrollState: ScrollState) => void)|undefined): () => void => + useCallback(() => { + if (!renderer.current || !lineMarks || lineMarks.length === 0 || !onScroll) { + return + } + + const scrollTop = renderer.current.scrollTop + + const lineMarksBeforeScrollTop = lineMarks.filter(lineMark => lineMark.position <= scrollTop) + if (lineMarksBeforeScrollTop.length === 0) { + return + } + + const lineMarksAfterScrollTop = lineMarks.filter(lineMark => lineMark.position > scrollTop) + if (lineMarksAfterScrollTop.length === 0) { + return + } + + const beforeLineMark = lineMarksBeforeScrollTop + .reduce((prevLineMark, currentLineMark) => + prevLineMark.line >= currentLineMark.line ? prevLineMark : currentLineMark) + + const afterLineMark = lineMarksAfterScrollTop + .reduce((prevLineMark, currentLineMark) => + prevLineMark.line < currentLineMark.line ? prevLineMark : currentLineMark) + + const componentHeight = afterLineMark.position - beforeLineMark.position + const distanceToBefore = scrollTop - beforeLineMark.position + const percentageRaw = (distanceToBefore / componentHeight) + const lineCount = afterLineMark.line - beforeLineMark.line + const line = Math.floor(lineCount * percentageRaw + beforeLineMark.line) + const lineHeight = componentHeight / lineCount + const innerScrolling = Math.floor((distanceToBefore % lineHeight) / lineHeight * 100) + + const newScrollState: ScrollState = { firstLineInView: line, scrolledPercentage: innerScrolling } + onScroll(newScrollState) + }, [lineMarks, onScroll, renderer])