118 lines
3.6 KiB
TypeScript
118 lines
3.6 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
|
import cx from 'classnames'
|
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
|
|
import { vs, vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism'
|
|
|
|
import { usePlaygroundContext } from 'components/context/PlaygroundContext'
|
|
import { LoadingIndicator } from './LoadingIndicator'
|
|
import { ActionBar } from './ActionBar'
|
|
|
|
import { CodeBlockRef, PlaygroundArticleT } from '../types'
|
|
import { useTheme } from '@primer/react'
|
|
|
|
const getNormalizedHighlight = (
|
|
highlight: Exclude<CodeBlockRef['highlight'], undefined>
|
|
): Array<[number, number]> => {
|
|
if (typeof highlight === 'number') {
|
|
return [[highlight, highlight]]
|
|
} else if (typeof highlight[0] === 'number') {
|
|
return [highlight as [number, number]]
|
|
} else {
|
|
return highlight as Array<[number, number]>
|
|
}
|
|
}
|
|
interface Props {
|
|
article: PlaygroundArticleT
|
|
}
|
|
|
|
export const Editor: React.FC<Props> = ({ article }) => {
|
|
const theme = useTheme()
|
|
|
|
const [isEditorReady, setIsEditorReady] = useState(false)
|
|
const [selectedFileIndex, setSelectedFileIndex] = useState(0)
|
|
|
|
const { activeSectionIndex } = usePlaygroundContext()
|
|
const normalizedHighlight = getNormalizedHighlight(
|
|
article.contentBlocks[activeSectionIndex]?.codeBlock.highlight || []
|
|
)
|
|
|
|
useEffect(() => {
|
|
// Some buffer to load the theme, otherwise it flashes the light theme momentarily
|
|
const timeout = setTimeout(() => {
|
|
setIsEditorReady(true)
|
|
}, 250)
|
|
|
|
return () => {
|
|
clearTimeout(timeout)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (selectedFileIndex !== 0) {
|
|
setSelectedFileIndex(0)
|
|
}
|
|
}, [activeSectionIndex])
|
|
|
|
// find the set of files we want displayed in the editor
|
|
const codeBlockId = article.contentBlocks[activeSectionIndex].codeBlock.id
|
|
|
|
let editorFiles = article.codeBlocks[codeBlockId]
|
|
if (!Array.isArray(editorFiles)) {
|
|
editorFiles = [editorFiles]
|
|
}
|
|
let activeFile = editorFiles[selectedFileIndex]
|
|
if (!activeFile) {
|
|
activeFile = editorFiles[0]
|
|
}
|
|
|
|
return (
|
|
<div className="mx-auto">
|
|
<div className="text-mono">
|
|
<ActionBar code={activeFile.code} />
|
|
|
|
<div className="d-flex flex-items-center px-3 border-left border-right">
|
|
{editorFiles.map((file, i) => {
|
|
return (
|
|
<button
|
|
className={cx('btn-link Link--secondary no-underline mr-2 f6 py-2 px-3', {
|
|
'color-bg-subtle': i === selectedFileIndex,
|
|
})}
|
|
onClick={() => setSelectedFileIndex(i)}
|
|
>
|
|
{file.fileName}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
<div className="border">
|
|
{isEditorReady ? (
|
|
<SyntaxHighlighter
|
|
style={theme.resolvedColorMode === 'night' ? vscDarkPlus : vs}
|
|
language={activeFile.language}
|
|
PreTag="div"
|
|
customStyle={{ margin: '0', padding: '1rem 0', border: 0 }}
|
|
showLineNumbers={true}
|
|
wrapLines={true}
|
|
lineProps={(lineNumber) => {
|
|
let className = ''
|
|
for (const highlight of normalizedHighlight) {
|
|
if (lineNumber >= highlight[0] && lineNumber <= highlight[1]) {
|
|
className = 'color-bg-accent'
|
|
}
|
|
}
|
|
return { style: { display: 'block' }, class: className }
|
|
}}
|
|
lineNumberStyle={{ minWidth: '3.25em' }}
|
|
>
|
|
{activeFile.code}
|
|
</SyntaxHighlighter>
|
|
) : (
|
|
<LoadingIndicator />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|