{
toggleTheme: true,
export: {
onExportToBackend,
- renderCustomUI: (elements, appState, files) => {
- return (
-
{
- excalidrawAPI?.updateScene({
- appState: {
- errorMessage: error.message,
- },
- });
- }}
- onSuccess={() => {
- excalidrawAPI?.updateScene({
- appState: { openDialog: null },
- });
- }}
- />
- );
- },
+ renderCustomUI: excalidrawAPI
+ ? (elements, appState, files) => {
+ return (
+ {
+ excalidrawAPI?.updateScene({
+ appState: {
+ errorMessage: error.message,
+ },
+ });
+ }}
+ onSuccess={() => {
+ excalidrawAPI.updateScene({
+ appState: { openDialog: null },
+ });
+ }}
+ />
+ );
+ }
+ : undefined,
},
},
}}
@@ -740,20 +783,22 @@ const ExcalidrawWrapper = () => {
renderCustomStats={renderCustomStats}
detectScroll={false}
handleKeyboardGlobally={true}
- onLibraryChange={onLibraryChange}
autoFocus={true}
- theme={theme}
+ theme={editorTheme}
renderTopRightUI={(isMobile) => {
if (isMobile || !collabAPI || isCollabDisabled) {
return null;
}
return (
-
- setShareDialogState({ isOpen: true, type: "share" })
- }
- />
+
+ {collabError.message && }
+
+ setShareDialogState({ isOpen: true, type: "share" })
+ }
+ />
+
);
}}
>
@@ -761,6 +806,8 @@ const ExcalidrawWrapper = () => {
onCollabDialogOpen={onCollabDialogOpen}
isCollaborating={isCollaborating}
isCollabEnabled={!isCollabDisabled}
+ theme={appTheme}
+ setTheme={(theme) => setAppTheme(theme)}
/>
{
excalidrawAPI.getSceneElements(),
excalidrawAPI.getAppState(),
excalidrawAPI.getFiles(),
+ excalidrawAPI.getName(),
);
}}
>
@@ -882,6 +930,181 @@ const ExcalidrawWrapper = () => {
{errorMessage}
)}
+
+ {
+ setShareDialogState({
+ isOpen: true,
+ type: "collaborationOnly",
+ });
+ },
+ },
+ {
+ label: t("roomDialog.button_stopSession"),
+ category: DEFAULT_CATEGORIES.app,
+ predicate: () => !!collabAPI?.isCollaborating(),
+ keywords: [
+ "stop",
+ "session",
+ "end",
+ "leave",
+ "close",
+ "exit",
+ "collaboration",
+ ],
+ perform: () => {
+ if (collabAPI) {
+ collabAPI.stopCollaboration();
+ if (!collabAPI.isCollaborating()) {
+ setShareDialogState({ isOpen: false });
+ }
+ }
+ },
+ },
+ {
+ label: t("labels.share"),
+ category: DEFAULT_CATEGORIES.app,
+ predicate: true,
+ icon: share,
+ keywords: [
+ "link",
+ "shareable",
+ "readonly",
+ "export",
+ "publish",
+ "snapshot",
+ "url",
+ "collaborate",
+ "invite",
+ ],
+ perform: async () => {
+ setShareDialogState({ isOpen: true, type: "share" });
+ },
+ },
+ {
+ label: "GitHub",
+ icon: GithubIcon,
+ category: DEFAULT_CATEGORIES.links,
+ predicate: true,
+ keywords: [
+ "issues",
+ "bugs",
+ "requests",
+ "report",
+ "features",
+ "social",
+ "community",
+ ],
+ perform: () => {
+ window.open(
+ "https://github.com/excalidraw/excalidraw",
+ "_blank",
+ "noopener noreferrer",
+ );
+ },
+ },
+ {
+ label: t("labels.followUs"),
+ icon: XBrandIcon,
+ category: DEFAULT_CATEGORIES.links,
+ predicate: true,
+ keywords: ["twitter", "contact", "social", "community"],
+ perform: () => {
+ window.open(
+ "https://x.com/excalidraw",
+ "_blank",
+ "noopener noreferrer",
+ );
+ },
+ },
+ {
+ label: t("labels.discordChat"),
+ category: DEFAULT_CATEGORIES.links,
+ predicate: true,
+ icon: DiscordIcon,
+ keywords: [
+ "chat",
+ "talk",
+ "contact",
+ "bugs",
+ "requests",
+ "report",
+ "feedback",
+ "suggestions",
+ "social",
+ "community",
+ ],
+ perform: () => {
+ window.open(
+ "https://discord.gg/UexuTaE",
+ "_blank",
+ "noopener noreferrer",
+ );
+ },
+ },
+ {
+ label: "YouTube",
+ icon: youtubeIcon,
+ category: DEFAULT_CATEGORIES.links,
+ predicate: true,
+ keywords: ["features", "tutorials", "howto", "help", "community"],
+ perform: () => {
+ window.open(
+ "https://youtube.com/@excalidraw",
+ "_blank",
+ "noopener noreferrer",
+ );
+ },
+ },
+ ...(isExcalidrawPlusSignedUser
+ ? [
+ {
+ ...ExcalidrawPlusAppCommand,
+ label: "Sign in / Go to Excalidraw+",
+ },
+ ]
+ : [ExcalidrawPlusCommand, ExcalidrawPlusAppCommand]),
+
+ {
+ label: t("overwriteConfirm.action.excalidrawPlus.button"),
+ category: DEFAULT_CATEGORIES.export,
+ icon: exportToPlus,
+ predicate: true,
+ keywords: ["plus", "export", "save", "backup"],
+ perform: () => {
+ if (excalidrawAPI) {
+ exportToExcalidrawPlus(
+ excalidrawAPI.getSceneElements(),
+ excalidrawAPI.getAppState(),
+ excalidrawAPI.getFiles(),
+ excalidrawAPI.getName(),
+ );
+ }
+ },
+ },
+ {
+ ...CommandPalette.defaultItems.toggleTheme,
+ perform: () => {
+ setAppTheme(
+ editorTheme === THEME.DARK ? THEME.LIGHT : THEME.DARK,
+ );
+ },
+ },
+ ]}
+ />
);
diff --git a/excalidraw-app/CustomStats.tsx b/excalidraw-app/CustomStats.tsx
index f2ce80f21..f609096b9 100644
--- a/excalidraw-app/CustomStats.tsx
+++ b/excalidraw-app/CustomStats.tsx
@@ -7,8 +7,8 @@ import {
import { DEFAULT_VERSION } from "../packages/excalidraw/constants";
import { t } from "../packages/excalidraw/i18n";
import { copyTextToSystemClipboard } from "../packages/excalidraw/clipboard";
-import { NonDeletedExcalidrawElement } from "../packages/excalidraw/element/types";
-import { UIAppState } from "../packages/excalidraw/types";
+import type { NonDeletedExcalidrawElement } from "../packages/excalidraw/element/types";
+import type { UIAppState } from "../packages/excalidraw/types";
type StorageSizes = { scene: number; total: number };
diff --git a/excalidraw-app/app_constants.ts b/excalidraw-app/app_constants.ts
index 3402bf106..f4b56496d 100644
--- a/excalidraw-app/app_constants.ts
+++ b/excalidraw-app/app_constants.ts
@@ -39,10 +39,14 @@ export const STORAGE_KEYS = {
LOCAL_STORAGE_ELEMENTS: "excalidraw",
LOCAL_STORAGE_APP_STATE: "excalidraw-state",
LOCAL_STORAGE_COLLAB: "excalidraw-collab",
- LOCAL_STORAGE_LIBRARY: "excalidraw-library",
LOCAL_STORAGE_THEME: "excalidraw-theme",
VERSION_DATA_STATE: "version-dataState",
VERSION_FILES: "version-files",
+
+ IDB_LIBRARY: "excalidraw-library",
+
+ // do not use apart from migrations
+ __LEGACY_LOCAL_STORAGE_LIBRARY: "excalidraw-library",
} as const;
export const COOKIES = {
diff --git a/excalidraw-app/collab/Collab.tsx b/excalidraw-app/collab/Collab.tsx
index 14538b674..7059a67c5 100644
--- a/excalidraw-app/collab/Collab.tsx
+++ b/excalidraw-app/collab/Collab.tsx
@@ -1,22 +1,25 @@
import throttle from "lodash.throttle";
import { PureComponent } from "react";
-import {
+import type {
ExcalidrawImperativeAPI,
SocketId,
} from "../../packages/excalidraw/types";
import { ErrorDialog } from "../../packages/excalidraw/components/ErrorDialog";
import { APP_NAME, ENV, EVENT } from "../../packages/excalidraw/constants";
-import { ImportedDataState } from "../../packages/excalidraw/data/types";
-import {
+import type { ImportedDataState } from "../../packages/excalidraw/data/types";
+import type {
ExcalidrawElement,
InitializedExcalidrawImageElement,
+ OrderedExcalidrawElement,
} from "../../packages/excalidraw/element/types";
import {
+ StoreAction,
getSceneVersion,
restoreElements,
zoomToFitBounds,
-} from "../../packages/excalidraw/index";
-import { Collaborator, Gesture } from "../../packages/excalidraw/types";
+ reconcileElements,
+} from "../../packages/excalidraw";
+import type { Collaborator, Gesture } from "../../packages/excalidraw/types";
import {
assertNever,
preventUnload,
@@ -33,12 +36,14 @@ import {
SYNC_FULL_SCENE_INTERVAL_MS,
WS_EVENTS,
} from "../app_constants";
+import type {
+ SocketUpdateDataSource,
+ SyncableExcalidrawElement,
+} from "../data";
import {
generateCollaborationLinkData,
getCollaborationLink,
getSyncableElements,
- SocketUpdateDataSource,
- SyncableExcalidrawElement,
} from "../data";
import {
isSavedToFirebase,
@@ -69,18 +74,19 @@ import {
isInitializedImageElement,
} from "../../packages/excalidraw/element/typeChecks";
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
-import {
- ReconciledElements,
- reconcileElements as _reconcileElements,
-} from "./reconciliation";
import { decryptData } from "../../packages/excalidraw/data/encryption";
import { resetBrowserStateVersions } from "../data/tabSync";
import { LocalData } from "../data/LocalData";
import { atom } from "jotai";
import { appJotaiStore } from "../app-jotai";
-import { Mutable, ValueOf } from "../../packages/excalidraw/utility-types";
+import type { Mutable, ValueOf } from "../../packages/excalidraw/utility-types";
import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds";
import { withBatchedUpdates } from "../../packages/excalidraw/reactUtils";
+import { collabErrorIndicatorAtom } from "./CollabError";
+import type {
+ ReconciledExcalidrawElement,
+ RemoteExcalidrawElement,
+} from "../../packages/excalidraw/data/reconcile";
export const collabAPIAtom = atom