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:
Aakansha Doshi 2022-01-27 17:51:55 +05:30 committed by GitHub
parent 18c526d877
commit ca89d47d4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 161 additions and 37 deletions

View file

@ -34,6 +34,7 @@ import {
import {
debounce,
getVersion,
isTestEnv,
preventUnload,
ResolvablePromise,
resolvablePromise,
@ -41,6 +42,8 @@ import {
import {
FIREBASE_STORAGE_PREFIXES,
SAVE_TO_LOCAL_STORAGE_TIMEOUT,
STORAGE_KEYS,
SYNC_BROWSER_TABS_TIMEOUT,
} from "./app_constants";
import CollabWrapper, {
CollabAPI,
@ -50,9 +53,10 @@ import CollabWrapper, {
import { LanguageList } from "./components/LanguageList";
import { exportToBackend, getCollaborationLinkData, loadScene } from "./data";
import {
getLibraryItemsFromStorage,
importFromLocalStorage,
importUsernameFromLocalStorage,
saveToLocalStorage,
STORAGE_KEYS,
} from "./data/localStorage";
import CustomStats from "./CustomStats";
import { restoreAppState, RestoredDataState } from "../data/restore";
@ -67,6 +71,10 @@ import { FileManager, updateStaleImageStatuses } from "./data/FileManager";
import { newElementWith } from "../element/mutateElement";
import { isInitializedImageElement } from "../element/typeChecks";
import { loadFilesFromFirebase } from "./data/firebase";
import {
isBrowserStorageStateNewer,
updateBrowserStateVersion,
} from "./data/tabSync";
const filesStore = createStore("files-db", "files-store");
@ -104,6 +112,11 @@ const localFileStorage = new FileManager({
const savedFiles = 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(
[...addedFiles].map(async ([id, fileData]) => {
try {
@ -142,7 +155,6 @@ const saveDebounced = debounce(
elements,
files,
});
onFilesSaved();
},
SAVE_TO_LOCAL_STORAGE_TIMEOUT,
@ -278,7 +290,6 @@ const ExcalidrawWrapper = () => {
currentLangCode = currentLangCode[0];
}
const [langCode, setLangCode] = useState(currentLangCode);
// initial state
// ---------------------------------------------------------------------------
@ -372,14 +383,7 @@ const ExcalidrawWrapper = () => {
}
}
try {
data.scene.libraryItems =
JSON.parse(
localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY) as string,
) || [];
} catch (error: any) {
console.error(error);
}
data.scene.libraryItems = getLibraryItemsFromStorage();
};
initializeScene({ collabAPI }).then((data) => {
@ -415,13 +419,71 @@ const ExcalidrawWrapper = () => {
() => (document.title = APP_NAME),
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.UNLOAD, onBlur, false);
window.addEventListener(EVENT.BLUR, onBlur, false);
document.addEventListener(EVENT.VISIBILITY_CHANGE, syncData, false);
window.addEventListener(EVENT.FOCUS, syncData, false);
return () => {
window.removeEventListener(EVENT.HASHCHANGE, onHashChange, false);
window.removeEventListener(EVENT.UNLOAD, onBlur, false);
window.removeEventListener(EVENT.BLUR, onBlur, false);
window.removeEventListener(EVENT.FOCUS, syncData, false);
document.removeEventListener(EVENT.VISIBILITY_CHANGE, syncData, false);
clearTimeout(titleTimeout);
};
}, [collabAPI, excalidrawAPI]);