Chapter 11

第8章 機能を実装してツールを仕上げる その1 -フローや記号の追加機能-

てべすてん
てべすてん
2022.03.11に更新

テスト用ボタンを削除する

今までのコードでは動作確認用にいくつかテスト用のボタンと確認用の処理を書いてきました。今回それらのボタンを削除して本番用のボタンなどを配置していきましょう。

確認用ボタンを取り除いたBuildPanel.tsxを示します。

プロジェクトルート/src/components/App/BuildPanel.tsx

import Stack from "@mui/material/Stack";
import { FC } from "react" ; 
import { useItems } from "redux/items/hooks";
import { useFlows } from "redux/meta/hooks";
import FlowComp from "sym/flow/Flow";


export interface BuildPanelProps{
}

const BuildPanel :FC<BuildPanelProps> = ({})=>{
  const {
    flows:flowIds,
  } = useFlows();
  const flows = useItems(flowIds) ;

  return (
    <div>
      <Stack spacing={2}>
        {Object.entries(flows).map(([flowId,_item])=>{
          return (
            <FlowComp flowId={flowId}/>
          ) ;
        })}
      </Stack>
    </div>
  )
} 
export default BuildPanel ;

機能を洗い出す

今回実装する機能は以下の通りです。

  • 編集パネル上のボタンをクリックするとフローを追加する
  • フローに対して記号を追加する

フローの追加

この機能を実装するためには、編集パネル上にボタンを配置し、それがクリックされたときにフローを追加する処理を記述する必要があります。

それぞれ実装します。

プロジェクトルート/src/components/App/BuildPanel.tsx

+ import Box from "@mui/material/Box";
+ import Button from "@mui/material/Button";
  import Stack from "@mui/material/Stack";
  import { FC } from "react" ; 
  import { useItems } from "redux/items/hooks";
  import { useFlows } from "redux/meta/hooks";
  import FlowComp from "sym/flow/Flow";


  export interface BuildPanelProps{
  }

  const BuildPanel :FC<BuildPanelProps> = ({})=>{
    const {
      flows:flowIds,
    } = useFlows();
    const flows = useItems(flowIds) ;

   //フローを追加する処理
+   const handleAddFlow = ()=>{
+     //フローオブジェクト作成
+     //フローのIDを決定
+     //idとオブジェクトを紐づける(setItem呼び出し)
+     //meta.flowsにフローのID追加する
+   } ;

    return (
      <div>
        <Stack spacing={2}>
          {Object.entries(flows).map(([flowId,_item])=>{
            return (
              <FlowComp flowId={flowId}/>
            ) ;
          })}
+         <Box sx={{border:"dashed 1px black",p:2,width:"fit-content"}}>
+           <Button onClick={handleAddFlow}>フローを追加</Button>
+         </Box>
        </Stack>
      </div>
    )
  } 
  export default BuildPanel ;


ここでhandleAddFlow関数内に記述すべき処理を考えます。フローを新規に作成し登録するためには、meta.flowsに追加する必要があります(そこに追加されることでBuildPanelコンポーネントで描画されます)。よってmeta.flowsに追加する処理がまず必要です。しかしこの時追加するのはフローオブジェクトではなくフローのIDです。IDをmeta.flowsに追加しただけだとuseItemshookでそのフローのフローオブジェクトを取得した時に、フローオブジェクトが取得できないため、フローのIDを追加すると同時にフローオブジェクトも登録する必要があります。よってuseItemOperationshookで取得できるsetItem関数を使用してフローオブジェクトも登録します。ということはこの時必要になるフローオブジェクトとそのIDも準備する必要があるため、実行すべき処理は4つになるわけです。

  • フローオブジェクトを準備
  • そのIDを準備
  • setItemでIDとフローオブジェクト登録
  • meta.flowsにフローのIDを登録

これを実装しましょう。

プロジェクトルート/src/components/App/BuildPanel.tsx

import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { FC } from "react" ; 
import { useItems,useItemOperations } from "redux/items/hooks";
import { Flow } from "redux/items/types";
import { useFlows } from "redux/meta/hooks";
import FlowComp from "sym/flow/Flow";


export interface BuildPanelProps{
}

