refactor: extract scroll hooks

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-04-26 19:24:48 +02:00
parent bded420cab
commit 340adbe69a
4 changed files with 89 additions and 70 deletions

View file

@ -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>(ScrollSource.EDITOR)
const editorSyncScroll: boolean = useApplicationState((state) => state.editorConfig.syncScroll)
const [scrollState, setScrollState] = useState<DualScrollState>(() => ({
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>(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(
() => (
<EditorPane
scrollState={scrollState.editorScrollState}
scrollState={editorScrollState}
onScroll={onEditorScroll}
onMakeScrollSource={setEditorToScrollSource}
/>
),
[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()

View file

@ -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: ScrollSource
): [scrollState: ScrollState, onScroll: (newScrollState: ScrollState) => void] => {
const editorSyncScroll: boolean = useApplicationState((state) => state.editorConfig.syncScroll)
const [scrollState, setScrollState] = useState<ScrollState>(() => ({
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]
}

View file

@ -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<ScrollSource>,
targetScrollSource: ScrollSource
) => {
return useCallback(() => {
if (scrollSourceReference.current !== targetScrollSource) {
scrollSourceReference.current = targetScrollSource
log.debug(`Make ${targetScrollSource} scroll source`)
}
}, [scrollSourceReference, targetScrollSource])
}

View file

@ -16,8 +16,3 @@ export interface ScrollState {
firstLineInView: number
scrolledPercentage: number
}
export interface DualScrollState {
editorScrollState: ScrollState
rendererScrollState: ScrollState
}