RoomDialog.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import { useRef, useState } from "react";
  2. import * as Popover from "@radix-ui/react-popover";
  3. import { copyTextToSystemClipboard } from "../../packages/excalidraw/clipboard";
  4. import { trackEvent } from "../../packages/excalidraw/analytics";
  5. import { getFrame } from "../../packages/excalidraw/utils";
  6. import { useI18n } from "../../packages/excalidraw/i18n";
  7. import { KEYS } from "../../packages/excalidraw/keys";
  8. import { Dialog } from "../../packages/excalidraw/components/Dialog";
  9. import {
  10. copyIcon,
  11. playerPlayIcon,
  12. playerStopFilledIcon,
  13. share,
  14. shareIOS,
  15. shareWindows,
  16. tablerCheckIcon,
  17. } from "../../packages/excalidraw/components/icons";
  18. import { TextField } from "../../packages/excalidraw/components/TextField";
  19. import { FilledButton } from "../../packages/excalidraw/components/FilledButton";
  20. import { ReactComponent as CollabImage } from "../../packages/excalidraw/assets/lock.svg";
  21. import "./RoomDialog.scss";
  22. const getShareIcon = () => {
  23. const navigator = window.navigator as any;
  24. const isAppleBrowser = /Apple/.test(navigator.vendor);
  25. const isWindowsBrowser = navigator.appVersion.indexOf("Win") !== -1;
  26. if (isAppleBrowser) {
  27. return shareIOS;
  28. } else if (isWindowsBrowser) {
  29. return shareWindows;
  30. }
  31. return share;
  32. };
  33. export type RoomModalProps = {
  34. handleClose: () => void;
  35. activeRoomLink: string;
  36. username: string;
  37. onUsernameChange: (username: string) => void;
  38. onRoomCreate: () => void;
  39. onRoomDestroy: () => void;
  40. setErrorMessage: (message: string) => void;
  41. };
  42. export const RoomModal = ({
  43. activeRoomLink,
  44. onRoomCreate,
  45. onRoomDestroy,
  46. setErrorMessage,
  47. username,
  48. onUsernameChange,
  49. handleClose,
  50. }: RoomModalProps) => {
  51. const { t } = useI18n();
  52. const [justCopied, setJustCopied] = useState(false);
  53. const timerRef = useRef<number>(0);
  54. const ref = useRef<HTMLInputElement>(null);
  55. const isShareSupported = "share" in navigator;
  56. const copyRoomLink = async () => {
  57. try {
  58. await copyTextToSystemClipboard(activeRoomLink);
  59. setJustCopied(true);
  60. if (timerRef.current) {
  61. window.clearTimeout(timerRef.current);
  62. }
  63. timerRef.current = window.setTimeout(() => {
  64. setJustCopied(false);
  65. }, 3000);
  66. } catch (error: any) {
  67. setErrorMessage(error.message);
  68. }
  69. ref.current?.select();
  70. };
  71. const shareRoomLink = async () => {
  72. try {
  73. await navigator.share({
  74. title: t("roomDialog.shareTitle"),
  75. text: t("roomDialog.shareTitle"),
  76. url: activeRoomLink,
  77. });
  78. } catch (error: any) {
  79. // Just ignore.
  80. }
  81. };
  82. if (activeRoomLink) {
  83. return (
  84. <>
  85. <h3 className="RoomDialog__active__header">
  86. {t("labels.liveCollaboration")}
  87. </h3>
  88. <TextField
  89. value={username}
  90. placeholder="Your name"
  91. label="Your name"
  92. onChange={onUsernameChange}
  93. onKeyDown={(event) => event.key === KEYS.ENTER && handleClose()}
  94. />
  95. <div className="RoomDialog__active__linkRow">
  96. <TextField
  97. ref={ref}
  98. label="Link"
  99. readonly
  100. fullWidth
  101. value={activeRoomLink}
  102. />
  103. {isShareSupported && (
  104. <FilledButton
  105. size="large"
  106. variant="icon"
  107. label="Share"
  108. startIcon={getShareIcon()}
  109. className="RoomDialog__active__share"
  110. onClick={shareRoomLink}
  111. />
  112. )}
  113. <Popover.Root open={justCopied}>
  114. <Popover.Trigger asChild>
  115. <FilledButton
  116. size="large"
  117. label="Copy link"
  118. startIcon={copyIcon}
  119. onClick={copyRoomLink}
  120. />
  121. </Popover.Trigger>
  122. <Popover.Content
  123. onOpenAutoFocus={(event) => event.preventDefault()}
  124. onCloseAutoFocus={(event) => event.preventDefault()}
  125. className="RoomDialog__popover"
  126. side="top"
  127. align="end"
  128. sideOffset={5.5}
  129. >
  130. {tablerCheckIcon} copied
  131. </Popover.Content>
  132. </Popover.Root>
  133. </div>
  134. <div className="RoomDialog__active__description">
  135. <p>
  136. <span
  137. role="img"
  138. aria-hidden="true"
  139. className="RoomDialog__active__description__emoji"
  140. >
  141. 🔒{" "}
  142. </span>
  143. {t("roomDialog.desc_privacy")}
  144. </p>
  145. <p>{t("roomDialog.desc_exitSession")}</p>
  146. </div>
  147. <div className="RoomDialog__active__actions">
  148. <FilledButton
  149. size="large"
  150. variant="outlined"
  151. color="danger"
  152. label={t("roomDialog.button_stopSession")}
  153. startIcon={playerStopFilledIcon}
  154. onClick={() => {
  155. trackEvent("share", "room closed");
  156. onRoomDestroy();
  157. }}
  158. />
  159. </div>
  160. </>
  161. );
  162. }
  163. return (
  164. <>
  165. <div className="RoomDialog__inactive__illustration">
  166. <CollabImage />
  167. </div>
  168. <div className="RoomDialog__inactive__header">
  169. {t("labels.liveCollaboration")}
  170. </div>
  171. <div className="RoomDialog__inactive__description">
  172. <strong>{t("roomDialog.desc_intro")}</strong>
  173. {t("roomDialog.desc_privacy")}
  174. </div>
  175. <div className="RoomDialog__inactive__start_session">
  176. <FilledButton
  177. size="large"
  178. label={t("roomDialog.button_startSession")}
  179. startIcon={playerPlayIcon}
  180. onClick={() => {
  181. trackEvent("share", "room creation", `ui (${getFrame()})`);
  182. onRoomCreate();
  183. }}
  184. />
  185. </div>
  186. </>
  187. );
  188. };
  189. const RoomDialog = (props: RoomModalProps) => {
  190. return (
  191. <Dialog size="small" onCloseRequest={props.handleClose} title={false}>
  192. <div className="RoomDialog">
  193. <RoomModal {...props} />
  194. </div>
  195. </Dialog>
  196. );
  197. };
  198. export default RoomDialog;