const BuildPanel :FC<BuildPanelProps> = ({})=>{
  const {
    flows:flowIds,
    addFlow,
  } = useFlows();
  const flows = useItems(flowIds) ;
  const { setItem } = useItemOperations() ;

  //フローを追加する処理
  const handleAddFlow = ()=>{
    //フローオブジェクト作成
    const flow :Flow = {
      itemType:"flow",
      childrenSyms:[],
    } ;
    //フローのIDを決定(ランダム)
    const flowId = `id-flow-${Math.floor(Math.random()*10000000)}` ;
    //idとオブジェクトを紐づける(setItem呼び出し)
    setItem(flowId,flow);
    //meta.flowsにフローのID追加する
    addFlow(flowId);
  } ;

  return (
    <div>
      <Stack spacing={2}>
        {Object.entries(flows).map(([flowId,_item])=>{
          return (
            <FlowComp flowId={flowId}/>
          ) ;
        })}
        <Box sx={{border:"dashed 1px black",p:2,width:"fit-content"}}>
          <Button onClick={handleAddFlow}>フローを追加</Button>
        </Box>
      </Stack>
    </div>
  )
} 
export default BuildPanel ;

おそらくこれでフローが追加できたはずですが今のままではフローの子アイテムがないときに何も表示されていないので、フローがないのかフローはあるが子要素がないのかが分かりにくいです(下の画像参照)。

もし子アイテムが1つもないときは「要素がありません」のような表示をしてほしいです。なのでFlow.tsxを編集します。

プロジェクトルート/src/sym/flow/Flow.tsx
  import Stack from "@mui/material/Stack";
  import { FC } from "react";
  import { useItem } from "redux/items/hooks";
  import { ItemId,Flow } from "redux/items/types";
  import RectSym from "sym/rect/RectSym";
  import Arrow from "./Arrow";

  export interface FlowProps {
      flowId:ItemId,
  }

  const FlowComp: FC<FlowProps> = ({flowId}) => {
      const flow = useItem(flowId) as Flow;
      return (
          <Stack direction="column">
              {flow.childrenSyms.map((symId, idx) => (
                  <>
                      {idx === 0 ? null : <Arrow />}
                      <RectSym itemId={symId} />
                  </>
              ))}
  
+             {/* 子要素がない */}
+             {flow.childrenSyms.length === 0 ? 
+             <>
+                 子要素がありません
+             </>
+             : ""}
          </Stack>
      );
  };
  export default FlowComp;

これで親切に表示してくれます。

これでフローを追加することができるようになりました。

記号の追加

記号の追加をするためにユーザはどのような操作をすればいいでしょうか?様々な方法が考えられますが、今回は記号を追加ボタンをクリックするまたは記号前後の追加ボタンをクリックすると記号が追加できるようにしてみます。

[[ここに完成イメージ入れたい]]

一番最初の子要素の追加

まず1つも記号がない場合には「子要素がありません」と表示するだけでなく記号を追加するボタンを配置してそれがクリックされたときに子要素を追加するようにします。

プロジェクトルート/src/sym/flow/Flow.tsx
  import Stack from "@mui/material/Stack";
+ import Button from "components/util/Button";
  import { FC } from "react";
  import { useItem } from "redux/items/hooks";
  import { ItemId,Flow } from "redux/items/types";
  import RectSym from "sym/rect/RectSym";
  import Arrow from "./Arrow";

  export interface FlowProps {
      flowId:ItemId,
  }

  const FlowComp: FC<FlowProps> = ({flowId}) => {
      const flow = useItem(flowId) as Flow;
      const handleAddSym = ()=>{
          //記号を追加する処理
      } ;
      return (
          <Stack direction="column">
              {flow.childrenSyms.map((symId, idx) => (
                  <>
                      {idx === 0 ? null : <Arrow />}
                      <RectSym itemId={symId} />
                  </>
              ))}
  
              {/* 子要素がない */}
              {flow.childrenSyms.length === 0 ? 
              <>
                  子要素がありません
+                 <Button onClick={handleAddSym}>
+                     記号を追加する
+                 </Button>
              </>
              : ""}
          </Stack>
      );
  };
  export default FlowComp;

handleAddSymにはどのような処理を書けばいいでしょうか?まずは記号をsetItemで登録しておきます(この時IDと記号オブジェクトを用意しておく必要があります)。その後flowのchildrenSymsに新しく作成した記号のIDを登録する必要があります。

