react-markdownとgraphviz-reactでフロー図作成

フローチャート

ReactでMarkdownアプリを作っています。今回はMarkdownでフロー図を描きたいと思いgraphviz-reactを導入しました。

前回の内容は以下を参照してください。

React + material-ui(MUI)でFloating action buttonコンポーネントをつかってみる

今回の手順は以下のとおりです。

graphviz-reactとreact-error-boundaryのインストール

graphviz-reactとreact-error-boundaryをインストールします。

前提として過去に紹介しているDocker環境を使います。

インストール前にdocker-composeを停止します。

docker-compose.ymlファイルがある場所で以下のコマンドを入力してください。

    
docker-compose down
    
  

インストールを行います。

    
docker-compose run --rm front sh -c "cd markdown-app && npm install graphviz-react react-error-boundary"
    
  

package.jsonに記載されていれば成功です。

    
project/
 ┗ front/
   ┣ Dockerfile
   ┗ src/
     ┗ markdown-app/
       ┗ package.json
    
  

以下のように記載されます。

    
・・・略・・・
  "dependencies": {
    "@emotion/react": "^11.9.0",
    "@emotion/styled": "^11.8.1",
    "@mui/icons-material": "^5.8.0",
    "@mui/material": "^5.8.0",
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.2.0",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.5.1",
    "@types/node": "^16.11.36",
    "@types/react": "^18.0.9",
    "@types/react-dom": "^18.0.4",
    "axios": "^0.27.2",
    "graphviz-react": "^1.2.0",
    "react": "^18.1.0",
    "react-dom": "^18.1.0",
    "react-error-boundary": "^3.1.4",
    "react-markdown": "^8.0.3",
    "react-router-dom": "^6.3.0",
    "react-scripts": "5.0.1",
    "remark-gfm": "^3.0.1",
    "typescript": "^4.6.4",
    "web-vitals": "^2.1.4"
  },
・・・略・・・
    
  

以上でインストールは終了です。

マークダウン表示テンプレートの変更

マークダウン表示テンプレート(ViewTemplate.tsx)の内容です。

    
import React, { FC, useState } from "react";

import remarkGfm from "remark-gfm";
import ReactMarkdown from "react-markdown";

import Graphviz from "graphviz-react";
import { Box, Container, ThemeProvider, Toolbar, CssBaseline } from "@mui/material";
import { MarkdownType } from "../../types/MarkdownType.type";

import { Theme } from "../uilibs/theme/Theme";

import { SubHeaderNavigation } from "../uilibs/organisms/SubHeaderNavigation";
import { HeaderNavigation } from "../uilibs/organisms/HeaderNavigation";
import { LeftNaviagtion } from "../uilibs/organisms/LeftNaviagtion";
import { ErrorBoundary } from "react-error-boundary";

interface Props {
  markdown: MarkdownType;
  setMarkdown: React.Dispatch<React.SetStateAction<MarkdownType>>;
  editMode: boolean;
  insertMode: boolean;
  setInsertMode: React.Dispatch<React.SetStateAction<boolean>>;
  onClickSwitchMode: (event: React.MouseEvent<HTMLButtonElement>) => void;
  setUrlLocation: React.Dispatch<React.SetStateAction<string>>;
};

const ErrorFallback = () => {
  return (
    <div role="alert">
      <pre>表示できません</pre>
    </div>
  )
}


export const ViewTemplate: FC<Props> = (props) => {
  const { markdown, setMarkdown, editMode, insertMode, setInsertMode, onClickSwitchMode, setUrlLocation } = props;

  return (
    <ThemeProvider theme={Theme}>
      <Box sx={{ display: "flex" }}>
        <CssBaseline />
        <HeaderNavigation />
        <LeftNaviagtion setUrlLocation={setUrlLocation} />
        <Box component="main" sx={{ flexGrow: 1 }}>
          <Toolbar />
          <SubHeaderNavigation markdown={markdown} setMarkdown={setMarkdown} editMode={editMode} insertMode={insertMode} setInsertMode={setInsertMode} onClickSwitchMode={onClickSwitchMode} />
          <Container className="markdown-view" maxWidth="xl">
            <ReactMarkdown
              children={markdown.body}
              remarkPlugins={[remarkGfm]}
              components={{
                code({ node, inline, className, children, ...props }) {
                  const match = /language-flow/.exec(className || '');
                  return match ? (
                    <ErrorBoundary FallbackComponent={ErrorFallback} >
                      <Graphviz dot={String(children)} />
                    </ErrorBoundary>
                  ) : (
                    <code className={className} {...props}>
                      {children}
                    </code>
                  )
                }
              }}
            />
          </Container>
        </Box>
      </Box>
    </ThemeProvider>
  );
};
    
  

