Skip to content

Commit

Permalink
Split SVG rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
charli117 committed Sep 14, 2024
1 parent 69e134e commit 30ffe19
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 76 deletions.
77 changes: 1 addition & 76 deletions web/app/components/base/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import RemarkBreaks from 'remark-breaks'
import RehypeKatex from 'rehype-katex'
import RemarkGfm from 'remark-gfm'
import RehypeRaw from 'rehype-raw'
import { SVG } from '@svgdotjs/svg.js'
import SyntaxHighlighter from 'react-syntax-highlighter'
import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs'
import type { RefObject } from 'react'
Expand All @@ -20,7 +19,7 @@ import ImageGallery from '@/app/components/base/image-gallery'
import { useChatContext } from '@/app/components/base/chat/chat/context'
import VideoGallery from '@/app/components/base/video-gallery'
import AudioGallery from '@/app/components/base/audio-gallery'
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
import SVGRenderer from '@/app/components/base/svg-gallery'

// Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD
const capitalizationLanguageNameMap: Record<string, string> = {
Expand Down Expand Up @@ -99,80 +98,6 @@ const useLazyLoad = (ref: RefObject<Element>): boolean => {
return isIntersecting
}

const SVGRenderer = ({ content }: { content: string }) => {
const svgRef = useRef<HTMLDivElement>(null)
const [imagePreview, setImagePreview] = useState('')
const [windowSize, setWindowSize] = useState({
width: typeof window !== 'undefined' ? window.innerWidth : 0,
height: typeof window !== 'undefined' ? window.innerHeight : 0,
})

const svgToDataURL = (svgElement: Element): string => {
const svgString = new XMLSerializer().serializeToString(svgElement)
return `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgString)))}`
}

useEffect(() => {
const handleResize = () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight })
}

window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])

useEffect(() => {
if (svgRef.current) {
try {
svgRef.current.innerHTML = ''
const draw = SVG().addTo(svgRef.current).size('100%', '100%')

const parser = new DOMParser()
const svgDoc = parser.parseFromString(content, 'image/svg+xml')
const svgElement = svgDoc.documentElement

if (!(svgElement instanceof SVGElement))
throw new Error('Invalid SVG content')

const originalWidth = parseInt(svgElement.getAttribute('width') || '400', 10)
const originalHeight = parseInt(svgElement.getAttribute('height') || '600', 10)
const scale = Math.min(windowSize.width / originalWidth, windowSize.height / originalHeight, 1)
const scaledWidth = originalWidth * scale
const scaledHeight = originalHeight * scale
draw.size(scaledWidth, scaledHeight)

const rootElement = draw.svg(content)
rootElement.scale(scale)

rootElement.click(() => {
setImagePreview(svgToDataURL(svgElement as Element))
})
}
catch (error) {
console.error('Error rendering SVG:', error)
if (svgRef.current)
svgRef.current.innerHTML = 'Error rendering SVG. Please check the console for details.'
}
}
}, [content, windowSize])

return (
<>
<div ref={svgRef} style={{
width: '100%',
height: '100%',
minHeight: '300px',
maxHeight: '80vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
cursor: 'pointer',
}} />
{imagePreview && (<ImagePreview url={imagePreview} title='Preview' onCancel={() => setImagePreview('')} />)}
</>
)
}

// **Add code block
// Avoid error #185 (Maximum update depth exceeded.
// This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
Expand Down
78 changes: 78 additions & 0 deletions web/app/components/base/svg-gallery/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useEffect, useRef, useState } from 'react'
import { SVG } from '@svgdotjs/svg.js'
import ImagePreview from '@/app/components/base/image-uploader/image-preview'

export const SVGRenderer = ({ content }: { content: string }) => {
const svgRef = useRef<HTMLDivElement>(null)
const [imagePreview, setImagePreview] = useState('')
const [windowSize, setWindowSize] = useState({
width: typeof window !== 'undefined' ? window.innerWidth : 0,
height: typeof window !== 'undefined' ? window.innerHeight : 0,
})

const svgToDataURL = (svgElement: Element): string => {
const svgString = new XMLSerializer().serializeToString(svgElement)
return `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgString)))}`
}

useEffect(() => {
const handleResize = () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight })
}

window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])

useEffect(() => {
if (svgRef.current) {
try {
svgRef.current.innerHTML = ''
const draw = SVG().addTo(svgRef.current).size('100%', '100%')

const parser = new DOMParser()
const svgDoc = parser.parseFromString(content, 'image/svg+xml')
const svgElement = svgDoc.documentElement

if (!(svgElement instanceof SVGElement))
throw new Error('Invalid SVG content')

const originalWidth = parseInt(svgElement.getAttribute('width') || '400', 10)
const originalHeight = parseInt(svgElement.getAttribute('height') || '600', 10)
const scale = Math.min(windowSize.width / originalWidth, windowSize.height / originalHeight, 1)
const scaledWidth = originalWidth * scale
const scaledHeight = originalHeight * scale
draw.size(scaledWidth, scaledHeight)

const rootElement = draw.svg(content)
rootElement.scale(scale)

rootElement.click(() => {
setImagePreview(svgToDataURL(svgElement as Element))
})
}
catch (error) {
if (svgRef.current)
svgRef.current.innerHTML = 'Error rendering SVG. Wait for the image content to complete.'
}
}
}, [content, windowSize])

return (
<>
<div ref={svgRef} style={{
width: '100%',
height: '100%',
minHeight: '300px',
maxHeight: '80vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
cursor: 'pointer',
}} />
{imagePreview && (<ImagePreview url={imagePreview} title='Preview' onCancel={() => setImagePreview('')} />)}
</>
)
}

export default SVGRenderer

0 comments on commit 30ffe19

Please sign in to comment.