プロジェクトルート/src/sym/flow/Flow.tsx
  import Stack from "@mui/material/Stack";
  import Button from "components/util/Button";
  import { FC } from "react";
+ import { useItem,useItemOperations } from "redux/items/hooks";
+ import { ItemId,Flow,Sym } from "redux/items/types";
  import RectSym from "sym/rect/RectSym";
  import Arrow from "./Arrow";

  export interface FlowProps {
      flowId:ItemId,
  }

  const FlowComp: FC<FlowProps> = ({flowId}) => {
      const flow = useItem(flowId) as Flow;
+     const { setItem } = useItemOperations() ;
      const handleAddSym = ()=>{
+         //記号を登録する処理
+         const sym :Sym = {
+             itemType:"rect",
+             options:[],
+         } ;
+         const symId = `rect-id-${Math.floor(Math.random()*100000)}` ;
+         setItem(symId,sym) ;
+         //フローに追加する処理
+         const newFlow = {
+             ...flow,
+             childrenSyms:[ symId ],
+         } ;
+         setItem(flowId,newFlow);
      } ;
      return (
          <Stack direction="column">
              {flow.childrenSyms.map((symId, idx) => (
                  <>
                      {idx === 0 ? null : <Arrow />}
                      <RectSym itemId={symId} />
                  </>
              ))}
  
              {/* 子要素がない */}
              {flow.childrenSyms.length === 0 ? 
              <>
                  子要素がありません
                  <Button onClick={handleAddSym}>
                      記号を追加する
                  </Button>
              </>
              : ""}
          </Stack>
      );
  };
  export default FlowComp;

これで一番最初の記号が追加できるようになりました。

2番目以降の子要素の追加

2番目以降の子要素はすでにある要素の前後にボタンを出現させ、それをクリックすることで追加できるようにします。各子要素の前後に追加ボタンを配置します。

プロジェクトルート/src/sym/flow/Flow.tsx
+ import Box from "@mui/material/Box";
  import Stack from "@mui/material/Stack";
  import Button from "components/util/Button";
  import { FC } from "react";
  import { useItem, useItemOperations } from "redux/items/hooks";
  import { ItemId,Flow, Sym } from "redux/items/types";
  import RectSym from "sym/rect/RectSym";
  import Arrow from "./Arrow";

  export interface FlowProps {
      flowId:ItemId,
  }

  const FlowComp: FC<FlowProps> = ({flowId}) => {
      const flow = useItem(flowId) as Flow;
      const { setItem } = useItemOperations() ;
      //一番初めに追加するときの処理
      const handleAddSym = ()=>{
          //記号を登録する処理
          const sym :Sym = {
              itemType:"rect",
              options:[],
          } ;
          const symId = `rect-id-${Math.floor(Math.random()*100000)}` ;
          setItem(symId,sym) ;
          //フローに追加する処理
          const newFlow = {
              ...flow,
              childrenSyms:[ symId ],
          } ;
          setItem(flowId,newFlow);
      } ;
+     //2つめ以降の子要素の追加処理
+     const handleAddSymSec = ()=>{
+         //子要素の追加処理
+     } ;
      return (
          <Stack direction="column">
              {flow.childrenSyms.map((symId, idx) => (
                  <>
                      {idx === 0 ? null : <Arrow />}
+                     <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
+                         <Button onClick={handleAddSymSec} sx={{width:"fit-content"}}>追加</Button>
+                     </Box>
                      <RectSym itemId={symId} />
+                     <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
+                         <Button onClick={handleAddSymSec} sx={{width:"fit-content"}}>追加</Button>
+                     </Box>
                  </>
              ))}

              {/* 子要素がない */}
              {flow.childrenSyms.length === 0 ? 
              <>
                  子要素がありません
                  <Button onClick={handleAddSym}>
                      記号を追加する
                  </Button>
              </>
              : ""}
          </Stack>
      );
  };
  export default FlowComp;

handleAddSymSecを実装しましょう。
ここで問題が一つ、ですが1つ目の追加処理とは違い、2つ目以降はどこに追加するのかという情報も必要です。なので

なのでhandleAddSymSecにはどこに追加するのかという情報を引数で渡す必要があります。定義時と呼び出し時に引数を追加しましょう。