マークダウン編集テンプレートの変更

マークダウン編集テンプレート(EditTemplate.tsx)の内容です。

    
import { ThemeProvider } from "@emotion/react";
import { Container, Toolbar } from "@mui/material";
import { Box } from "@mui/system";
import Graphviz from "graphviz-react";
import { FC, useEffect } from "react";
import { ErrorBoundary } from "react-error-boundary";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { MarkdownType } from "../../types/MarkdownType.type";
import { MarkdownEditTextFiled } from "../uilibs/atoms/MarkdownEditTextFiled";
import { HeaderNavigation } from "../uilibs/organisms/HeaderNavigation";
import { SubHeaderNavigation } from "../uilibs/organisms/SubHeaderNavigation";
import { Theme } from "../uilibs/theme/Theme";


interface Props {
  markdown: MarkdownType;
  setMarkdown: React.Dispatch<React.SetStateAction<MarkdownType>>;
  editMode: boolean;
  insertMode: boolean;
  setInsertMode: React.Dispatch<React.SetStateAction<boolean>>;
  onClickSwitchMode: (event: React.MouseEvent<HTMLButtonElement>) => void;
};

const  ErrorFallback = () => {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>表示できません</pre>
    </div>
  )
}


export const EditTemplate: FC<Props> = (props) => {

  const { markdown, setMarkdown, editMode, insertMode, setInsertMode, onClickSwitchMode } = props;

  const StyleTemplateBox = { display: "flex", flexDirection: "column", height: "100vh" };
  const StyleMainBox = { display: "flex", flexDirection: "row", height: "100vh", width: '100%' };
  const StyleLeftBox = { height: "100vh", width: '50%', backgroundColor: Theme.palette.primary.main };
  const StyleRightBox = { height: "100vh", width: '50%' };

  const onChangeValue = (event: React.ChangeEvent<HTMLTextAreaElement>) => { setMarkdown({ url: markdown.url, title: markdown.title, body: event.target.value }); };

  useEffect(() => {
  }, [markdown.body]);


  return (
    <ThemeProvider theme={Theme}>
      <Box sx={StyleTemplateBox}>
        <HeaderNavigation />
        <Toolbar />
        <SubHeaderNavigation markdown={markdown} setMarkdown={setMarkdown} editMode={editMode} insertMode={insertMode} setInsertMode={setInsertMode} onClickSwitchMode={onClickSwitchMode} />
        <Box sx={StyleMainBox}>
          <Box sx={StyleLeftBox}>
            <Container>
              <MarkdownEditTextFiled markdown={markdown} onChangeValue={onChangeValue} />
            </Container >
          </Box>
          <Box sx={StyleRightBox}>
            <Container className="markdown-view" >
            <ReactMarkdown
              children={markdown.body}
              remarkPlugins={[remarkGfm]}
              components={{
                code({ node, inline, className, children, ...props }) {
                  const match = /language-flow/.exec(className || '');
                  return match ? (
                    <ErrorBoundary FallbackComponent={ErrorFallback} >
                      <Graphviz dot={String(children)} />
                    </ErrorBoundary>
                  ) : (
                    <code className={className} {...props}>
                      {children}
                    </code>
                  )
                }
              }}
            />
            </Container >
          </Box>
        </Box>
      </Box>
    </ThemeProvider>
  );
};

    
  

画面確認

docker-compose upを使って各コンテナを起動します。

docker-compose.ymlファイルがある場所で以下のコマンドを実行します。

    
docker-compose up
    
  

起動したら以下のURLをブラウザに入力します。

http://localhost:3000

以下ブラウザの表示です。

markdownページの表示モードです。

まとめ

今回はMarkdownでフロー図を書くために、graphviz-reactを導入しました。

コメント

0 件のコメント:

コメントを投稿

コメントをお待ちしています。