Line data Source code
1 : import * as React from 'react';
2 : import * as styles from './session_page.module.css';
3 :
4 : import { useLocation } from 'react-router-dom';
5 :
6 : import { useSessionScripts } from './scripts/session_scripts_registry.js';
7 : import { useConnectionRegistry } from '../connections/connection_registry.js';
8 : import type { ConnectionState } from '../connections/connection_state.js';
9 : import { isEphemeralSession, newScriptConnection, sessionConnectionIds } from './scripts/session_scripts.js';
10 : import { useLogger } from '../platform/logger/logger_provider.js';
11 : import { PROMOTE_SESSION, useRouteContext, useRouterNavigate } from '../app/router/router.js';
12 :
13 : import { SessionFeedPage } from './feed/session_feed_page.js';
14 : import { SessionViewMode, useSessionViewMode } from './session_view_mode.js';
15 :
16 1 : const SessionShellsPage = React.lazy(() => import('./shell/session_shells_page.js'));
17 :
18 1 : const LOG_CTX = 'session_page';
19 :
20 : export interface SessionPageDependencies {
21 : useSessionScripts: typeof useSessionScripts;
22 : useConnectionRegistry: typeof useConnectionRegistry;
23 : useLogger: typeof useLogger;
24 : useRouteContext: typeof useRouteContext;
25 : useRouterNavigate: typeof useRouterNavigate;
26 : useLocation: typeof useLocation;
27 : useSessionViewMode: typeof useSessionViewMode;
28 : SessionFeedPage: typeof SessionFeedPage;
29 : SessionShellsPage: typeof SessionShellsPage;
30 : }
31 :
32 1 : const DEFAULT_DEPENDENCIES: SessionPageDependencies = {
33 1 : useSessionScripts,
34 1 : useConnectionRegistry,
35 1 : useLogger,
36 1 : useRouteContext,
37 1 : useRouterNavigate,
38 1 : useLocation,
39 1 : useSessionViewMode,
40 1 : SessionFeedPage,
41 1 : SessionShellsPage,
42 1 : };
43 :
44 : interface Props {
45 : dependencies?: SessionPageDependencies;
46 : }
47 :
48 1 : export const SessionPage: React.FC<Props> = (props: Props) => {
49 1 : const dependencies = props.dependencies ?? DEFAULT_DEPENDENCIES;
50 1 : const route = dependencies.useRouteContext();
51 1 : const logger = dependencies.useLogger();
52 1 : const [sessionScripts, modifySessionScripts] = dependencies.useSessionScripts(route.sessionId ?? null);
53 : // The connections that the scripts of the session run on, and the one the next script runs on
54 1 : const [connectionRegistry] = dependencies.useConnectionRegistry();
55 1 : const nextConnectionId = sessionScripts == null ? null : newScriptConnection(sessionScripts, null)?.connectionId ?? null;
56 1 : const conn = nextConnectionId == null ? null : connectionRegistry.connections.get(nextConnectionId) ?? null;
57 1 : const scriptConnections = new Map<string, ConnectionState>();
58 1 : for (const connectionId of sessionScripts == null ? [] : sessionConnectionIds(sessionScripts)) {
59 1 : const connection = connectionRegistry.connections.get(connectionId);
60 1 : if (connection != null) scriptConnections.set(connectionId, connection);
61 1 : }
62 1 : const { mode: sessionMode } = dependencies.useSessionViewMode();
63 1 : const sessionPageVisible = sessionMode !== SessionViewMode.Shell;
64 : // The connection of the visible shell, the connection of the next script until another one is picked.
65 : // Kept while the shell is closed, the shell reopens on the picked connection of the session.
66 1 : const [shellPick, setShellPick] = React.useState<{ sessionId: string; connectionId: string } | null>(null);
67 1 : const shellConnectionId = shellPick != null && shellPick.sessionId === route.sessionId ? shellPick.connectionId : null;
68 1 : const shellConnection = (shellConnectionId == null ? null : connectionRegistry.connections.get(shellConnectionId)) ?? conn ?? null;
69 :
70 1 : React.useEffect(() => {
71 1 : if (route.sessionId === null) {
72 1 : logger.warn('missing session id', {}, LOG_CTX);
73 : }
74 1 : }, [route.sessionId]);
75 :
76 : // The greeter stays at the root until a script promotes its session
77 1 : const navigate = dependencies.useRouterNavigate();
78 1 : const pathname = dependencies.useLocation().pathname;
79 1 : const promotedSessionId = sessionScripts != null && !isEphemeralSession(sessionScripts) && pathname === '/' ? sessionScripts.sessionId : null;
80 1 : React.useEffect(() => {
81 1 : if (promotedSessionId != null) navigate({ type: PROMOTE_SESSION, value: promotedSessionId });
82 1 : }, [navigate, promotedSessionId]);
83 :
84 1 : if (route.sessionId === null || sessionScripts == null) {
85 1 : return <div />;
86 : }
87 1 : return (
88 1 : <div className={styles.page}>
89 1 : <div className={sessionPageVisible ? styles.view : styles.view_hidden}>
90 1 : <dependencies.SessionFeedPage
91 1 : sessionScripts={sessionScripts}
92 1 : modifySessionScripts={modifySessionScripts}
93 1 : connection={conn ?? null}
94 1 : scriptConnections={scriptConnections}
95 1 : active={sessionMode === SessionViewMode.Session}
96 1 : />
97 1 : </div>
98 1 : {sessionMode === SessionViewMode.Shell && (
99 1 : <div className={styles.view}>
100 1 : <React.Suspense fallback={(
101 1 : <div className={styles.shellLoading} role="status">
102 1 : <strong>[ RUN ]</strong> Loading shell
103 1 : </div>
104 : )}>
105 1 : <dependencies.SessionShellsPage
106 1 : key={sessionScripts.sessionId}
107 1 : sessionId={sessionScripts.sessionId}
108 1 : sessionName={sessionScripts.name}
109 1 : connectionId={shellConnection?.connectionId ?? null}
110 1 : onPickConnection={connectionId => setShellPick({ sessionId: sessionScripts.sessionId, connectionId })}
111 1 : />
112 1 : </React.Suspense>
113 1 : </div>
114 : )}
115 1 : </div>
116 : );
117 1 : };
|