mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-18 00:54:43 -04:00
refactor: extract scroll hooks
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
bded420cab
commit
340adbe69a
4 changed files with 89 additions and 70 deletions
|
@ -3,10 +3,8 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* 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 { 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 { 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 { MotdModal } from '../common/motd-modal/motd-modal'
|
||||||
import { CommunicatorImageLightbox } from '../markdown-renderer/extensions/image/communicator-image-lightbox'
|
import { CommunicatorImageLightbox } from '../markdown-renderer/extensions/image/communicator-image-lightbox'
|
||||||
import { ExtensionEventEmitterProvider } from '../markdown-renderer/hooks/use-extension-event-emitter'
|
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 { EditorPane } from './editor-pane/editor-pane'
|
||||||
import { useComponentsFromAppExtensions } from './editor-pane/hooks/use-components-from-app-extensions'
|
import { useComponentsFromAppExtensions } from './editor-pane/hooks/use-components-from-app-extensions'
|
||||||
import { HeadMetaProperties } from './head-meta-properties/head-meta-properties'
|
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 { useUpdateLocalHistoryEntry } from './hooks/use-update-local-history-entry'
|
||||||
import { RealtimeConnectionAlert } from './realtime-connection-alert/realtime-connection-alert'
|
import { RealtimeConnectionAlert } from './realtime-connection-alert/realtime-connection-alert'
|
||||||
import { RendererPane } from './renderer-pane/renderer-pane'
|
import { RendererPane } from './renderer-pane/renderer-pane'
|
||||||
import { Sidebar } from './sidebar/sidebar'
|
import { Sidebar } from './sidebar/sidebar'
|
||||||
import { Splitter } from './splitter/splitter'
|
import { Splitter } from './splitter/splitter'
|
||||||
import type { DualScrollState, ScrollState } from './synced-scroll/scroll-props'
|
import React, { useMemo, useRef } from 'react'
|
||||||
import equal from 'fast-deep-equal'
|
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export enum ScrollSource {
|
export enum ScrollSource {
|
||||||
|
@ -30,82 +28,31 @@ export enum ScrollSource {
|
||||||
RENDERER = 'renderer'
|
RENDERER = 'renderer'
|
||||||
}
|
}
|
||||||
|
|
||||||
const log = new Logger('EditorPage')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the content of the actual editor page.
|
* This is the content of the actual editor page.
|
||||||
*/
|
*/
|
||||||
export const EditorPageContent: React.FC = () => {
|
export const EditorPageContent: React.FC = () => {
|
||||||
useTranslation()
|
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()
|
useApplyDarkModeStyle()
|
||||||
useSaveDarkModePreferenceToLocalStorage()
|
useSaveDarkModePreferenceToLocalStorage()
|
||||||
useUpdateLocalHistoryEntry()
|
useUpdateLocalHistoryEntry()
|
||||||
|
|
||||||
const setRendererToScrollSource = useCallback(() => {
|
const scrollSource = useRef<ScrollSource>(ScrollSource.EDITOR)
|
||||||
if (scrollSource.current !== ScrollSource.RENDERER) {
|
const [editorScrollState, onMarkdownRendererScroll] = useScrollState(scrollSource, ScrollSource.EDITOR)
|
||||||
scrollSource.current = ScrollSource.RENDERER
|
const [rendererScrollState, onEditorScroll] = useScrollState(scrollSource, ScrollSource.RENDERER)
|
||||||
log.debug('Make renderer scroll source')
|
const setRendererToScrollSource = useSetScrollSource(scrollSource, ScrollSource.RENDERER)
|
||||||
}
|
const setEditorToScrollSource = useSetScrollSource(scrollSource, ScrollSource.EDITOR)
|
||||||
}, [])
|
|
||||||
|
|
||||||
const setEditorToScrollSource = useCallback(() => {
|
|
||||||
if (scrollSource.current !== ScrollSource.EDITOR) {
|
|
||||||
scrollSource.current = ScrollSource.EDITOR
|
|
||||||
log.debug('Make editor scroll source')
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const leftPane = useMemo(
|
const leftPane = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<EditorPane
|
<EditorPane
|
||||||
scrollState={scrollState.editorScrollState}
|
scrollState={editorScrollState}
|
||||||
onScroll={onEditorScroll}
|
onScroll={onEditorScroll}
|
||||||
onMakeScrollSource={setEditorToScrollSource}
|
onMakeScrollSource={setEditorToScrollSource}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[onEditorScroll, scrollState.editorScrollState, setEditorToScrollSource]
|
[onEditorScroll, editorScrollState, setEditorToScrollSource]
|
||||||
)
|
)
|
||||||
|
|
||||||
const rightPane = useMemo(
|
const rightPane = useMemo(
|
||||||
|
@ -114,10 +61,10 @@ export const EditorPageContent: React.FC = () => {
|
||||||
frameClasses={'h-100 w-100'}
|
frameClasses={'h-100 w-100'}
|
||||||
onMakeScrollSource={setRendererToScrollSource}
|
onMakeScrollSource={setRendererToScrollSource}
|
||||||
onScroll={onMarkdownRendererScroll}
|
onScroll={onMarkdownRendererScroll}
|
||||||
scrollState={scrollState.rendererScrollState}
|
scrollState={rendererScrollState}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[onMarkdownRendererScroll, scrollState.rendererScrollState, setRendererToScrollSource]
|
[onMarkdownRendererScroll, rendererScrollState, setRendererToScrollSource]
|
||||||
)
|
)
|
||||||
|
|
||||||
const editorExtensionComponents = useComponentsFromAppExtensions()
|
const editorExtensionComponents = useComponentsFromAppExtensions()
|
||||||
|
|
|
@ -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]
|
||||||
|
}
|
|
@ -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])
|
||||||
|
}
|
|
@ -16,8 +16,3 @@ export interface ScrollState {
|
||||||
firstLineInView: number
|
firstLineInView: number
|
||||||
scrolledPercentage: number
|
scrolledPercentage: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DualScrollState {
|
|
||||||
editorScrollState: ScrollState
|
|
||||||
rendererScrollState: ScrollState
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue