LCOV - code coverage report
Current view: top level - dashql-app/src/session/snippets - snippets_dialog.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 98.4 % 64 63
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : 
       3              : import type { ConnectorType } from '../../connections/connector_info.js';
       4              : import { SnippetBrowser } from './snippets_overlay.js';
       5              : import type { SnippetRegistry } from './snippet_registry.js';
       6              : import { useFocusTrap } from '../../ui/foundations/focus.js';
       7              : import { Overlay, OverlaySize } from '../../ui/foundations/overlay.js';
       8              : 
       9            1 : const IGNORE_OUTSIDE_CLICK = () => {};
      10              : 
      11              : interface PendingRequest {
      12              :     resolve: (text: string | null) => void;
      13              :     signal?: AbortSignal;
      14              :     onAbort?: () => void;
      15              : }
      16              : 
      17              : export interface SnippetsDialogController {
      18              :     /// Browse the snippets, resolves with the text of the used snippet or null if the dialog was closed
      19              :     request(signal?: AbortSignal): Promise<string | null>;
      20              : }
      21              : 
      22              : export interface SnippetsDialogHookResult {
      23              :     controller: SnippetsDialogController;
      24              :     dialog: React.ReactElement | null;
      25              : }
      26              : 
      27              : /// The snippet browser of the script cards as a dialog of the shell, listing the snippets of the connector
      28            1 : export function useSnippetsDialog(connectorType: ConnectorType, registry?: SnippetRegistry): SnippetsDialogHookResult {
      29            1 :     const [open, setOpen] = React.useState(false);
      30            1 :     const pendingRequestRef = React.useRef<PendingRequest | null>(null);
      31              : 
      32            1 :     const finish = React.useCallback((text: string | null) => {
      33            1 :         const pending = pendingRequestRef.current;
      34            1 :         pendingRequestRef.current = null;
      35            1 :         if (pending?.signal != null && pending.onAbort != null) {
      36            1 :             pending.signal.removeEventListener('abort', pending.onAbort);
      37              :         }
      38            1 :         setOpen(false);
      39            1 :         pending?.resolve(text);
      40            1 :     }, []);
      41            1 :     const dismiss = React.useCallback(() => finish(null), [finish]);
      42              : 
      43            1 :     const request = React.useCallback((signal?: AbortSignal) => {
      44            1 :         if (pendingRequestRef.current != null) {
      45            1 :             return Promise.reject(new Error('A snippets request is already pending.'));
      46              :         }
      47            1 :         if (signal?.aborted) return Promise.resolve(null);
      48              : 
      49            1 :         setOpen(true);
      50            1 :         return new Promise<string | null>(resolve => {
      51            1 :             const pending: PendingRequest = { resolve, signal };
      52            1 :             pending.onAbort = dismiss;
      53            1 :             pendingRequestRef.current = pending;
      54            1 :             signal?.addEventListener('abort', pending.onAbort, { once: true });
      55            1 :         });
      56            1 :     }, [dismiss]);
      57              : 
      58            1 :     React.useEffect(() => () => {
      59            1 :         const pending = pendingRequestRef.current;
      60            1 :         pendingRequestRef.current = null;
      61            1 :         if (pending?.signal != null && pending.onAbort != null) {
      62            0 :             pending.signal.removeEventListener('abort', pending.onAbort);
      63              :         }
      64            1 :         pending?.resolve(null);
      65            1 :     }, []);
      66              : 
      67            1 :     const controller = React.useMemo<SnippetsDialogController>(() => ({ request }), [request]);
      68            1 :     return {
      69            1 :         controller,
      70            1 :         dialog: open ? <SnippetsDialog connectorType={connectorType} registry={registry} onApply={finish} onClose={dismiss} /> : null,
      71            1 :     };
      72            1 : }
      73              : 
      74              : interface SnippetsDialogProps {
      75              :     connectorType: ConnectorType;
      76              :     registry?: SnippetRegistry;
      77              :     onApply: (text: string) => void;
      78              :     onClose: () => void;
      79              : }
      80              : 
      81            1 : function SnippetsDialog(props: SnippetsDialogProps): React.ReactElement {
      82            1 :     const overlayRef = React.useRef<HTMLDivElement>(null);
      83            1 :     const listRef = React.useRef<HTMLDivElement>(null);
      84              : 
      85            1 :     useFocusTrap({
      86            1 :         containerRef: overlayRef as React.RefObject<HTMLElement>,
      87            1 :         initialFocusRef: listRef as React.RefObject<HTMLElement>,
      88            1 :         restoreFocusOnCleanUp: true,
      89            1 :     });
      90              : 
      91            1 :     return (
      92            1 :         <Overlay
      93            1 :             ref={overlayRef}
      94            1 :             centered
      95            1 :             width={OverlaySize.XXL}
      96            1 :             height={OverlaySize.L}
      97            1 :             preventFocusOnOpen
      98            1 :             onEscape={props.onClose}
      99            1 :             onClickOutside={IGNORE_OUTSIDE_CLICK}
     100              :         >
     101            1 :             <SnippetBrowser
     102            1 :                 connectorType={props.connectorType}
     103            1 :                 registry={props.registry}
     104            1 :                 listRef={listRef}
     105            1 :                 onApply={props.onApply}
     106            1 :                 onClose={props.onClose}
     107            1 :             />
     108            1 :         </Overlay>
     109              :     );
     110            1 : }
        

Generated by: LCOV version 2.0-1