1- import React from 'react' ;
1+ import React , { ReactNode , useCallback , 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 , MenuProps , Dropdown } from 'antd' ;
5+ import {
6+ ArrowRightOutlined ,
7+ FileTextOutlined ,
8+ PlayCircleOutlined ,
9+ PlusOutlined ,
10+ StopOutlined ,
11+ WarningOutlined ,
12+ CloseOutlined ,
13+ MoreOutlined ,
14+ } from '@ant-design/icons' ;
15+ import { useStoreActions , useStoreState } from 'store' ;
16+ import { Network } from 'types' ;
17+ import { useAsyncCallback } from 'react-async-hook' ;
18+ import { Status } from 'shared/types' ;
19+ import { ButtonType } from 'antd/lib/button' ;
20+
21+ interface Props {
22+ network : Network ;
23+ }
824
925const Styled = {
1026 Title : styled . div `
@@ -14,15 +30,203 @@ const Styled = {
1430 margin-bottom: 10px;
1531 font-weight: bold;
1632 ` ,
33+ Button : styled ( Button ) `
34+ margin-left: 0;
35+ margin-top: 20px;
36+ width: 100%;
37+ ` ,
38+ SimContainer : styled . div `
39+ display: flex;
40+ align-items: center;
41+ justify-content: space-between;
42+ width: 100%;
43+ height: 46px;
44+ padding: 10px 15px;
45+ margin-top: 20px;
46+ border: 1px solid rgba(255, 255, 255, 0.2);
47+ border-radius: 4px;
48+ font-weight: bold;
49+ ` ,
50+ NodeWrapper : styled . div `
51+ display: flex;
52+ align-items: center;
53+ justify-content: start;
54+ column-gap: 15px;
55+ width: 100%;
56+ ` ,
57+ Dropdown : styled ( Dropdown ) `
58+ margin-left: 12px;
59+ ` ,
60+ } ;
61+
62+ const config : {
63+ [ key : number ] : {
64+ label : string ;
65+ type : ButtonType ;
66+ danger ?: boolean ;
67+ icon : ReactNode ;
68+ } ;
69+ } = {
70+ [ Status . Starting ] : {
71+ label : 'Starting' ,
72+ type : 'primary' ,
73+ icon : '' ,
74+ } ,
75+ [ Status . Started ] : {
76+ label : 'Stop' ,
77+ type : 'primary' ,
78+ danger : true ,
79+ icon : < StopOutlined /> ,
80+ } ,
81+ [ Status . Stopping ] : {
82+ label : 'Stopping' ,
83+ type : 'default' ,
84+ icon : '' ,
85+ } ,
86+ [ Status . Stopped ] : {
87+ label : 'Start' ,
88+ type : 'primary' ,
89+ icon : < PlayCircleOutlined /> ,
90+ } ,
91+ [ Status . Error ] : {
92+ label : 'Restart' ,
93+ type : 'primary' ,
94+ danger : true ,
95+ icon : < WarningOutlined /> ,
96+ } ,
1797} ;
1898
19- const SimulationDesignerTab : React . FC = ( ) => {
99+ const SimulationDesignerTab : React . FC < Props > = ( { network } ) => {
20100 const { l } = usePrefixedTranslation (
21101 'cmps.designer.default.DefaultSidebar.SimulationDesignerTab' ,
22102 ) ;
23103
104+ // Getting the network from the store makes this component to
105+ // re-render when the network is updated (i.e when we add a simulation).
106+ const { networks } = useStoreState ( s => s . network ) ;
107+ const currentNetwork = networks . find ( n => n . id === network . id ) ;
108+
24109 const { showAddSimulation } = useStoreActions ( s => s . modals ) ;
25110
111+ const { notify, openWindow } = useStoreActions ( s => s . app ) ;
112+
113+ const { startSimulation, stopSimulation, removeSimulation } = useStoreActions (
114+ s => s . network ,
115+ ) ;
116+
117+ const loading =
118+ currentNetwork ?. simulation ?. status === Status . Starting ||
119+ currentNetwork ?. simulation ?. status === Status . Stopping ;
120+ const started = currentNetwork ?. simulation ?. status === Status . Started ;
121+ const { label, type, danger, icon } =
122+ config [ currentNetwork ?. simulation ?. status || Status . Stopped ] ;
123+
124+ const startSimulationAsync = useAsyncCallback ( async ( ) => {
125+ if ( ! network . simulation ) return ;
126+ try {
127+ await startSimulation ( { id : network . simulation . networkId } ) ;
128+ } catch ( error : any ) {
129+ notify ( { message : l ( 'startError' ) , error } ) ;
130+ }
131+ } ) ;
132+
133+ const stopSimulationAsync = useAsyncCallback ( async ( ) => {
134+ if ( ! network . simulation ) return ;
135+ try {
136+ await stopSimulation ( { id : network . simulation . networkId } ) ;
137+ } catch ( error : any ) {
138+ notify ( { message : l ( 'stopError' ) , error } ) ;
139+ }
140+ } ) ;
141+
142+ const addSimulation = ( ) => {
143+ showAddSimulation ( { } ) ;
144+ } ;
145+
146+ let modal : any ;
147+ const showRemoveModal = ( ) => {
148+ modal = Modal . confirm ( {
149+ title : l ( 'removeTitle' ) ,
150+ content : l ( 'removeDesc' ) ,
151+ okText : l ( 'removeBtn' ) ,
152+ okType : 'danger' ,
153+ cancelText : l ( 'cancelBtn' ) ,
154+ onOk : async ( ) => {
155+ try {
156+ if ( ! network . simulation ) return ;
157+ await removeSimulation ( network . simulation ) ;
158+ notify ( { message : l ( 'removeSuccess' ) } ) ;
159+ } catch ( error : any ) {
160+ notify ( { message : l ( 'removeError' ) , error : error } ) ;
161+ }
162+ } ,
163+ } ) ;
164+ } ;
165+
166+ const openAsync = useAsyncCallback ( async ( ) => {
167+ await openWindow ( `/logs/simln/polar-n${ network . id } -simln` ) ;
168+ } ) ;
169+
170+ const handleClick : MenuProps [ 'onClick' ] = useCallback ( ( info : { key : string } ) => {
171+ switch ( info . key ) {
172+ case 'logs' :
173+ openAsync . execute ( ) ;
174+ break ;
175+ case 'delete' :
176+ showRemoveModal ( ) ;
177+ break ;
178+ }
179+ } , [ ] ) ;
180+
181+ const items : MenuProps [ 'items' ] = [
182+ { key : 'logs' , label : 'View Logs' , icon : < FileTextOutlined /> } ,
183+ { key : 'delete' , label : 'Delete' , icon : < CloseOutlined /> } ,
184+ ] ;
185+
186+ // cleanup the modal when the component unmounts
187+ useEffect ( ( ) => ( ) => modal && modal . destroy ( ) , [ modal ] ) ;
188+
189+ let cmp : ReactNode ;
190+
191+ if ( network . simulation ) {
192+ cmp = (
193+ < >
194+ < Styled . SimContainer >
195+ < Styled . NodeWrapper >
196+ < span > { network . simulation . source . name } </ span >
197+ < ArrowRightOutlined />
198+ < span > { network . simulation . destination . name } </ span >
199+ </ Styled . NodeWrapper >
200+ < Styled . Dropdown
201+ key = "options"
202+ menu = { { theme : 'dark' , items, onClick : handleClick } }
203+ >
204+ < MoreOutlined />
205+ </ Styled . Dropdown >
206+ </ Styled . SimContainer >
207+ < Styled . Button
208+ key = "start"
209+ type = { type }
210+ danger = { danger }
211+ icon = { icon }
212+ loading = { loading }
213+ ghost = { started }
214+ onClick = { started ? stopSimulationAsync . execute : startSimulationAsync . execute }
215+ >
216+ { l ( `primaryBtn${ label } ` ) }
217+ </ Styled . Button >
218+ </ >
219+ ) ;
220+ } else {
221+ cmp = (
222+ < Empty image = { Empty . PRESENTED_IMAGE_SIMPLE } description = { l ( 'emptyMsg' ) } >
223+ < Button type = "primary" icon = { < PlusOutlined /> } onClick = { addSimulation } >
224+ { l ( 'createBtn' ) }
225+ </ Button >
226+ </ Empty >
227+ ) ;
228+ }
229+
26230 return (
27231 < div >
28232 < Styled . Title >
@@ -31,19 +235,12 @@ const SimulationDesignerTab: React.FC = () => {
31235 < Button
32236 type = "text"
33237 icon = { < PlusOutlined /> }
34- onClick = { ( ) => showAddSimulation ( { } ) }
238+ onClick = { addSimulation }
239+ disabled = { loading || network . simulation !== undefined }
35240 />
36241 </ Tooltip >
37242 </ 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 >
243+ { cmp }
47244 </ div >
48245 ) ;
49246} ;
0 commit comments