diff --git a/package.json b/package.json index 8f27edb95a84..b70462ba7c0c 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "prettier": "@excalidraw/prettier-config", "private": true, "scripts": { + "setup": "yarn && cd src/packages/excalidraw && yarn", "build-node": "node ./scripts/build-node.js", "build:app:docker": "REACT_APP_DISABLE_SENTRY=true react-scripts build", "build:app": "REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build", @@ -112,6 +113,8 @@ "test:typecheck": "tsc", "test:update": "yarn test:app --updateSnapshot --watchAll=false", "test": "yarn test:app", - "autorelease": "node scripts/autorelease.js" + "autorelease": "node scripts/autorelease.js", + "pull": "git pull upstream master --rebase", + "publish": "cd src/packages/excalidraw && npm run build:umd && yarn publish" } } diff --git a/src/actions/manager.tsx b/src/actions/manager.tsx index 7481d56a35c0..d060cec4c283 100644 --- a/src/actions/manager.tsx +++ b/src/actions/manager.tsx @@ -83,7 +83,7 @@ export class ActionManager { .sort((a, b) => (b.keyPriority || 0) - (a.keyPriority || 0)) .filter( (action) => - (action.name in canvasActions + (canvasActions && action.name in canvasActions ? canvasActions[action.name as keyof typeof canvasActions] : true) && action.keyTest && @@ -141,7 +141,7 @@ export class ActionManager { if ( this.actions[name] && "PanelComponent" in this.actions[name] && - (name in canvasActions + (canvasActions && name in canvasActions ? canvasActions[name as keyof typeof canvasActions] : true) ) { diff --git a/src/components/App.tsx b/src/components/App.tsx index ec25b12909c5..55701a6efd8f 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -165,7 +165,10 @@ import { } from "../keys"; import { distance2d, getGridPoint, isPathALoop } from "../math"; import { renderScene } from "../renderer"; -import { invalidateShapeForElement } from "../renderer/renderElement"; +import { + clearRenderCache, + invalidateShapeForElement, +} from "../renderer/renderElement"; import { calculateScrollCenter, getTextBindableContainerAtPosition, @@ -267,7 +270,7 @@ const DeviceTypeContext = React.createContext(defaultDeviceTypeContext); export const useDeviceType = () => useContext(DeviceTypeContext); const ExcalidrawContainerContext = React.createContext<{ container: HTMLDivElement | null; - id: string | null; + id?: string | null; }>({ container: null, id: null }); export const useExcalidrawContainer = () => useContext(ExcalidrawContainerContext); @@ -385,6 +388,7 @@ class App extends React.Component { setActiveTool: this.setActiveTool, setCursor: this.setCursor, resetCursor: this.resetCursor, + app: this, } as const; if (typeof excalidrawRef === "function") { excalidrawRef(api); @@ -484,6 +488,7 @@ class App extends React.Component { return (
{ > { } showThemeBtn={ typeof this.props?.theme === "undefined" && + this.props.UIOptions.canvasActions && this.props.UIOptions.canvasActions.theme } libraryReturnUrl={this.props.libraryReturnUrl} @@ -787,6 +794,13 @@ class App extends React.Component { }; } + if (initialData?.scrollX != null) { + scene.appState.scrollX = initialData.scrollX; + } + if (initialData?.scrollY != null) { + scene.appState.scrollY = initialData.scrollY; + } + this.resetHistory(); this.syncActionResult({ ...scene, @@ -882,6 +896,25 @@ class App extends React.Component { this.unmounted = true; this.removeEventListeners(); this.scene.destroy(); + clearRenderCache(); + + this.scene = new Scene(); + this.history = new History(); + this.actionManager = new ActionManager( + this.syncActionResult, + () => this.state, + () => this.scene.getElementsIncludingDeleted(), + this, + ); + this.library = new Library(this); + this.canvas = null; + this.rc = null; + + // @ts-ignore + this.excalidrawContainerRef.current = undefined; + this.nearestScrollableContainer = undefined; + this.excalidrawContainerValue = { container: null, id: "unmounted" }; + clearTimeout(touchTimeout); touchTimeout = 0; } @@ -922,6 +955,7 @@ class App extends React.Component { this.disableEvent, false, ); + document.fonts?.removeEventListener?.("loadingdone", this.onFontLoaded); document.removeEventListener( EVENT.GESTURE_START, @@ -1059,6 +1093,15 @@ class App extends React.Component { }); } + if ( + !this.props.UIOptions.canvasActions && + this.state.openMenu === "canvas" + ) { + this.setState({ + openMenu: null, + }); + } + if (this.props.name && prevProps.name !== this.props.name) { this.setState({ name: this.props.name, @@ -1205,6 +1248,7 @@ class App extends React.Component { this.scene.getElementsIncludingDeleted(), this.state, this.files, + this.props.id, ); } } diff --git a/src/components/Avatar.scss b/src/components/Avatar.scss index d3f8c8bd309a..0a27c45b4736 100644 --- a/src/components/Avatar.scss +++ b/src/components/Avatar.scss @@ -4,7 +4,7 @@ .Avatar { width: 2.5rem; height: 2.5rem; - border-radius: 1.25rem; + border-radius: 50%; display: flex; justify-content: center; align-items: center; diff --git a/src/components/Island.scss b/src/components/Island.scss index 499d13ad3d51..95e8e82b7006 100644 --- a/src/components/Island.scss +++ b/src/components/Island.scss @@ -6,10 +6,49 @@ border-radius: var(--border-radius-lg); padding: calc(var(--padding) * var(--space-factor)); position: relative; - transition: box-shadow 0.5s ease-in-out; &.zen-mode { box-shadow: none; } + + &::-webkit-scrollbar { + width: 10px; + } + + &::-webkit-scrollbar-track { + background-color: transparent; + } + + &::-webkit-scrollbar-thumb { + background-color: var(--color-scrollbar-thumb); + } + &::-webkit-scrollbar-thumb:hover { + background-color: var(--color-scrollbar-thumb-hover); + } + + &::-webkit-scrollbar-thumb:active { + background-color: var(--color-scrollbar-thumb-active); + } + } + + .App-menu_top { + .Stack_vertical { + .Island { + min-width: 216px; + } + .Stack_horizontal { + justify-content: center !important; + } + } + } + + &.excalidraw--view-mode { + .App-menu_top { + .Stack_vertical { + .Island { + min-width: auto; + } + } + } } } diff --git a/src/components/JSONExportDialog.tsx b/src/components/JSONExportDialog.tsx index 5f29360d6805..048994f3e7c2 100644 --- a/src/components/JSONExportDialog.tsx +++ b/src/components/JSONExportDialog.tsx @@ -2,9 +2,9 @@ import React, { useState } from "react"; import { NonDeletedExcalidrawElement } from "../element/types"; import { t } from "../i18n"; import { useDeviceType } from "./App"; -import { AppState, ExportOpts, BinaryFiles } from "../types"; +import { AppState, CanvasActions, ExportOpts, BinaryFiles } from "../types"; import { Dialog } from "./Dialog"; -import { exportFile, exportToFileIcon, link } from "./icons"; +import { exportToFileIcon, link } from "./icons"; import { ToolButton } from "./ToolButton"; import { actionSaveFileToDisk } from "../actions/actionExport"; import { Card } from "./Card"; @@ -98,7 +98,7 @@ export const JSONExportDialog = ({ appState: AppState; files: BinaryFiles; actionManager: ActionManager; - exportOpts: ExportOpts; + exportOpts: CanvasActions["export"]; canvas: HTMLCanvasElement | null; }) => { const [modalIsShown, setModalIsShown] = useState(false); @@ -111,16 +111,20 @@ export const JSONExportDialog = ({ <> { - setModalIsShown(true); + if (typeof exportOpts === "function") { + actionManager.executeAction(actionSaveFileToDisk); + } else { + setModalIsShown(true); + } }} data-testid="json-export-button" - icon={exportFile} + icon={exportToFileIcon} type="button" aria-label={t("buttons.export")} showAriaLabel={useDeviceType().isMobile} title={t("buttons.export")} /> - {modalIsShown && ( + {modalIsShown && exportOpts && typeof exportOpts !== "function" && ( void; actionManager: ActionManager; appState: AppState; files: BinaryFiles; @@ -59,6 +59,7 @@ interface LayerUIProps { renderTopRightUI?: ( isMobile: boolean, appState: AppState, + canvas: HTMLCanvasElement | null, ) => JSX.Element | null; renderCustomFooter?: (isMobile: boolean, appState: AppState) => JSX.Element; viewModeEnabled: boolean; @@ -71,6 +72,7 @@ interface LayerUIProps { } const LayerUI = ({ + onHomeButtonClick, actionManager, appState, files, @@ -99,7 +101,7 @@ const LayerUI = ({ const deviceType = useDeviceType(); const renderJSONExportDialog = () => { - if (!UIOptions.canvasActions.export) { + if (!UIOptions.canvasActions || !UIOptions.canvasActions.export) { return null; } @@ -116,7 +118,7 @@ const LayerUI = ({ }; const renderImageExportDialog = () => { - if (!UIOptions.canvasActions.saveAsImage) { + if (!UIOptions.canvasActions || !UIOptions.canvasActions.saveAsImage) { return null; } @@ -163,10 +165,6 @@ const LayerUI = ({ ); }; - const Separator = () => { - return
; - }; - const renderViewModeCanvasActions = () => { return (
- + {actionManager.renderAction("clearCanvas")} - {actionManager.renderAction("loadScene")} {renderJSONExportDialog()} {renderImageExportDialog()} - {onCollabButtonClick && ( - {viewModeEnabled - ? renderViewModeCanvasActions() - : renderCanvasActions()} + {UIOptions.canvasActions && + (viewModeEnabled + ? renderViewModeCanvasActions() + : renderCanvasActions())} {shouldRenderSelectedShapeActions && renderSelectedShapeActions()} {!viewModeEnabled && ( @@ -377,24 +374,16 @@ const LayerUI = ({ }, )} > - - {appState.collaborators.size > 0 && - Array.from(appState.collaborators) - // Collaborator is either not initialized or is actually the current user. - .filter(([_, client]) => Object.keys(client).length !== 0) - .map(([clientId, client]) => ( - - {actionManager.renderAction("goToCollaborator", { - id: clientId, - })} - - ))} - - {renderTopRightUI?.(deviceType.isMobile, appState)} + {renderTopRightUI?.(deviceType.isMobile, appState, canvas)}
+
); @@ -527,6 +516,7 @@ const LayerUI = ({ <> {dialogs} ) : ( @@ -588,6 +579,7 @@ const areEqual = (prev: LayerUIProps, next: LayerUIProps) => { const keys = Object.keys(prevAppState) as (keyof Partial)[]; return ( + prev.renderTopRightUI === next.renderTopRightUI && prev.renderCustomFooter === next.renderCustomFooter && prev.langCode === next.langCode && prev.elements === next.elements && diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index 9b40a437c460..ff24061e3125 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { AppState } from "../types"; +import { AppProps, AppState } from "../types"; import { ActionManager } from "../actions/manager"; import { t } from "../i18n"; import Stack from "./Stack"; @@ -7,7 +7,6 @@ import { showSelectedShapeActions } from "../element"; import { NonDeletedExcalidrawElement } from "../element/types"; import { FixedSideContainer } from "./FixedSideContainer"; import { Island } from "./Island"; -import { HintViewer } from "./HintViewer"; import { calculateScrollCenter, getSelectedElements } from "../scene"; import { SelectedShapeActions, ShapesSwitcher } from "./Actions"; import { Section } from "./Section"; @@ -18,8 +17,11 @@ import { UserList } from "./UserList"; import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle"; import { LibraryButton } from "./LibraryButton"; import { PenModeButton } from "./PenModeButton"; +import { ToolButton } from "./ToolButton"; +import { home } from "./icons"; type MobileMenuProps = { + onHomeButtonClick?: () => void; appState: AppState; actionManager: ActionManager; renderJSONExportDialog: () => React.ReactNode; @@ -39,10 +41,13 @@ type MobileMenuProps = { renderTopRightUI?: ( isMobile: boolean, appState: AppState, + canvas: HTMLCanvasElement | null, ) => JSX.Element | null; + UIOptions: AppProps["UIOptions"]; }; export const MobileMenu = ({ + onHomeButtonClick, appState, elements, libraryMenu, @@ -60,10 +65,13 @@ export const MobileMenu = ({ showThemeBtn, onImageAction, renderTopRightUI, + UIOptions, }: MobileMenuProps) => { const renderToolbar = () => { return ( + {/* placeholder for grid 3-column template */} +
{(heading) => ( @@ -84,7 +92,15 @@ export const MobileMenu = ({ /> - {renderTopRightUI && renderTopRightUI(true, appState)} + )}
- +
+ {renderTopRightUI?.(true, appState, canvas)} +
); }; @@ -121,16 +139,19 @@ export const MobileMenu = ({ getSelectedElements(elements, appState).length === 0; if (viewModeEnabled) { - return ( + return UIOptions.canvasActions ? (
{actionManager.renderAction("toggleCanvasMenu")}
+ ) : ( +
Excalidraw
); } return (
- {actionManager.renderAction("toggleCanvasMenu")} + {UIOptions.canvasActions && + actionManager.renderAction("toggleCanvasMenu")} {actionManager.renderAction("toggleEditMenu")} {actionManager.renderAction("undo")} @@ -190,7 +211,7 @@ export const MobileMenu = ({ }} > - {appState.openMenu === "canvas" ? ( + {UIOptions.canvasActions && appState.openMenu === "canvas" ? (
@@ -199,20 +220,12 @@ export const MobileMenu = ({ {appState.collaborators.size > 0 && (
{t("labels.collaborators")} - - {Array.from(appState.collaborators) - // Collaborator is either not initialized or is actually the current user. - .filter( - ([_, client]) => Object.keys(client).length !== 0, - ) - .map(([clientId, client]) => ( - - {actionManager.renderAction("goToCollaborator", { - id: clientId, - })} - - ))} - +
)}
diff --git a/src/components/PublishLibrary.tsx b/src/components/PublishLibrary.tsx index 37543b6b063a..5491db14d856 100644 --- a/src/components/PublishLibrary.tsx +++ b/src/components/PublishLibrary.tsx @@ -225,10 +225,13 @@ const PublishLibrary = ({ formData.append("twitterHandle", libraryData.twitterHandle); formData.append("website", libraryData.website); - fetch(`${process.env.REACT_APP_LIBRARY_BACKEND}/submit`, { - method: "post", - body: formData, - }) + fetch( + `https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries/submit`, + { + method: "post", + body: formData, + }, + ) .then( (response) => { if (response.ok) { diff --git a/src/components/ToolIcon.scss b/src/components/ToolIcon.scss index 7057110c9f33..0f40a3119de4 100644 --- a/src/components/ToolIcon.scss +++ b/src/components/ToolIcon.scss @@ -187,7 +187,7 @@ .ToolIcon.ToolIcon_type_floating { display: inline-block; position: absolute; - right: -8px; + right: calc(-2 * var(--space-factor)); margin-left: 0; border-radius: 20px 0 0 20px; diff --git a/src/components/UserList.scss b/src/components/UserList.scss index 139d1b2dc09b..feadde6f6b39 100644 --- a/src/components/UserList.scss +++ b/src/components/UserList.scss @@ -1,21 +1,50 @@ .excalidraw { + $marginTop: 60px; + // eye-balled + $bottomOffset: 60px; + .UserList { pointer-events: none; /*github corner*/ - padding: var(--space-factor) var(--space-factor) var(--space-factor) - var(--space-factor); + padding: var(--space-factor); display: flex; - flex-wrap: wrap; justify-content: flex-end; + overflow: hidden; + border-radius: 60px; + &:empty { display: none; } + + &.layout-vertical { + grid-column: 3; + flex-direction: column-reverse; + position: absolute; + top: $marginTop; + right: var(--space-factor); + max-height: calc( + 100vh - var(--space-factor) - #{$marginTop} - #{$bottomOffset} + + var(--itemOffset) + ); + + .Avatar { + width: 2.4rem; + height: 2.4rem; + } + + padding-bottom: max(calc(var(--itemOffset) * -1), 0px); + } } .UserList > * { pointer-events: all; - margin: 0 0 var(--space-factor) var(--space-factor); + } + .UserList.layout-vertical > * { + margin-bottom: var(--itemOffset); + } + .UserList.layout-horizontal > * { + margin-right: var(--itemOffset); } .UserList_mobile { diff --git a/src/components/UserList.tsx b/src/components/UserList.tsx index 7cd1e12483b2..ec92b0fc80db 100644 --- a/src/components/UserList.tsx +++ b/src/components/UserList.tsx @@ -2,17 +2,59 @@ import "./UserList.scss"; import React from "react"; import clsx from "clsx"; +import { AppState } from "../types"; +import { ActionManager } from "../actions/manager"; +import { Tooltip } from "./Tooltip"; type UserListProps = { - children: React.ReactNode; className?: string; mobile?: boolean; + collaborators: AppState["collaborators"]; + layout: "vertical" | "horizontal"; + actionManager: ActionManager; }; -export const UserList = ({ children, className, mobile }: UserListProps) => { - return ( -
- {children} -
- ); -}; +export const UserList = React.memo( + ({ + className, + mobile, + collaborators = new Map(), + layout, + actionManager, + }: UserListProps) => { + const threshold = layout === "vertical" ? 6 : 3; + const offset = + collaborators.size > threshold + ? Math.min(collaborators.size - threshold, 15) * -2 + : 4; + return ( +
+ {collaborators.size > 0 && + Array.from(collaborators) + // Collaborator is either not initialized or is actually the current user. + .filter(([_, client]) => Object.keys(client).length !== 0) + .map(([clientId, client]) => + mobile ? ( + actionManager.renderAction("goToCollaborator", { id: clientId }) + ) : ( + + {actionManager.renderAction("goToCollaborator", { + id: clientId, + })} + + ), + )} +
+ ); + }, +); diff --git a/src/components/icons.tsx b/src/components/icons.tsx index 9e0ce7e84309..41cc762293cc 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -938,3 +938,7 @@ export const editIcon = createIcon( export const eraser = createIcon( , ); + +export const home = createIcon( + "M280.37 148.26L96 300.11V464a16 16 0 0 0 16 16l112.06-.29a16 16 0 0 0 15.92-16V368a16 16 0 0 1 16-16h64a16 16 0 0 1 16 16v95.64a16 16 0 0 0 16 16.05L464 480a16 16 0 0 0 16-16V300L295.67 148.26a12.19 12.19 0 0 0-15.3 0zM571.6 251.47L488 182.56V44.05a12 12 0 0 0-12-12h-56a12 12 0 0 0-12 12v72.61L318.47 43a48 48 0 0 0-61 0L4.34 251.47a12 12 0 0 0-1.6 16.9l25.5 31A12 12 0 0 0 45.15 301l235.22-193.74a12.19 12.19 0 0 1 15.3 0L530.9 301a12 12 0 0 0 16.9-1.6l25.5-31a12 12 0 0 0-1.7-16.93z", +); diff --git a/src/constants.ts b/src/constants.ts index afb7ecf3e369..fbdbe2087e64 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,5 @@ import cssVariables from "./css/variables.module.scss"; -import { AppProps } from "./types"; +import { AppProps, CanvasActions, ExportOpts } from "./types"; import { FontFamilyValues } from "./element/types"; export const APP_NAME = "Excalidraw"; @@ -143,7 +143,12 @@ export const URL_HASH_KEYS = { addLibrary: "addLibrary", } as const; -export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = { +export const DEFAULT_UI_OPTIONS: Merge< + AppProps["UIOptions"], + { + canvasActions: Merge, { export: ExportOpts }>; + } +> = { canvasActions: { changeViewBackgroundColor: true, clearCanvas: true, @@ -155,7 +160,7 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = { }, }; -export const MQ_MAX_WIDTH_PORTRAIT = 730; +export const MQ_MAX_WIDTH_PORTRAIT = 800; export const MQ_MAX_WIDTH_LANDSCAPE = 1000; export const MQ_MAX_HEIGHT_LANDSCAPE = 500; diff --git a/src/css/styles.scss b/src/css/styles.scss index 0de93901bd98..dfc9ea35d4a6 100644 --- a/src/css/styles.scss +++ b/src/css/styles.scss @@ -249,9 +249,8 @@ .App-top-bar { z-index: var(--zIndex-layerUI); - display: flex; - flex-direction: column; - align-items: center; + display: grid; + grid-template-columns: 1fr max-content 1fr; } .App-bottom-bar { @@ -567,6 +566,30 @@ display: none; } } + + .HomeButton.ToolIcon.ToolIcon_type_floating { + display: inline-block; + position: absolute; + top: 60px; + left: calc(-2 * var(--space-factor)); + right: auto; + border-radius: 0 20px 20px 0; + + background-color: var(--button-gray-1); + &:hover { + background-color: var(--button-gray-1); + } + &:active { + background-color: var(--button-gray-2); + } + + .ToolIcon__icon { + border-radius: inherit; + } + svg { + position: static; + } + } } .ErrorSplash.excalidraw { diff --git a/src/css/theme.scss b/src/css/theme.scss index 1827a74bc2ad..b28e1fac84b6 100644 --- a/src/css/theme.scss +++ b/src/css/theme.scss @@ -78,7 +78,7 @@ --popup-bg-color: #2c2c2c; --popup-secondary-bg-color: #222; --popup-text-color: #{$oc-gray-4}; - --popup-text-inverted-color: #2c2c2c; + --popup-text-inverted-color: #635c5c; --select-highlight-color: #{$oc-blue-4}; --shadow-island: 1px 1px 5px #{transparentize($oc-black, 0.7)}; --text-primary-color: #{$oc-gray-4}; diff --git a/src/excalidraw-app/index.tsx b/src/excalidraw-app/index.tsx index 2f89e3c33ce6..b62b79e7b4cc 100644 --- a/src/excalidraw-app/index.tsx +++ b/src/excalidraw-app/index.tsx @@ -1,7 +1,6 @@ import LanguageDetector from "i18next-browser-languagedetector"; import { useCallback, useContext, useEffect, useRef, useState } from "react"; import { trackEvent } from "../analytics"; -import { getDefaultAppState } from "../appState"; import { ErrorDialog } from "../components/ErrorDialog"; import { TopErrorBoundary } from "../components/TopErrorBoundary"; import { @@ -12,18 +11,10 @@ import { VERSION_TIMEOUT, } from "../constants"; import { loadFromBlob } from "../data/blob"; -import { - ExcalidrawElement, - FileId, - NonDeletedExcalidrawElement, -} from "../element/types"; +import { ExcalidrawElement, FileId } from "../element/types"; import { useCallbackRefState } from "../hooks/useCallbackRefState"; import { Language, t } from "../i18n"; -import { - Excalidraw, - defaultLang, - languages, -} from "../packages/excalidraw/index"; +import { Excalidraw, defaultLang } from "../packages/excalidraw/index"; import { AppState, LibraryItems, @@ -50,8 +41,7 @@ import CollabWrapper, { CollabContext, CollabContextConsumer, } from "./collab/CollabWrapper"; -import { LanguageList } from "./components/LanguageList"; -import { exportToBackend, getCollaborationLinkData, loadScene } from "./data"; +import { getCollaborationLinkData, loadScene } from "./data"; import { getLibraryItemsFromStorage, importFromLocalStorage, @@ -59,11 +49,8 @@ import { } from "./data/localStorage"; import CustomStats from "./CustomStats"; import { restoreAppState, RestoredDataState } from "../data/restore"; -import { Tooltip } from "../components/Tooltip"; -import { shield } from "../components/icons"; import "./index.scss"; -import { ExportToExcalidrawPlus } from "./components/ExportToExcalidrawPlus"; import { updateStaleImageStatuses } from "./data/FileManager"; import { newElementWith } from "../element/mutateElement"; @@ -192,38 +179,14 @@ const initializeScene = async (opts: { return { scene: null, isExternalScene: false }; }; -const PlusLPLinkJSX = ( -

- Introducing Excalidraw+ -
- - Try out now! - -

-); - -const PlusAppLinkJSX = ( - - Go to Excalidraw+ - -); - const ExcalidrawWrapper = () => { const [errorMessage, setErrorMessage] = useState(""); let currentLangCode = languageDetector.detect() || defaultLang.code; if (Array.isArray(currentLangCode)) { currentLangCode = currentLangCode[0]; } - const [langCode, setLangCode] = useState(currentLangCode); + const [langCode] = useState(currentLangCode); + // initial state // --------------------------------------------------------------------------- @@ -363,7 +326,6 @@ const ExcalidrawWrapper = () => { if (Array.isArray(langCode)) { langCode = langCode[0]; } - setLangCode(langCode); excalidrawAPI.updateScene({ ...localDataState, }); @@ -509,124 +471,16 @@ const ExcalidrawWrapper = () => { } }; - const onExportToBackend = async ( - exportedElements: readonly NonDeletedExcalidrawElement[], - appState: AppState, - files: BinaryFiles, - canvas: HTMLCanvasElement | null, - ) => { - if (exportedElements.length === 0) { - return window.alert(t("alerts.cannotExportEmptyCanvas")); - } - if (canvas) { - try { - await exportToBackend( - exportedElements, - { - ...appState, - viewBackgroundColor: appState.exportBackground - ? appState.viewBackgroundColor - : getDefaultAppState().viewBackgroundColor, - }, - files, - ); - } catch (error: any) { - if (error.name !== "AbortError") { - const { width, height } = canvas; - console.error(error, { width, height }); - setErrorMessage(error.message); - } - } - } - }; - const renderTopRightUI = useCallback( (isMobile: boolean, appState: AppState) => { - if (isMobile) { - return null; - } - - return ( -
- {isExcalidrawPlusSignedUser ? PlusAppLinkJSX : PlusLPLinkJSX} -
- ); + return
placeholder
; }, [], ); - const renderFooter = useCallback( - (isMobile: boolean) => { - const renderEncryptedIcon = () => ( - - - {shield} - - - ); - - const renderLanguageList = () => ( - setLangCode(langCode)} - languages={languages} - currentLangCode={langCode} - /> - ); - if (isMobile) { - const isTinyDevice = window.innerWidth < 362; - return ( -
-
- {t("labels.language")} - {renderLanguageList()} -
- {/* FIXME remove after 2021-05-20 */} -
- {isExcalidrawPlusSignedUser ? PlusAppLinkJSX : PlusLPLinkJSX} -
-
- ); - } - return ( - <> - {renderEncryptedIcon()} - {renderLanguageList()} - - ); - }, - [langCode], - ); + const renderFooter = useCallback((isMobile: boolean) => { + return <>; + }, []); const renderCustomStats = () => { return ( @@ -657,6 +511,7 @@ const ExcalidrawWrapper = () => { })} > { onPointerUpdate={collabAPI?.onPointerUpdate} UIOptions={{ canvasActions: { - export: { - onExportToBackend, - renderCustomUI: (elements, appState, files) => { - return ( - { - excalidrawAPI?.updateScene({ - appState: { - errorMessage: error.message, - }, - }); - }} - /> - ); - }, - }, + clearCanvas: false, + export: () => {}, }, }} renderTopRightUI={renderTopRightUI} @@ -694,6 +532,7 @@ const ExcalidrawWrapper = () => { handleKeyboardGlobally={true} onLibraryChange={onLibraryChange} autoFocus={true} + theme="light" /> {excalidrawAPI && ( any, ): void; + removeEventListener?( + type: "loading" | "loadingdone" | "loadingerror", + listener: (this: Document, ev: Event) => any, + ): void; }; } @@ -31,6 +35,8 @@ interface Clipboard extends EventTarget { write(data: any[]): Promise; } +type Merge = Omit & N; + type Mutable = { -readonly [P in keyof T]: T[P]; }; diff --git a/src/i18n.ts b/src/i18n.ts index 7845c2557c3b..540eecfabc0c 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -86,7 +86,7 @@ export const setLanguage = async (lang: Language) => { currentLangData = {}; } else { currentLangData = await import( - /* webpackChunkName: "i18n-[request]" */ `./locales/${currentLang.code}.json` + /* webpackChunkName: "i18n-[request]" */ `./locales/en.json` ); } }; diff --git a/src/locales/en.json b/src/locales/en.json index f6d887cf555b..ecfe1c0d8b19 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -71,7 +71,7 @@ "layers": "Layers", "actions": "Actions", "language": "Language", - "liveCollaboration": "Live collaboration", + "liveCollaboration": "Sharing", "duplicateSelection": "Duplicate", "untitled": "Untitled", "name": "Name", @@ -132,7 +132,7 @@ "copyToClipboard": "Copy to clipboard", "copyPngToClipboard": "Copy PNG to clipboard", "scale": "Scale", - "save": "Save to current file", + "save": "Save (to active file)", "saveAs": "Save as", "load": "Load", "getShareableLink": "Get shareable link", @@ -148,6 +148,7 @@ "undo": "Undo", "redo": "Redo", "resetLibrary": "Reset library", + "roomDialog": "Sharing", "createNewRoom": "Create new room", "fullScreen": "Full screen", "darkMode": "Dark mode", diff --git a/src/packages/excalidraw/index.tsx b/src/packages/excalidraw/index.tsx index 936effdb265c..395ade629f7c 100644 --- a/src/packages/excalidraw/index.tsx +++ b/src/packages/excalidraw/index.tsx @@ -14,6 +14,7 @@ import { jotaiScope, jotaiStore } from "../../jotai"; const ExcalidrawBase = (props: ExcalidrawProps) => { const { + onHomeButtonClick, onChange, initialData, excalidrawRef, @@ -39,20 +40,23 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { onLinkOpen, onPointerDown, onScrollChange, + id, } = props; const canvasActions = props.UIOptions?.canvasActions; const UIOptions: AppProps["UIOptions"] = { - canvasActions: { - ...DEFAULT_UI_OPTIONS.canvasActions, - ...canvasActions, - }, + canvasActions: canvasActions + ? { + ...DEFAULT_UI_OPTIONS.canvasActions, + ...canvasActions, + } + : false, }; - if (canvasActions?.export) { - UIOptions.canvasActions.export.saveFileToDisk = - canvasActions.export?.saveFileToDisk ?? + if (canvasActions && typeof canvasActions.export === "object") { + canvasActions.export.saveFileToDisk = + canvasActions.export?.saveFileToDisk || DEFAULT_UI_OPTIONS.canvasActions.export.saveFileToDisk; } @@ -78,6 +82,8 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { jotaiStore} scope={jotaiScope}> )[]; canvasOptionKeys.every((key) => { + if (!prevUIOptions?.canvasActions || !nextUIOptions?.canvasActions) { + return prevUIOptions?.canvasActions === nextUIOptions?.canvasActions; + } if ( key === "export" && prevUIOptions?.canvasActions?.export && nextUIOptions?.canvasActions?.export ) { + if ( + typeof prevUIOptions.canvasActions.export === "function" || + typeof nextUIOptions.canvasActions.export === "function" + ) { + return ( + prevUIOptions.canvasActions.export === + nextUIOptions.canvasActions.export + ); + } return ( prevUIOptions.canvasActions.export.saveFileToDisk === nextUIOptions.canvasActions.export.saveFileToDisk @@ -193,8 +211,8 @@ export { restoreLibraryItems, } from "../../data/restore"; export { - exportToCanvas, exportToBlob, + exportToCanvas, exportToSvg, serializeAsJSON, serializeLibraryAsJSON, @@ -224,3 +242,11 @@ export { sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, } from "../../utils"; +export { + getDefaultAppState, + cleanAppStateForExport, + clearAppStateForLocalStorage, +} from "../../appState"; + +export { jotaiScope, jotaiStore } from "../../jotai"; +export { libraryItemsAtom } from "../../data/library"; diff --git a/src/packages/excalidraw/main.js b/src/packages/excalidraw/main.js index b1668faf70a4..25c55a77b099 100644 --- a/src/packages/excalidraw/main.js +++ b/src/packages/excalidraw/main.js @@ -1,4 +1,7 @@ -if (process.env.NODE_ENV === "production") { +if ( + process.env.NODE_ENV === "production" || + window.__excalidraw_env__ === "production" +) { module.exports = require("./dist/excalidraw.production.min.js"); } else { module.exports = require("./dist/excalidraw.development.js"); diff --git a/src/packages/excalidraw/package.json b/src/packages/excalidraw/package.json index d96daafa3646..87868a8bbe3a 100644 --- a/src/packages/excalidraw/package.json +++ b/src/packages/excalidraw/package.json @@ -1,6 +1,6 @@ { - "name": "@excalidraw/excalidraw", - "version": "0.11.0", + "name": "@dwelle/excalidraw", + "version": "0.3.67", "main": "main.js", "types": "types/packages/excalidraw/index.d.ts", "files": [ @@ -11,15 +11,8 @@ "access": "public" }, "description": "Excalidraw as a React component", - "repository": "https://github.com/excalidraw/excalidraw", "license": "MIT", - "keywords": [ - "excalidraw", - "excalidraw-embed", - "react", - "npm", - "npm excalidraw" - ], + "keywords": [], "browserslist": { "production": [ ">0.2%", @@ -69,7 +62,7 @@ "webpack-dev-server": "4.9.0", "webpack-merge": "5.8.0" }, - "bugs": "https://github.com/excalidraw/excalidraw/issues", + "repository": "https://github.com/dwelle/excalidraw", "homepage": "https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw", "scripts": { "gen:types": "tsc --project ../../../tsconfig-types.json", diff --git a/src/packages/utils/package.json b/src/packages/utils/package.json index 0c7c907ecd5d..e569d2412513 100644 --- a/src/packages/utils/package.json +++ b/src/packages/utils/package.json @@ -34,7 +34,7 @@ ] }, "devDependencies": { - "@babel/core": "7.17.2", + "@babel/core": "7.18.2", "@babel/plugin-transform-arrow-functions": "7.16.0", "@babel/plugin-transform-async-to-generator": "7.16.5", "@babel/plugin-transform-runtime": "7.17.10", diff --git a/src/packages/utils/yarn.lock b/src/packages/utils/yarn.lock index 07bcdec0f78b..ac60a1896415 100644 --- a/src/packages/utils/yarn.lock +++ b/src/packages/utils/yarn.lock @@ -2,12 +2,13 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.0.tgz#72becdf17ee44b2d1ac5651fb12f1952c336fe23" - integrity sha512-d5RysTlJ7hmw5Tw4UxgxcY3lkMe92n8sXCcuLPAyIAHK6j8DefDwtGnVVDgOnv+RnEosulDJ9NPKQL27bDId0g== +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== dependencies: - "@jridgewell/trace-mapping" "^0.3.0" + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" "@babel/code-frame@^7.16.7": version "7.16.7" @@ -21,35 +22,40 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== -"@babel/core@7.17.2": - version "7.17.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.2.tgz#2c77fc430e95139d816d39b113b31bf40fb22337" - integrity sha512-R3VH5G42VSDolRHyUO4V2cfag8WHcZyxdq5Z/m8Xyb92lW/Erm/6kM+XtRFGf3Mulre3mveni2NHfEUws8wSvw== +"@babel/compat-data@^7.17.10": + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.10.tgz#711dc726a492dfc8be8220028b1b92482362baab" + integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw== + +"@babel/core@7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.2.tgz#87b2fcd7cce9becaa7f5acebdc4f09f3dd19d876" + integrity sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ== dependencies: - "@ampproject/remapping" "^2.0.0" + "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.0" - "@babel/helper-compilation-targets" "^7.16.7" - "@babel/helper-module-transforms" "^7.16.7" - "@babel/helpers" "^7.17.2" - "@babel/parser" "^7.17.0" + "@babel/generator" "^7.18.2" + "@babel/helper-compilation-targets" "^7.18.2" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helpers" "^7.18.2" + "@babel/parser" "^7.18.0" "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.0" - "@babel/types" "^7.17.0" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.1.2" + json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.0.tgz#7bd890ba706cd86d3e2f727322346ffdbf98f65e" - integrity sha512-I3Omiv6FGOC29dtlZhkfXO6pgkmukJSlT26QjVvS1DGZe/NzSVCPG41X0tS21oZkJYlovfj9qDWgKP+Cn4bXxw== +"@babel/generator@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d" + integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== dependencies: - "@babel/types" "^7.17.0" + "@babel/types" "^7.18.2" + "@jridgewell/gen-mapping" "^0.3.0" jsesc "^2.5.1" - source-map "^0.5.0" "@babel/helper-annotate-as-pure@^7.16.0": version "7.16.0" @@ -73,14 +79,14 @@ "@babel/helper-explode-assignable-expression" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz#06e66c5f299601e6c7da350049315e83209d551b" - integrity sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA== +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" + integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== dependencies: - "@babel/compat-data" "^7.16.4" + "@babel/compat-data" "^7.17.10" "@babel/helper-validator-option" "^7.16.7" - browserslist "^4.17.5" + browserslist "^4.20.2" semver "^6.3.0" "@babel/helper-create-class-features-plugin@^7.16.0": @@ -152,6 +158,11 @@ dependencies: "@babel/types" "^7.16.7" +"@babel/helper-environment-visitor@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd" + integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== + "@babel/helper-explode-assignable-expression@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a" @@ -177,6 +188,14 @@ "@babel/template" "^7.16.7" "@babel/types" "^7.16.7" +"@babel/helper-function-name@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" + integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== + dependencies: + "@babel/template" "^7.16.7" + "@babel/types" "^7.17.0" + "@babel/helper-get-function-arity@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" @@ -212,19 +231,19 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-module-transforms@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz#7665faeb721a01ca5327ddc6bba15a5cb34b6a41" - integrity sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng== +"@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" + integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== dependencies: "@babel/helper-environment-visitor" "^7.16.7" "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-simple-access" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" "@babel/helper-split-export-declaration" "^7.16.7" "@babel/helper-validator-identifier" "^7.16.7" "@babel/template" "^7.16.7" - "@babel/traverse" "^7.16.7" - "@babel/types" "^7.16.7" + "@babel/traverse" "^7.18.0" + "@babel/types" "^7.18.0" "@babel/helper-optimise-call-expression@^7.16.7": version "7.16.7" @@ -274,6 +293,13 @@ dependencies: "@babel/types" "^7.16.7" +"@babel/helper-simple-access@^7.17.7": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" + integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ== + dependencies: + "@babel/types" "^7.18.2" + "@babel/helper-skip-transparent-expression-wrappers@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09" @@ -318,14 +344,14 @@ "@babel/traverse" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/helpers@^7.17.2": - version "7.17.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.2.tgz#23f0a0746c8e287773ccd27c14be428891f63417" - integrity sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ== +"@babel/helpers@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384" + integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg== dependencies: "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.0" - "@babel/types" "^7.17.0" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" "@babel/highlight@^7.16.7": version "7.16.7" @@ -336,10 +362,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.16.7", "@babel/parser@^7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.0.tgz#f0ac33eddbe214e4105363bb17c3341c5ffcc43c" - integrity sha512-VKXSCQx5D8S04ej+Dqsr1CzYvvWgf20jIw2D+YhQCrIlr2UZGaDds23Y0xg75/skOxpLCRpUZvk/1EAVkGoDOw== +"@babel/parser@^7.16.7", "@babel/parser@^7.18.0": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.3.tgz#39e99c7b0c4c56cef4d1eed8de9f506411c2ebc2" + integrity sha512-rL50YcEuHbbauAFAysNsJA4/f89fGTOBRNs9P81sniKnKAr4xULe5AecolcsKbi88xu0ByWYDj/S1AJ3FSFuSQ== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": version "7.16.7" @@ -1002,26 +1028,26 @@ "@babel/parser" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.5", "@babel/traverse@^7.16.7", "@babel/traverse@^7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.0.tgz#3143e5066796408ccc880a33ecd3184f3e75cd30" - integrity sha512-fpFIXvqD6kC7c7PUNnZ0Z8cQXlarCLtCUpt2S1Dx7PjoRtCFffvOkHHSom+m5HIxMZn5bIBVb71lhabcmjEsqg== +"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.5", "@babel/traverse@^7.16.7", "@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.2.tgz#b77a52604b5cc836a9e1e08dca01cba67a12d2e8" + integrity sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA== dependencies: "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.0" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-environment-visitor" "^7.18.2" + "@babel/helper-function-name" "^7.17.9" "@babel/helper-hoist-variables" "^7.16.7" "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.17.0" - "@babel/types" "^7.17.0" + "@babel/parser" "^7.18.0" + "@babel/types" "^7.18.2" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.4.4": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" - integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== +"@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.4.4": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.2.tgz#191abfed79ebe6f4242f643a9a5cbaa36b10b091" + integrity sha512-0On6B8A4/+mFUto5WERt3EEuG1NznDirvwca1O8UwXQHVY8g3R7OzYgxXdOfMwLO08UrpUD/2+3Bclyq+/C94Q== dependencies: "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" @@ -1031,20 +1057,42 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9" + integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/resolve-uri@^3.0.3": version "3.0.4" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.4.tgz#b876e3feefb9c8d3aa84014da28b5e52a0640d72" integrity sha512-cz8HFjOFfUBtvN+NXYSFMHYRdxZMaEl0XypVrhzxBgadKIXhIkRd8aMeHhmF56Sl7SuS8OnUpQ73/k9LE4VnLg== +"@jridgewell/set-array@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" + integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== + "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.10" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.10.tgz#baf57b4e2a690d4f38560171f91783656b7f8186" integrity sha512-Ht8wIW5v165atIX1p+JvKR5ONzUyF4Ac8DZIQ5kZs9zrb6M8SJNXpx1zn04rn65VjBMygRoMXcyYwNK0fT7bEg== -"@jridgewell/trace-mapping@^0.3.0": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" - integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" + integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== dependencies: "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" @@ -1452,7 +1500,7 @@ braces@^3.0.1: dependencies: fill-range "^7.0.1" -browserslist@^4.14.5, browserslist@^4.17.5, browserslist@^4.17.6, browserslist@^4.19.1: +browserslist@^4.14.5, browserslist@^4.17.6, browserslist@^4.19.1: version "4.19.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.3.tgz#29b7caad327ecf2859485f696f9604214bedd383" integrity sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg== @@ -1463,6 +1511,17 @@ browserslist@^4.14.5, browserslist@^4.17.5, browserslist@^4.17.6, browserslist@^ node-releases "^2.0.2" picocolors "^1.0.0" +browserslist@^4.20.2: + version "4.20.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf" + integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg== + dependencies: + caniuse-lite "^1.0.30001332" + electron-to-chromium "^1.4.118" + escalade "^3.1.1" + node-releases "^2.0.3" + picocolors "^1.0.0" + buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -1481,6 +1540,11 @@ caniuse-lite@^1.0.30001312: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz#e11eba4b87e24d22697dae05455d5aea28550d5f" integrity sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ== +caniuse-lite@^1.0.30001332: + version "1.0.30001344" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001344.tgz#8a1e7fdc4db9c2ec79a05e9fd68eb93a761888bb" + integrity sha512-0ZFjnlCaXNOAYcV7i+TtdKBp0L/3XEU2MF/x6Du1lrh+SRX4IfzIVL4HNJg5pB2PmFb8rszIGyOvsZnqqRoc2g== + chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -1658,6 +1722,11 @@ duplexer@^0.1.2: resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== +electron-to-chromium@^1.4.118: + version "1.4.141" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.141.tgz#4dd9119e8a99f1c83c51dfcf1bed79ea541f08d6" + integrity sha512-mfBcbqc0qc6RlxrsIgLG2wCqkiPAjEezHxGTu7p3dHHFOurH4EjS9rFZndX5axC8264rI1Pcbw8uQP39oZckeA== + electron-to-chromium@^1.4.71: version "1.4.75" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz#d1ad9bb46f2f1bf432118c2be21d27ffeae82fdd" @@ -1981,12 +2050,10 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" +json5@^2.1.2, json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== kind-of@^6.0.2: version "6.0.3" @@ -2085,11 +2152,6 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimist@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -2115,6 +2177,11 @@ node-releases@^2.0.2: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== +node-releases@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" + integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== + npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -2457,11 +2524,6 @@ source-map-support@~0.5.19: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.5.0: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" diff --git a/src/renderer/renderElement.ts b/src/renderer/renderElement.ts index 92f48a76b61e..3bd3dbcaad22 100644 --- a/src/renderer/renderElement.ts +++ b/src/renderer/renderElement.ts @@ -300,12 +300,12 @@ const drawElementOnCanvas = ( context.globalAlpha = 1; }; -const elementWithCanvasCache = new WeakMap< +let elementWithCanvasCache = new WeakMap< ExcalidrawElement, ExcalidrawElementWithCanvas >(); -const shapeCache = new WeakMap(); +let shapeCache = new WeakMap(); type ElementShape = Drawable | Drawable[] | null; @@ -332,6 +332,12 @@ export const setShapeForElement = ( export const invalidateShapeForElement = (element: ExcalidrawElement) => shapeCache.delete(element); +export const clearRenderCache = () => { + elementWithCanvasCache = new WeakMap(); + shapeCache = new WeakMap(); + pathsCache = new WeakMap(); +}; + export const generateRoughOptions = ( element: ExcalidrawElement, continuousPath = false, @@ -1044,7 +1050,7 @@ export const renderElementToSvg = ( } }; -export const pathsCache = new WeakMap([]); +export let pathsCache = new WeakMap([]); export function generateFreeDrawShape(element: ExcalidrawFreeDrawElement) { const svgPathData = getFreeDrawSvgPath(element); diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 9b2d7cd4475b..37f073a56b5d 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -318,7 +318,11 @@ export const renderScene = ( selectionColors.push( ...renderConfig.remoteSelectedElementIds[element.id].map( (socketId) => { - const { background } = getClientColors(socketId, appState); + const picture = appState.collaborators.get(socketId)?.avatarUrl; + const { background } = getClientColors( + picture || socketId, + appState, + ); return background; }, ), @@ -426,7 +430,6 @@ export const renderScene = ( // Paint remote pointers for (const clientId in renderConfig.remotePointerViewportCoords) { let { x, y } = renderConfig.remotePointerViewportCoords[clientId]; - x -= appState.offsetLeft; y -= appState.offsetTop; @@ -444,7 +447,11 @@ export const renderScene = ( y = Math.max(y, 0); y = Math.min(y, normalizedCanvasHeight - height); - const { background, stroke } = getClientColors(clientId, appState); + const picture = appState.collaborators.get(clientId)?.avatarUrl; + const { background, stroke } = getClientColors( + picture || clientId, + appState, + ); context.save(); context.strokeStyle = stroke; diff --git a/src/scene/export.ts b/src/scene/export.ts index 9dacc755b2d8..6b3ec048e9c3 100644 --- a/src/scene/export.ts +++ b/src/scene/export.ts @@ -135,11 +135,11 @@ export const exportToSvg = async ( diff --git a/src/types.ts b/src/types.ts index 0eb01d578beb..efdac2c4f8a5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -245,6 +245,8 @@ export type ExcalidrawAPIRefValue = export type ExcalidrawInitialDataState = Merge< ImportedDataState, { + scrollX?: number; + scrollY?: number; libraryItems?: | Required["libraryItems"] | Promise["libraryItems"]>; @@ -252,15 +254,21 @@ export type ExcalidrawInitialDataState = Merge< >; export interface ExcalidrawProps { + id?: string | null; onChange?: ( elements: readonly ExcalidrawElement[], appState: AppState, files: BinaryFiles, + id?: string | null, ) => void; initialData?: | ExcalidrawInitialDataState | null | Promise; + onHomeButtonClick?: () => void; + user?: { + name?: string | null; + }; excalidrawRef?: ForwardRef; onCollabButtonClick?: () => void; isCollaborating?: boolean; @@ -276,6 +284,7 @@ export interface ExcalidrawProps { renderTopRightUI?: ( isMobile: boolean, appState: AppState, + canvas: HTMLCanvasElement | null, ) => JSX.Element | null; renderFooter?: (isMobile: boolean, appState: AppState) => JSX.Element; langCode?: Language["code"]; @@ -337,10 +346,10 @@ export type ExportOpts = { ) => JSX.Element; }; -type CanvasActions = { +export type CanvasActions = { changeViewBackgroundColor?: boolean; clearCanvas?: boolean; - export?: false | ExportOpts; + export?: false | ExportOpts | (() => void); loadScene?: boolean; saveToActiveFile?: boolean; theme?: boolean; @@ -348,12 +357,12 @@ type CanvasActions = { }; export type UIOptions = { - canvasActions?: CanvasActions; + canvasActions?: CanvasActions | false; }; export type AppProps = ExcalidrawProps & { UIOptions: { - canvasActions: Required & { export: ExportOpts }; + canvasActions: Required | false; }; detectScroll: boolean; handleKeyboardGlobally: boolean; @@ -468,6 +477,7 @@ export type ExcalidrawImperativeAPI = { setActiveTool: InstanceType["setActiveTool"]; setCursor: InstanceType["setCursor"]; resetCursor: InstanceType["resetCursor"]; + app: InstanceType; }; export type DeviceType = {