mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-04-05 02:56:44 +00:00
feat(renderer): Use callback instead of redux for renderer ready
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
285daeef8b
commit
2817740c94
12 changed files with 82 additions and 118 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -11,6 +11,7 @@ import { DocumentInfobar } from './document-infobar'
|
|||
import { RenderIframe } from '../editor-page/renderer-pane/render-iframe'
|
||||
import { RendererType } from '../render-page/window-post-message-communicator/rendering-message'
|
||||
import { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../hooks/common/use-trimmed-note-markdown-content-without-frontmatter'
|
||||
import { setRendererStatus } from '../../redux/renderer-status/methods'
|
||||
|
||||
/**
|
||||
* Renders the read-only version of a note with a header bar that contains information about the note.
|
||||
|
@ -29,6 +30,7 @@ export const DocumentReadOnlyPageContent: React.FC = () => {
|
|||
markdownContentLines={markdownContentLines}
|
||||
onFirstHeadingChange={updateNoteTitleByFirstHeading}
|
||||
rendererType={RendererType.DOCUMENT}
|
||||
onRendererStatusChange={setRendererStatus}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import type { PropsWithChildren } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
import { NoteInfoLine } from './note-info-line'
|
||||
|
@ -14,8 +14,8 @@ import { useEditorToRendererCommunicator } from '../../render-context/editor-to-
|
|||
import type { OnWordCountCalculatedMessage } from '../../../render-page/window-post-message-communicator/rendering-message'
|
||||
import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message'
|
||||
import { useEditorReceiveHandler } from '../../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler'
|
||||
import { useEffectOnRendererReady } from '../../../render-page/window-post-message-communicator/hooks/use-effect-on-renderer-ready'
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
|
||||
/**
|
||||
* Creates a new info line for the document information dialog that holds the
|
||||
|
@ -31,11 +31,12 @@ export const NoteInfoLineWordCount: React.FC<PropsWithChildren<unknown>> = () =>
|
|||
useCallback((values: OnWordCountCalculatedMessage) => setWordCount(values.words), [setWordCount])
|
||||
)
|
||||
|
||||
useEffectOnRendererReady(
|
||||
useCallback(() => {
|
||||
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
|
||||
useEffect(() => {
|
||||
if (rendererReady) {
|
||||
editorToRendererCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.GET_WORD_COUNT })
|
||||
}, [editorToRendererCommunicator])
|
||||
)
|
||||
}
|
||||
}, [editorToRendererCommunicator, rendererReady])
|
||||
|
||||
return (
|
||||
<NoteInfoLine icon={'align-left'} size={'2x'}>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -14,6 +14,7 @@ import { RendererType } from '../../render-page/window-post-message-communicator
|
|||
import { useSetCheckboxInEditor } from './hooks/use-set-checkbox-in-editor'
|
||||
import { useOnScrollWithLineOffset } from './hooks/use-on-scroll-with-line-offset'
|
||||
import { useScrollStateWithoutLineOffset } from './hooks/use-scroll-state-without-line-offset'
|
||||
import { setRendererStatus } from '../../../redux/renderer-status/methods'
|
||||
|
||||
export type EditorDocumentRendererProps = Omit<
|
||||
RenderIframeProps,
|
||||
|
@ -42,6 +43,7 @@ export const EditorDocumentRenderer: React.FC<EditorDocumentRendererProps> = ({
|
|||
onTaskCheckedChange={setCheckboxInEditor}
|
||||
rendererType={noteType === NoteType.SLIDE ? RendererType.SLIDESHOW : RendererType.DOCUMENT}
|
||||
markdownContentLines={trimmedContentLines}
|
||||
onRendererStatusChange={setRendererStatus}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -13,8 +13,9 @@ import { useSendToRenderer } from '../../../render-page/window-post-message-comm
|
|||
* Sends the current dark mode setting to the renderer.
|
||||
*
|
||||
* @param forcedDarkMode Overwrites the value from the global application states if set.
|
||||
* @param rendererReady Defines if the target renderer is ready
|
||||
*/
|
||||
export const useSendDarkModeStatusToRenderer = (forcedDarkMode?: boolean): void => {
|
||||
export const useSendDarkModeStatusToRenderer = (forcedDarkMode: boolean | undefined, rendererReady: boolean): void => {
|
||||
const savedDarkMode = useIsDarkModeActivated()
|
||||
|
||||
useSendToRenderer(
|
||||
|
@ -24,6 +25,7 @@ export const useSendDarkModeStatusToRenderer = (forcedDarkMode?: boolean): void
|
|||
activated: forcedDarkMode ?? savedDarkMode
|
||||
}),
|
||||
[forcedDarkMode, savedDarkMode]
|
||||
)
|
||||
),
|
||||
rendererReady
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -12,8 +12,9 @@ import { CommunicationMessageType } from '../../../render-page/window-post-messa
|
|||
* Sends the given markdown content to the renderer.
|
||||
*
|
||||
* @param markdownContentLines The markdown content to send.
|
||||
* @param rendererReady Defines if the target renderer is ready
|
||||
*/
|
||||
export const useSendMarkdownToRenderer = (markdownContentLines: string[]): void => {
|
||||
export const useSendMarkdownToRenderer = (markdownContentLines: string[], rendererReady: boolean): void => {
|
||||
return useSendToRenderer(
|
||||
useMemo(
|
||||
() => ({
|
||||
|
@ -21,6 +22,7 @@ export const useSendMarkdownToRenderer = (markdownContentLines: string[]): void
|
|||
content: markdownContentLines
|
||||
}),
|
||||
[markdownContentLines]
|
||||
)
|
||||
),
|
||||
rendererReady
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import type { ScrollState } from '../../synced-scroll/scroll-props'
|
||||
import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message'
|
||||
import { useEffectOnRendererReady } from '../../../render-page/window-post-message-communicator/hooks/use-effect-on-renderer-ready'
|
||||
import equal from 'fast-deep-equal'
|
||||
import { useEditorToRendererCommunicator } from '../../render-context/editor-to-renderer-communicator-context-provider'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
|
||||
/**
|
||||
* Sends the given {@link ScrollState scroll state} to the renderer if the content changed.
|
||||
|
@ -19,13 +19,12 @@ import { useEditorToRendererCommunicator } from '../../render-context/editor-to-
|
|||
export const useSendScrollState = (scrollState: ScrollState | undefined): void => {
|
||||
const iframeCommunicator = useEditorToRendererCommunicator()
|
||||
const oldScrollState = useRef<ScrollState | undefined>(undefined)
|
||||
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
|
||||
|
||||
useEffectOnRendererReady(
|
||||
useCallback(() => {
|
||||
if (scrollState && !equal(scrollState, oldScrollState.current)) {
|
||||
oldScrollState.current = scrollState
|
||||
iframeCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.SET_SCROLL_STATE, scrollState })
|
||||
}
|
||||
}, [iframeCommunicator, scrollState])
|
||||
)
|
||||
useEffect(() => {
|
||||
if (rendererReady && scrollState && !equal(scrollState, oldScrollState.current)) {
|
||||
oldScrollState.current = scrollState
|
||||
iframeCommunicator.sendMessageToOtherSide({ type: CommunicationMessageType.SET_SCROLL_STATE, scrollState })
|
||||
}
|
||||
}, [iframeCommunicator, rendererReady, scrollState])
|
||||
}
|
||||
|
|
|
@ -17,9 +17,7 @@ import { CommunicationMessageType } from '../../render-page/window-post-message-
|
|||
import { useEditorToRendererCommunicator } from '../render-context/editor-to-renderer-communicator-context-provider'
|
||||
import { useForceRenderPageUrlOnIframeLoadCallback } from './hooks/use-force-render-page-url-on-iframe-load-callback'
|
||||
import { CommunicatorImageLightbox } from './communicator-image-lightbox'
|
||||
import { setRendererStatus } from '../../../redux/renderer-status/methods'
|
||||
import { useEditorReceiveHandler } from '../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler'
|
||||
import { useIsRendererReady } from '../../render-page/window-post-message-communicator/hooks/use-is-renderer-ready'
|
||||
import { useSendDarkModeStatusToRenderer } from './hooks/use-send-dark-mode-status-to-renderer'
|
||||
import { useSendMarkdownToRenderer } from './hooks/use-send-markdown-to-renderer'
|
||||
import { useSendScrollState } from './hooks/use-send-scroll-state'
|
||||
|
@ -33,6 +31,7 @@ export interface RenderIframeProps extends RendererProps {
|
|||
rendererType: RendererType
|
||||
forcedDarkMode?: boolean
|
||||
frameClasses?: string
|
||||
onRendererStatusChange?: undefined | ((rendererReady: boolean) => void)
|
||||
}
|
||||
|
||||
const log = new Logger('RenderIframe')
|
||||
|
@ -51,6 +50,7 @@ const log = new Logger('RenderIframe')
|
|||
* @param frameClasses CSS classes that should be applied to the iframe
|
||||
* @param rendererType The {@link RendererType type} of the renderer to use.
|
||||
* @param forcedDarkMode If set, the dark mode will be set to the given value. Otherwise, the dark mode won't be changed.
|
||||
* @param onRendererStatusChange Callback that is fired when the renderer in the iframe is ready
|
||||
*/
|
||||
export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||
markdownContentLines,
|
||||
|
@ -61,20 +61,25 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
|||
onMakeScrollSource,
|
||||
frameClasses,
|
||||
rendererType,
|
||||
forcedDarkMode
|
||||
forcedDarkMode,
|
||||
onRendererStatusChange
|
||||
}) => {
|
||||
const [rendererReady, setRendererReady] = useState<boolean>(false)
|
||||
const frameReference = useRef<HTMLIFrameElement>(null)
|
||||
const rendererBaseUrl = useBaseUrl(ORIGIN.RENDERER)
|
||||
const iframeCommunicator = useEditorToRendererCommunicator()
|
||||
const resetRendererReady = useCallback(() => {
|
||||
log.debug('Reset render status')
|
||||
setRendererStatus(false)
|
||||
setRendererReady(false)
|
||||
}, [])
|
||||
const rendererReady = useIsRendererReady()
|
||||
const onIframeLoad = useForceRenderPageUrlOnIframeLoadCallback(frameReference, resetRendererReady)
|
||||
const [frameHeight, setFrameHeight] = useState<number>(0)
|
||||
|
||||
useEffect(() => () => setRendererStatus(false), [iframeCommunicator])
|
||||
useEffect(() => {
|
||||
onRendererStatusChange?.(rendererReady)
|
||||
}, [onRendererStatusChange, rendererReady])
|
||||
|
||||
useEffect(() => () => setRendererReady(false), [iframeCommunicator])
|
||||
|
||||
useEffect(() => {
|
||||
if (!rendererReady) {
|
||||
|
@ -108,7 +113,9 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
|||
|
||||
useEditorReceiveHandler(
|
||||
CommunicationMessageType.ON_HEIGHT_CHANGE,
|
||||
useCallback((values: OnHeightChangeMessage) => setFrameHeight?.(values.height), [setFrameHeight])
|
||||
useCallback((values: OnHeightChangeMessage) => {
|
||||
setFrameHeight?.(values.height)
|
||||
}, [])
|
||||
)
|
||||
|
||||
useEditorReceiveHandler(
|
||||
|
@ -135,13 +142,13 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
|||
rendererType
|
||||
}
|
||||
})
|
||||
setRendererStatus(true)
|
||||
setRendererReady(true)
|
||||
}, [iframeCommunicator, rendererBaseUrl, rendererType])
|
||||
)
|
||||
|
||||
useEffectOnRenderTypeChange(rendererType, onIframeLoad)
|
||||
useSendDarkModeStatusToRenderer(forcedDarkMode)
|
||||
useSendMarkdownToRenderer(markdownContentLines)
|
||||
useSendDarkModeStatusToRenderer(forcedDarkMode, rendererReady)
|
||||
useSendMarkdownToRenderer(markdownContentLines, rendererReady)
|
||||
|
||||
useSendScrollState(scrollState)
|
||||
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { fetchFrontPageContent } from '../requests'
|
||||
|
||||
export const useIntroPageContent = (): string[] | undefined => {
|
||||
const { t } = useTranslation()
|
||||
const [content, setContent] = useState<string[] | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
fetchFrontPageContent()
|
||||
.then((content) => setContent(content.split('\n')))
|
||||
.catch(() => setContent(undefined))
|
||||
}, [t])
|
||||
|
||||
return content
|
||||
}
|
|
@ -4,43 +4,34 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useMemo } from 'react'
|
||||
import { useIntroPageContent } from './hooks/use-intro-page-content'
|
||||
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { WaitSpinner } from '../common/wait-spinner/wait-spinner'
|
||||
import { RenderIframe } from '../editor-page/renderer-pane/render-iframe'
|
||||
import { RendererType } from '../render-page/window-post-message-communicator/rendering-message'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { fetchFrontPageContent } from './requests'
|
||||
|
||||
/**
|
||||
* Fetches the content for the customizable part of the intro page and renders it.
|
||||
*/
|
||||
export const IntroCustomContent: React.FC = () => {
|
||||
const introPageContent = useIntroPageContent()
|
||||
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
|
||||
const { t } = useTranslation()
|
||||
const [content, setContent] = useState<string[] | undefined>(undefined)
|
||||
|
||||
const spinner = useMemo(() => {
|
||||
if (!rendererReady && introPageContent !== undefined) {
|
||||
return <WaitSpinner />
|
||||
}
|
||||
}, [introPageContent, rendererReady])
|
||||
useEffect(() => {
|
||||
fetchFrontPageContent()
|
||||
.then((content) => setContent(content.split('\n')))
|
||||
.catch(() => setContent(undefined))
|
||||
}, [t])
|
||||
|
||||
const introContent = useMemo(() => {
|
||||
if (introPageContent !== undefined) {
|
||||
return (
|
||||
<RenderIframe
|
||||
frameClasses={'w-100 overflow-y-hidden'}
|
||||
markdownContentLines={introPageContent}
|
||||
rendererType={RendererType.INTRO}
|
||||
forcedDarkMode={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}, [introPageContent])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{spinner}
|
||||
{introContent}
|
||||
</Fragment>
|
||||
return content === undefined ? (
|
||||
<WaitSpinner />
|
||||
) : (
|
||||
<RenderIframe
|
||||
frameClasses={'w-100 overflow-y-hidden'}
|
||||
markdownContentLines={content}
|
||||
rendererType={RendererType.INTRO}
|
||||
forcedDarkMode={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||
|
||||
/**
|
||||
* Executes the given callback if it changes or the renderer is ready for receiving messages.
|
||||
*
|
||||
* @param sendOnReadyCallback The callback that should get executed.
|
||||
*/
|
||||
export const useEffectOnRendererReady = (sendOnReadyCallback: () => void): void => {
|
||||
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
|
||||
|
||||
useEffect(() => {
|
||||
if (rendererReady) {
|
||||
sendOnReadyCallback()
|
||||
}
|
||||
}, [rendererReady, sendOnReadyCallback])
|
||||
}
|
|
@ -4,27 +4,26 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import type { CommunicationMessages, EditorToRendererMessageType } from '../rendering-message'
|
||||
import { useEditorToRendererCommunicator } from '../../../editor-page/render-context/editor-to-renderer-communicator-context-provider'
|
||||
import type { MessagePayload } from '../window-post-message-communicator'
|
||||
import { useEffectOnRendererReady } from './use-effect-on-renderer-ready'
|
||||
|
||||
/**
|
||||
* Sends the given message to the renderer.
|
||||
*
|
||||
* @param message The message to send
|
||||
* @param rendererReady Defines if the target renderer is ready
|
||||
*/
|
||||
export const useSendToRenderer = (
|
||||
message: undefined | Extract<CommunicationMessages, MessagePayload<EditorToRendererMessageType>>
|
||||
message: undefined | Extract<CommunicationMessages, MessagePayload<EditorToRendererMessageType>>,
|
||||
rendererReady: boolean
|
||||
): void => {
|
||||
const iframeCommunicator = useEditorToRendererCommunicator()
|
||||
|
||||
useEffectOnRendererReady(
|
||||
useCallback(() => {
|
||||
if (message) {
|
||||
iframeCommunicator.sendMessageToOtherSide(message)
|
||||
}
|
||||
}, [iframeCommunicator, message])
|
||||
)
|
||||
useEffect(() => {
|
||||
if (message && rendererReady) {
|
||||
iframeCommunicator.sendMessageToOtherSide(message)
|
||||
}
|
||||
}, [iframeCommunicator, message, rendererReady])
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import { useTranslation } from 'react-i18next'
|
|||
import { useTrimmedNoteMarkdownContentWithoutFrontmatter } from '../../hooks/common/use-trimmed-note-markdown-content-without-frontmatter'
|
||||
import { useSendToRenderer } from '../render-page/window-post-message-communicator/hooks/use-send-to-renderer'
|
||||
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||
import { setRendererStatus } from '../../redux/renderer-status/methods'
|
||||
|
||||
/**
|
||||
* Renders the current markdown content as a slideshow.
|
||||
|
@ -24,6 +25,7 @@ export const SlideShowPageContent: React.FC = () => {
|
|||
useTranslation()
|
||||
|
||||
const slideOptions = useApplicationState((state) => state.noteDetails.frontmatter.slideOptions)
|
||||
const rendererReady = useApplicationState((state) => state.rendererStatus.rendererReady)
|
||||
useSendToRenderer(
|
||||
useMemo(
|
||||
() => ({
|
||||
|
@ -31,7 +33,8 @@ export const SlideShowPageContent: React.FC = () => {
|
|||
slideOptions
|
||||
}),
|
||||
[slideOptions]
|
||||
)
|
||||
),
|
||||
rendererReady
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -41,6 +44,7 @@ export const SlideShowPageContent: React.FC = () => {
|
|||
markdownContentLines={markdownContentLines}
|
||||
rendererType={RendererType.SLIDESHOW}
|
||||
onFirstHeadingChange={updateNoteTitleByFirstHeading}
|
||||
onRendererStatusChange={setRendererStatus}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue