From 573143c57da9b26d1aba5ad37e53ea8d92f6ff1d Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Fri, 2 Aug 2024 17:15:39 -0700 Subject: [PATCH] Extract PlantUML renderer --- src/modules/blocks/code/RenderCode.tsx | 65 +++---------------- .../blocks/code/RenderCodePlantUML.tsx | 60 +++++++++++++++++ 2 files changed, 68 insertions(+), 57 deletions(-) create mode 100644 src/modules/blocks/code/RenderCodePlantUML.tsx diff --git a/src/modules/blocks/code/RenderCode.tsx b/src/modules/blocks/code/RenderCode.tsx index e059b5002..eca783cba 100644 --- a/src/modules/blocks/code/RenderCode.tsx +++ b/src/modules/blocks/code/RenderCode.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { useQuery } from '@tanstack/react-query'; import { useShallow } from 'zustand/react/shallow'; import type { SxProps } from '@mui/joy/styles/types'; @@ -13,7 +12,6 @@ import SchemaIcon from '@mui/icons-material/Schema'; import WrapTextIcon from '@mui/icons-material/WrapText'; import { copyToClipboard } from '~/common/util/clipboardUtils'; -import { frontendSideFetch } from '~/common/util/clientFetchers'; import { useUIPreferencesStore } from '~/common/state/store-ui'; import type { CodeBlock } from '../blocks.types'; @@ -23,58 +21,13 @@ import { ButtonStackBlitz, isStackBlitzSupported } from './ButtonStackBlitz'; import { RenderCodeHtmlIFrame } from './RenderCodeHtmlIFrame'; import { heuristicIsBlockTextHTML } from '../html/RenderHtml'; import { patchSvgString, RenderCodeMermaid } from './RenderCodeMermaid'; +import { usePlantUmlSvg } from './RenderCodePlantUML'; // style for line-numbers import './RenderCode.css'; -export function getPlantUmlServerUrl(): string { - // set at nextjs build time - return process.env.NEXT_PUBLIC_PLANTUML_SERVER_URL || 'https://www.plantuml.com/plantuml/svg/'; -} - - -async function fetchPlantUmlSvg(plantUmlCode: string): Promise { - // Get the PlantUML server from inline env var - let plantUmlServerUrl = getPlantUmlServerUrl(); - if (!plantUmlServerUrl.endsWith('/')) - plantUmlServerUrl += '/'; - - // fetch the PlantUML SVG - let text: string = ''; - try { - // Dynamically import the PlantUML encoder - it's a large library that slows down app loading - const { encode: plantUmlEncode } = await import('plantuml-encoder'); - - // retrieve and manually adapt the SVG, to remove the background - const encodedPlantUML: string = plantUmlEncode(plantUmlCode); - const response = await frontendSideFetch(`${plantUmlServerUrl}${encodedPlantUML}`); - text = await response.text(); - } catch (error) { - console.error('Error rendering PlantUML on server:', plantUmlServerUrl, error); - return null; - } - - // validate/extract the SVG - const start = text.indexOf(''); - if (start < 0 || end <= start) - throw new Error('Could not render PlantUML'); - - // remove the background color - const svg = text - .slice(start, end + 6) // - .replace('background:#FFFFFF;', ''); - - // check for syntax errors - if (svg.includes('>Syntax Error?')) - throw new Error('llm syntax issue (it happens!). Please regenerate or change the language model.'); - - return svg; -} - - export const OverlayButton = styled(IconButton)(({ theme, variant }) => ({ backgroundColor: variant === 'outlined' ? theme.palette.background.surface : undefined, '--Icon-fontSize': theme.fontSize.lg, @@ -84,7 +37,7 @@ export const OverlayButton = styled(IconButton)(({ theme, variant }) => ({ export const overlayButtonsSx: SxProps = { position: 'absolute', top: 0, right: 0, zIndex: 2, /* top of message and its chips */ display: 'flex', flexDirection: 'row', gap: 1, - opacity: 0, transition: 'opacity 0.2s cubic-bezier(.17,.84,.44,1)', + opacity: 0.1, transition: 'opacity 0.2s cubic-bezier(.17,.84,.44,1)', // buttongroup: background '& > div > button': { // backgroundColor: 'background.surface', @@ -145,12 +98,7 @@ function RenderCodeImpl(props: RenderCodeImplProps) { || (blockCode.startsWith('@startgantt') && blockCode.endsWith('@endgantt')); let renderPlantUML = isPlantUML && showPlantUML; - const { data: plantUmlHtmlData, error: plantUmlError } = useQuery({ - enabled: renderPlantUML, - queryKey: ['plantuml', blockCode], - queryFn: () => fetchPlantUmlSvg(blockCode), - staleTime: 24 * 60 * 60 * 1000, // 1 day - }); + const { plantUmlHtmlData, plantUmlError } = usePlantUmlSvg(renderPlantUML, blockCode); renderPlantUML = renderPlantUML && (!!plantUmlHtmlData || !!plantUmlError); const isSVG = (blockCode.startsWith('\n'); @@ -215,7 +163,8 @@ function RenderCodeImpl(props: RenderCodeImplProps) { // lots more style, incl font, background, embossing, radius, etc. ...props.sx, - }}> + }} + > {/* Markdown Title (File/Type) */} {showBlockTitle && ( @@ -248,8 +197,10 @@ function RenderCodeImpl(props: RenderCodeImplProps) { }} />} - {/* Buttons */} + + {/* Overlay Buttons */} + {/* Show HTML */} {isHTML && ( diff --git a/src/modules/blocks/code/RenderCodePlantUML.tsx b/src/modules/blocks/code/RenderCodePlantUML.tsx new file mode 100644 index 000000000..cd34fca62 --- /dev/null +++ b/src/modules/blocks/code/RenderCodePlantUML.tsx @@ -0,0 +1,60 @@ +import { useQuery } from '@tanstack/react-query'; + +import { frontendSideFetch } from '~/common/util/clientFetchers'; + + +// PlantUML -> SVG fetchers + +export function usePlantUmlSvg(enabled: boolean, blockCode: string) { + const { data: plantUmlHtmlData, error: plantUmlError } = useQuery({ + enabled, + queryKey: ['plantuml', blockCode], + queryFn: () => _fetchPlantUmlSvg(blockCode), + staleTime: 24 * 60 * 60 * 1000, // 1 day + }); + return { plantUmlHtmlData, plantUmlError }; +} + +export function getPlantUmlServerUrl(): string { + // set at nextjs build time + return process.env.NEXT_PUBLIC_PLANTUML_SERVER_URL || 'https://www.plantuml.com/plantuml/svg/'; +} + +async function _fetchPlantUmlSvg(plantUmlCode: string): Promise { + // Get the PlantUML server from inline env var + let plantUmlServerUrl = getPlantUmlServerUrl(); + if (!plantUmlServerUrl.endsWith('/')) + plantUmlServerUrl += '/'; + + // fetch the PlantUML SVG + let text: string = ''; + try { + // Dynamically import the PlantUML encoder - it's a large library that slows down app loading + const { encode: plantUmlEncode } = await import('plantuml-encoder'); + + // retrieve and manually adapt the SVG, to remove the background + const encodedPlantUML: string = plantUmlEncode(plantUmlCode); + const response = await frontendSideFetch(`${plantUmlServerUrl}${encodedPlantUML}`); + text = await response.text(); + } catch (error) { + console.error('Error rendering PlantUML on server:', plantUmlServerUrl, error); + return null; + } + + // validate/extract the SVG + const start = text.indexOf(''); + if (start < 0 || end <= start) + throw new Error('Could not render PlantUML'); + + // remove the background color + const svg = text + .slice(start, end + 6) // + .replace('background:#FFFFFF;', ''); + + // check for syntax errors + if (svg.includes('>Syntax Error?')) + throw new Error('llm syntax issue (it happens!). Please regenerate or change the language model.'); + + return svg; +}