22// SPDX-License-Identifier: Apache-2.0
33
44import { BlockNodeModel } from "@/app/block/blocktypes" ;
5- import { WOS } from "@/app/store/global" ;
5+ import { atoms , globalStore , WOS } from "@/app/store/global" ;
6+ import { waveEventSubscribe } from "@/app/store/wps" ;
7+ import { RpcApi } from "@/app/store/wshclientapi" ;
8+ import { TabRpcClient } from "@/app/store/wshrpcutil" ;
9+ import * as services from "@/store/services" ;
610import * as jotai from "jotai" ;
711import { memo } from "react" ;
812
@@ -12,19 +16,74 @@ class TsunamiViewModel implements ViewModel {
1216 blockId : string ;
1317 viewIcon : jotai . Atom < string > ;
1418 viewName : jotai . Atom < string > ;
19+ shellProcFullStatus : jotai . PrimitiveAtom < BlockControllerRuntimeStatus > ;
20+ shellProcStatusUnsubFn : ( ) => void ;
21+ isRestarting : jotai . PrimitiveAtom < boolean > ;
1522
1623 constructor ( blockId : string , nodeModel : BlockNodeModel ) {
1724 this . viewType = "tsunami" ;
1825 this . blockId = blockId ;
1926 this . blockAtom = WOS . getWaveObjectAtom < Block > ( `block:${ blockId } ` ) ;
2027 this . viewIcon = jotai . atom ( "cube" ) ;
2128 this . viewName = jotai . atom ( "Tsunami" ) ;
29+ this . isRestarting = jotai . atom ( false ) ;
30+
31+ this . shellProcFullStatus = jotai . atom ( null ) as jotai . PrimitiveAtom < BlockControllerRuntimeStatus > ;
32+ const initialShellProcStatus = services . BlockService . GetControllerStatus ( blockId ) ;
33+ initialShellProcStatus . then ( ( rts ) => {
34+ this . updateShellProcStatus ( rts ) ;
35+ } ) ;
36+ this . shellProcStatusUnsubFn = waveEventSubscribe ( {
37+ eventType : "controllerstatus" ,
38+ scope : WOS . makeORef ( "block" , blockId ) ,
39+ handler : ( event ) => {
40+ let bcRTS : BlockControllerRuntimeStatus = event . data ;
41+ this . updateShellProcStatus ( bcRTS ) ;
42+ } ,
43+ } ) ;
2244 }
2345
2446 get viewComponent ( ) : ViewComponent {
2547 return TsunamiView ;
2648 }
2749
50+ updateShellProcStatus ( fullStatus : BlockControllerRuntimeStatus ) {
51+ console . log ( "tsunami-status" , fullStatus ) ;
52+ if ( fullStatus == null ) {
53+ return ;
54+ }
55+ const curStatus = globalStore . get ( this . shellProcFullStatus ) ;
56+ if ( curStatus == null || curStatus . version < fullStatus . version ) {
57+ globalStore . set ( this . shellProcFullStatus , fullStatus ) ;
58+ }
59+ }
60+
61+ triggerRestartAtom ( ) {
62+ globalStore . set ( this . isRestarting , true ) ;
63+ setTimeout ( ( ) => {
64+ globalStore . set ( this . isRestarting , false ) ;
65+ } , 300 ) ;
66+ }
67+
68+ forceRestartController ( ) {
69+ if ( globalStore . get ( this . isRestarting ) ) {
70+ return ;
71+ }
72+ this . triggerRestartAtom ( ) ;
73+ const prtn = RpcApi . ControllerResyncCommand ( TabRpcClient , {
74+ tabid : globalStore . get ( atoms . staticTabId ) ,
75+ blockid : this . blockId ,
76+ forcerestart : true ,
77+ } ) ;
78+ prtn . catch ( ( e ) => console . log ( "error controller resync (force restart)" , e ) ) ;
79+ }
80+
81+ dispose ( ) {
82+ if ( this . shellProcStatusUnsubFn ) {
83+ this . shellProcStatusUnsubFn ( ) ;
84+ }
85+ }
86+
2887 getSettingsMenuItems ( ) : ContextMenuItem [ ] {
2988 return [ ] ;
3089 }
@@ -35,13 +94,69 @@ type TsunamiViewProps = {
3594} ;
3695
3796const TsunamiView = memo ( ( { model } : TsunamiViewProps ) => {
97+ const shellProcFullStatus = jotai . useAtomValue ( model . shellProcFullStatus ) ;
98+ const blockData = jotai . useAtomValue ( model . blockAtom ) ;
99+ const isRestarting = jotai . useAtomValue ( model . isRestarting ) ;
100+
101+ const appPath = blockData ?. meta ?. [ "tsunami:apppath" ] ;
102+ const controller = blockData ?. meta ?. controller ;
103+
104+ // Check for configuration errors
105+ const errors = [ ] ;
106+ if ( ! appPath ) {
107+ errors . push ( "App path must be set (tsunami:apppath)" ) ;
108+ }
109+ if ( controller !== "tsunami" ) {
110+ errors . push ( "Invalid controller (must be 'tsunami')" ) ;
111+ }
112+
113+ // Show errors if any exist
114+ if ( errors . length > 0 ) {
115+ return (
116+ < div className = "w-full h-full flex flex-col items-center justify-center gap-4" >
117+ < h1 className = "text-4xl font-bold text-main-text-color" > Tsunami</ h1 >
118+ < div className = "flex flex-col gap-2" >
119+ { errors . map ( ( error , index ) => (
120+ < div key = { index } className = "text-sm" style = { { color : "var(--color-error)" } } >
121+ { error }
122+ </ div >
123+ ) ) }
124+ </ div >
125+ </ div >
126+ ) ;
127+ }
128+
129+ // Check if we should show the iframe
130+ const shouldShowIframe =
131+ shellProcFullStatus ?. shellprocstatus === "running" &&
132+ shellProcFullStatus ?. tsunamiport &&
133+ shellProcFullStatus . tsunamiport !== 0 ;
134+
135+ if ( shouldShowIframe ) {
136+ const iframeUrl = `http://localhost:${ shellProcFullStatus . tsunamiport } /?clientid=wave:${ model . blockId } ` ;
137+ return < iframe src = { iframeUrl } className = "w-full h-full border-0" title = "Tsunami Application" /> ;
138+ }
139+
140+ const status = shellProcFullStatus ?. shellprocstatus ?? "init" ;
141+ const isNotRunning = status === "done" || status === "init" ;
142+
38143 return (
39- < div className = "w-full h-full flex items-center justify-center" >
144+ < div className = "w-full h-full flex flex-col items-center justify-center gap-4 " >
40145 < h1 className = "text-4xl font-bold text-main-text-color" > Tsunami</ h1 >
146+ { appPath && < div className = "text-sm text-main-text-color opacity-70" > { appPath } </ div > }
147+ { isNotRunning && ! isRestarting && (
148+ < button
149+ onClick = { ( ) => model . forceRestartController ( ) }
150+ className = "px-4 py-2 bg-accent-color text-primary-text-color rounded hover:bg-accent-color/80 transition-colors cursor-pointer"
151+ >
152+ Start
153+ </ button >
154+ ) }
155+ { isRestarting && < div className = "text-sm text-success-color" > Starting...</ div > }
41156 </ div >
42157 ) ;
43158} ) ;
44159
45160TsunamiView . displayName = "TsunamiView" ;
46161
47- export { TsunamiViewModel } ;
162+ export { TsunamiViewModel } ;
0 commit comments