プロジェクトルート/src/sym/flow/Flow.tsx
  import Box from "@mui/material/Box";
  import Stack from "@mui/material/Stack";
  import Button from "components/util/Button";
  import { FC } from "react";
  import { useItem, useItemOperations } from "redux/items/hooks";
  import { ItemId,Flow, Sym } from "redux/items/types";
  import { baseSetting } from "sym/base/SymBase";
  import RectSym from "sym/rect/RectSym";
  import Arrow from "./Arrow";

  export interface FlowProps {
      flowId:ItemId,
  }

  const FlowComp: FC<FlowProps> = ({flowId}) => {
      const flow = useItem(flowId) as Flow;
      const { setItem } = useItemOperations() ;
      //一番初めに追加するときの処理
      const handleAddSym = ()=>{
          //記号を登録する処理
          const sym :Sym = {
              itemType:"rect",
              options:[],
          } ;
          const symId = `rect-id-${Math.floor(Math.random()*100000)}` ;
          setItem(symId,sym) ;
          //フローに追加する処理
          const newFlow = {
              ...flow,
              childrenSyms:[ symId ],
          } ;
          setItem(flowId,newFlow);
      } ;
      //2つめ以降の子要素の追加処理
+     const handleAddSymSec = (idx:number)=>{
          //子要素の追加処理
          //idxで指定した位置に追加する
      } ;
      return (
          <Stack direction="column">
              {flow.childrenSyms.map((symId, idx) => (
                  <>
                      {idx === 0 ? null : <Arrow />}
                      <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
+                         <Button onClick={()=>handleAddSymSec(idx)} sx={{width:"fit-content"}}>追加</Button>
                      </Box>
                      <RectSym itemId={symId} />
                      <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
+                         <Button onClick={()=>handleAddSymSec(idx+1)} sx={{width:"fit-content"}}>追加</Button>
                      </Box>
                  </>
              ))}

              {/* 子要素がない */}
              {flow.childrenSyms.length === 0 ? 
              <>
                  子要素がありません
                  <Button onClick={handleAddSym}>
                      記号を追加する
                  </Button>
              </>
              : ""}
          </Stack>
      );
  };
  export default FlowComp;

いよいよ追加する処理を考えていきます。前と同じように①追加する記号のオブジェクトを用意し、②記号をsetItemし、③フローに追加して、④フローをsetItemする4手順で実装します。

プロジェクトルート/src/sym/flow/Flow.tsx
  import Box from "@mui/material/Box";
  import Stack from "@mui/material/Stack";
  import Button from "components/util/Button";
  import { FC } from "react";
  import { useItem, useItemOperations } from "redux/items/hooks";
  import { ItemId,Flow, Sym } from "redux/items/types";
  import { baseSetting } from "sym/base/SymBase";
  import RectSym from "sym/rect/RectSym";
  import Arrow from "./Arrow";

  export interface FlowProps {
      flowId:ItemId,
  }

  const FlowComp: FC<FlowProps> = ({flowId}) => {
      const flow = useItem(flowId) as Flow;
      const { setItem } = useItemOperations() ;
      //一番初めに追加するときの処理
      const handleAddSym = ()=>{
          //記号を登録する処理
          const sym :Sym = {
              itemType:"rect",
              options:[],
          } ;
          const symId = `rect-id-${Math.floor(Math.random()*100000)}` ;
          setItem(symId,sym) ;
          //フローに追加する処理
          const newFlow = {
              ...flow,
              childrenSyms:[ symId ],
          } ;
          setItem(flowId,newFlow);
      } ;
      //2つめ以降の子要素の追加処理
      const handleAddSymSec = (idx:number)=>{
          //子要素の追加処理
          //idxで指定した位置に追加する
+         const sym :Sym = {
+             itemType:"rect",
+             options:[],
+         } ;
+         const symId = `rect-id-${Math.floor(Math.random()*100000)}` ;
+         setItem(symId,sym);
+         const newChildrenSyms = [...flow.childrenSyms] ;
+         newChildrenSyms.splice(idx,0,symId) ;
+         const newFlow = {
+             ...flow,
+             childrenSyms:newChildrenSyms,
+         } ;
+         setItem(flowId,newFlow)
      } ;
      return (
          <Stack direction="column">
              {flow.childrenSyms.map((symId, idx) => (
                  <>
                      {idx === 0 ? null : <Arrow />}
                      <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
                          <Button onClick={()=>handleAddSymSec(idx)} sx={{width:"fit-content"}}>追加</Button>
                      </Box>
                      <RectSym itemId={symId} />
                      <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
                          <Button onClick={()=>handleAddSymSec(idx+1)} sx={{width:"fit-content"}}>追加</Button>
                      </Box>
                  </>
              ))}

              {/* 子要素がない */}
              {flow.childrenSyms.length === 0 ? 
              <>
                  子要素がありません
                  <Button onClick={handleAddSym}>
                      記号を追加する
                  </Button>
              </>
              : ""}
          </Stack>
      );
  };
  export default FlowComp;

