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 : }
|