LCOV - code coverage report
Current view: top level - dashql-app/src/session/persistence - session_storage_overlay.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 78.9 % 90 71
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : import * as styles from './session_storage_overlay.module.css';
       3              : 
       4              : import { XIcon, FileDirectoryIcon } from '../../ui/foundations/symbol_icon.js';
       5              : 
       6              : import { AnchorAlignment, AnchorSide } from '../../ui/foundations/anchored_position.js';
       7              : import { AnchoredOverlay } from '../../ui/foundations/anchored_overlay.js';
       8              : import { ButtonVariant, IconButton } from '../../ui/foundations/button.js';
       9              : import { OverlaySize } from '../../ui/foundations/overlay.js';
      10              : import { PlatformType, usePlatformType } from '../../platform/platform_type.js';
      11              : import { StorageBackendType } from '../../storage/storage_backend.js';
      12              : import { displayPath } from '../../storage/session_locator.js';
      13              : import { useStorageReader, useStorageWriter } from '../../storage/storage_provider.js';
      14              : import { useLogger } from '../../platform/logger/logger_provider.js';
      15              : import { relocateSessionToNative } from './session_relocation.js';
      16              : 
      17              : /// The shared header for the storage view: title on the left, actions + close button on the right.
      18            1 : function StorageViewHeader(props: {
      19              :     onClose: () => void;
      20              :     canRelocate?: boolean;
      21              :     migrating?: boolean;
      22              :     onRelocate?: () => void;
      23            1 : }) {
      24            1 :     return (
      25            1 :         <div className={styles.header_container}>
      26            1 :             <div className={styles.header_left_container}>
      27            1 :                 <div className={styles.title}>Session Storage</div>
      28            1 :             </div>
      29            1 :             <div className={styles.header_right_container}>
      30            1 :                 {props.canRelocate && (
      31            0 :                     <IconButton
      32            0 :                         variant={ButtonVariant.Invisible}
      33            0 :                         aria-label="Store locally"
      34            0 :                         disabled={props.migrating}
      35            0 :                         onClick={props.onRelocate}
      36              :                     >
      37            0 :                         <FileDirectoryIcon />
      38            0 :                     </IconButton>
      39              :                 )}
      40            1 :                 <IconButton
      41            1 :                     variant={ButtonVariant.Invisible}
      42            1 :                     aria-label="close-overlay"
      43            1 :                     onClick={props.onClose}
      44              :                 >
      45            1 :                     <XIcon />
      46            1 :                 </IconButton>
      47            1 :             </div>
      48            1 :         </div>
      49              :     );
      50            1 : }
      51              : 
      52              : /// A single parameter row (label + monospace value).
      53            1 : function ParamRow(props: { label: string; value: string }) {
      54            1 :     return (
      55            1 :         <div className={styles.param_row}>
      56            1 :             <div className={styles.param_label}>{props.label}</div>
      57            1 :             <div className={styles.param_value} title={props.value}>{props.value}</div>
      58            1 :         </div>
      59              :     );
      60            1 : }
      61              : 
      62              : interface SessionStorageViewerProps {
      63              :     sessionId: string | null;
      64              :     onClose: () => void;
      65              : }
      66              : 
      67            1 : export const SessionStorageViewer: React.FC<SessionStorageViewerProps> = (props) => {
      68            1 :     const reader = useStorageReader();
      69            1 :     const writer = useStorageWriter();
      70            1 :     const logger = useLogger();
      71            1 :     const platform = usePlatformType();
      72              : 
      73            1 :     const [migrating, setMigrating] = React.useState(false);
      74              : 
      75            1 :     const location = props.sessionId ? reader.getSessionLocation(props.sessionId) : null;
      76            1 :     const isNative = location?.type === StorageBackendType.Native;
      77            1 :     const canRelocate =
      78            1 :         platform === PlatformType.DESKTOP &&
      79            0 :         !isNative &&
      80            0 :         props.sessionId != null &&
      81            0 :         reader.sessionFolders != null;
      82              : 
      83            1 :     const onRelocate = React.useCallback(async () => {
      84            0 :         const sessionFolders = reader.sessionFolders;
      85            0 :         if (props.sessionId == null || sessionFolders == null) return;
      86            0 :         setMigrating(true);
      87            0 :         try {
      88            0 :             await relocateSessionToNative(props.sessionId, sessionFolders, writer, logger);
      89            0 :         } catch {
      90            0 :             setMigrating(false);
      91            0 :         }
      92            1 :     }, [props.sessionId, reader.sessionFolders, writer, logger]);
      93              : 
      94            1 :     const backendValue = isNative ? 'Host File System' : 'Origin Private File System (Browser)';
      95            1 :     const schemaValue = props.sessionId && location ? displayPath(props.sessionId, location) : '';
      96              : 
      97            1 :     return (
      98            1 :         <div className={styles.body}>
      99            1 :             <StorageViewHeader
     100            1 :                 onClose={props.onClose}
     101            1 :                 canRelocate={canRelocate}
     102            1 :                 migrating={migrating}
     103            1 :                 onRelocate={onRelocate}
     104            1 :             />
     105            1 :             <div className={styles.body_content}>
     106            1 :                 <ParamRow label="Backend" value={backendValue} />
     107            1 :                 <ParamRow label="Location" value={schemaValue} />
     108            1 :                 {isNative && location?.nativePath && (
     109            0 :                     <ParamRow label="Folder" value={location.nativePath} />
     110              :                 )}
     111            1 :             </div>
     112            1 :         </div>
     113              :     );
     114            1 : };
     115              : 
     116              : type SessionStorageOverlayProps = {
     117              :     sessionId: string | null;
     118              :     isOpen: boolean;
     119              :     onOpen?: () => void;
     120              :     onClose: () => void;
     121              :     renderAnchor: (p: object) => React.ReactElement;
     122              :     side?: AnchorSide;
     123              :     align?: AnchorAlignment;
     124              :     anchorOffset?: number;
     125              : };
     126              : 
     127            1 : export function SessionStorageOverlay(props: SessionStorageOverlayProps) {
     128            1 :     return (
     129            1 :         <AnchoredOverlay
     130            1 :             open={props.isOpen}
     131            1 :             onOpen={props.onOpen}
     132            1 :             onClose={props.onClose}
     133            1 :             renderAnchor={props.renderAnchor}
     134            1 :             side={props.side}
     135            1 :             align={props.align}
     136            1 :             anchorOffset={props.anchorOffset}
     137            1 :             overlayProps={{
     138            1 :                 width: OverlaySize.L,
     139            1 :                 height: OverlaySize.S,
     140            1 :             }}
     141              :         >
     142            1 :             <SessionStorageViewer sessionId={props.sessionId} onClose={props.onClose} />
     143            1 :         </AnchoredOverlay>
     144              :     );
     145            1 : }
        

Generated by: LCOV version 2.0-1