これで子要素の追加もできるようになりました。

リファクタリング

ここでちょっとしたリファクタリングを行います。具体的には共通部分の関数化などを行っていきます。

hadleAddSymの統一

1つ目の子要素の追加処理handleAddSym()は2つ目の子要素の追加処理handleAddSymSec(0)と同等です。よっていままでhandleAddSymを呼び出していたところはhandleAddSymSec(0)に置き換えることができます。これで無駄な関数定義を排除してコードを見やすくします。またこれで雇用を追加する関数はhandleAddSymSecのみになったのでhandleAddSymに名前を変えておきましょう。

プロジェクトルート/src/sym/flow/Flow.tsx
  import Box from "@mui/material/Box";
  import Stack from "@mui/material/Stack";
  import Button from "components/util/Button";
  import { FC } from "react";
  import { useItem, useItemOperations } from "redux/items/hooks";
  import { ItemId,Flow, Sym } from "redux/items/types";
  import { baseSetting } from "sym/base/SymBase";
  import RectSym from "sym/rect/RectSym";
  import Arrow from "./Arrow";

  export interface FlowProps {
      flowId:ItemId,
  }

  const FlowComp: FC<FlowProps> = ({flowId}) => {
      const flow = useItem(flowId) as Flow;
      const { setItem } = useItemOperations() ;
      //追加処理
+     const handleAddSym = (idx:number)=>{
          //子要素の追加処理
          //idxで指定した位置に追加する
          const sym :Sym = {
              itemType:"rect",
              options:[],
          } ;
          const symId = `rect-id-${Math.floor(Math.random()*100000)}` ;
          setItem(symId,sym);
          const newChildrenSyms = [...flow.childrenSyms] ;
          newChildrenSyms.splice(idx,0,symId) ;
          const newFlow = {
              ...flow,
              childrenSyms:newChildrenSyms,
          } ;
          setItem(flowId,newFlow)
      } ;
      return (
          <Stack direction="column">
              {flow.childrenSyms.map((symId, idx) => (
                  <>
                      {idx === 0 ? null : <Arrow />}
                      <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
                          <Button onClick={()=>handleAddSym(idx)} sx={{width:"fit-content"}}>追加</Button>
                      </Box>
                      <RectSym itemId={symId} />
                      <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
                          <Button onClick={()=>handleAddSym(idx+1)} sx={{width:"fit-content"}}>追加</Button>
                      </Box>
                  </>
              ))}

              {/* 子要素がない */}
              {flow.childrenSyms.length === 0 ? 
              <>
                  子要素がありません
+                 <Button onClick={()=>handleAddSym(0)}>
                      記号を追加する
                  </Button>
              </>
              : ""}
          </Stack>
      );
  };
  export default FlowComp;

IDを自動生成する関数を作成する

新たな記号などを追加するとき、以下のように適当なIDをランダムに生成していました。

IDの作成
const symId = `rect-id-${Math.floor(Math.random()*100000)}` ;

この記述ではランダムなIDを生成している処理であることが分かりにくいため、ランダムなIDを返す関数createRandomItemId()を作成します。これはフローに限ら内処理なのでプロジェクトルート/src/sym/util.tsに作成します。

プロジェクトルート/src/sym/util.ts

export function createRandomItemId(itemType:string){
    return `${itemType}-id-${Math.floor(Math.random()*100000)}` ;
}

呼び出すときは次のように使用します。

