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

            Line data    Source code
       1              : import * as React from 'react';
       2              : 
       3              : import { AnchoredOverlay } from '../../ui/foundations/anchored_overlay.js';
       4              : import { AnchorAlignment, AnchorSide } from '../../ui/foundations/anchored_position.js';
       5              : import { Button } from '../../ui/foundations/button.js';
       6              : import { OverlaySize } from '../../ui/foundations/overlay.js';
       7              : import { SessionImportCard, type SessionImportCardDependencies } from './session_import_card.js';
       8              : 
       9              : import * as styles from './session_import_conflict_dialog.module.css';
      10              : 
      11              : interface SessionImportConflictDialogBaseProps {
      12              :     sessionName: string;
      13              :     sessionUuid: string;
      14              :     existingDisplayLocation: string;
      15              :     existingIsNative: boolean;
      16              :     folderCount: number;
      17              :     scriptCount: number;
      18              :     busy: boolean;
      19              :     onReplace: () => void;
      20              :     onCreateNew: () => void;
      21              :     onCancel: () => void;
      22              :     dependencies?: SessionImportCardDependencies;
      23              : }
      24              : 
      25              : interface CenteredSessionImportConflictDialogProps extends SessionImportConflictDialogBaseProps {
      26              :     mode: 'centered';
      27              : }
      28              : 
      29              : interface AnchoredSessionImportConflictDialogProps extends SessionImportConflictDialogBaseProps {
      30              :     mode: 'anchored';
      31              :     anchorRef: React.RefObject<Element | null>;
      32              :     returnFocusRef: React.RefObject<HTMLElement | null>;
      33              : }
      34              : 
      35              : export type SessionImportConflictDialogProps =
      36              :     | CenteredSessionImportConflictDialogProps
      37              :     | AnchoredSessionImportConflictDialogProps;
      38              : 
      39              : interface DialogContentProps extends SessionImportConflictDialogBaseProps {
      40              :     dialogRef: React.RefObject<HTMLElement | null>;
      41              : }
      42              : 
      43            1 : function DialogContent(props: DialogContentProps) {
      44            1 :     const titleId = React.useId();
      45            1 :     const descriptionId = React.useId();
      46              : 
      47            1 :     return (
      48            1 :         <section
      49            1 :             ref={props.dialogRef}
      50            1 :             className={styles.dialog}
      51            1 :             role="dialog"
      52            1 :             aria-modal="true"
      53            1 :             aria-labelledby={titleId}
      54            1 :             aria-describedby={descriptionId}
      55            1 :             aria-busy={props.busy}
      56            1 :             tabIndex={-1}
      57              :         >
      58            1 :             <header className={styles.header}>
      59            1 :                 <h2 id={titleId}>Session already exists</h2>
      60            1 :             </header>
      61            1 :             <div className={styles.body}>
      62            1 :                 <p id={descriptionId} className={styles.description}>
      63              :                     A session with this UUID already exists. Replace it, or create a new session with a different UUID.
      64            1 :                 </p>
      65            1 :                 <dl className={styles.details}>
      66            1 :                     <div>
      67            1 :                         <dt>Session</dt>
      68            1 :                         <dd>{props.sessionName}</dd>
      69            1 :                     </div>
      70            1 :                     <div>
      71            1 :                         <dt>UUID</dt>
      72            1 :                         <dd className={styles.uuid}>{props.sessionUuid}</dd>
      73            1 :                     </div>
      74            1 :                     <div>
      75            1 :                         <dt>Existing location</dt>
      76            1 :                         <dd className={styles.path}>{props.existingDisplayLocation}</dd>
      77            1 :                     </div>
      78            1 :                 </dl>
      79            1 :             </div>
      80            1 :             <footer className={styles.actions}>
      81            1 :                 <Button data-testid="session-import-cancel" disabled={props.busy} onClick={props.onCancel}>Cancel</Button>
      82            1 :                 <Button data-testid="session-import-replace" disabled={props.busy} onClick={props.onReplace}>
      83              :                     Replace
      84            1 :                 </Button>
      85            1 :                 <Button
      86            1 :                     data-testid="session-import-create-new"
      87            1 :                     disabled={props.busy}
      88            1 :                     onClick={props.onCreateNew}
      89            1 :                 >
      90              :                     Create New
      91            1 :                 </Button>
      92            1 :             </footer>
      93            1 :         </section>
      94              :     );
      95            1 : }
      96              : 
      97            1 : export function SessionImportConflictDialog(props: SessionImportConflictDialogProps) {
      98            1 :     return props.mode === 'centered'
      99            1 :         ? <CenteredConflictCard {...props} />
     100            1 :         : <AnchoredConflictDialog {...props} />;
     101            1 : }
     102              : 
     103            1 : function CenteredConflictCard(props: CenteredSessionImportConflictDialogProps) {
     104            1 :     return (
     105            1 :         <SessionImportCard
     106            1 :             phase="conflict"
     107            1 :             sessionName={props.sessionName}
     108            1 :             sessionUuid={props.sessionUuid}
     109            1 :             existingLocation={props.existingDisplayLocation}
     110            1 :             existingIsNative={props.existingIsNative}
     111            1 :             folderCount={props.folderCount}
     112            1 :             scriptCount={props.scriptCount}
     113            1 :             busy={props.busy}
     114            1 :             onClose={props.onCancel}
     115            1 :             onReplace={props.onReplace}
     116            1 :             onCreateNew={props.onCreateNew}
     117            1 :             dependencies={props.dependencies}
     118            1 :         />
     119              :     );
     120            1 : }
     121              : 
     122            1 : function AnchoredConflictDialog(props: AnchoredSessionImportConflictDialogProps) {
     123            1 :     const dialogRef = React.useRef<HTMLElement>(null);
     124            1 :     const cancel = React.useCallback(() => {
     125            0 :         if (!props.busy) props.onCancel();
     126            1 :     }, [props.busy, props.onCancel]);
     127            1 :     const content = (
     128            1 :         <DialogContent
     129            1 :             {...props}
     130            1 :             dialogRef={dialogRef}
     131            1 :         />
     132              :     );
     133              : 
     134            1 :     return (
     135            1 :         <AnchoredOverlay
     136            1 :             renderAnchor={null}
     137            1 :             anchorRef={props.anchorRef}
     138            1 :             returnFocusRef={props.returnFocusRef}
     139            1 :             open
     140            1 :             onClose={cancel}
     141            1 :             side={AnchorSide.OutsideBottom}
     142            1 :             align={AnchorAlignment.End}
     143            1 :             width={OverlaySize.L}
     144            1 :             overlayProps={{ initialFocusRef: dialogRef }}
     145            1 :             focusTrapSettings={{
     146            1 :                 initialFocusRef: dialogRef as React.RefObject<HTMLElement>,
     147            1 :                 returnFocusRef: props.returnFocusRef as React.RefObject<HTMLElement>,
     148            1 :             }}
     149              :         >
     150            1 :             {content}
     151            1 :         </AnchoredOverlay>
     152              :     );
     153            1 : }
        

Generated by: LCOV version 2.0-1