mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-11-04 04:44:31 +01:00 
			
		
		
		
	Merge branch 'master' into arnost/scroll-in-read-only-links
# Conflicts: # packages/excalidraw/components/App.tsx # packages/excalidraw/components/MobileMenu.tsx
This commit is contained in:
		@@ -21,7 +21,6 @@ import {
 | 
			
		||||
  APP_NAME,
 | 
			
		||||
  EVENT,
 | 
			
		||||
  THEME,
 | 
			
		||||
  TITLE_TIMEOUT,
 | 
			
		||||
  VERSION_TIMEOUT,
 | 
			
		||||
  debounce,
 | 
			
		||||
  getVersion,
 | 
			
		||||
@@ -134,6 +133,7 @@ import {
 | 
			
		||||
  LibraryIndexedDBAdapter,
 | 
			
		||||
  LibraryLocalStorageMigrationAdapter,
 | 
			
		||||
  LocalData,
 | 
			
		||||
  localStorageQuotaExceededAtom,
 | 
			
		||||
} from "./data/LocalData";
 | 
			
		||||
import { isBrowserStorageStateNewer } from "./data/tabSync";
 | 
			
		||||
import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog";
 | 
			
		||||
@@ -791,11 +791,6 @@ const ExcalidrawWrapper = () => {
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const titleTimeout = setTimeout(
 | 
			
		||||
      () => (document.title = APP_NAME),
 | 
			
		||||
      TITLE_TIMEOUT,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const syncData = debounce(() => {
 | 
			
		||||
      if (isTestEnv()) {
 | 
			
		||||
        return;
 | 
			
		||||
@@ -886,7 +881,6 @@ const ExcalidrawWrapper = () => {
 | 
			
		||||
        visibilityChange,
 | 
			
		||||
        false,
 | 
			
		||||
      );
 | 
			
		||||
      clearTimeout(titleTimeout);
 | 
			
		||||
    };
 | 
			
		||||
  }, [isCollabDisabled, collabAPI, excalidrawAPI, setLangCode]);
 | 
			
		||||
 | 
			
		||||
@@ -1026,6 +1020,8 @@ const ExcalidrawWrapper = () => {
 | 
			
		||||
 | 
			
		||||
  const isOffline = useAtomValue(isOfflineAtom);
 | 
			
		||||
 | 
			
		||||
  const localStorageQuotaExceeded = useAtomValue(localStorageQuotaExceededAtom);
 | 
			
		||||
 | 
			
		||||
  const onCollabDialogOpen = useCallback(
 | 
			
		||||
    () => setShareDialogState({ isOpen: true, type: "collaborationOnly" }),
 | 
			
		||||
    [setShareDialogState],
 | 
			
		||||
@@ -1233,10 +1229,15 @@ const ExcalidrawWrapper = () => {
 | 
			
		||||
 | 
			
		||||
        <TTDDialogTrigger />
 | 
			
		||||
        {isCollaborating && isOffline && (
 | 
			
		||||
          <div className="collab-offline-warning">
 | 
			
		||||
          <div className="alertalert--warning">
 | 
			
		||||
            {t("alerts.collabOfflineWarning")}
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
        {localStorageQuotaExceeded && (
 | 
			
		||||
          <div className="alert alert--danger">
 | 
			
		||||
            {t("alerts.localStorageQuotaExceeded")}
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
        {latestShareableLink && (
 | 
			
		||||
          <ShareableLinkDialog
 | 
			
		||||
            link={latestShareableLink}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,8 @@ export const SYNC_BROWSER_TABS_TIMEOUT = 50;
 | 
			
		||||
export const CURSOR_SYNC_TIMEOUT = 33; // ~30fps
 | 
			
		||||
export const DELETED_ELEMENT_TIMEOUT = 24 * 60 * 60 * 1000; // 1 day
 | 
			
		||||
 | 
			
		||||
export const FILE_UPLOAD_MAX_BYTES = 3 * 1024 * 1024; // 3 MiB
 | 
			
		||||
// should be aligned with MAX_ALLOWED_FILE_BYTES
 | 
			
		||||
export const FILE_UPLOAD_MAX_BYTES = 4 * 1024 * 1024; // 4 MiB
 | 
			
		||||
// 1 year (https://stackoverflow.com/a/25201898/927631)
 | 
			
		||||
export const FILE_CACHE_MAX_AGE_SEC = 31536000;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -530,7 +530,10 @@ class Collab extends PureComponent<CollabProps, CollabState> {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!existingRoomLinkData) {
 | 
			
		||||
    if (existingRoomLinkData) {
 | 
			
		||||
      // when joining existing room, don't merge it with current scene data
 | 
			
		||||
      this.excalidrawAPI.resetScene();
 | 
			
		||||
    } else {
 | 
			
		||||
      const elements = this.excalidrawAPI.getSceneElements().map((element) => {
 | 
			
		||||
        if (isImageElement(element) && element.status === "saved") {
 | 
			
		||||
          return newElementWith(element, { status: "pending" });
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,8 @@ import {
 | 
			
		||||
  get,
 | 
			
		||||
} from "idb-keyval";
 | 
			
		||||
 | 
			
		||||
import { appJotaiStore, atom } from "excalidraw-app/app-jotai";
 | 
			
		||||
 | 
			
		||||
import type { LibraryPersistedData } from "@excalidraw/excalidraw/data/library";
 | 
			
		||||
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
 | 
			
		||||
import type { ExcalidrawElement, FileId } from "@excalidraw/element/types";
 | 
			
		||||
@@ -45,6 +47,8 @@ import { updateBrowserStateVersion } from "./tabSync";
 | 
			
		||||
 | 
			
		||||
const filesStore = createStore("files-db", "files-store");
 | 
			
		||||
 | 
			
		||||
export const localStorageQuotaExceededAtom = atom(false);
 | 
			
		||||
 | 
			
		||||
class LocalFileManager extends FileManager {
 | 
			
		||||
  clearObsoleteFiles = async (opts: { currentFileIds: FileId[] }) => {
 | 
			
		||||
    await entries(filesStore).then((entries) => {
 | 
			
		||||
@@ -69,6 +73,9 @@ const saveDataStateToLocalStorage = (
 | 
			
		||||
  elements: readonly ExcalidrawElement[],
 | 
			
		||||
  appState: AppState,
 | 
			
		||||
) => {
 | 
			
		||||
  const localStorageQuotaExceeded = appJotaiStore.get(
 | 
			
		||||
    localStorageQuotaExceededAtom,
 | 
			
		||||
  );
 | 
			
		||||
  try {
 | 
			
		||||
    const _appState = clearAppStateForLocalStorage(appState);
 | 
			
		||||
 | 
			
		||||
@@ -88,12 +95,22 @@ const saveDataStateToLocalStorage = (
 | 
			
		||||
      JSON.stringify(_appState),
 | 
			
		||||
    );
 | 
			
		||||
    updateBrowserStateVersion(STORAGE_KEYS.VERSION_DATA_STATE);
 | 
			
		||||
    if (localStorageQuotaExceeded) {
 | 
			
		||||
      appJotaiStore.set(localStorageQuotaExceededAtom, false);
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error: any) {
 | 
			
		||||
    // Unable to access window.localStorage
 | 
			
		||||
    console.error(error);
 | 
			
		||||
    if (isQuotaExceededError(error) && !localStorageQuotaExceeded) {
 | 
			
		||||
      appJotaiStore.set(localStorageQuotaExceededAtom, true);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isQuotaExceededError = (error: any) => {
 | 
			
		||||
  return error instanceof DOMException && error.name === "QuotaExceededError";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type SavingLockTypes = "collaboration";
 | 
			
		||||
 | 
			
		||||
export class LocalData {
 | 
			
		||||
 
 | 
			
		||||
@@ -259,7 +259,9 @@ export const loadFromFirebase = async (
 | 
			
		||||
  }
 | 
			
		||||
  const storedScene = docSnap.data() as FirebaseStoredScene;
 | 
			
		||||
  const elements = getSyncableElements(
 | 
			
		||||
    restoreElements(await decryptElements(storedScene, roomKey), null),
 | 
			
		||||
    restoreElements(await decryptElements(storedScene, roomKey), null, {
 | 
			
		||||
      deleteInvisibleElements: true,
 | 
			
		||||
    }),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  if (socket) {
 | 
			
		||||
 
 | 
			
		||||
@@ -258,11 +258,16 @@ export const loadScene = async (
 | 
			
		||||
      await importFromBackend(id, privateKey),
 | 
			
		||||
      localDataState?.appState,
 | 
			
		||||
      localDataState?.elements,
 | 
			
		||||
      { repairBindings: true, refreshDimensions: false },
 | 
			
		||||
      {
 | 
			
		||||
        repairBindings: true,
 | 
			
		||||
        refreshDimensions: false,
 | 
			
		||||
        deleteInvisibleElements: true,
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  } else {
 | 
			
		||||
    data = restore(localDataState || null, null, null, {
 | 
			
		||||
      repairBindings: true,
 | 
			
		||||
      deleteInvisibleElements: true,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,7 @@
 | 
			
		||||
<html lang="en">
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <title>
 | 
			
		||||
      Free, collaborative whiteboard • Hand-drawn look & feel | Excalidraw
 | 
			
		||||
    </title>
 | 
			
		||||
    <title>Excalidraw Whiteboard</title>
 | 
			
		||||
    <meta
 | 
			
		||||
      name="viewport"
 | 
			
		||||
      content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, shrink-to-fit=no"
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .collab-offline-warning {
 | 
			
		||||
  .alert {
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 6.5rem;
 | 
			
		||||
@@ -69,10 +69,18 @@
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    line-height: 1.5;
 | 
			
		||||
    border-radius: var(--border-radius-md);
 | 
			
		||||
    background-color: var(--color-warning);
 | 
			
		||||
    color: var(--color-text-warning);
 | 
			
		||||
    z-index: 6;
 | 
			
		||||
    white-space: pre;
 | 
			
		||||
 | 
			
		||||
    &--warning {
 | 
			
		||||
      background-color: var(--color-warning);
 | 
			
		||||
      color: var(--color-text-warning);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &--danger {
 | 
			
		||||
      background-color: var(--color-danger-dark);
 | 
			
		||||
      color: var(--color-danger-text);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ describe("Test MobileMenu", () => {
 | 
			
		||||
        },
 | 
			
		||||
        "isTouchScreen": false,
 | 
			
		||||
        "viewport": {
 | 
			
		||||
          "isLandscape": false,
 | 
			
		||||
          "isLandscape": true,
 | 
			
		||||
          "isMobile": true,
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user