プロジェクトルート/src/sym/flow/Flow.tsx
  import Box from "@mui/material/Box";
  import Stack from "@mui/material/Stack";
  import Button from "components/util/Button";
  import { FC } from "react";
  import { useItem, useItemOperations } from "redux/items/hooks";
  import { ItemId,Flow, Sym } from "redux/items/types";
  import { baseSetting } from "sym/base/SymBase";
  import RectSym from "sym/rect/RectSym";
+ import { createRandomItemId } from "sym/util";
  import Arrow from "./Arrow";

  export interface FlowProps {
      flowId:ItemId,
  }

  const FlowComp: FC<FlowProps> = ({flowId}) => {
      const flow = useItem(flowId) as Flow;
      const { setItem } = useItemOperations() ;
      //追加処理
      const handleAddSym = (idx:number)=>{
          //子要素の追加処理
          //idxで指定した位置に追加する
          const sym :Sym = {
              itemType:"rect",
              options:[],
          } ;
+         const symId = createRandomItemId(sym.itemType) ;
          setItem(symId,sym);
          const newChildrenSyms = [...flow.childrenSyms] ;
          newChildrenSyms.splice(idx,0,symId) ;
          const newFlow = {
              ...flow,
              childrenSyms:newChildrenSyms,
          } ;
          setItem(flowId,newFlow)
      } ;
      return (
          <Stack direction="column">
              {flow.childrenSyms.map((symId, idx) => (
                  <>
                      {idx === 0 ? null : <Arrow />}
                      <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
                          <Button onClick={()=>handleAddSym(idx)} sx={{width:"fit-content"}}>追加</Button>
                      </Box>
                      <RectSym itemId={symId} />
                      <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
                          <Button onClick={()=>handleAddSym(idx+1)} sx={{width:"fit-content"}}>追加</Button>
                      </Box>
                  </>
              ))}

              {/* 子要素がない */}
              {flow.childrenSyms.length === 0 ? 
              <>
                  子要素がありません
                  <Button onClick={()=>handleAddSym(0)}>
                      記号を追加する
                  </Button>
              </>
              : ""}
          </Stack>
      );
  };
  export default FlowComp;

アイテムオブジェクトを生成する処理を関数化する

アイテムオブジェクト(記号オブジェクト・フローオブジェクト)を作成するときに毎回{ itemType:"〇〇", ...}と書くのは面倒です。今は([]で)省略していますが、本来はoptionsにOptionを設定する必要もあります。なのでこれを関数化します。だいぶ前の章で触れましたが、アイテムオブジェクトを作成する関数をアイテムクリエイターと呼ぶことにしていました。アイテムクリエイターを記号やフローごとに定義します。

まずはrect(長方形)のアイテムクリエイターです。

rectのアイテムクリエイター
import { Sym } from "redux/items/types";

export default function rectCreator() :Sym{
    return {
        itemType:"rect",  //アイテムのプロパティ
        options:[],  //rectのプロパティ
    } ;
}

つづいてフローのアイテムクリエイターです。

rectのアイテムクリエイター
import { Flow } from "redux/items/types";

export default function flowCreator() :Flow{
    return {
        itemType:"flow",  //アイテムのプロパティ
        childrenSyms:[],  //フローのプロパティ
    } ;
}

長方形記号もフローもどちらもアイテムです。なのでそれらのアイテムオブジェクトにはitemTypeプロパティが含まれます。なのでこれもitemCreatorとして共通化してしまいましょう。

プロジェクトルート/src/sym/base/creator.tsx
import { Item } from "redux/items/types";

export default function itemCreator(itemType:string) :Item{
    return {
        itemType,
    } ;
}

これを使って長方形記号のアイテムクリエイターを定義しなおすと次のようになります。(これでアイテムクリエイターは完成なのでファイルに記述してもOKです)

プロジェクトルート/src/sym/rect/creator.tsx
import { Sym } from "redux/items/types";
import itemCreator from "sym/base/creator";

export default function rectCreator() :Sym{
    return {
        ...itemCreator("rect"),
        options:[],
    } ;
}

フローのアイテムクリエイターも定義し直します。

プロジェクトルート/src/sym/flow/creator.tsx
import { Flow } from "redux/items/types";
import itemCreator from "sym/base/creator";

export default function flowCreator():Flow{
    return {
        ...itemCreator("flow"),
        childrenSyms:[],
    } ;
}

