Feature/lazy load components (#590)

This commit is contained in:
mrdrogdrog 2020-09-26 09:54:17 +02:00 committed by GitHub
parent 9c38655a92
commit 101292da92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 261 additions and 248 deletions

View file

@ -1,19 +1,21 @@
import React, { useEffect, useRef } from 'react'
import { renderAbc } from 'abcjs'
export interface AbcFrameProps {
code: string
}
export const AbcFrame: React.FC<AbcFrameProps> = ({ code }) => {
const container = useRef<HTMLDivElement>(null)
useEffect(() => {
if (container.current) {
renderAbc(container.current, code)
if (!container.current) {
return
}
const actualContainer = container.current
import(/* webpackChunkName: "abc.js" */ 'abcjs').then((imp) => {
imp.renderAbc(actualContainer, code)
}).catch(() => { console.error('error while loading abcjs') })
}, [code])
return (
<div ref={container} className={'bg-white text-center'}/>
)
return <div ref={container} className={'bg-white text-center'}/>
}

View file

@ -1,4 +1,3 @@
import { parse } from 'flowchart.js'
import React, { useEffect, useRef, useState } from 'react'
import { Alert } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
@ -17,20 +16,21 @@ export const FlowChart: React.FC<FlowChartProps> = ({ code }) => {
if (diagramRef.current === null) {
return
}
const parserOutput = parse(code)
try {
parserOutput.drawSVG(diagramRef.current, {
'line-width': 2,
fill: 'none',
'font-size': '16px',
'font-family': 'Source Code Pro, twemoji, monospace'
})
setError(false)
} catch (error) {
setError(true)
}
const currentDiagramRef = diagramRef.current
import(/* webpackChunkName: "flowchart.js" */ 'flowchart.js').then((imp) => {
const parserOutput = imp.parse(code)
try {
parserOutput.drawSVG(currentDiagramRef, {
'line-width': 2,
fill: 'none',
'font-size': '16px',
'font-family': 'Source Code Pro, twemoji, monospace'
})
setError(false)
} catch (error) {
setError(true)
}
}).catch(() => { console.error('error while loading flowchart.js') })
return () => {
Array.from(currentDiagramRef.children).forEach(value => value.remove())
@ -41,8 +41,8 @@ export const FlowChart: React.FC<FlowChartProps> = ({ code }) => {
return (
<Alert variant={'danger'}>
<Trans i18nKey={'renderer.flowchart.invalidSyntax'}/>
</Alert>
)
</Alert>)
} else {
return <div ref={diagramRef} className={'text-center'}/>
}
return <div ref={diagramRef} className={'text-center'}/>
}

View file

@ -1,7 +1,5 @@
import { graphviz } from 'd3-graphviz'
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
import { Alert } from 'react-bootstrap'
import '@hpcc-js/wasm'
import { ShowIf } from '../../../common/show-if/show-if'
export interface GraphvizFrameProps {
@ -25,14 +23,18 @@ export const GraphvizFrame: React.FC<GraphvizFrameProps> = ({ code }) => {
if (!container.current) {
return
}
try {
setError(undefined)
graphviz(container.current, { useWorker: false, zoom: false })
.onerror(showError)
.renderDot(code)
} catch (error) {
showError(error)
}
const actualContainer = container.current
Promise.all([import(/* webpackChunkName: "d3-graphviz" */ 'd3-graphviz'), import('@hpcc-js/wasm')]).then(([imp]) => {
try {
setError(undefined)
imp.graphviz(actualContainer, { useWorker: false, zoom: false })
.onerror(showError)
.renderDot(code)
} catch (error) {
showError(error)
}
}).catch(() => { console.error('error while loading graphviz') })
}, [code, error, showError])
return <Fragment>
@ -42,3 +44,5 @@ export const GraphvizFrame: React.FC<GraphvizFrameProps> = ({ code }) => {
<div className={'text-center'} ref={container} />
</Fragment>
}
export default GraphvizFrame

View file

@ -1,5 +1,4 @@
import hljs from 'highlight.js'
import React, { Fragment, useMemo } from 'react'
import React, { Fragment, ReactElement, useEffect, useState } from 'react'
import ReactHtmlParser from 'react-html-parser'
import { CopyToClipboardButton } from '../../../../common/copyable/copy-to-clipboard-button/copy-to-clipboard-button'
import '../../../utils/button-inside.scss'
@ -21,10 +20,6 @@ export const escapeHtml = (unsafe: string): string => {
.replace(/'/g, '&#039;')
}
const checkIfLanguageIsSupported = (language: string): boolean => {
return hljs.listLanguages().includes(language)
}
const correctLanguage = (language: string | undefined): string | undefined => {
switch (language) {
case 'html':
@ -34,29 +29,36 @@ const correctLanguage = (language: string | undefined): string | undefined => {
}
}
const replaceCode = (code: string): ReactElement[][] => {
return code.split('\n')
.filter(line => !!line)
.map(line => ReactHtmlParser(line))
}
export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language, startLineNumber, wrapLines }) => {
const highlightedCode = useMemo(() => {
const replacedLanguage = correctLanguage(language)
return ((!!replacedLanguage && checkIfLanguageIsSupported(replacedLanguage)) ? hljs.highlight(replacedLanguage, code).value : escapeHtml(code))
.split('\n')
.filter(line => !!line)
.map(line => ReactHtmlParser(line))
}, [code, language])
const [dom, setDom] = useState<ReactElement[]>()
useEffect(() => {
import(/* webpackChunkName: "highlight.js" */ 'highlight.js').then((hljs) => {
const correctedLanguage = correctLanguage(language)
const languageSupported = (lang: string) => hljs.listLanguages().includes(lang)
const unreplacedCode = !!correctedLanguage && languageSupported(correctedLanguage) ? hljs.highlight(correctedLanguage, code).value : escapeHtml(code)
const replacedDom = replaceCode(unreplacedCode).map((line, index) => (
<Fragment key={index}>
<span className={'linenumber'} data-line-number={(startLineNumber || 1) + index}/>
<div className={'codeline'}>
{line}
</div>
</Fragment>
))
setDom(replacedDom)
}).catch(() => { console.error('error while loading highlight.js') })
}, [code, language, startLineNumber])
return (
<Fragment>
<code className={`hljs ${startLineNumber !== undefined ? 'showGutter' : ''} ${wrapLines ? 'wrapLines' : ''}`}>
{
highlightedCode
.map((line, index) => (
<Fragment key={index}>
<span className={'linenumber'} data-line-number={(startLineNumber || 1) + index}/>
<div className={'codeline'}>
{line}
</div>
</Fragment>
))
}
{ dom }
</code>
<div className={'text-right button-inside'}>
<CopyToClipboardButton content={code}/>

View file

@ -1,8 +1,7 @@
import { DomElement } from 'domhandler'
import React from 'react'
import 'katex/dist/katex.min.css'
import TeX from '@matejmazur/react-katex'
import { ComponentReplacer } from '../ComponentReplacer'
import './katex.scss'
const getNodeIfKatexBlock = (node: DomElement): (DomElement|undefined) => {
if (node.name !== 'p' || !node.children || node.children.length === 0) {
@ -17,13 +16,15 @@ const getNodeIfInlineKatex = (node: DomElement): (DomElement|undefined) => {
return (node.name === 'app-katex' && node.attribs?.inline !== undefined) ? node : undefined
}
const KaTeX = React.lazy(() => import(/* webpackChunkName: "katex" */ '@matejmazur/react-katex'))
export class KatexReplacer extends ComponentReplacer {
public getReplacement (node: DomElement): React.ReactElement | undefined {
const katex = getNodeIfKatexBlock(node) || getNodeIfInlineKatex(node)
if (katex?.children && katex.children[0]) {
const mathJaxContent = katex.children[0]?.data as string
const isInline = (katex.attribs?.inline) !== undefined
return <TeX block={!isInline} math={mathJaxContent} errorColor={'#cc0000'}/>
return <KaTeX block={!isInline} math={mathJaxContent} errorColor={'#cc0000'}/>
}
}
}

View file

@ -0,0 +1 @@
@import '../../../../../node_modules/katex/dist/katex.min';

View file

@ -1,5 +1,3 @@
import { transform } from 'markmap-lib/dist/transform'
import { Markmap } from 'markmap-lib/dist/view'
import React, { useEffect, useRef } from 'react'
export interface MarkmapFrameProps {
@ -13,12 +11,16 @@ export const MarkmapFrame: React.FC<MarkmapFrameProps> = ({ code }) => {
if (!diagramContainer.current) {
return
}
const svg: SVGSVGElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
svg.setAttribute('width', '100%')
diagramContainer.current.querySelectorAll('svg').forEach(child => child.remove())
diagramContainer.current.appendChild(svg)
const data = transform(code)
Markmap.create(svg, {}, data)
const actualContainer = diagramContainer.current
Promise.all([import(/* webpackChunkName: "markmap" */ 'markmap-lib/dist/transform'), import(/* webpackChunkName: "markmap" */ 'markmap-lib/dist/view')])
.then(([transform, view]) => {
const svg: SVGSVGElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
svg.setAttribute('width', '100%')
actualContainer.querySelectorAll('svg').forEach(child => child.remove())
actualContainer.appendChild(svg)
const data = transform.transform(code)
view.Markmap.create(svg, {}, data)
}).catch(() => { console.error('error while loading markmap') })
}, [code])
return <div className={'text-center'} ref={diagramContainer}/>

View file

@ -1,7 +1,7 @@
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
import { Alert } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import embed, { VisualizationSpec } from 'vega-embed'
import { VisualizationSpec } from 'vega-embed'
import { ShowIf } from '../../../common/show-if/show-if'
export interface VegaChartProps {
@ -25,27 +25,31 @@ export const VegaChart: React.FC<VegaChartProps> = ({ code }) => {
if (!diagramContainer.current) {
return
}
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const spec: VisualizationSpec = JSON.parse(code)
showError('')
embed(diagramContainer.current, spec, {
actions: {
export: true,
source: false,
compiled: false,
editor: false
},
i18n: {
PNG_ACTION: t('renderer.vega-lite.png'),
SVG_ACTION: t('renderer.vega-lite.svg')
import(/* webpackChunkName: "vega" */ 'vega-embed').then((embed) => {
try {
if (!diagramContainer.current) {
return
}
})
.then(result => console.log(result))
.catch(err => showError(err))
} catch (err) {
showError(t('renderer.vega-lite.errorJson'))
}
const spec = JSON.parse(code) as VisualizationSpec
embed.default(diagramContainer.current, spec, {
actions: {
export: true,
source: false,
compiled: false,
editor: false
},
i18n: {
PNG_ACTION: t('renderer.vega-lite.png'),
SVG_ACTION: t('renderer.vega-lite.svg')
}
})
.then(() => showError(''))
.catch(err => showError(err))
} catch (err) {
showError(t('renderer.vega-lite.errorJson'))
}
}).catch(() => { console.error('error while loading vega-light') })
}, [code, showError, t])
return <Fragment>

View file

@ -1,5 +1,5 @@
import { DomElement } from 'domhandler'
import React, { Fragment, ReactElement } from 'react'
import React, { ReactElement, Suspense } from 'react'
import { convertNodeToElement, Transform } from 'react-html-parser'
import {
ComponentReplacer,
@ -69,7 +69,9 @@ export const buildTransformer = (lineKeys: (LineKeys[] | undefined), allReplacer
} else if (tryReplacement === undefined) {
return nativeRenderer(node, key)
} else {
return <Fragment key={key}>{tryReplacement}</Fragment>
return <Suspense key={key} fallback={<span>Loading...</span>}>
{ tryReplacement }
</Suspense>
}
}
return transform