diff --git a/frontend/src/components/editor-page/editor-page-content.tsx b/frontend/src/components/editor-page/editor-page-content.tsx index ca9be3010..89960e8a2 100644 --- a/frontend/src/components/editor-page/editor-page-content.tsx +++ b/frontend/src/components/editor-page/editor-page-content.tsx @@ -3,10 +3,8 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { useApplicationState } from '../../hooks/common/use-application-state' import { useApplyDarkModeStyle } from '../../hooks/dark-mode/use-apply-dark-mode-style' import { useSaveDarkModePreferenceToLocalStorage } from '../../hooks/dark-mode/use-save-dark-mode-preference-to-local-storage' -import { Logger } from '../../utils/logger' import { MotdModal } from '../common/motd-modal/motd-modal' import { CommunicatorImageLightbox } from '../markdown-renderer/extensions/image/communicator-image-lightbox' import { ExtensionEventEmitterProvider } from '../markdown-renderer/hooks/use-extension-event-emitter' @@ -15,14 +13,14 @@ import { ChangeEditorContentContextProvider } from './change-content-context/cod import { EditorPane } from './editor-pane/editor-pane' import { useComponentsFromAppExtensions } from './editor-pane/hooks/use-components-from-app-extensions' import { HeadMetaProperties } from './head-meta-properties/head-meta-properties' +import { useScrollState } from './hooks/use-scroll-state' +import { useSetScrollSource } from './hooks/use-set-scroll-source' import { useUpdateLocalHistoryEntry } from './hooks/use-update-local-history-entry' import { RealtimeConnectionAlert } from './realtime-connection-alert/realtime-connection-alert' import { RendererPane } from './renderer-pane/renderer-pane' import { Sidebar } from './sidebar/sidebar' import { Splitter } from './splitter/splitter' -import type { DualScrollState, ScrollState } from './synced-scroll/scroll-props' -import equal from 'fast-deep-equal' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import React, { useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' export enum ScrollSource { @@ -30,82 +28,31 @@ export enum ScrollSource { RENDERER = 'renderer' } -const log = new Logger('EditorPage') - /** * This is the content of the actual editor page. */ export const EditorPageContent: React.FC = () => { useTranslation() - const scrollSource = useRef(ScrollSource.EDITOR) - const editorSyncScroll: boolean = useApplicationState((state) => state.editorConfig.syncScroll) - - const [scrollState, setScrollState] = useState(() => ({ - editorScrollState: { firstLineInView: 1, scrolledPercentage: 0 }, - rendererScrollState: { firstLineInView: 1, scrolledPercentage: 0 } - })) - - const onMarkdownRendererScroll = useCallback( - (newScrollState: ScrollState) => { - if (scrollSource.current === ScrollSource.RENDERER && editorSyncScroll) { - setScrollState((old) => { - const newState: DualScrollState = { - editorScrollState: newScrollState, - rendererScrollState: old.rendererScrollState - } - return equal(newState, old) ? old : newState - }) - } - }, - [editorSyncScroll] - ) - - useEffect(() => { - log.debug('New scroll state', scrollState, scrollSource.current) - }, [scrollState]) - - const onEditorScroll = useCallback( - (newScrollState: ScrollState) => { - if (scrollSource.current === ScrollSource.EDITOR && editorSyncScroll) { - setScrollState((old) => { - const newState: DualScrollState = { - rendererScrollState: newScrollState, - editorScrollState: old.editorScrollState - } - return equal(newState, old) ? old : newState - }) - } - }, - [editorSyncScroll] - ) useApplyDarkModeStyle() useSaveDarkModePreferenceToLocalStorage() useUpdateLocalHistoryEntry() - const setRendererToScrollSource = useCallback(() => { - if (scrollSource.current !== ScrollSource.RENDERER) { - scrollSource.current = ScrollSource.RENDERER - log.debug('Make renderer scroll source') - } - }, []) - - const setEditorToScrollSource = useCallback(() => { - if (scrollSource.current !== ScrollSource.EDITOR) { - scrollSource.current = ScrollSource.EDITOR - log.debug('Make editor scroll source') - } - }, []) + const scrollSource = useRef(ScrollSource.EDITOR) + const [editorScrollState, onMarkdownRendererScroll] = useScrollState(scrollSource, ScrollSource.EDITOR) + const [rendererScrollState, onEditorScroll] = useScrollState(scrollSource, ScrollSource.RENDERER) + const setRendererToScrollSource = useSetScrollSource(scrollSource, ScrollSource.RENDERER) + const setEditorToScrollSource = useSetScrollSource(scrollSource, ScrollSource.EDITOR) const leftPane = useMemo( () => ( ), - [onEditorScroll, scrollState.editorScrollState, setEditorToScrollSource] + [onEditorScroll, editorScrollState, setEditorToScrollSource] ) const rightPane = useMemo( @@ -114,10 +61,10 @@ export const EditorPageContent: React.FC = () => { frameClasses={'h-100 w-100'} onMakeScrollSource={setRendererToScrollSource} onScroll={onMarkdownRendererScroll} - scrollState={scrollState.rendererScrollState} + scrollState={rendererScrollState} /> ), - [onMarkdownRendererScroll, scrollState.rendererScrollState, setRendererToScrollSource] + [onMarkdownRendererScroll, rendererScrollState, setRendererToScrollSource] ) const editorExtensionComponents = useComponentsFromAppExtensions() diff --git a/frontend/src/components/editor-page/hooks/use-scroll-state.ts b/frontend/src/components/editor-page/hooks/use-scroll-state.ts new file mode 100644 index 000000000..b88d959c7 --- /dev/null +++ b/frontend/src/components/editor-page/hooks/use-scroll-state.ts @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { useApplicationState } from '../../../hooks/common/use-application-state' +import { Logger } from '../../../utils/logger' +import type { ScrollSource } from '../editor-page-content' +import type { ScrollState } from '../synced-scroll/scroll-props' +import type { RefObject } from 'react' +import { useCallback, useEffect, useState } from 'react' + +const log = new Logger('useScrollState') + +/** + * Provides a {@link ScrollState} state and a function that updates the scroll state to a new value. + * + * @param scrollSourceRef The reference that defines which scroll source is active + * @param scrollSource The source for which the state should be created + * @return the created scroll state and the update function. The update function accepts only new values if the given scroll source isn't active to prevent callback loops. + */ +export const useScrollState = ( + scrollSourceRef: RefObject, + scrollSource: ScrollSource +): [scrollState: ScrollState, onScroll: (newScrollState: ScrollState) => void] => { + const editorSyncScroll: boolean = useApplicationState((state) => state.editorConfig.syncScroll) + + const [scrollState, setScrollState] = useState(() => ({ + firstLineInView: 1, + scrolledPercentage: 0 + })) + + const onScroll = useCallback( + (newScrollState: ScrollState) => { + if (scrollSourceRef.current !== scrollSource && editorSyncScroll) { + setScrollState(newScrollState) + } + }, + [editorSyncScroll, scrollSource, scrollSourceRef] + ) + + useEffect(() => { + log.debug(`New scroll state for ${scrollSource}`, scrollState) + }, [scrollSource, scrollState]) + + return [scrollState, onScroll] +} diff --git a/frontend/src/components/editor-page/hooks/use-set-scroll-source.ts b/frontend/src/components/editor-page/hooks/use-set-scroll-source.ts new file mode 100644 index 000000000..f0c9e3d77 --- /dev/null +++ b/frontend/src/components/editor-page/hooks/use-set-scroll-source.ts @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { Logger } from '../../../utils/logger' +import type { ScrollSource } from '../editor-page-content' +import type { MutableRefObject } from 'react' +import { useCallback } from 'react' + +const log = new Logger('useSetScrollSource') + +/** + * Provides a function that updates the given {@link ScrollSource} reference to a given value. + * + * @param scrollSourceReference The reference to update + * @param targetScrollSource The value that should be set in the reference + * @return The function that sets the reference + */ +export const useSetScrollSource = ( + scrollSourceReference: MutableRefObject, + targetScrollSource: ScrollSource +) => { + return useCallback(() => { + if (scrollSourceReference.current !== targetScrollSource) { + scrollSourceReference.current = targetScrollSource + log.debug(`Make ${targetScrollSource} scroll source`) + } + }, [scrollSourceReference, targetScrollSource]) +} diff --git a/frontend/src/components/editor-page/synced-scroll/scroll-props.ts b/frontend/src/components/editor-page/synced-scroll/scroll-props.ts index a81ba1839..2b07e527d 100644 --- a/frontend/src/components/editor-page/synced-scroll/scroll-props.ts +++ b/frontend/src/components/editor-page/synced-scroll/scroll-props.ts @@ -16,8 +16,3 @@ export interface ScrollState { firstLineInView: number scrolledPercentage: number } - -export interface DualScrollState { - editorScrollState: ScrollState - rendererScrollState: ScrollState -}