feat: Sync local storage state across tabs when out of sync (#4545)
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
18c526d877
commit
ca89d47d4c
@ -201,13 +201,7 @@ describe("textWysiwyg", () => {
|
|||||||
|
|
||||||
describe("Test bounded text", () => {
|
describe("Test bounded text", () => {
|
||||||
let rectangle: any;
|
let rectangle: any;
|
||||||
const {
|
const { h } = window;
|
||||||
h,
|
|
||||||
}: {
|
|
||||||
h: {
|
|
||||||
elements: any;
|
|
||||||
};
|
|
||||||
} = window;
|
|
||||||
|
|
||||||
const DUMMY_HEIGHT = 240;
|
const DUMMY_HEIGHT = 240;
|
||||||
const DUMMY_WIDTH = 160;
|
const DUMMY_WIDTH = 160;
|
||||||
@ -222,6 +216,7 @@ describe("textWysiwyg", () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await render(<ExcalidrawApp />);
|
await render(<ExcalidrawApp />);
|
||||||
|
h.elements = [];
|
||||||
|
|
||||||
rectangle = UI.createElement("rectangle", {
|
rectangle = UI.createElement("rectangle", {
|
||||||
x: 10,
|
x: 10,
|
||||||
@ -249,9 +244,9 @@ describe("textWysiwyg", () => {
|
|||||||
".excalidraw-textEditorContainer > textarea",
|
".excalidraw-textEditorContainer > textarea",
|
||||||
) as HTMLTextAreaElement;
|
) as HTMLTextAreaElement;
|
||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 0));
|
|
||||||
|
|
||||||
fireEvent.change(editor, { target: { value: "Hello World!" } });
|
fireEvent.change(editor, { target: { value: "Hello World!" } });
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
editor.blur();
|
editor.blur();
|
||||||
expect(rectangle.boundElements).toStrictEqual([
|
expect(rectangle.boundElements).toStrictEqual([
|
||||||
{ id: text.id, type: "text" },
|
{ id: text.id, type: "text" },
|
||||||
@ -285,6 +280,8 @@ describe("textWysiwyg", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should update font family correctly on undo/redo by selecting bounded text when font family was updated", async () => {
|
it("should update font family correctly on undo/redo by selecting bounded text when font family was updated", async () => {
|
||||||
|
expect(h.elements.length).toBe(1);
|
||||||
|
|
||||||
mouse.doubleClickAt(
|
mouse.doubleClickAt(
|
||||||
rectangle.x + rectangle.width / 2,
|
rectangle.x + rectangle.width / 2,
|
||||||
rectangle.y + rectangle.height / 2,
|
rectangle.y + rectangle.height / 2,
|
||||||
@ -316,19 +313,25 @@ describe("textWysiwyg", () => {
|
|||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 0));
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
editor.blur();
|
editor.blur();
|
||||||
expect(h.elements[1].fontFamily).toEqual(FONT_FAMILY.Cascadia);
|
expect(
|
||||||
|
(h.elements[1] as ExcalidrawTextElementWithContainer).fontFamily,
|
||||||
|
).toEqual(FONT_FAMILY.Cascadia);
|
||||||
|
|
||||||
//undo
|
//undo
|
||||||
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
||||||
Keyboard.keyPress(KEYS.Z);
|
Keyboard.keyPress(KEYS.Z);
|
||||||
});
|
});
|
||||||
expect(h.elements[1].fontFamily).toEqual(FONT_FAMILY.Virgil);
|
expect(
|
||||||
|
(h.elements[1] as ExcalidrawTextElementWithContainer).fontFamily,
|
||||||
|
).toEqual(FONT_FAMILY.Virgil);
|
||||||
|
|
||||||
//redo
|
//redo
|
||||||
Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => {
|
Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => {
|
||||||
Keyboard.keyPress(KEYS.Z);
|
Keyboard.keyPress(KEYS.Z);
|
||||||
});
|
});
|
||||||
expect(h.elements[1].fontFamily).toEqual(FONT_FAMILY.Cascadia);
|
expect(
|
||||||
|
(h.elements[1] as ExcalidrawTextElementWithContainer).fontFamily,
|
||||||
|
).toEqual(FONT_FAMILY.Cascadia);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should wrap text and vertcially center align once text submitted", async () => {
|
it("should wrap text and vertcially center align once text submitted", async () => {
|
||||||
@ -365,10 +368,9 @@ describe("textWysiwyg", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
Keyboard.withModifierKeys({}, () => {
|
expect(h.elements.length).toBe(1);
|
||||||
Keyboard.keyPress(KEYS.ENTER);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
Keyboard.keyDown(KEYS.ENTER);
|
||||||
let text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
let text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||||
let editor = document.querySelector(
|
let editor = document.querySelector(
|
||||||
".excalidraw-textEditorContainer > textarea",
|
".excalidraw-textEditorContainer > textarea",
|
||||||
|
@ -4,6 +4,7 @@ export const INITIAL_SCENE_UPDATE_TIMEOUT = 5000;
|
|||||||
export const FILE_UPLOAD_TIMEOUT = 300;
|
export const FILE_UPLOAD_TIMEOUT = 300;
|
||||||
export const LOAD_IMAGES_TIMEOUT = 500;
|
export const LOAD_IMAGES_TIMEOUT = 500;
|
||||||
export const SYNC_FULL_SCENE_INTERVAL_MS = 20000;
|
export const SYNC_FULL_SCENE_INTERVAL_MS = 20000;
|
||||||
|
export const SYNC_BROWSER_TABS_TIMEOUT = 50;
|
||||||
|
|
||||||
export const FILE_UPLOAD_MAX_BYTES = 3 * 1024 * 1024; // 3 MiB
|
export const FILE_UPLOAD_MAX_BYTES = 3 * 1024 * 1024; // 3 MiB
|
||||||
// 1 year (https://stackoverflow.com/a/25201898/927631)
|
// 1 year (https://stackoverflow.com/a/25201898/927631)
|
||||||
@ -25,3 +26,13 @@ export const FIREBASE_STORAGE_PREFIXES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ROOM_ID_BYTES = 10;
|
export const ROOM_ID_BYTES = 10;
|
||||||
|
|
||||||
|
export const STORAGE_KEYS = {
|
||||||
|
LOCAL_STORAGE_ELEMENTS: "excalidraw",
|
||||||
|
LOCAL_STORAGE_APP_STATE: "excalidraw-state",
|
||||||
|
LOCAL_STORAGE_COLLAB: "excalidraw-collab",
|
||||||
|
LOCAL_STORAGE_KEY_COLLAB_FORCE_FLAG: "collabLinkForceLoadFlag",
|
||||||
|
LOCAL_STORAGE_LIBRARY: "excalidraw-library",
|
||||||
|
VERSION_DATA_STATE: "version-dataState",
|
||||||
|
VERSION_FILES: "version-files",
|
||||||
|
} as const;
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
INITIAL_SCENE_UPDATE_TIMEOUT,
|
INITIAL_SCENE_UPDATE_TIMEOUT,
|
||||||
LOAD_IMAGES_TIMEOUT,
|
LOAD_IMAGES_TIMEOUT,
|
||||||
SCENE,
|
SCENE,
|
||||||
|
STORAGE_KEYS,
|
||||||
SYNC_FULL_SCENE_INTERVAL_MS,
|
SYNC_FULL_SCENE_INTERVAL_MS,
|
||||||
} from "../app_constants";
|
} from "../app_constants";
|
||||||
import {
|
import {
|
||||||
@ -39,7 +40,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
importUsernameFromLocalStorage,
|
importUsernameFromLocalStorage,
|
||||||
saveUsernameToLocalStorage,
|
saveUsernameToLocalStorage,
|
||||||
STORAGE_KEYS,
|
|
||||||
} from "../data/localStorage";
|
} from "../data/localStorage";
|
||||||
import Portal from "./Portal";
|
import Portal from "./Portal";
|
||||||
import RoomDialog from "./RoomDialog";
|
import RoomDialog from "./RoomDialog";
|
||||||
@ -65,6 +65,7 @@ import {
|
|||||||
reconcileElements as _reconcileElements,
|
reconcileElements as _reconcileElements,
|
||||||
} from "./reconciliation";
|
} from "./reconciliation";
|
||||||
import { decryptData } from "../../data/encryption";
|
import { decryptData } from "../../data/encryption";
|
||||||
|
import { resetBrowserStateVersions } from "../data/tabSync";
|
||||||
|
|
||||||
interface CollabState {
|
interface CollabState {
|
||||||
modalIsShown: boolean;
|
modalIsShown: boolean;
|
||||||
@ -86,6 +87,7 @@ export interface CollabAPI {
|
|||||||
onCollabButtonClick: CollabInstance["onCollabButtonClick"];
|
onCollabButtonClick: CollabInstance["onCollabButtonClick"];
|
||||||
broadcastElements: CollabInstance["broadcastElements"];
|
broadcastElements: CollabInstance["broadcastElements"];
|
||||||
fetchImageFilesFromFirebase: CollabInstance["fetchImageFilesFromFirebase"];
|
fetchImageFilesFromFirebase: CollabInstance["fetchImageFilesFromFirebase"];
|
||||||
|
setUsername: (username: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -246,6 +248,10 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
|
|
||||||
this.saveCollabRoomToFirebase();
|
this.saveCollabRoomToFirebase();
|
||||||
if (window.confirm(t("alerts.collabStopOverridePrompt"))) {
|
if (window.confirm(t("alerts.collabStopOverridePrompt"))) {
|
||||||
|
// hack to ensure that we prefer we disregard any new browser state
|
||||||
|
// that could have been saved in other tabs while we were collaborating
|
||||||
|
resetBrowserStateVersions();
|
||||||
|
|
||||||
window.history.pushState({}, APP_NAME, window.location.origin);
|
window.history.pushState({}, APP_NAME, window.location.origin);
|
||||||
this.destroySocketClient();
|
this.destroySocketClient();
|
||||||
trackEvent("share", "room closed");
|
trackEvent("share", "room closed");
|
||||||
@ -677,8 +683,12 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
this.setState({ modalIsShown: false });
|
this.setState({ modalIsShown: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
onUsernameChange = (username: string) => {
|
setUsername = (username: string) => {
|
||||||
this.setState({ username });
|
this.setState({ username });
|
||||||
|
};
|
||||||
|
|
||||||
|
onUsernameChange = (username: string) => {
|
||||||
|
this.setUsername(username);
|
||||||
saveUsernameToLocalStorage(username);
|
saveUsernameToLocalStorage(username);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -712,6 +722,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
this.contextValue.broadcastElements = this.broadcastElements;
|
this.contextValue.broadcastElements = this.broadcastElements;
|
||||||
this.contextValue.fetchImageFilesFromFirebase =
|
this.contextValue.fetchImageFilesFromFirebase =
|
||||||
this.fetchImageFilesFromFirebase;
|
this.fetchImageFilesFromFirebase;
|
||||||
|
this.contextValue.setUsername = this.setUsername;
|
||||||
return this.contextValue;
|
return this.contextValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,14 +5,8 @@ import {
|
|||||||
getDefaultAppState,
|
getDefaultAppState,
|
||||||
} from "../../appState";
|
} from "../../appState";
|
||||||
import { clearElementsForLocalStorage } from "../../element";
|
import { clearElementsForLocalStorage } from "../../element";
|
||||||
|
import { updateBrowserStateVersion } from "./tabSync";
|
||||||
export const STORAGE_KEYS = {
|
import { STORAGE_KEYS } from "../app_constants";
|
||||||
LOCAL_STORAGE_ELEMENTS: "excalidraw",
|
|
||||||
LOCAL_STORAGE_APP_STATE: "excalidraw-state",
|
|
||||||
LOCAL_STORAGE_COLLAB: "excalidraw-collab",
|
|
||||||
LOCAL_STORAGE_KEY_COLLAB_FORCE_FLAG: "collabLinkForceLoadFlag",
|
|
||||||
LOCAL_STORAGE_LIBRARY: "excalidraw-library",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const saveUsernameToLocalStorage = (username: string) => {
|
export const saveUsernameToLocalStorage = (username: string) => {
|
||||||
try {
|
try {
|
||||||
@ -53,6 +47,7 @@ export const saveToLocalStorage = (
|
|||||||
STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,
|
STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,
|
||||||
JSON.stringify(clearAppStateForLocalStorage(appState)),
|
JSON.stringify(clearAppStateForLocalStorage(appState)),
|
||||||
);
|
);
|
||||||
|
updateBrowserStateVersion(STORAGE_KEYS.VERSION_DATA_STATE);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Unable to access window.localStorage
|
// Unable to access window.localStorage
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -125,3 +120,17 @@ export const getTotalStorageSize = () => {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getLibraryItemsFromStorage = () => {
|
||||||
|
try {
|
||||||
|
const libraryItems =
|
||||||
|
JSON.parse(
|
||||||
|
localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY) as string,
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
return libraryItems;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
29
src/excalidraw-app/data/tabSync.ts
Normal file
29
src/excalidraw-app/data/tabSync.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { STORAGE_KEYS } from "../app_constants";
|
||||||
|
|
||||||
|
// in-memory state (this tab's current state) versions. Currently just
|
||||||
|
// timestamps of the last time the state was saved to browser storage.
|
||||||
|
const LOCAL_STATE_VERSIONS = {
|
||||||
|
[STORAGE_KEYS.VERSION_DATA_STATE]: -1,
|
||||||
|
[STORAGE_KEYS.VERSION_FILES]: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
type BrowserStateTypes = keyof typeof LOCAL_STATE_VERSIONS;
|
||||||
|
|
||||||
|
export const isBrowserStorageStateNewer = (type: BrowserStateTypes) => {
|
||||||
|
const storageTimestamp = JSON.parse(localStorage.getItem(type) || "-1");
|
||||||
|
return storageTimestamp > LOCAL_STATE_VERSIONS[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateBrowserStateVersion = (type: BrowserStateTypes) => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
localStorage.setItem(type, JSON.stringify(timestamp));
|
||||||
|
LOCAL_STATE_VERSIONS[type] = timestamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetBrowserStateVersions = () => {
|
||||||
|
for (const key of Object.keys(LOCAL_STATE_VERSIONS) as BrowserStateTypes[]) {
|
||||||
|
const timestamp = -1;
|
||||||
|
localStorage.setItem(key, JSON.stringify(timestamp));
|
||||||
|
LOCAL_STATE_VERSIONS[key] = timestamp;
|
||||||
|
}
|
||||||
|
};
|
@ -34,6 +34,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
getVersion,
|
getVersion,
|
||||||
|
isTestEnv,
|
||||||
preventUnload,
|
preventUnload,
|
||||||
ResolvablePromise,
|
ResolvablePromise,
|
||||||
resolvablePromise,
|
resolvablePromise,
|
||||||
@ -41,6 +42,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
FIREBASE_STORAGE_PREFIXES,
|
FIREBASE_STORAGE_PREFIXES,
|
||||||
SAVE_TO_LOCAL_STORAGE_TIMEOUT,
|
SAVE_TO_LOCAL_STORAGE_TIMEOUT,
|
||||||
|
STORAGE_KEYS,
|
||||||
|
SYNC_BROWSER_TABS_TIMEOUT,
|
||||||
} from "./app_constants";
|
} from "./app_constants";
|
||||||
import CollabWrapper, {
|
import CollabWrapper, {
|
||||||
CollabAPI,
|
CollabAPI,
|
||||||
@ -50,9 +53,10 @@ import CollabWrapper, {
|
|||||||
import { LanguageList } from "./components/LanguageList";
|
import { LanguageList } from "./components/LanguageList";
|
||||||
import { exportToBackend, getCollaborationLinkData, loadScene } from "./data";
|
import { exportToBackend, getCollaborationLinkData, loadScene } from "./data";
|
||||||
import {
|
import {
|
||||||
|
getLibraryItemsFromStorage,
|
||||||
importFromLocalStorage,
|
importFromLocalStorage,
|
||||||
|
importUsernameFromLocalStorage,
|
||||||
saveToLocalStorage,
|
saveToLocalStorage,
|
||||||
STORAGE_KEYS,
|
|
||||||
} from "./data/localStorage";
|
} from "./data/localStorage";
|
||||||
import CustomStats from "./CustomStats";
|
import CustomStats from "./CustomStats";
|
||||||
import { restoreAppState, RestoredDataState } from "../data/restore";
|
import { restoreAppState, RestoredDataState } from "../data/restore";
|
||||||
@ -67,6 +71,10 @@ import { FileManager, updateStaleImageStatuses } from "./data/FileManager";
|
|||||||
import { newElementWith } from "../element/mutateElement";
|
import { newElementWith } from "../element/mutateElement";
|
||||||
import { isInitializedImageElement } from "../element/typeChecks";
|
import { isInitializedImageElement } from "../element/typeChecks";
|
||||||
import { loadFilesFromFirebase } from "./data/firebase";
|
import { loadFilesFromFirebase } from "./data/firebase";
|
||||||
|
import {
|
||||||
|
isBrowserStorageStateNewer,
|
||||||
|
updateBrowserStateVersion,
|
||||||
|
} from "./data/tabSync";
|
||||||
|
|
||||||
const filesStore = createStore("files-db", "files-store");
|
const filesStore = createStore("files-db", "files-store");
|
||||||
|
|
||||||
@ -104,6 +112,11 @@ const localFileStorage = new FileManager({
|
|||||||
const savedFiles = new Map<FileId, true>();
|
const savedFiles = new Map<FileId, true>();
|
||||||
const erroredFiles = new Map<FileId, true>();
|
const erroredFiles = new Map<FileId, true>();
|
||||||
|
|
||||||
|
// before we use `storage` event synchronization, let's update the flag
|
||||||
|
// optimistically. Hopefully nothing fails, and an IDB read executed
|
||||||
|
// before an IDB write finishes will read the latest value.
|
||||||
|
updateBrowserStateVersion(STORAGE_KEYS.VERSION_FILES);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
[...addedFiles].map(async ([id, fileData]) => {
|
[...addedFiles].map(async ([id, fileData]) => {
|
||||||
try {
|
try {
|
||||||
@ -142,7 +155,6 @@ const saveDebounced = debounce(
|
|||||||
elements,
|
elements,
|
||||||
files,
|
files,
|
||||||
});
|
});
|
||||||
|
|
||||||
onFilesSaved();
|
onFilesSaved();
|
||||||
},
|
},
|
||||||
SAVE_TO_LOCAL_STORAGE_TIMEOUT,
|
SAVE_TO_LOCAL_STORAGE_TIMEOUT,
|
||||||
@ -278,7 +290,6 @@ const ExcalidrawWrapper = () => {
|
|||||||
currentLangCode = currentLangCode[0];
|
currentLangCode = currentLangCode[0];
|
||||||
}
|
}
|
||||||
const [langCode, setLangCode] = useState(currentLangCode);
|
const [langCode, setLangCode] = useState(currentLangCode);
|
||||||
|
|
||||||
// initial state
|
// initial state
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -372,14 +383,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
data.scene.libraryItems = getLibraryItemsFromStorage();
|
||||||
data.scene.libraryItems =
|
|
||||||
JSON.parse(
|
|
||||||
localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY) as string,
|
|
||||||
) || [];
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
initializeScene({ collabAPI }).then((data) => {
|
initializeScene({ collabAPI }).then((data) => {
|
||||||
@ -415,13 +419,71 @@ const ExcalidrawWrapper = () => {
|
|||||||
() => (document.title = APP_NAME),
|
() => (document.title = APP_NAME),
|
||||||
TITLE_TIMEOUT,
|
TITLE_TIMEOUT,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const syncData = debounce(() => {
|
||||||
|
if (isTestEnv()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!document.hidden && !collabAPI.isCollaborating()) {
|
||||||
|
// don't sync if local state is newer or identical to browser state
|
||||||
|
if (isBrowserStorageStateNewer(STORAGE_KEYS.VERSION_DATA_STATE)) {
|
||||||
|
const localDataState = importFromLocalStorage();
|
||||||
|
const username = importUsernameFromLocalStorage();
|
||||||
|
let langCode = languageDetector.detect() || defaultLang.code;
|
||||||
|
if (Array.isArray(langCode)) {
|
||||||
|
langCode = langCode[0];
|
||||||
|
}
|
||||||
|
setLangCode(langCode);
|
||||||
|
excalidrawAPI.updateScene({
|
||||||
|
...localDataState,
|
||||||
|
libraryItems: getLibraryItemsFromStorage(),
|
||||||
|
});
|
||||||
|
collabAPI.setUsername(username || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBrowserStorageStateNewer(STORAGE_KEYS.VERSION_FILES)) {
|
||||||
|
const elements = excalidrawAPI.getSceneElementsIncludingDeleted();
|
||||||
|
const currFiles = excalidrawAPI.getFiles();
|
||||||
|
const fileIds =
|
||||||
|
elements?.reduce((acc, element) => {
|
||||||
|
if (
|
||||||
|
isInitializedImageElement(element) &&
|
||||||
|
// only load and update images that aren't already loaded
|
||||||
|
!currFiles[element.fileId]
|
||||||
|
) {
|
||||||
|
return acc.concat(element.fileId);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, [] as FileId[]) || [];
|
||||||
|
if (fileIds.length) {
|
||||||
|
localFileStorage
|
||||||
|
.getFiles(fileIds)
|
||||||
|
.then(({ loadedFiles, erroredFiles }) => {
|
||||||
|
if (loadedFiles.length) {
|
||||||
|
excalidrawAPI.addFiles(loadedFiles);
|
||||||
|
}
|
||||||
|
updateStaleImageStatuses({
|
||||||
|
excalidrawAPI,
|
||||||
|
erroredFiles,
|
||||||
|
elements: excalidrawAPI.getSceneElementsIncludingDeleted(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, SYNC_BROWSER_TABS_TIMEOUT);
|
||||||
|
|
||||||
window.addEventListener(EVENT.HASHCHANGE, onHashChange, false);
|
window.addEventListener(EVENT.HASHCHANGE, onHashChange, false);
|
||||||
window.addEventListener(EVENT.UNLOAD, onBlur, false);
|
window.addEventListener(EVENT.UNLOAD, onBlur, false);
|
||||||
window.addEventListener(EVENT.BLUR, onBlur, false);
|
window.addEventListener(EVENT.BLUR, onBlur, false);
|
||||||
|
document.addEventListener(EVENT.VISIBILITY_CHANGE, syncData, false);
|
||||||
|
window.addEventListener(EVENT.FOCUS, syncData, false);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener(EVENT.HASHCHANGE, onHashChange, false);
|
window.removeEventListener(EVENT.HASHCHANGE, onHashChange, false);
|
||||||
window.removeEventListener(EVENT.UNLOAD, onBlur, false);
|
window.removeEventListener(EVENT.UNLOAD, onBlur, false);
|
||||||
window.removeEventListener(EVENT.BLUR, onBlur, false);
|
window.removeEventListener(EVENT.BLUR, onBlur, false);
|
||||||
|
window.removeEventListener(EVENT.FOCUS, syncData, false);
|
||||||
|
document.removeEventListener(EVENT.VISIBILITY_CHANGE, syncData, false);
|
||||||
clearTimeout(titleTimeout);
|
clearTimeout(titleTimeout);
|
||||||
};
|
};
|
||||||
}, [collabAPI, excalidrawAPI]);
|
}, [collabAPI, excalidrawAPI]);
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
|
|
||||||
import * as toolQueries from "./queries/toolQueries";
|
import * as toolQueries from "./queries/toolQueries";
|
||||||
import { ImportedDataState } from "../data/types";
|
import { ImportedDataState } from "../data/types";
|
||||||
import { STORAGE_KEYS } from "../excalidraw-app/data/localStorage";
|
import { STORAGE_KEYS } from "../excalidraw-app/app_constants";
|
||||||
|
|
||||||
import { SceneData } from "../types";
|
import { SceneData } from "../types";
|
||||||
import { getSelectedElements } from "../scene/selection";
|
import { getSelectedElements } from "../scene/selection";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user