From 0513b647ec13bc2688eaf75d41c2b45049f2624b Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:04:23 +0100 Subject: [PATCH] feat: change collab trigger & add share dialog (#7647) --- excalidraw-app/App.tsx | 88 ++++-- excalidraw-app/collab/Collab.tsx | 89 +++--- excalidraw-app/components/AppMainMenu.tsx | 4 +- .../components/AppWelcomeScreen.tsx | 4 +- .../ShareDialog.scss} | 27 +- excalidraw-app/share/ShareDialog.tsx | 290 ++++++++++++++++++ packages/excalidraw/assets/lock.svg | 20 -- packages/excalidraw/components/Button.tsx | 1 + .../excalidraw/components/FilledButton.scss | 1 + .../components/JSONExportDialog.tsx | 2 +- .../components/ShareableLinkDialog.scss | 4 +- packages/excalidraw/components/TextField.tsx | 17 +- .../LiveCollaborationTrigger.scss | 8 +- .../LiveCollaborationTrigger.tsx | 8 +- packages/excalidraw/css/variables.module.scss | 1 + packages/excalidraw/locales/en.json | 7 +- packages/excalidraw/types.ts | 1 - packages/excalidraw/utils.ts | 2 +- 18 files changed, 439 insertions(+), 135 deletions(-) rename excalidraw-app/{collab/RoomDialog.scss => share/ShareDialog.scss} (82%) create mode 100644 excalidraw-app/share/ShareDialog.tsx delete mode 100644 packages/excalidraw/assets/lock.svg diff --git a/excalidraw-app/App.tsx b/excalidraw-app/App.tsx index 4a3d42847..e38dd7a94 100644 --- a/excalidraw-app/App.tsx +++ b/excalidraw-app/App.tsx @@ -1,6 +1,6 @@ import polyfill from "../packages/excalidraw/polyfill"; import LanguageDetector from "i18next-browser-languagedetector"; -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { trackEvent } from "../packages/excalidraw/analytics"; import { getDefaultAppState } from "../packages/excalidraw/appState"; import { ErrorDialog } from "../packages/excalidraw/components/ErrorDialog"; @@ -54,7 +54,6 @@ import { import Collab, { CollabAPI, collabAPIAtom, - collabDialogShownAtom, isCollaboratingAtom, isOfflineAtom, } from "./collab/Collab"; @@ -104,6 +103,7 @@ import { ShareableLinkDialog } from "../packages/excalidraw/components/Shareable import { openConfirmModal } from "../packages/excalidraw/components/OverwriteConfirm/OverwriteConfirmState"; import { OverwriteConfirmDialog } from "../packages/excalidraw/components/OverwriteConfirm/OverwriteConfirm"; import Trans from "../packages/excalidraw/components/Trans"; +import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog"; polyfill(); @@ -305,8 +305,8 @@ const ExcalidrawWrapper = () => { const [excalidrawAPI, excalidrawRefCallback] = useCallbackRefState(); + const [, setShareDialogState] = useAtom(shareDialogStateAtom); const [collabAPI] = useAtom(collabAPIAtom); - const [, setCollabDialogShown] = useAtom(collabDialogShownAtom); const [isCollaborating] = useAtomWithInitialValue(isCollaboratingAtom, () => { return isCollaborationLink(window.location.href); }); @@ -607,37 +607,38 @@ const ExcalidrawWrapper = () => { exportedElements: readonly NonDeletedExcalidrawElement[], appState: Partial, files: BinaryFiles, - canvas: HTMLCanvasElement, ) => { if (exportedElements.length === 0) { throw new Error(t("alerts.cannotExportEmptyCanvas")); } - if (canvas) { - try { - const { url, errorMessage } = await exportToBackend( - exportedElements, - { - ...appState, - viewBackgroundColor: appState.exportBackground - ? appState.viewBackgroundColor - : getDefaultAppState().viewBackgroundColor, - }, - files, - ); + try { + const { url, errorMessage } = await exportToBackend( + exportedElements, + { + ...appState, + viewBackgroundColor: appState.exportBackground + ? appState.viewBackgroundColor + : getDefaultAppState().viewBackgroundColor, + }, + files, + ); - if (errorMessage) { - throw new Error(errorMessage); - } + if (errorMessage) { + throw new Error(errorMessage); + } - if (url) { - setLatestShareableLink(url); - } - } catch (error: any) { - if (error.name !== "AbortError") { - const { width, height } = canvas; - console.error(error, { width, height }); - throw new Error(error.message); - } + if (url) { + setLatestShareableLink(url); + } + } catch (error: any) { + if (error.name !== "AbortError") { + const { width, height } = appState; + console.error(error, { + width, + height, + devicePixelRatio: window.devicePixelRatio, + }); + throw new Error(error.message); } } }; @@ -666,6 +667,11 @@ const ExcalidrawWrapper = () => { const isOffline = useAtomValue(isOfflineAtom); + const onCollabDialogOpen = useCallback( + () => setShareDialogState({ isOpen: true, type: "collaborationOnly" }), + [setShareDialogState], + ); + // browsers generally prevent infinite self-embedding, there are // cases where it still happens, and while we disallow self-embedding // by not whitelisting our own origin, this serves as an additional guard @@ -741,18 +747,20 @@ const ExcalidrawWrapper = () => { return ( setCollabDialogShown(true)} + onSelect={() => + setShareDialogState({ isOpen: true, type: "share" }) + } /> ); }} > @@ -848,6 +856,24 @@ const ExcalidrawWrapper = () => { {excalidrawAPI && !isCollabDisabled && ( )} + + { + if (excalidrawAPI) { + try { + await onExportToBackend( + excalidrawAPI.getSceneElements(), + excalidrawAPI.getAppState(), + excalidrawAPI.getFiles(), + ); + } catch (error: any) { + setErrorMessage(error.message); + } + } + }} + /> + {errorMessage && ( setErrorMessage("")}> {errorMessage} diff --git a/excalidraw-app/collab/Collab.tsx b/excalidraw-app/collab/Collab.tsx index 267dee66c..14538b674 100644 --- a/excalidraw-app/collab/Collab.tsx +++ b/excalidraw-app/collab/Collab.tsx @@ -52,7 +52,6 @@ import { saveUsernameToLocalStorage, } from "../data/localStorage"; import Portal from "./Portal"; -import RoomDialog from "./RoomDialog"; import { t } from "../../packages/excalidraw/i18n"; import { UserIdleState } from "../../packages/excalidraw/types"; import { @@ -77,23 +76,24 @@ import { import { decryptData } from "../../packages/excalidraw/data/encryption"; import { resetBrowserStateVersions } from "../data/tabSync"; import { LocalData } from "../data/LocalData"; -import { atom, useAtom } from "jotai"; +import { atom } from "jotai"; import { appJotaiStore } from "../app-jotai"; import { Mutable, ValueOf } from "../../packages/excalidraw/utility-types"; import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds"; import { withBatchedUpdates } from "../../packages/excalidraw/reactUtils"; export const collabAPIAtom = atom(null); -export const collabDialogShownAtom = atom(false); export const isCollaboratingAtom = atom(false); export const isOfflineAtom = atom(false); interface CollabState { - errorMessage: string; + errorMessage: string | null; username: string; - activeRoomLink: string; + activeRoomLink: string | null; } +export const activeRoomLinkAtom = atom(null); + type CollabInstance = InstanceType; export interface CollabAPI { @@ -104,19 +104,20 @@ export interface CollabAPI { stopCollaboration: CollabInstance["stopCollaboration"]; syncElements: CollabInstance["syncElements"]; fetchImageFilesFromFirebase: CollabInstance["fetchImageFilesFromFirebase"]; - setUsername: (username: string) => void; + setUsername: CollabInstance["setUsername"]; + getUsername: CollabInstance["getUsername"]; + getActiveRoomLink: CollabInstance["getActiveRoomLink"]; + setErrorMessage: CollabInstance["setErrorMessage"]; } -interface PublicProps { +interface CollabProps { excalidrawAPI: ExcalidrawImperativeAPI; } -type Props = PublicProps & { modalIsShown: boolean }; - -class Collab extends PureComponent { +class Collab extends PureComponent { portal: Portal; fileManager: FileManager; - excalidrawAPI: Props["excalidrawAPI"]; + excalidrawAPI: CollabProps["excalidrawAPI"]; activeIntervalId: number | null; idleTimeoutId: number | null; @@ -124,12 +125,12 @@ class Collab extends PureComponent { private lastBroadcastedOrReceivedSceneVersion: number = -1; private collaborators = new Map(); - constructor(props: Props) { + constructor(props: CollabProps) { super(props); this.state = { - errorMessage: "", + errorMessage: null, username: importUsernameFromLocalStorage() || "", - activeRoomLink: "", + activeRoomLink: null, }; this.portal = new Portal(this); this.fileManager = new FileManager({ @@ -194,6 +195,9 @@ class Collab extends PureComponent { fetchImageFilesFromFirebase: this.fetchImageFilesFromFirebase, stopCollaboration: this.stopCollaboration, setUsername: this.setUsername, + getUsername: this.getUsername, + getActiveRoomLink: this.getActiveRoomLink, + setErrorMessage: this.setErrorMessage, }; appJotaiStore.set(collabAPIAtom, collabAPI); @@ -341,9 +345,7 @@ class Collab extends PureComponent { this.fileManager.reset(); if (!opts?.isUnload) { this.setIsCollaborating(false); - this.setState({ - activeRoomLink: "", - }); + this.setActiveRoomLink(null); this.collaborators = new Map(); this.excalidrawAPI.updateScene({ collaborators: this.collaborators, @@ -409,7 +411,7 @@ class Collab extends PureComponent { if (!this.state.username) { import("@excalidraw/random-username").then(({ getRandomUsername }) => { const username = getRandomUsername(); - this.onUsernameChange(username); + this.setUsername(username); }); } @@ -624,9 +626,7 @@ class Collab extends PureComponent { this.initializeIdleDetector(); - this.setState({ - activeRoomLink: window.location.href, - }); + this.setActiveRoomLink(window.location.href); return scenePromise; }; @@ -909,41 +909,31 @@ class Collab extends PureComponent { { leading: false }, ); - handleClose = () => { - appJotaiStore.set(collabDialogShownAtom, false); - }; - setUsername = (username: string) => { this.setState({ username }); + saveUsernameToLocalStorage(username); }; - onUsernameChange = (username: string) => { - this.setUsername(username); - saveUsernameToLocalStorage(username); + getUsername = () => this.state.username; + + setActiveRoomLink = (activeRoomLink: string | null) => { + this.setState({ activeRoomLink }); + appJotaiStore.set(activeRoomLinkAtom, activeRoomLink); }; - render() { - const { username, errorMessage, activeRoomLink } = this.state; + getActiveRoomLink = () => this.state.activeRoomLink; + + setErrorMessage = (errorMessage: string | null) => { + this.setState({ errorMessage }); + }; - const { modalIsShown } = this.props; + render() { + const { errorMessage } = this.state; return ( <> - {modalIsShown && ( - this.startCollaboration(null)} - onRoomDestroy={this.stopCollaboration} - setErrorMessage={(errorMessage) => { - this.setState({ errorMessage }); - }} - /> - )} - {errorMessage && ( - this.setState({ errorMessage: "" })}> + {errorMessage != null && ( + this.setState({ errorMessage: null })}> {errorMessage} )} @@ -962,11 +952,6 @@ if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) { window.collab = window.collab || ({} as Window["collab"]); } -const _Collab: React.FC = (props) => { - const [collabDialogShown] = useAtom(collabDialogShownAtom); - return ; -}; - -export default _Collab; +export default Collab; export type TCollabClass = Collab; diff --git a/excalidraw-app/components/AppMainMenu.tsx b/excalidraw-app/components/AppMainMenu.tsx index 34a2ee3ae..6806c969c 100644 --- a/excalidraw-app/components/AppMainMenu.tsx +++ b/excalidraw-app/components/AppMainMenu.tsx @@ -4,7 +4,7 @@ import { MainMenu } from "../../packages/excalidraw/index"; import { LanguageList } from "./LanguageList"; export const AppMainMenu: React.FC<{ - setCollabDialogShown: (toggle: boolean) => any; + onCollabDialogOpen: () => any; isCollaborating: boolean; isCollabEnabled: boolean; }> = React.memo((props) => { @@ -17,7 +17,7 @@ export const AppMainMenu: React.FC<{ {props.isCollabEnabled && ( props.setCollabDialogShown(true)} + onSelect={() => props.onCollabDialogOpen()} /> )} diff --git a/excalidraw-app/components/AppWelcomeScreen.tsx b/excalidraw-app/components/AppWelcomeScreen.tsx index a5176c2ff..f74bc14e2 100644 --- a/excalidraw-app/components/AppWelcomeScreen.tsx +++ b/excalidraw-app/components/AppWelcomeScreen.tsx @@ -6,7 +6,7 @@ import { isExcalidrawPlusSignedUser } from "../app_constants"; import { POINTER_EVENTS } from "../../packages/excalidraw/constants"; export const AppWelcomeScreen: React.FC<{ - setCollabDialogShown: (toggle: boolean) => any; + onCollabDialogOpen: () => any; isCollabEnabled: boolean; }> = React.memo((props) => { const { t } = useI18n(); @@ -52,7 +52,7 @@ export const AppWelcomeScreen: React.FC<{ {props.isCollabEnabled && ( props.setCollabDialogShown(true)} + onSelect={() => props.onCollabDialogOpen()} /> )} {!isExcalidrawPlusSignedUser && ( diff --git a/excalidraw-app/collab/RoomDialog.scss b/excalidraw-app/share/ShareDialog.scss similarity index 82% rename from excalidraw-app/collab/RoomDialog.scss rename to excalidraw-app/share/ShareDialog.scss index 61624664b..87fde8491 100644 --- a/excalidraw-app/collab/RoomDialog.scss +++ b/excalidraw-app/share/ShareDialog.scss @@ -1,7 +1,7 @@ @import "../../packages/excalidraw/css/variables.module.scss"; .excalidraw { - .RoomDialog { + .ShareDialog { display: flex; flex-direction: column; gap: 1.5rem; @@ -10,8 +10,25 @@ height: calc(100vh - 5rem); } + &__separator { + border-top: 1px solid var(--dialog-border-color); + text-align: center; + display: flex; + justify-content: center; + align-items: center; + margin-top: 1em; + + span { + background: var(--island-bg-color); + padding: 0px 0.75rem; + transform: translateY(-1ch); + display: inline-flex; + line-height: 1; + } + } + &__popover { - @keyframes RoomDialog__popover__scaleIn { + @keyframes ShareDialog__popover__scaleIn { from { opacity: 0; } @@ -50,10 +67,10 @@ } transform-origin: var(--radix-popover-content-transform-origin); - animation: RoomDialog__popover__scaleIn 150ms ease-out; + animation: ShareDialog__popover__scaleIn 150ms ease-out; } - &__inactive { + &__picker { font-family: "Assistant"; &__illustration { @@ -95,7 +112,7 @@ } } - &__start_session { + &__button { display: flex; align-items: center; diff --git a/excalidraw-app/share/ShareDialog.tsx b/excalidraw-app/share/ShareDialog.tsx new file mode 100644 index 000000000..2fa92dff8 --- /dev/null +++ b/excalidraw-app/share/ShareDialog.tsx @@ -0,0 +1,290 @@ +import { 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 { activeRoomLinkAtom, CollabAPI } from "../collab/Collab"; +import { atom, useAtom, useAtomValue } from "jotai"; + +import "./ShareDialog.scss"; + +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); + + setJustCopied(true); + + if (timerRef.current) { + window.clearTimeout(timerRef.current); + } + + timerRef.current = window.setTimeout(() => { + setJustCopied(false); + }, 3000); + } catch (error: any) { + collabAPI.setErrorMessage(error.message); + } + + 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); + + if (!shareDialogState.isOpen) { + return null; + } + + return ( + setShareDialogState({ isOpen: false })} + collabAPI={props.collabAPI} + onExportToBackend={props.onExportToBackend} + type={shareDialogState.type} + > + ); +}; diff --git a/packages/excalidraw/assets/lock.svg b/packages/excalidraw/assets/lock.svg deleted file mode 100644 index aa9dbf170..000000000 --- a/packages/excalidraw/assets/lock.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/packages/excalidraw/components/Button.tsx b/packages/excalidraw/components/Button.tsx index 43b6de9e1..779cee582 100644 --- a/packages/excalidraw/components/Button.tsx +++ b/packages/excalidraw/components/Button.tsx @@ -1,4 +1,5 @@ import clsx from "clsx"; +import React from "react"; import { composeEventHandlers } from "../utils"; import "./Button.scss"; diff --git a/packages/excalidraw/components/FilledButton.scss b/packages/excalidraw/components/FilledButton.scss index 5891698e8..70f75cbbb 100644 --- a/packages/excalidraw/components/FilledButton.scss +++ b/packages/excalidraw/components/FilledButton.scss @@ -24,6 +24,7 @@ } } + &, &__contents { display: flex; justify-content: center; diff --git a/packages/excalidraw/components/JSONExportDialog.tsx b/packages/excalidraw/components/JSONExportDialog.tsx index b5cea4af6..95f4117fc 100644 --- a/packages/excalidraw/components/JSONExportDialog.tsx +++ b/packages/excalidraw/components/JSONExportDialog.tsx @@ -78,7 +78,7 @@ const JSONExportModal = ({ onClick={async () => { try { trackEvent("export", "link", `ui (${getFrame()})`); - await onExportToBackend(elements, appState, files, canvas); + await onExportToBackend(elements, appState, files); onCloseRequest(); } catch (error: any) { setAppState({ errorMessage: error.message }); diff --git a/packages/excalidraw/components/ShareableLinkDialog.scss b/packages/excalidraw/components/ShareableLinkDialog.scss index 2b89f09d6..2429d50ca 100644 --- a/packages/excalidraw/components/ShareableLinkDialog.scss +++ b/packages/excalidraw/components/ShareableLinkDialog.scss @@ -22,7 +22,7 @@ } &__popover { - @keyframes RoomDialog__popover__scaleIn { + @keyframes ShareableLinkDialog__popover__scaleIn { from { opacity: 0; } @@ -61,7 +61,7 @@ } transform-origin: var(--radix-popover-content-transform-origin); - animation: RoomDialog__popover__scaleIn 150ms ease-out; + animation: ShareableLinkDialog__popover__scaleIn 150ms ease-out; } &__linkRow { diff --git a/packages/excalidraw/components/TextField.tsx b/packages/excalidraw/components/TextField.tsx index 10b3d9b53..44a7c25ff 100644 --- a/packages/excalidraw/components/TextField.tsx +++ b/packages/excalidraw/components/TextField.tsx @@ -13,8 +13,6 @@ import { Button } from "./Button"; import { eyeIcon, eyeClosedIcon } from "./icons"; type TextFieldProps = { - value?: string; - onChange?: (value: string) => void; onClick?: () => void; onKeyDown?: (event: KeyboardEvent) => void; @@ -26,12 +24,11 @@ type TextFieldProps = { label?: string; placeholder?: string; isRedacted?: boolean; -}; +} & ({ value: string } | { defaultValue: string }); export const TextField = forwardRef( ( { - value, onChange, label, fullWidth, @@ -40,6 +37,7 @@ export const TextField = forwardRef( selectOnRender, onKeyDown, isRedacted = false, + ...rest }, ref, ) => { @@ -73,10 +71,17 @@ export const TextField = forwardRef( > onChange?.(event.target.value)} diff --git a/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.scss b/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.scss index edbcf198f..573fbccce 100644 --- a/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.scss +++ b/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.scss @@ -3,7 +3,7 @@ .excalidraw { .collab-button { --button-bg: var(--color-primary); - --button-color: white; + --button-color: var(--color-surface-lowest); --button-border: var(--color-primary); --button-width: var(--lg-button-size); @@ -35,12 +35,6 @@ } } - &.theme--dark { - .collab-button { - color: var(--color-gray-90); - } - } - .CollabButton.is-collaborating { background-color: var(--button-special-active-bg-color); diff --git a/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.tsx b/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.tsx index 3111680cb..a22bc523a 100644 --- a/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.tsx +++ b/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.tsx @@ -1,5 +1,5 @@ import { t } from "../../i18n"; -import { usersIcon } from "../icons"; +import { share } from "../icons"; import { Button } from "../Button"; import clsx from "clsx"; @@ -17,16 +17,18 @@ const LiveCollaborationTrigger = ({ } & React.ButtonHTMLAttributes) => { const appState = useUIAppState(); + const showIconOnly = appState.width < 830; + return ( . Please include information below by copying and pasting into the GitHub issue.", "sceneContent": "Scene content:" }, + "shareDialog": { + "or": "Or" + }, "roomDialog": { - "desc_intro": "You can invite people to your current scene to collaborate with you.", - "desc_privacy": "Don't worry, the session uses end-to-end encryption, so whatever you draw will stay private. Not even our server will be able to see what you come up with.", + "desc_intro": "Invite people to collaborate on your drawing.", + "desc_privacy": "Don't worry, the session is end-to-end encrypted, and fully private. Not even our server can see what you draw.", "button_startSession": "Start session", "button_stopSession": "Stop session", "desc_inProgressIntro": "Live-collaboration session is now in progress.", diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index e29bb9f89..89b121b2f 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -495,7 +495,6 @@ export type ExportOpts = { exportedElements: readonly NonDeletedExcalidrawElement[], appState: UIAppState, files: BinaryFiles, - canvas: HTMLCanvasElement, ) => void; renderCustomUI?: ( exportedElements: readonly NonDeletedExcalidrawElement[], diff --git a/packages/excalidraw/utils.ts b/packages/excalidraw/utils.ts index 47fa52311..525652e6b 100644 --- a/packages/excalidraw/utils.ts +++ b/packages/excalidraw/utils.ts @@ -845,7 +845,7 @@ export const composeEventHandlers = ( if ( !checkForDefaultPrevented || - !(event as unknown as Event).defaultPrevented + !(event as unknown as Event)?.defaultPrevented ) { return ourEventHandler?.(event); }