これをそれぞれ追加処理を行う場所で利用します。

プロジェクトルート/src/components/App/BuildPanel.tsx

  import Box from "@mui/material/Box";
  import Button from "@mui/material/Button";
  import Stack from "@mui/material/Stack";
  import { FC } from "react" ; 
  import { useItems,useItemOperations } from "redux/items/hooks";
  import { Flow } from "redux/items/types";
  import { useFlows } from "redux/meta/hooks";
+ import flowCreator from "sym/flow/creator";
  import FlowComp from "sym/flow/Flow";


  export interface BuildPanelProps{
  }

  const BuildPanel :FC<BuildPanelProps> = ({})=>{
    const {
      flows:flowIds,
      addFlow,
    } = useFlows();
    const flows = useItems(flowIds) ;
    const { setItem } = useItemOperations() ;

    //フローを追加する処理
    const handleAddFlow = ()=>{
      //フローオブジェクト作成
+     const flow = flowCreator() ;
      //フローのIDを決定(ランダム)
      const flowId = `id-flow-${Math.floor(Math.random()*10000000)}` ;
      //idとオブジェクトを紐づける(setItem呼び出し)
      setItem(flowId,flow);
      //meta.flowsにフローのID追加する
      addFlow(flowId);
    } ;

    return (
      <div>
        <Stack spacing={2}>
          {Object.entries(flows).map(([flowId,_item])=>{
            return (
              <FlowComp flowId={flowId}/>
            ) ;
          })}
          <Box sx={{border:"dashed 1px black",p:2,width:"fit-content"}}>
            <Button onClick={handleAddFlow}>フローを追加</Button>
          </Box>
        </Stack>
      </div>
    )
  } 
  export default BuildPanel ;
  
プロジェクトルート/src/sym/flow/Flow.tsx
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Button from "components/util/Button";
import { FC } from "react";
import { useItem, useItemOperations } from "redux/items/hooks";
  import { ItemId,Flow, Sym } from "redux/items/types";
  import { baseSetting } from "sym/base/SymBase";
+ import rectCreator from "sym/rect/creator";
  import RectSym from "sym/rect/RectSym";
  import { createRandomItemId } from "sym/util";
  import Arrow from "./Arrow";

  export interface FlowProps {
      flowId:ItemId,
  }

  const FlowComp: FC<FlowProps> = ({flowId}) => {
      const flow = useItem(flowId) as Flow;
      const { setItem } = useItemOperations() ;
      //追加処理
      const handleAddSym = (idx:number)=>{
          //子要素の追加処理
          //idxで指定した位置に追加する
+         const sym :Sym = rectCreator() ;
          const symId = createRandomItemId(sym.itemType) ;
          setItem(symId,sym);
          const newChildrenSyms = [...flow.childrenSyms] ;
          newChildrenSyms.splice(idx,0,symId) ;
          const newFlow = {
              ...flow,
              childrenSyms:newChildrenSyms,
          } ;
          setItem(flowId,newFlow)
      } ;
      return (
          <Stack direction="column">
              {flow.childrenSyms.map((symId, idx) => (
                  <>
                      {idx === 0 ? null : <Arrow />}
                      <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
                          <Button onClick={()=>handleAddSym(idx)} sx={{width:"fit-content"}}>追加</Button>
                      </Box>
                      <RectSym itemId={symId} />
                      <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
                          <Button onClick={()=>handleAddSym(idx+1)} sx={{width:"fit-content"}}>追加</Button>
                      </Box>
                  </>
              ))}

              {/* 子要素がない */}
              {flow.childrenSyms.length === 0 ? 
              <>
                  子要素がありません
                  <Button onClick={()=>handleAddSym(0)}>
                      記号を追加する
                  </Button>
              </>
              : ""}
          </Stack>
      );
  };
  export default FlowComp;

これでアイテムオブジェクトに変更があっても(オプションの追加など)変更しなければいけないファイルが1つになります。

ここまでのソースコード
プロジェクトルート/src/components/App/BuildPanel.tsx

import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { FC } from "react" ; 
import { useItems,useItemOperations } from "redux/items/hooks";
import { Flow } from "redux/items/types";
import { useFlows } from "redux/meta/hooks";
import flowCreator from "sym/flow/creator";
import FlowComp from "sym/flow/Flow";


