index.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import React, { useEffect, useRef, useState } from 'react'
  2. import mermaid from 'mermaid'
  3. import CryptoJS from 'crypto-js'
  4. let mermaidAPI: any
  5. mermaidAPI = null
  6. if (typeof window !== 'undefined') {
  7. mermaid.initialize({
  8. startOnLoad: true,
  9. theme: 'default',
  10. flowchart: {
  11. htmlLabels: true,
  12. useMaxWidth: true,
  13. },
  14. })
  15. mermaidAPI = mermaid.mermaidAPI
  16. }
  17. const style = {
  18. minWidth: '480px',
  19. height: 'auto',
  20. overflow: 'auto',
  21. }
  22. const Flowchart = React.forwardRef((props: {
  23. PrimitiveCode: string
  24. }, ref) => {
  25. const [svgCode, setSvgCode] = useState(null)
  26. const chartId = useRef(`flowchart_${CryptoJS.MD5(props.PrimitiveCode).toString()}`)
  27. const [isRender, setIsRender] = useState(true)
  28. const clearFlowchartCache = () => {
  29. for (let i = localStorage.length - 1; i >= 0; --i) {
  30. const key = localStorage.key(i)
  31. if (key && key.startsWith('flowchart_'))
  32. localStorage.removeItem(key)
  33. }
  34. }
  35. const renderFlowchart = async (PrimitiveCode: string) => {
  36. try {
  37. const cachedSvg: any = localStorage.getItem(chartId.current)
  38. if (cachedSvg) {
  39. setSvgCode(cachedSvg)
  40. return
  41. }
  42. if (typeof window !== 'undefined' && mermaidAPI) {
  43. const svgGraph = await mermaidAPI.render(chartId.current, PrimitiveCode)
  44. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  45. const base64Svg: any = await svgToBase64(svgGraph.svg)
  46. setSvgCode(base64Svg)
  47. if (chartId.current && base64Svg)
  48. localStorage.setItem(chartId.current, base64Svg)
  49. }
  50. }
  51. catch (error) {
  52. clearFlowchartCache()
  53. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  54. handleReRender()
  55. }
  56. }
  57. const svgToBase64 = (svgGraph: string) => {
  58. const svgBytes = new TextEncoder().encode(svgGraph)
  59. const blob = new Blob([svgBytes], { type: 'image/svg+xml;charset=utf-8' })
  60. return new Promise((resolve, reject) => {
  61. const reader = new FileReader()
  62. reader.onloadend = () => resolve(reader.result)
  63. reader.onerror = reject
  64. reader.readAsDataURL(blob)
  65. })
  66. }
  67. const handleReRender = () => {
  68. setIsRender(false)
  69. setSvgCode(null)
  70. if (chartId.current)
  71. localStorage.removeItem(chartId.current)
  72. setTimeout(() => {
  73. setIsRender(true)
  74. renderFlowchart(props.PrimitiveCode)
  75. }, 100)
  76. }
  77. useEffect(() => {
  78. setIsRender(false)
  79. setTimeout(() => {
  80. setIsRender(true)
  81. renderFlowchart(props.PrimitiveCode)
  82. }, 100)
  83. }, [props.PrimitiveCode])
  84. return (
  85. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  86. // @ts-expect-error
  87. <div ref={ref}>
  88. {
  89. isRender
  90. && <div id={chartId.current} className="mermaid" style={style}>
  91. {svgCode && <img src={svgCode} style={{ width: '100%', height: 'auto' }} alt="Mermaid chart" />}
  92. </div>
  93. }
  94. </div>
  95. )
  96. })
  97. export default Flowchart