1- import React from 'react' ;
1+ import React , { ReactNode , useEffect } from 'react' ;
22import styled from '@emotion/styled' ;
33import { usePrefixedTranslation } from 'hooks' ;
4- import { Button , Empty } from 'antd' ;
5- import { Tooltip } from 'antd' ;
6- import { PlusOutlined } from '@ant-design/icons' ;
7- import { useStoreActions } from 'store' ;
4+ import { Button , Empty , Modal , Tooltip } from 'antd' ;
5+ import {
6+ ArrowRightOutlined ,
7+ DeleteOutlined ,
8+ PlayCircleOutlined ,
9+ PlusOutlined ,
10+ StopOutlined ,
11+ WarningOutlined ,
12+ } from '@ant-design/icons' ;
13+ import { useStoreActions , useStoreState } from 'store' ;
14+ import { Network } from 'types' ;
15+ import { useAsyncCallback } from 'react-async-hook' ;
16+ import { Status } from 'shared/types' ;
17+ import { ButtonType } from 'antd/lib/button' ;
18+
19+ interface Props {
20+ network : Network ;
21+ }
822
923const Styled = {
1024 Title : styled . div `
@@ -14,15 +28,192 @@ const Styled = {
1428 margin-bottom: 10px;
1529 font-weight: bold;
1630 ` ,
31+ Button : styled ( Button ) `
32+ margin-left: 0;
33+ margin-top: 20px;
34+ width: 100%;
35+ ` ,
36+ SimContainer : styled . div `
37+ display: flex;
38+ align-items: center;
39+ justify-content: space-between;
40+ width: 100%;
41+ height: 46px;
42+ padding: 10px 15px;
43+ margin-top: 20px;
44+ border: 1px solid rgba(255, 255, 255, 0.2);
45+ border-radius: 4px;
46+ font-weight: bold;
47+ &:hover {
48+ border: 1px solid rgba(255, 255, 255, 0.3);
49+ color: #f7f2f2f2;
50+ }
51+ ` ,
52+ NodeWrapper : styled . div `
53+ display: flex;
54+ align-items: center;
55+ justify-content: start;
56+ column-gap: 15px;
57+ width: 100%;
58+ ` ,
59+ DeleteButton : styled ( Button ) `
60+ border: none;
61+ height: 100%;
62+ color: red;
63+ opacity: 0.5;
64+ &:hover {
65+ opacity: 1;
66+ }
67+ ` ,
1768} ;
1869
19- const SimulationDesignerTab : React . FC = ( ) => {
70+ const config : {
71+ [ key : number ] : {
72+ label : string ;
73+ type : ButtonType ;
74+ danger ?: boolean ;
75+ icon : ReactNode ;
76+ } ;
77+ } = {
78+ [ Status . Starting ] : {
79+ label : 'Starting' ,
80+ type : 'primary' ,
81+ icon : '' ,
82+ } ,
83+ [ Status . Started ] : {
84+ label : 'Stop' ,
85+ type : 'primary' ,
86+ danger : true ,
87+ icon : < StopOutlined /> ,
88+ } ,
89+ [ Status . Stopping ] : {
90+ label : 'Stopping' ,
91+ type : 'default' ,
92+ icon : '' ,
93+ } ,
94+ [ Status . Stopped ] : {
95+ label : 'Start' ,
96+ type : 'primary' ,
97+ icon : < PlayCircleOutlined /> ,
98+ } ,
99+ [ Status . Error ] : {
100+ label : 'Restart' ,
101+ type : 'primary' ,
102+ danger : true ,
103+ icon : < WarningOutlined /> ,
104+ } ,
105+ } ;
106+
107+ const SimulationDesignerTab : React . FC < Props > = ( { network } ) => {
20108 const { l } = usePrefixedTranslation (
21109 'cmps.designer.default.DefaultSidebar.SimulationDesignerTab' ,
22110 ) ;
23111
112+ // Getting the network from the store makes this component to
113+ // re-render when the network is updated (i.e when we add a simulation).
114+ const { networks } = useStoreState ( s => s . network ) ;
115+ const currentNetwork = networks . find ( n => n . id === network . id ) ;
116+
24117 const { showAddSimulation } = useStoreActions ( s => s . modals ) ;
25118
119+ const { notify } = useStoreActions ( s => s . app ) ;
120+
121+ const { startSimulation, stopSimulation, removeSimulation } = useStoreActions (
122+ s => s . network ,
123+ ) ;
124+
125+ const loading =
126+ currentNetwork ?. simulation ?. status === Status . Starting ||
127+ currentNetwork ?. simulation ?. status === Status . Stopping ;
128+ const started = currentNetwork ?. simulation ?. status === Status . Started ;
129+ const { label, type, danger, icon } =
130+ config [ currentNetwork ?. simulation ?. status || Status . Stopped ] ;
131+
132+ const startSimulationAsync = useAsyncCallback ( async ( ) => {
133+ if ( ! network . simulation ) return ;
134+ try {
135+ await startSimulation ( { id : network . simulation . networkId } ) ;
136+ } catch ( error : any ) {
137+ notify ( { message : l ( 'startError' ) , error } ) ;
138+ }
139+ } ) ;
140+
141+ const stopSimulationAsync = useAsyncCallback ( async ( ) => {
142+ if ( ! network . simulation ) return ;
143+ try {
144+ await stopSimulation ( { id : network . simulation . networkId } ) ;
145+ } catch ( error : any ) {
146+ notify ( { message : l ( 'stopError' ) , error } ) ;
147+ }
148+ } ) ;
149+
150+ const addSimulation = ( ) => {
151+ showAddSimulation ( { } ) ;
152+ } ;
153+
154+ let modal : any ;
155+ const showRemoveModal = ( ) => {
156+ modal = Modal . confirm ( {
157+ title : l ( 'removeTitle' ) ,
158+ content : l ( 'removeDesc' ) ,
159+ okText : l ( 'removeBtn' ) ,
160+ okType : 'danger' ,
161+ cancelText : l ( 'cancelBtn' ) ,
162+ onOk : async ( ) => {
163+ try {
164+ if ( ! network . simulation ) return ;
165+ await removeSimulation ( network . simulation ) ;
166+ notify ( { message : l ( 'removeSuccess' ) } ) ;
167+ } catch ( error : any ) {
168+ notify ( { message : l ( 'removeError' ) , error : error } ) ;
169+ }
170+ } ,
171+ } ) ;
172+ } ;
173+
174+ // cleanup the modal when the component unmounts
175+ useEffect ( ( ) => ( ) => modal && modal . destroy ( ) , [ modal ] ) ;
176+
177+ let cmp : ReactNode ;
178+
179+ if ( network . simulation ) {
180+ cmp = (
181+ < >
182+ < Styled . SimContainer >
183+ < Styled . NodeWrapper >
184+ < span > { network . simulation . source . name } </ span >
185+ < ArrowRightOutlined />
186+ < span > { network . simulation . destination . name } </ span >
187+ </ Styled . NodeWrapper >
188+ < Styled . DeleteButton
189+ role = "remove"
190+ icon = { < DeleteOutlined /> }
191+ onClick = { showRemoveModal }
192+ />
193+ </ Styled . SimContainer >
194+ < Styled . Button
195+ key = "start"
196+ type = { type }
197+ danger = { danger }
198+ icon = { icon }
199+ loading = { loading }
200+ ghost = { started }
201+ onClick = { started ? stopSimulationAsync . execute : startSimulationAsync . execute }
202+ >
203+ { l ( `primaryBtn${ label } ` ) }
204+ </ Styled . Button >
205+ </ >
206+ ) ;
207+ } else {
208+ cmp = (
209+ < Empty image = { Empty . PRESENTED_IMAGE_SIMPLE } description = { l ( 'emptyMsg' ) } >
210+ < Button type = "primary" icon = { < PlusOutlined /> } onClick = { addSimulation } >
211+ { l ( 'createBtn' ) }
212+ </ Button >
213+ </ Empty >
214+ ) ;
215+ }
216+
26217 return (
27218 < div >
28219 < Styled . Title >
@@ -31,19 +222,12 @@ const SimulationDesignerTab: React.FC = () => {
31222 < Button
32223 type = "text"
33224 icon = { < PlusOutlined /> }
34- onClick = { ( ) => showAddSimulation ( { } ) }
225+ onClick = { addSimulation }
226+ disabled = { loading || network . simulation !== undefined }
35227 />
36228 </ Tooltip >
37229 </ Styled . Title >
38- < Empty image = { Empty . PRESENTED_IMAGE_SIMPLE } description = { l ( 'emptyMsg' ) } >
39- < Button
40- type = "primary"
41- icon = { < PlusOutlined /> }
42- onClick = { ( ) => showAddSimulation ( { } ) }
43- >
44- { l ( 'createBtn' ) }
45- </ Button >
46- </ Empty >
230+ { cmp }
47231 </ div >
48232 ) ;
49233} ;
0 commit comments