Merge remote-tracking branch 'origin/master' into danieljgeiger-mathjax

This commit is contained in:
Daniel J. Geiger
2023-02-07 21:10:26 -06:00
70 changed files with 1217 additions and 383 deletions
+10
View File
@@ -75,6 +75,7 @@ import { jotaiStore } from "../../jotai";
export const collabAPIAtom = atom<CollabAPI | null>(null);
export const collabDialogShownAtom = atom(false);
export const isCollaboratingAtom = atom(false);
export const isOfflineAtom = atom(false);
interface CollabState {
errorMessage: string;
@@ -152,6 +153,8 @@ class Collab extends PureComponent<Props, CollabState> {
componentDidMount() {
window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
window.addEventListener("online", this.onOfflineStatusToggle);
window.addEventListener("offline", this.onOfflineStatusToggle);
window.addEventListener(EVENT.UNLOAD, this.onUnload);
const collabAPI: CollabAPI = {
@@ -165,6 +168,7 @@ class Collab extends PureComponent<Props, CollabState> {
};
jotaiStore.set(collabAPIAtom, collabAPI);
this.onOfflineStatusToggle();
if (
process.env.NODE_ENV === ENV.TEST ||
@@ -180,7 +184,13 @@ class Collab extends PureComponent<Props, CollabState> {
}
}
onOfflineStatusToggle = () => {
jotaiStore.set(isOfflineAtom, !window.navigator.onLine);
};
componentWillUnmount() {
window.removeEventListener("online", this.onOfflineStatusToggle);
window.removeEventListener("offline", this.onOfflineStatusToggle);
window.removeEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
window.removeEventListener(EVENT.UNLOAD, this.onUnload);
window.removeEventListener(EVENT.POINTER_MOVE, this.onPointerMove);
+18 -34
View File
@@ -1,6 +1,7 @@
import { PRECEDING_ELEMENT_KEY } from "../../constants";
import { ExcalidrawElement } from "../../element/types";
import { AppState } from "../../types";
import { arrayToMapWithIndex } from "../../utils";
export type ReconciledElements = readonly ExcalidrawElement[] & {
_brand: "reconciledElements";
@@ -33,30 +34,13 @@ const shouldDiscardRemoteElement = (
return false;
};
const getElementsMapWithIndex = <T extends ExcalidrawElement>(
elements: readonly T[],
) =>
elements.reduce(
(
acc: {
[key: string]: [element: T, index: number] | undefined;
},
element: T,
idx,
) => {
acc[element.id] = [element, idx];
return acc;
},
{},
);
export const reconcileElements = (
localElements: readonly ExcalidrawElement[],
remoteElements: readonly BroadcastedExcalidrawElement[],
localAppState: AppState,
): ReconciledElements => {
const localElementsData =
getElementsMapWithIndex<ExcalidrawElement>(localElements);
arrayToMapWithIndex<ExcalidrawElement>(localElements);
const reconciledElements: ExcalidrawElement[] = localElements.slice();
@@ -69,7 +53,7 @@ export const reconcileElements = (
for (const remoteElement of remoteElements) {
remoteElementIdx++;
const local = localElementsData[remoteElement.id];
const local = localElementsData.get(remoteElement.id);
if (shouldDiscardRemoteElement(localAppState, local?.[0], remoteElement)) {
if (remoteElement[PRECEDING_ELEMENT_KEY]) {
@@ -105,21 +89,21 @@ export const reconcileElements = (
offset++;
if (cursor === 0) {
reconciledElements.unshift(remoteElement);
localElementsData[remoteElement.id] = [
localElementsData.set(remoteElement.id, [
remoteElement,
cursor - offset,
];
]);
} else {
reconciledElements.splice(cursor + 1, 0, remoteElement);
localElementsData[remoteElement.id] = [
localElementsData.set(remoteElement.id, [
remoteElement,
cursor + 1 - offset,
];
]);
cursor++;
}
} else {
let idx = localElementsData[parent]
? localElementsData[parent]![1]
let idx = localElementsData.has(parent)
? localElementsData.get(parent)![1]
: null;
if (idx != null) {
idx += offset;
@@ -127,38 +111,38 @@ export const reconcileElements = (
if (idx != null && idx >= cursor) {
reconciledElements.splice(idx + 1, 0, remoteElement);
offset++;
localElementsData[remoteElement.id] = [
localElementsData.set(remoteElement.id, [
remoteElement,
idx + 1 - offset,
];
]);
cursor = idx + 1;
} else if (idx != null) {
reconciledElements.splice(cursor + 1, 0, remoteElement);
offset++;
localElementsData[remoteElement.id] = [
localElementsData.set(remoteElement.id, [
remoteElement,
cursor + 1 - offset,
];
]);
cursor++;
} else {
reconciledElements.push(remoteElement);
localElementsData[remoteElement.id] = [
localElementsData.set(remoteElement.id, [
remoteElement,
reconciledElements.length - 1 - offset,
];
]);
}
}
// no parent z-index information, local element exists → replace in place
} else if (local) {
reconciledElements[local[1]] = remoteElement;
localElementsData[remoteElement.id] = [remoteElement, local[1]];
localElementsData.set(remoteElement.id, [remoteElement, local[1]]);
// otherwise push to the end
} else {
reconciledElements.push(remoteElement);
localElementsData[remoteElement.id] = [
localElementsData.set(remoteElement.id, [
remoteElement,
reconciledElements.length - 1 - offset,
];
]);
}
}
+17
View File
@@ -45,6 +45,23 @@
}
}
}
.collab-offline-warning {
pointer-events: none;
position: absolute;
top: 6.5rem;
left: 50%;
transform: translateX(-50%);
padding: 0.5rem 1rem;
font-size: 0.875rem;
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;
}
}
.excalidraw-app.is-collaborating {
+11 -4
View File
@@ -53,6 +53,7 @@ import Collab, {
collabAPIAtom,
collabDialogShownAtom,
isCollaboratingAtom,
isOfflineAtom,
} from "./collab/Collab";
import {
exportToBackend,
@@ -67,10 +68,7 @@ import {
} from "./data/localStorage";
import CustomStats from "./CustomStats";
import { restore, restoreAppState, RestoredDataState } from "../data/restore";
import "./index.scss";
import { ExportToExcalidrawPlus } from "./components/ExportToExcalidrawPlus";
import { updateStaleImageStatuses } from "./data/FileManager";
import { newElementWith } from "../element/mutateElement";
import { isInitializedImageElement } from "../element/typeChecks";
@@ -78,7 +76,7 @@ import { loadFilesFromFirebase } from "./data/firebase";
import { LocalData } from "./data/LocalData";
import { isBrowserStorageStateNewer } from "./data/tabSync";
import clsx from "clsx";
import { atom, Provider, useAtom } from "jotai";
import { atom, Provider, useAtom, useAtomValue } from "jotai";
import { jotaiStore, useAtomWithInitialValue } from "../jotai";
import { reconcileElements } from "./collab/reconciliation";
import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library";
@@ -86,6 +84,8 @@ import { AppMainMenu } from "./components/AppMainMenu";
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
import { AppFooter } from "./components/AppFooter";
import "./index.scss";
polyfill();
window.EXCALIDRAW_THROTTLE_RENDER = true;
@@ -604,6 +604,8 @@ const ExcalidrawWrapper = () => {
localStorage.setItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY, serializedItems);
};
const isOffline = useAtomValue(isOfflineAtom);
return (
<div
style={{ height: "100%" }}
@@ -666,6 +668,11 @@ const ExcalidrawWrapper = () => {
/>
<AppWelcomeScreen setCollabDialogShown={setCollabDialogShown} />
<AppFooter />
{isCollaborating && isOffline && (
<div className="collab-offline-warning">
{t("alerts.collabOfflineWarning")}
</div>
)}
</Excalidraw>
{excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />}
{errorMessage && (