export interface BuildPanelProps{
}

const BuildPanel :FC<BuildPanelProps> = ({})=>{
  const {
    flows:flowIds,
    addFlow,
  } = useFlows();
  const flows = useItems(flowIds) ;
  const { setItem } = useItemOperations() ;

  //フローを追加する処理
  const handleAddFlow = ()=>{
    //フローオブジェクト作成
    const flow = flowCreator() ;
    //フローのIDを決定(ランダム)
    const flowId = `id-flow-${Math.floor(Math.random()*10000000)}` ;
    //idとオブジェクトを紐づける(setItem呼び出し)
    setItem(flowId,flow);
    //meta.flowsにフローのID追加する
    addFlow(flowId);
  } ;

  return (
    <div>
      <Stack spacing={2}>
        {Object.entries(flows).map(([flowId,_item])=>{
          return (
            <FlowComp flowId={flowId}/>
          ) ;
        })}
        <Box sx={{border:"dashed 1px black",p:2,width:"fit-content"}}>
          <Button onClick={handleAddFlow}>フローを追加</Button>
        </Box>
      </Stack>
    </div>
  )
} 
export default BuildPanel ;


プロジェクトルート/src/sym/flow/Flow.tsx
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Button from "components/util/Button";
import { FC } from "react";
import { useItem, useItemOperations } from "redux/items/hooks";
import { ItemId,Flow, Sym } from "redux/items/types";
import { baseSetting } from "sym/base/SymBase";
import rectCreator from "sym/rect/creator";
import RectSym from "sym/rect/RectSym";
import { createRandomItemId } from "sym/util";
import Arrow from "./Arrow";

export interface FlowProps {
    flowId:ItemId,
}

const FlowComp: FC<FlowProps> = ({flowId}) => {
    const flow = useItem(flowId) as Flow;
    const { setItem } = useItemOperations() ;
    //追加処理
    const handleAddSym = (idx:number)=>{
        //子要素の追加処理
        //idxで指定した位置に追加する
        const sym :Sym = rectCreator() ;
        const symId = createRandomItemId(sym.itemType) ;
        setItem(symId,sym);
        const newChildrenSyms = [...flow.childrenSyms] ;
        newChildrenSyms.splice(idx,0,symId) ;
        const newFlow = {
            ...flow,
            childrenSyms:newChildrenSyms,
        } ;
        setItem(flowId,newFlow)
    } ;
    return (
        <Stack direction="column">
            {flow.childrenSyms.map((symId, idx) => (
                <>
                    {idx === 0 ? null : <Arrow />}
                    <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
                        <Button onClick={()=>handleAddSym(idx)} sx={{width:"fit-content"}}>追加</Button>
                    </Box>
                    <RectSym itemId={symId} />
                    <Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
                        <Button onClick={()=>handleAddSym(idx+1)} sx={{width:"fit-content"}}>追加</Button>
                    </Box>
                </>
            ))}

            {/* 子要素がない */}
            {flow.childrenSyms.length === 0 ? 
            <>
                子要素がありません
                <Button onClick={()=>handleAddSym(0)}>
                    記号を追加する
                </Button>
            </>
            : ""}
        </Stack>
    );
};
export default FlowComp;

プロジェクトルート/src/sym/rect/creator.tsx
import { Sym } from "redux/items/types";
import itemCreator from "sym/base/creator";

export default function rectCreator() :Sym{
    return {
        ...itemCreator("rect"),
        options:[],
    } ;
}


プロジェクトルート/src/flow/flow/creator.tsx
import { Flow } from "redux/items/types";
import itemCreator from "sym/base/creator";

export default function flowCreator():Flow{
    return {
        ...itemCreator("flow"),
        childrenSyms:[],
    } ;
}
プロジェクトルート/src/base/base/creator.tsx
import { Item } from "redux/items/types";

export default function itemCreator(itemType:string) :Item{
    return {
        itemType,
    } ;
}


プロジェクトルート/src/sym/util.ts

export function createRandomItemId(itemType:string){
    return `${itemType}-id-${Math.floor(Math.random()*100000)}` ;
}

質問・指摘などはこちらから

https://zenn.dev/tbsten/scraps/7123b1257c2097