import { useEffect, useRef, useState } from "react"; import * as Popover from "@radix-ui/react-popover"; import { copyTextToSystemClipboard } from "../../packages/excalidraw/clipboard"; import { trackEvent } from "../../packages/excalidraw/analytics"; import { getFrame } from "../../packages/excalidraw/utils"; import { useI18n } from "../../packages/excalidraw/i18n"; import { KEYS } from "../../packages/excalidraw/keys"; import { Dialog } from "../../packages/excalidraw/components/Dialog"; import { copyIcon, LinkIcon, playerPlayIcon, playerStopFilledIcon, share, shareIOS, shareWindows, tablerCheckIcon, } from "../../packages/excalidraw/components/icons"; import { TextField } from "../../packages/excalidraw/components/TextField"; import { FilledButton } from "../../packages/excalidraw/components/FilledButton"; import type { CollabAPI } from "../collab/Collab"; import { activeRoomLinkAtom } from "../collab/Collab"; import { atom, useAtom, useAtomValue } from "jotai"; import "./ShareDialog.scss"; import { useUIAppState } from "../../packages/excalidraw/context/ui-appState"; type OnExportToBackend = () => void; type ShareDialogType = "share" | "collaborationOnly"; export const shareDialogStateAtom = atom< { isOpen: false } | { isOpen: true; type: ShareDialogType } >({ isOpen: false }); const getShareIcon = () => { const navigator = window.navigator as any; const isAppleBrowser = /Apple/.test(navigator.vendor); const isWindowsBrowser = navigator.appVersion.indexOf("Win") !== -1; if (isAppleBrowser) { return shareIOS; } else if (isWindowsBrowser) { return shareWindows; } return share; }; export type ShareDialogProps = { collabAPI: CollabAPI | null; handleClose: () => void; onExportToBackend: OnExportToBackend; type: ShareDialogType; }; const ActiveRoomDialog = ({ collabAPI, activeRoomLink, handleClose, }: { collabAPI: CollabAPI; activeRoomLink: string; handleClose: () => void; }) => { const { t } = useI18n(); const [justCopied, setJustCopied] = useState(false); const timerRef = useRef(0); const ref = useRef(null); const isShareSupported = "share" in navigator; const copyRoomLink = async () => { try { await copyTextToSystemClipboard(activeRoomLink); } catch (e) { collabAPI.setCollabError(t("errors.copyToSystemClipboardFailed")); } setJustCopied(true); if (timerRef.current) { window.clearTimeout(timerRef.current); } timerRef.current = window.setTimeout(() => { setJustCopied(false); }, 3000); ref.current?.select(); }; const shareRoomLink = async () => { try { await navigator.share({ title: t("roomDialog.shareTitle"), text: t("roomDialog.shareTitle"), url: activeRoomLink, }); } catch (error: any) { // Just ignore. } }; return ( <>

{t("labels.liveCollaboration").replace(/\./g, "")}

event.key === KEYS.ENTER && handleClose()} />
{isShareSupported && ( )} event.preventDefault()} onCloseAutoFocus={(event) => event.preventDefault()} className="ShareDialog__popover" side="top" align="end" sideOffset={5.5} > {tablerCheckIcon} copied

{t("roomDialog.desc_privacy")}

{t("roomDialog.desc_exitSession")}

{ trackEvent("share", "room closed"); collabAPI.stopCollaboration(); if (!collabAPI.isCollaborating()) { handleClose(); } }} />
); }; const ShareDialogPicker = (props: ShareDialogProps) => { const { t } = useI18n(); const { collabAPI } = props; const startCollabJSX = collabAPI ? ( <>
{t("labels.liveCollaboration").replace(/\./g, "")}
{t("roomDialog.desc_intro")}
{t("roomDialog.desc_privacy")}
{ trackEvent("share", "room creation", `ui (${getFrame()})`); collabAPI.startCollaboration(null); }} />
{props.type === "share" && (
{t("shareDialog.or")}
)} ) : null; return ( <> {startCollabJSX} {props.type === "share" && ( <>
{t("exportDialog.link_title")}
{t("exportDialog.link_details")}
{ await props.onExportToBackend(); props.handleClose(); }} />
)} ); }; const ShareDialogInner = (props: ShareDialogProps) => { const activeRoomLink = useAtomValue(activeRoomLinkAtom); return (
{props.collabAPI && activeRoomLink ? ( ) : ( )}
); }; export const ShareDialog = (props: { collabAPI: CollabAPI | null; onExportToBackend: OnExportToBackend; }) => { const [shareDialogState, setShareDialogState] = useAtom(shareDialogStateAtom); const { openDialog } = useUIAppState(); useEffect(() => { if (openDialog) { setShareDialogState({ isOpen: false }); } }, [openDialog, setShareDialogState]); if (!shareDialogState.isOpen) { return null; } return ( setShareDialogState({ isOpen: false })} collabAPI={props.collabAPI} onExportToBackend={props.onExportToBackend} type={shareDialogState.type} /> ); };