feat: move logic from index.tsx into Collab.tsx

This commit is contained in:
Arnošt Pleskot 2023-06-04 11:40:10 +02:00
parent aa91af8f7d
commit e1ff9791f2
No known key found for this signature in database
4 changed files with 61 additions and 76 deletions

View File

@ -7,13 +7,12 @@ export const SYNC_FULL_SCENE_INTERVAL_MS = 20000;
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 PAUSE_COLLABORATION_TIMEOUT = 30000;
export const FILE_UPLOAD_MAX_BYTES = 3 * 1024 * 1024; // 3 MiB
// 1 year (https://stackoverflow.com/a/25201898/927631)
export const FILE_CACHE_MAX_AGE_SEC = 31536000;
export const PAUSE_COLLABORATION_TIMEOUT = 30000;
export const WS_EVENTS = {
SERVER_VOLATILE: "server-volatile-broadcast",
SERVER: "server-broadcast",

View File

@ -1,6 +1,6 @@
import throttle from "lodash.throttle";
import { PureComponent } from "react";
import { ExcalidrawImperativeAPI } from "../../types";
import { ExcalidrawImperativeAPI, PauseCollaborationState } from "../../types";
import { ErrorDialog } from "../../components/ErrorDialog";
import { APP_NAME, ENV, EVENT } from "../../constants";
import { ImportedDataState } from "../../data/types";
@ -24,6 +24,7 @@ import {
FIREBASE_STORAGE_PREFIXES,
INITIAL_SCENE_UPDATE_TIMEOUT,
LOAD_IMAGES_TIMEOUT,
PAUSE_COLLABORATION_TIMEOUT,
WS_SCENE_EVENT_TYPES,
SYNC_FULL_SCENE_INTERVAL_MS,
} from "../app_constants";
@ -92,8 +93,6 @@ export interface CollabAPI {
onPointerUpdate: CollabInstance["onPointerUpdate"];
startCollaboration: CollabInstance["startCollaboration"];
stopCollaboration: CollabInstance["stopCollaboration"];
pauseCollaboration: CollabInstance["pauseCollaboration"];
resumeCollaboration: CollabInstance["resumeCollaboration"];
syncElements: CollabInstance["syncElements"];
fetchImageFilesFromFirebase: CollabInstance["fetchImageFilesFromFirebase"];
setUsername: (username: string) => void;
@ -112,6 +111,7 @@ class Collab extends PureComponent<Props, CollabState> {
excalidrawAPI: Props["excalidrawAPI"];
activeIntervalId: number | null;
idleTimeoutId: number | null;
pauseTimeoutId: number | null;
private socketInitializationTimer?: number;
private lastBroadcastedOrReceivedSceneVersion: number = -1;
@ -153,6 +153,7 @@ class Collab extends PureComponent<Props, CollabState> {
this.excalidrawAPI = props.excalidrawAPI;
this.activeIntervalId = null;
this.idleTimeoutId = null;
this.pauseTimeoutId = null;
}
componentDidMount() {
@ -171,8 +172,6 @@ class Collab extends PureComponent<Props, CollabState> {
fetchImageFilesFromFirebase: this.fetchImageFilesFromFirebase,
stopCollaboration: this.stopCollaboration,
setUsername: this.setUsername,
pauseCollaboration: this.pauseCollaboration,
resumeCollaboration: this.resumeCollaboration,
isPaused: this.isPaused,
};
@ -214,6 +213,10 @@ class Collab extends PureComponent<Props, CollabState> {
window.clearTimeout(this.idleTimeoutId);
this.idleTimeoutId = null;
}
if (this.pauseTimeoutId) {
window.clearTimeout(this.pauseTimeoutId);
this.pauseTimeoutId = null;
}
}
isCollaborating = () => appJotaiStore.get(isCollaboratingAtom)!;
@ -317,31 +320,44 @@ class Collab extends PureComponent<Props, CollabState> {
}
};
pauseCollaboration = (callback?: () => void) => {
if (this.portal.socket) {
this.reportIdle();
this.portal.socket.disconnect();
this.portal.socketInitialized = false;
this.setIsCollaborationPaused(true);
onPauseCollaborationChange = (state: PauseCollaborationState) => {
switch (state) {
case PauseCollaborationState.PAUSE: {
if (this.portal.socket) {
this.portal.socket.disconnect();
this.portal.socketInitialized = false;
this.setIsCollaborationPaused(true);
if (callback) {
callback();
}
}
};
resumeCollaboration = (callback?: () => void) => {
if (this.portal.socket) {
this.reportActive();
this.portal.socket.connect();
this.portal.socketInitialized = true;
this.portal.socket.emit(WS_SCENE_EVENT_TYPES.INIT);
this.portal.socket.once("client-broadcast", () => {
this.setIsCollaborationPaused(false);
if (callback) {
callback();
this.excalidrawAPI.updateScene({
appState: { viewModeEnabled: true },
});
}
});
break;
}
case PauseCollaborationState.RESUME: {
if (this.portal.socket && this.isPaused()) {
this.portal.socket.connect();
this.portal.socketInitialized = true;
this.portal.socket.emit(WS_SCENE_EVENT_TYPES.INIT);
this.excalidrawAPI.setToast({
message: t("toast.reconnectRoomServer"),
duration: Infinity,
closable: false,
});
}
break;
}
case PauseCollaborationState.SYNC: {
if (this.isPaused()) {
this.setIsCollaborationPaused(false);
this.excalidrawAPI.updateScene({
appState: { viewModeEnabled: false },
});
this.excalidrawAPI.setToast(null);
}
}
}
};
@ -550,6 +566,7 @@ class Collab extends PureComponent<Props, CollabState> {
this.handleRemoteSceneUpdate(
this.reconcileElements(decryptedData.payload.elements),
);
this.onPauseCollaborationChange(PauseCollaborationState.SYNC);
break;
case "MOUSE_LOCATION": {
const { pointer, button, username, selectedElementIds } =
@ -737,6 +754,10 @@ class Collab extends PureComponent<Props, CollabState> {
window.clearInterval(this.activeIntervalId);
this.activeIntervalId = null;
}
this.pauseTimeoutId = window.setTimeout(
() => this.onPauseCollaborationChange(PauseCollaborationState.PAUSE),
PAUSE_COLLABORATION_TIMEOUT,
);
this.onIdleStateChange(UserIdleState.AWAY);
} else {
this.idleTimeoutId = window.setTimeout(this.reportIdle, IDLE_THRESHOLD);
@ -745,6 +766,11 @@ class Collab extends PureComponent<Props, CollabState> {
ACTIVE_THRESHOLD,
);
this.onIdleStateChange(UserIdleState.ACTIVE);
if (this.pauseTimeoutId) {
window.clearTimeout(this.pauseTimeoutId);
this.onPauseCollaborationChange(PauseCollaborationState.RESUME);
this.pauseTimeoutId = null;
}
}
};

View File

@ -46,7 +46,6 @@ import {
} from "../utils";
import {
FIREBASE_STORAGE_PREFIXES,
PAUSE_COLLABORATION_TIMEOUT,
STORAGE_KEYS,
SYNC_BROWSER_TABS_TIMEOUT,
} from "./app_constants";
@ -294,10 +293,6 @@ const ExcalidrawWrapper = () => {
getInitialLibraryItems: getLibraryItemsFromStorage,
});
const pauseCollaborationTimeoutRef = useRef<ReturnType<
typeof setTimeout
> | null>(null);
useEffect(() => {
if (!excalidrawAPI || (!isCollabDisabled && !collabAPI)) {
return;
@ -477,47 +472,6 @@ const ExcalidrawWrapper = () => {
) {
syncData();
}
if (event.type === EVENT.VISIBILITY_CHANGE) {
switch (true) {
// user switches to another tab
case document.hidden && collabAPI.isCollaborating():
if (!pauseCollaborationTimeoutRef.current) {
pauseCollaborationTimeoutRef.current = setTimeout(() => {
collabAPI.pauseCollaboration(() =>
excalidrawAPI.updateScene({
appState: { viewModeEnabled: true },
}),
);
}, PAUSE_COLLABORATION_TIMEOUT);
}
break;
// user returns to the tab with Excalidraw
case !document.hidden && collabAPI.isPaused():
excalidrawAPI.setToast({
message: t("toast.reconnectRoomServer"),
duration: Infinity,
closable: true,
});
collabAPI.resumeCollaboration(() => {
excalidrawAPI.updateScene({
appState: { viewModeEnabled: false },
});
excalidrawAPI.setToast(null);
});
break;
// user returns and timeout hasn't fired yet
case !document.hidden && Boolean(pauseCollaborationTimeoutRef):
if (pauseCollaborationTimeoutRef.current) {
clearTimeout(pauseCollaborationTimeoutRef.current);
pauseCollaborationTimeoutRef.current = null;
}
break;
}
}
};
window.addEventListener(EVENT.HASHCHANGE, onHashChange, false);

View File

@ -376,6 +376,12 @@ export enum UserIdleState {
IDLE = "idle",
}
export enum PauseCollaborationState {
PAUSE = "pause",
RESUME = "resume",
SYNC = "sync",
}
export type ExportOpts = {
saveFileToDisk?: boolean;
onExportToBackend?: (