この章では作成したユーザが作成したフローチャートを画像に出力するための機能を実装します。
プレビューモードの実装
今回フローチャートを実装する機能を実装するために表示されているDOM要素をスクリーンショットする方法をとります。画像として出力する場合、追加ボタンなどは不要なためプレビューモードを用意し、プレビューモード中は追加ボタンなどを非表示にするようソースコードを編集していきます。
まずプレビューモード中は非表示にしたいUIを列挙します。
- 「フローを追加」ボタン
- 「子要素がありません」の表示
- 「記号を追加する」ボタン
- 「追加」ボタン
これらを表示しているコンポーネント内で、プレビューモードならこれらを表示しないようにします。
また今がプレビューモードなのか編集モードなのかはstoreState.app.mode
に保持することにします。
そのため、まずはreduxのstoreStateにmodeを追加します。またモードの種類もtypes.tsに定義しておきます。
export type Mode = "edit" | "preview" ;
+ import { Mode } from "./types";
import { ItemId } from "redux/items/types";
import { reducerWithInitialState } from "typescript-fsa-reducers";
import * as actions from "./actions" ;
export const init = {
selectItemId:null as (null|ItemId),
+ mode:"edit" as Mode,
} ;
export const app = reducerWithInitialState(init)
.case( actions.setSelectItemId, (state,payload)=>{
if(state.selectItemId === payload.itemId){
return state ;
}
return {
...state,
selectItemId:payload.itemId,
} ;
} ) ;
つづいてモードを取得するselector,モードを変更するactionを定義します。
import { StoreState } from "redux/store";
export const getSelectItemId = ()=>{
return (store :StoreState)=>store.app.selectItemId ;
} ;
+ export const getMode = ()=>{
+ return (store :StoreState)=>store.app.mode ;
+ } ;
import { ItemId } from "redux/items/types";
import actionCreatorFactory from "typescript-fsa" ;
+ import { Mode } from "./types";
const actionCreator = actionCreatorFactory() ;
//いつ使うのかわからないけど公式ドキュメントに載っていたのでおいておきます
export const init = actionCreator("app/init");
//選択中アイテムを更新
export const setSelectItemId = actionCreator<{itemId:null|ItemId}>("app/selectItemId/set");
+ //モードを切り替える
+ export const setMode = actionCreator<{mode:Mode}>("app/mode/set") ;
reducerでactionsを処理します。
```diff tsx:プロジェクトルート/src/redux/app/reducers.ts
import { Mode } from "./types";
import { ItemId } from "redux/items/types";
import { reducerWithInitialState } from "typescript-fsa-reducers";
import * as actions from "./actions" ;
export const init = {
selectItemId:null as (null|ItemId),
mode:"edit" as Mode,
} ;
export const app = reducerWithInitialState(init)
.case( actions.setSelectItemId, (state,payload)=>{
if(state.selectItemId === payload.itemId){
return state ;
}
return {
...state,
selectItemId:payload.itemId,
} ;
} )
+ .case( actions.setMode, (state,payload)=>{
+ if(state.mode === payload.mode){
+ return state ;
+ }
+ return {
+ ...state,
+ mode:payload.mode,
+ } ;
+ }) ;
後はhooksで扱いやすくします。
import { useDispatch, useSelector } from "react-redux";
import { ItemId } from "redux/items/types";
import { setSelectItemId,setMode } from "./actions";
+ import { getMode, getSelectItemId } from "./selectors";
+ import { Mode } from "./types";
export function useSelectItem(){
const selectItemId = useSelector(getSelectItemId());
const dispatch = useDispatch() ;
const changeSelectItemId = (itemId:null|ItemId)=>{
dispatch(setSelectItemId({itemId}));
}
return {
selectItemId,
changeSelectItemId,
} ;
}
+ export function useMode() {
+ const mode = useSelector(getMode()) ;
+ const dispatch = useDispatch() ;
+ const set = (mode:Mode)=>{
+ dispatch(setMode(mode)) ;
+ } ;
+ return {
+ mode,
+ setMode:set,
+ } ;
+ }
これでmode stateが追加できました。先ほども示した通り、
- 「フローを追加」ボタン
- 「子要素がありません」の表示
- 「記号を追加する」ボタン
- 「追加」ボタン
これらを表示しているコンポーネントでuseMode
を使って"edit"モード中のみ表示するように編集していきます。
「フローを追加」ボタンを表示しないように変更
「フローを追加」ボタンはBuildPanel
で表示されているので編集していきます。
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { FC } from "react" ;
+ import { useMode } from "redux/app/hooks";
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);
} ;
//previewモード中はボタンを表示しない
+ const {
+ mode,
+ } = useMode() ;
return (
<div>
<Stack spacing={2}>
{Object.entries(flows).map(([flowId,_item])=>{
return (
<FlowComp flowId={flowId}/>
) ;
})}
+ {
+ mode!=="preview"?
<Box sx={{border:"dashed 1px black",p:2,width:"fit-content"}}>
<Button onClick={handleAddFlow}>フローを追加</Button>
</Box>
+ :
+ ""
+ }
</Stack>
</div>
)
}
export default BuildPanel ;
「子要素がありません」,「追加」ボタン,「記号を追加する」ボタンを表示しない
つづいて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 { useMode } from "redux/app/hooks";
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(flowId) ;
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)
} ;
+ const {mode} = useMode() ;
return (
<Stack direction="column">
{flow.childrenSyms.map((symId, idx) => (
<>
{idx === 0 ? null : <Arrow />}
+ {mode!=="preview"?
<Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
<Button onClick={()=>handleAddSym(idx)} sx={{width:"fit-content"}}>追加</Button>
</Box>
+ :""}
<RectSym itemId={symId} />
+ {mode!=="preview"?
<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 && mode !== "preview"?
<>
子要素がありません
<Button onClick={()=>handleAddSym(0)}>
記号を追加する
</Button>
</>
+ : ""}
</Stack>
);
};
export default FlowComp;
これでモードが切り替わるとこれらが非表示になるはずです。ヘッダーにモード切替ボタンを用意してモードを切り替えてみましょう。
import AppBar, { AppBarProps } from "@mui/material/AppBar";
import IconButton from "@mui/material/IconButton/IconButton";
import Toolbar from "@mui/material/Toolbar/Toolbar";
import MenuIcon from "@mui/icons-material/Menu" ;
+ import PreviewIcon from "@mui/icons-material/Preview" ;
+ import EditIcon from "@mui/icons-material/Edit" ;
import { FC } from "react";
import { useTitle } from "redux/meta/hooks";
import TextField from "components/util/TextField";
+ import { useMode } from "redux/app/hooks";
export type HeaderProps = {} & AppBarProps;
const Header: FC<HeaderProps> = (props) => {
const {title,setTitle} = useTitle() ;
+ //モード関係
+ const {mode,setMode} = useMode();
+ const handleToggleMode = ()=>{
+ if(mode === "edit") setMode("preview")
+ if(mode === "preview") setMode("edit")
+ } ;
return (
<AppBar position="static">
<Toolbar>
<IconButton
size="large"
edge="start"
color="inherit"
sx={{ mr: 2 }}
>
<MenuIcon />
</IconButton>
<TextField value={title} onChange={e=>setTitle(e.target.value)} style={{flexGrow:1,color:"white"}}/>
+ <IconButton
+ size="large"
+ edge="start"
+ color="inherit"
+ sx={{ml:2}}
+ onClick={handleToggleMode}>
+ {
+ mode==="edit"?
+ <EditIcon />
+ :
+ mode==="preview"?
+ <PreviewIcon />
+ :
+ "???"
+ }
+ </IconButton>
</Toolbar>
</AppBar>
);
};
export default Header;
うまく非表示にできました!
画像を出力する
プレビューモードの時のみ「画像として出力する」ボタンをサイドバーに表示します。
ここでサイドバーの表示を考え直します。今まで表示していた記号のオプション編集は編集モード時に、プレビューモード時には出力系のメニューを表示するように変更します。また、それぞれのモード時の表示内容をEditSidebar
とPreviewSidebar
としてコンポーネントに切り抜きます。
//ほぼ変更前のSidebarと同じです
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import Button from "components/util/Button";
import TextField from "components/util/TextField";
import {ChangeEvent, FC, } from "react" ;
import { useSelectItem } from "redux/app/hooks";
import { useItem, useItemOperations, } from "redux/items/hooks";
import { Sym } from "redux/items/types";
export interface EditSidebarProps {}
const EditSidebar: FC<EditSidebarProps> = ({}) => {
const {
selectItemId,
} = useSelectItem() ;
const selectItem = useItem(selectItemId??"") as Sym ;
const { setItem,removeItem } = useItemOperations();
const handleOptionChange = (idx:number, e :ChangeEvent<HTMLInputElement|HTMLTextAreaElement>)=>{
//オプションの更新処理
const newValue = e.target.value ;
const newOptions = [...selectItem.options] ;
newOptions[idx] = {
...newOptions[idx],
value:newValue,
} ;
const newItem = {
...selectItem,
options:newOptions,
} ;
setItem(selectItemId??"",newItem);
}
const handleRemoveSym = ()=>{
if(selectItemId){
//親から削除
//アイテム自体を削除
removeItem(selectItemId);
}
} ;
return (
<div>
{selectItemId && selectItem?
<>
<List>
{selectItem.options.map((option,idx)=>{
return (
<ListItem>
{option.name}
:
<TextField value={option.value} onChange={e=>handleOptionChange(idx,e)}/>
</ListItem>
) ;
})}
</List>
<Button onClick={handleRemoveSym}>
記号を削除する
</Button>
</>
:"アイテムが選択されていません"}
</div>
)
};
export default EditSidebar;
import Button from "components/util/Button";
import { FC } from "react";
export interface PreviewSidebarProps {}
const PreviewSidebar: FC<PreviewSidebarProps> = ({}) => {
const handleDownloadImage = ()=>{
//画像として保存する処理
} ;
return (
<div>
<Button onClick={handleDownloadImage}>
画像として保存
</Button>
</div>
);
};
export default PreviewSidebar;
import { FC, } from "react" ;
import { useMode } from "redux/app/hooks";
import EditSidebar from "./EditSidebar";
import PreviewSidebar from "./PreviewSidebar";
export interface SidebarProps{
}
const Sidebar :FC<SidebarProps> = ({})=>{
const {mode} = useMode() ;
return (
<div>
{mode==="edit"?<EditSidebar />:""}
{mode==="preview"?<PreviewSidebar />:""}
</div>
)
}
export default Sidebar ;
あとは画像に変換する処理を考えます。html-to-image便利なライブラリがあるみたいなのでそれを使って、以下の手順で行うことにします。
- html-to-imageをインストール
- 対象DOM要素にid
"flowchart"
を付与 -
document.getElemnetById("flowchart")
でDOM要素を取得し、それをhtml-to-imageのtoPng
関数で画像に変換 - 画像をダウンロード
html-to-imageをインストールします。
npm i html-to-image
対象DOM要素にid"flowchart"
を付与します。
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { FC } from "react" ;
import { useMode } from "redux/app/hooks";
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);
} ;
//previewモード中はボタンを表示しない
const {
mode,
} = useMode() ;
return (
<div>
+ <Stack spacing={2} id="flowchart">
{Object.entries(flows).map(([flowId,_item])=>{
return (
<FlowComp flowId={flowId}/>
) ;
})}
{
mode!=="preview"?
<Box sx={{border:"dashed 1px black",p:2,width:"fit-content"}}>
<Button onClick={handleAddFlow}>フローを追加</Button>
</Box>
:
""
}
</Stack>
</div>
)
}
export default BuildPanel ;
#flowchart
のDOM要素を画像に変換し、ダウンロードします。
import Button from "components/util/Button";
import { FC } from "react";
import {toPng} from "html-to-image" ;
import { useTitle } from "redux/meta/hooks";
export interface PreviewSidebarProps {}
function downloadImage(title:string, image:string){
const aEle = document.createElement("a") ;
aEle.download = title+".png" ;
aEle.href = image ;
aEle.click();
aEle.remove();
}
const PreviewSidebar: FC<PreviewSidebarProps> = ({}) => {
const {title} = useTitle() ;
const handleDownloadImage = async ()=>{
//DOM要素を取得
const ele = document.getElementById("flowchart");
if(ele){
//画像に変換
const image = await toPng(ele) ;
//画像をダウンロード
downloadImage(title,image) ;
}else{
alert("エラー:画像に変換できませんでした")
}
} ;
return (
<div>
<Button onClick={handleDownloadImage}>
画像として保存
</Button>
</div>
);
};
export default PreviewSidebar;
これでボタンをクリックすると画像がダウンロードできました!
画像への変換はhtml-to-imageのtoPngを呼び出すだけなのでとても簡単ですね!
ここまでのソースコード
export type Mode = "edit" | "preview" ;
import { Mode } from "./types";
import { ItemId } from "redux/items/types";
import { reducerWithInitialState } from "typescript-fsa-reducers";
import * as actions from "./actions" ;
export const init = {
selectItemId:null as (null|ItemId),
mode:"edit" as Mode,
} ;
export const app = reducerWithInitialState(init)
.case( actions.setSelectItemId, (state,payload)=>{
if(state.selectItemId === payload.itemId){
return state ;
}
return {
...state,
selectItemId:payload.itemId,
} ;
} )
.case( actions.setMode, (state,payload)=>{
if(state.mode === payload.mode){
return state ;
}
return {
...state,
mode:payload.mode,
} ;
}) ;
import { StoreState } from "redux/store";
export const getSelectItemId = ()=>{
return (store :StoreState)=>store.app.selectItemId ;
} ;
export const getMode = ()=>{
return (store :StoreState)=>store.app.mode ;
} ;
import { ItemId } from "redux/items/types";
import actionCreatorFactory from "typescript-fsa" ;
import { Mode } from "./types";
const actionCreator = actionCreatorFactory() ;
//いつ使うのかわからないけど公式ドキュメントに載っていたのでおいておきます
export const init = actionCreator("app/init");
//選択中アイテムを更新
export const setSelectItemId = actionCreator<{itemId:null|ItemId}>("app/selectItemId/set");
//モードを切り替える
export const setMode = actionCreator<{mode:Mode}>("app/mode/set") ;
import { useDispatch, useSelector } from "react-redux";
import { ItemId } from "redux/items/types";
import { setSelectItemId,setMode } from "./actions";
import { getMode, getSelectItemId } from "./selectors";
import { Mode } from "./types";
export function useSelectItem(){
const selectItemId = useSelector(getSelectItemId());
const dispatch = useDispatch() ;
const changeSelectItemId = (itemId:null|ItemId)=>{
dispatch(setSelectItemId({itemId}));
}
return {
selectItemId,
changeSelectItemId,
} ;
}
export function useMode() {
const mode = useSelector(getMode()) ;
const dispatch = useDispatch() ;
const set = (mode:Mode)=>{
dispatch(setMode({mode})) ;
} ;
return {
mode,
setMode:set,
} ;
}
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Button from "components/util/Button";
import { FC } from "react";
import { useMode } from "redux/app/hooks";
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(flowId) ;
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)
} ;
const {mode} = useMode() ;
return (
<Stack direction="column" sx={{width:"fit-content"}}>
{flow.childrenSyms.map((symId, idx) => (
<>
{idx === 0 ? null : <Arrow />}
{mode!=="preview"?
<Box sx={{display:"flex",justifyContent:"center",width:baseSetting.size.width}}>
<Button onClick={()=>handleAddSym(idx)} sx={{width:"fit-content"}}>追加</Button>
</Box>
:""}
<RectSym itemId={symId} />
{mode!=="preview"?
<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 && mode !== "preview"?
<>
子要素がありません
<Button onClick={()=>handleAddSym(0)}>
記号を追加する
</Button>
</>
: ""}
</Stack>
);
};
export default FlowComp;
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { FC } from "react" ;
import { useMode } from "redux/app/hooks";
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);
} ;
//previewモード中はボタンを表示しない
const {
mode,
} = useMode() ;
return (
<div>
<Stack spacing={2} id="flowchart">
{Object.entries(flows).map(([flowId,_item])=>{
return (
<FlowComp flowId={flowId}/>
) ;
})}
{
mode!=="preview"?
<Box sx={{border:"dashed 1px black",p:2,width:"fit-content"}}>
<Button onClick={handleAddFlow}>フローを追加</Button>
</Box>
:
""
}
</Stack>
</div>
)
}
export default BuildPanel ;
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import Button from "components/util/Button";
import TextField from "components/util/TextField";
import {ChangeEvent, FC, } from "react" ;
import { useSelectItem } from "redux/app/hooks";
import { useItem, useItemOperations, } from "redux/items/hooks";
import { Sym } from "redux/items/types";
export interface EditSidebarProps {}
const EditSidebar: FC<EditSidebarProps> = ({}) => {
const {
selectItemId,
} = useSelectItem() ;
const selectItem = useItem(selectItemId??"") as Sym ;
const { setItem,removeItem } = useItemOperations();
const handleOptionChange = (idx:number, e :ChangeEvent<HTMLInputElement|HTMLTextAreaElement>)=>{
//オプションの更新処理
const newValue = e.target.value ;
const newOptions = [...selectItem.options] ;
newOptions[idx] = {
...newOptions[idx],
value:newValue,
} ;
const newItem = {
...selectItem,
options:newOptions,
} ;
setItem(selectItemId??"",newItem);
}
const handleRemoveSym = ()=>{
if(selectItemId){
//親から削除
//アイテム自体を削除
removeItem(selectItemId);
}
} ;
return (
<div>
{selectItemId && selectItem?
<>
<List>
{selectItem.options.map((option,idx)=>{
return (
<ListItem>
{option.name}
:
<TextField value={option.value} onChange={e=>handleOptionChange(idx,e)}/>
</ListItem>
) ;
})}
</List>
<Button onClick={handleRemoveSym}>
記号を削除する
</Button>
</>
:"アイテムが選択されていません"}
</div>
)
};
export default EditSidebar;
import Button from "components/util/Button";
import { FC } from "react";
import {toPng} from "html-to-image" ;
import { useTitle } from "redux/meta/hooks";
export interface PreviewSidebarProps {}
function downloadImage(title:string, image:string){
const aEle = document.createElement("a") ;
aEle.download = title+".png" ;
aEle.href = image ;
aEle.click();
aEle.remove();
}
const PreviewSidebar: FC<PreviewSidebarProps> = ({}) => {
const {title} = useTitle() ;
const handleDownloadImage = async ()=>{
//DOM要素を取得
const ele = document.getElementById("flowchart");
if(ele){
//画像に変換
const image = await toPng(ele) ;
//画像をダウンロード
downloadImage(title,image) ;
}else{
alert("エラー:画像に変換できませんでした")
}
} ;
return (
<div>
<Button onClick={handleDownloadImage}>
画像として保存
</Button>
</div>
);
};
export default PreviewSidebar;
import { FC, } from "react" ;
import { useMode } from "redux/app/hooks";
import EditSidebar from "./EditSidebar";
import PreviewSidebar from "./PreviewSidebar";
export interface SidebarProps{
}
const Sidebar :FC<SidebarProps> = ({})=>{
const {mode} = useMode() ;
return (
<div>
{mode==="edit"?<EditSidebar />:""}
{mode==="preview"?<PreviewSidebar />:""}
</div>
)
}
export default Sidebar ;