Line data Source code
1 : import * as React from 'react';
2 :
3 : import * as styles from './session_url_share_overlay.module.css';
4 :
5 : import { AnchorAlignment } from '../../ui/foundations/anchored_position.js';
6 : import { AnchoredOverlay } from '../../ui/foundations/anchored_overlay.js';
7 : import { SessionExportSettings, SessionExportSettingsView } from './session_export_settings_view.js';
8 : import { classNames } from '../../utils/classnames.js';
9 : import { exportSessionAsUrl, SessionLinkTarget } from './session_export.js';
10 : import { connectionParamsHaveLoginHint, getConnectionParamsFromStateDetails } from '../../connections/connection_params.js';
11 : import { resolveSessionConnections, useConnectionReferences, useConnectionRegistry, useSessionConnections } from '../../connections/connection_registry.js';
12 : import { useRouteContext } from '../../app/router/router.js';
13 : import { useSessionScripts } from '../scripts/session_scripts_registry.js';
14 : import { useStorageReader } from '../../storage/storage_provider.js';
15 : import { ShareURLField } from './share_url_field.js';
16 :
17 : interface Props {
18 : className?: string;
19 : isOpen: boolean;
20 : sessionId?: string;
21 : setIsOpen: (v: boolean) => void;
22 : anchorRef?: React.RefObject<HTMLButtonElement | null>;
23 : }
24 :
25 :
26 1 : export const SessionURLShareOverlay: React.FC<Props> = (props: Props) => {
27 1 : const route = useRouteContext();
28 1 : const anchorRef = React.createRef<HTMLDivElement>();
29 1 : const buttonRef = React.createRef<HTMLButtonElement>();
30 :
31 1 : const [sessionScripts] = useSessionScripts(props.sessionId ?? route.sessionId ?? null);
32 1 : const connections = useSessionConnections(sessionScripts?.sessionId ?? null);
33 : // The connections are resolved anew on every render, the link follows their ids
34 1 : const connectionIds = connections?.map(connection => connection.connectionId).join(',') ?? null;
35 1 : const [databaseRegistry] = useConnectionRegistry();
36 1 : const references = useConnectionReferences();
37 1 : const storage = useStorageReader();
38 1 : const [publicURLText, setPublicURLText] = React.useState<string | null>(null);
39 1 : const [settings, setSettings] = React.useState<SessionExportSettings>({
40 1 : withCatalog: false,
41 1 : withLoginHint: true,
42 1 : preserveUUIDs: false,
43 1 : });
44 :
45 1 : React.useEffect(() => {
46 1 : if (!props.isOpen) {
47 1 : return;
48 : }
49 :
50 1 : let cancelled = false;
51 1 : setPublicURLText(null);
52 :
53 1 : async function generateURL() {
54 1 : let setupUrl: URL | null = null;
55 1 : if (sessionScripts != null && connectionIds != null) {
56 1 : const attached = resolveSessionConnections(databaseRegistry, references, sessionScripts.sessionId);
57 1 : if (attached != null) {
58 1 : const params = new Map();
59 1 : const connectionNames = new Map<string, string>();
60 1 : for (const database of attached) {
61 1 : const connectionParams = database.params;
62 1 : if (connectionParams != null) params.set(database.connectionId, connectionParams);
63 1 : if (database.name != null) connectionNames.set(database.connectionId, database.name);
64 1 : }
65 1 : setupUrl = await exportSessionAsUrl(
66 1 : storage.backend,
67 1 : sessionScripts.sessionId,
68 1 : params,
69 1 : SessionLinkTarget.WEB,
70 1 : { ...settings, connectionNames },
71 1 : );
72 1 : }
73 1 : }
74 :
75 1 : if (!cancelled) setPublicURLText(setupUrl?.toString() ?? null);
76 1 : }
77 :
78 1 : generateURL().catch(() => {
79 0 : if (!cancelled) setPublicURLText(null);
80 1 : });
81 :
82 1 : return () => {
83 1 : cancelled = true;
84 1 : };
85 1 : }, [databaseRegistry, references, settings, sessionScripts, connectionIds, props.isOpen]);
86 :
87 1 : const hasLoginHint = React.useMemo(
88 1 : () => (connections ?? []).some(connection => connectionParamsHaveLoginHint(getConnectionParamsFromStateDetails(connection.details))),
89 1 : [connections],
90 1 : );
91 :
92 1 : return (
93 1 : <AnchoredOverlay
94 1 : renderAnchor={props.anchorRef ? null : () => <div ref={anchorRef} />}
95 1 : open={props.isOpen}
96 1 : onClose={() => props.setIsOpen(false)}
97 1 : anchorRef={props.anchorRef ?? anchorRef}
98 1 : returnFocusRef={props.anchorRef}
99 1 : align={AnchorAlignment.End}
100 1 : overlayProps={{
101 1 : initialFocusRef: buttonRef,
102 1 : }}
103 : >
104 1 : <div className={classNames(styles.sharing_overlay, props.className)}>
105 1 : <ShareURLField url={publicURLText} ariaLabel="Session share URL" buttonRef={buttonRef} />
106 1 : <SessionExportSettingsView
107 1 : withCatalog={true}
108 1 : withLoginHint={hasLoginHint}
109 1 : settings={settings}
110 1 : setSettings={setSettings}
111 1 : />
112 1 : </div>
113 1 : </AnchoredOverlay>
114 : );
115 1 : };
|