Fix reveal (#1563)

Fix race condition in slide show

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2021-10-16 21:45:22 +02:00 committed by GitHub
parent 3958ef550d
commit e84ed1398f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 19 deletions

View file

@ -8,7 +8,7 @@ import React, { useCallback, useEffect, useRef } from 'react'
export const useExtractFirstHeadline = (
documentElement: React.RefObject<HTMLDivElement>,
content: string,
content: string | undefined,
onFirstHeadingChange?: (firstHeading: string | undefined) => void
): void => {
const extractInnerText = useCallback((node: ChildNode | null): string => {

View file

@ -4,22 +4,39 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import Reveal from 'reveal.js'
import { Logger } from '../../../utils/logger'
import { SlideOptions } from '../../common/note-frontmatter/types'
const log = new Logger('reveal.js')
export const useReveal = (content: string, slideOptions?: SlideOptions): void => {
export enum REVEAL_STATUS {
NOT_INITIALISED,
INITIALISING,
INITIALISED
}
export interface SlideState {
indexHorizontal: number
indexVertical: number
}
const initialSlideState: SlideState = {
indexHorizontal: 0,
indexVertical: 0
}
export const useReveal = (content: string, slideOptions?: SlideOptions): REVEAL_STATUS => {
const [deck, setDeck] = useState<Reveal>()
const [isInitialized, setIsInitialized] = useState<boolean>(false)
const [revealStatus, setRevealStatus] = useState<REVEAL_STATUS>(REVEAL_STATUS.NOT_INITIALISED)
const currentSlideState = useRef<SlideState>(initialSlideState)
useEffect(() => {
if (isInitialized) {
if (revealStatus !== REVEAL_STATUS.NOT_INITIALISED) {
return
}
setIsInitialized(true)
setRevealStatus(REVEAL_STATUS.INITIALISING)
log.debug('Initialize with slide options', slideOptions)
const reveal = new Reveal({})
reveal
@ -27,27 +44,43 @@ export const useReveal = (content: string, slideOptions?: SlideOptions): void =>
.then(() => {
reveal.layout()
reveal.slide(0, 0, 0)
reveal.addEventListener('slidechanged', (event) => {
currentSlideState.current = {
indexHorizontal: event.indexh,
indexVertical: event.indexv ?? 0
} as SlideState
})
setDeck(reveal)
setRevealStatus(REVEAL_STATUS.INITIALISED)
log.debug('Initialisation finished')
})
.catch((error: Error) => {
log.error('Error while initializing reveal.js', error)
})
}, [isInitialized, slideOptions])
}, [revealStatus, slideOptions])
useEffect(() => {
if (!deck) {
if (!deck || revealStatus !== REVEAL_STATUS.INITIALISED) {
return
}
log.debug('Sync deck')
deck.layout()
}, [content, deck])
deck.sync()
deck.slide(currentSlideState.current.indexHorizontal, currentSlideState.current.indexVertical)
}, [content, deck, revealStatus])
useEffect(() => {
if (!deck || slideOptions === undefined || Object.keys(slideOptions).length === 0) {
if (
!deck ||
slideOptions === undefined ||
Object.keys(slideOptions).length === 0 ||
revealStatus !== REVEAL_STATUS.INITIALISED
) {
return
}
log.debug('Apply config', slideOptions)
deck.configure(slideOptions)
}, [deck, slideOptions])
}, [deck, revealStatus, slideOptions])
return revealStatus
}

View file

@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React from 'react'
import { Trans, useTranslation } from 'react-i18next'
/**
* Shows a static text placeholder while reveal.js is loading.
*/
export const LoadingSlide: React.FC = () => {
useTranslation()
return (
<section>
<h1>
<Trans i18nKey={'common.loading'} />
</h1>
</section>
)
}

View file

@ -12,7 +12,7 @@ import { useExtractFirstHeadline } from './hooks/use-extract-first-headline'
import { TocAst } from 'markdown-it-toc-done-right'
import { useOnRefChange } from './hooks/use-on-ref-change'
import { useTrimmedContent } from './hooks/use-trimmed-content'
import { useReveal } from './hooks/use-reveal'
import { REVEAL_STATUS, useReveal } from './hooks/use-reveal'
import './slideshow.scss'
import { ScrollProps } from '../editor-page/synced-scroll/scroll-props'
import { DocumentLengthLimitReachedAlert } from './document-length-limit-reached-alert'
@ -20,6 +20,7 @@ import { BasicMarkdownItConfigurator } from './markdown-it-configurator/basic-ma
import { SlideOptions } from '../common/note-frontmatter/types'
import { processRevealCommentNodes } from './process-reveal-comment-nodes'
import { CommonMarkdownRendererProps } from './common-markdown-renderer-props'
import { LoadingSlide } from './loading-slide'
export interface SlideshowMarkdownRendererProps extends CommonMarkdownRendererProps {
slideOptions: SlideOptions
@ -59,17 +60,26 @@ export const SlideshowMarkdownRenderer: React.FC<SlideshowMarkdownRendererProps
replacers,
processRevealCommentNodes
)
const revealStatus = useReveal(content, slideOptions)
useExtractFirstHeadline(markdownBodyRef, content, onFirstHeadingChange)
useExtractFirstHeadline(
markdownBodyRef,
revealStatus === REVEAL_STATUS.INITIALISED ? content : undefined,
onFirstHeadingChange
)
useOnRefChange(tocAst, onTocChange)
useReveal(content, slideOptions)
const slideShowDOM = useMemo(
() => (revealStatus === REVEAL_STATUS.INITIALISED ? markdownReactDom : <LoadingSlide />),
[markdownReactDom, revealStatus]
)
return (
<Fragment>
<DocumentLengthLimitReachedAlert show={contentExceedsLimit} />
<div className={'reveal'}>
<div ref={markdownBodyRef} className={`${className ?? ''} slides`}>
{markdownReactDom}
{slideShowDOM}
</div>
</div>
</Fragment>

View file

@ -184,9 +184,9 @@ declare module 'reveal.js' {
public getPlugins(): { [name: string]: Plugin }
// States
// public addEventListener(type: string, listener: (event: any) => void, useCapture?: boolean): void
// public removeEventListener(type: string, listener: (event: any) => void, useCapture?: boolean): void
// Added only the events we need
public addEventListener(type: 'slidechanged', listener: (event: SlideEvent) => void, useCapture?: boolean): void
public removeEventListener(type: 'slidechanged', listener: (event: SlideEvent) => void, useCapture?: boolean): void
// State Checks
public isFirstSlide(): boolean