Line data Source code
1 : import * as React from 'react';
2 : import { ConnectionHealth, type ConnectionState } from '../connections/connection_state.js';
3 : import { Tooltip } from '../ui/foundations/tooltip.js';
4 :
5 : export const CONNECTION_ATTENTION_DURATION_MS = 900;
6 :
7 : interface ConnectionAttention {
8 : sessionId: string | null;
9 : active: boolean;
10 : request: (sessionId: string) => void;
11 : }
12 :
13 1 : export const SessionConnectionAttentionContext = React.createContext<ConnectionAttention | null>(null);
14 :
15 : /// The connections whose health resets the attention, as a key that changes with them
16 1 : export function connectionHealthKey(connections: readonly ConnectionState[] | null): string {
17 1 : return (connections ?? []).map(connection => `${connection.connectionId}:${connection.connectionHealth}`).join(',');
18 1 : }
19 :
20 : /// The connections of a session that are not online, a session runs once they are
21 1 : export function connectionsToConnect(connections: Iterable<ConnectionState>): ConnectionState[] {
22 1 : return [...connections].filter(connection => connection.connectionHealth !== ConnectionHealth.ONLINE);
23 1 : }
24 :
25 1 : export function useSessionConnectionAttention(sessionId: string | null, healthKey: string): ConnectionAttention {
26 1 : const shared = React.useContext(SessionConnectionAttentionContext);
27 1 : const [active, setActive] = React.useState(false);
28 1 : const timer = React.useRef<ReturnType<typeof setTimeout> | null>(null);
29 1 : React.useLayoutEffect(() => {
30 1 : setActive(false);
31 1 : if (timer.current != null) clearTimeout(timer.current);
32 1 : timer.current = null;
33 1 : return () => {
34 1 : if (timer.current != null) clearTimeout(timer.current);
35 1 : timer.current = null;
36 1 : };
37 1 : }, [sessionId, healthKey]);
38 : // Callers request attention for the connections the scripts of the session run on
39 1 : const request = React.useCallback((targetSessionId: string) => {
40 1 : if (targetSessionId !== sessionId || timer.current != null) return;
41 1 : setActive(true);
42 1 : timer.current = setTimeout(() => {
43 1 : timer.current = null;
44 1 : setActive(false);
45 1 : }, CONNECTION_ATTENTION_DURATION_MS);
46 1 : }, [sessionId]);
47 : // The value is provided as context to every execute button in the feed, keep it stable
48 1 : const own = React.useMemo(() => ({ sessionId, active, request }), [sessionId, active, request]);
49 1 : return shared?.sessionId === sessionId ? shared : own;
50 1 : }
51 :
52 1 : export function ExecuteConnectionGuard(props: { sessionId: string; blocked: boolean; children: React.ReactElement }) {
53 1 : const attention = React.useContext(SessionConnectionAttentionContext);
54 1 : return <Tooltip type="description" direction="s" text={props.blocked ? 'Connect to execute queries' : 'Query execution control'}>
55 1 : <span data-execution-connection-blocked={props.blocked ? 'true' : undefined}
56 1 : style={{ display: 'inline-flex' }} tabIndex={props.blocked ? 0 : undefined}
57 1 : onPointerOverCapture={event => {
58 1 : if (props.blocked && !(event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget))) {
59 1 : attention?.request(props.sessionId);
60 : }
61 1 : }}>
62 1 : {props.children}
63 1 : </span>
64 1 : </Tooltip>;
65 1 : }
|