|
|
|
@ -38,11 +38,14 @@ import Portal from "./Portal";
|
|
|
|
|
import RoomDialog from "./RoomDialog";
|
|
|
|
|
import { createInverseContext } from "../../createInverseContext";
|
|
|
|
|
import { t } from "../../i18n";
|
|
|
|
|
import { UserIdleState } from "./types";
|
|
|
|
|
import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../constants";
|
|
|
|
|
|
|
|
|
|
interface CollabState {
|
|
|
|
|
modalIsShown: boolean;
|
|
|
|
|
errorMessage: string;
|
|
|
|
|
username: string;
|
|
|
|
|
userState: UserIdleState;
|
|
|
|
|
activeRoomLink: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -52,6 +55,7 @@ export interface CollabAPI {
|
|
|
|
|
/** function so that we can access the latest value from stale callbacks */
|
|
|
|
|
isCollaborating: () => boolean;
|
|
|
|
|
username: CollabState["username"];
|
|
|
|
|
userState: CollabState["userState"];
|
|
|
|
|
onPointerUpdate: CollabInstance["onPointerUpdate"];
|
|
|
|
|
initializeSocketClient: CollabInstance["initializeSocketClient"];
|
|
|
|
|
onCollabButtonClick: CollabInstance["onCollabButtonClick"];
|
|
|
|
@ -78,6 +82,8 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
|
|
|
portal: Portal;
|
|
|
|
|
excalidrawAPI: Props["excalidrawAPI"];
|
|
|
|
|
isCollaborating: boolean = false;
|
|
|
|
|
activeIntervalId: number | null;
|
|
|
|
|
idleTimeoutId: number | null;
|
|
|
|
|
|
|
|
|
|
private socketInitializationTimer?: NodeJS.Timeout;
|
|
|
|
|
private lastBroadcastedOrReceivedSceneVersion: number = -1;
|
|
|
|
@ -89,10 +95,13 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
|
|
|
modalIsShown: false,
|
|
|
|
|
errorMessage: "",
|
|
|
|
|
username: importUsernameFromLocalStorage() || "",
|
|
|
|
|
userState: UserIdleState.ACTIVE,
|
|
|
|
|
activeRoomLink: "",
|
|
|
|
|
};
|
|
|
|
|
this.portal = new Portal(this);
|
|
|
|
|
this.excalidrawAPI = props.excalidrawAPI;
|
|
|
|
|
this.activeIntervalId = null;
|
|
|
|
|
this.idleTimeoutId = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
@ -116,6 +125,19 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
|
window.removeEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
|
|
|
|
|
window.removeEventListener(EVENT.UNLOAD, this.onUnload);
|
|
|
|
|
window.removeEventListener(EVENT.POINTER_MOVE, this.onPointerMove);
|
|
|
|
|
window.removeEventListener(
|
|
|
|
|
EVENT.VISIBILITY_CHANGE,
|
|
|
|
|
this.onVisibilityChange,
|
|
|
|
|
);
|
|
|
|
|
if (this.activeIntervalId) {
|
|
|
|
|
window.clearInterval(this.activeIntervalId);
|
|
|
|
|
this.activeIntervalId = null;
|
|
|
|
|
}
|
|
|
|
|
if (this.idleTimeoutId) {
|
|
|
|
|
window.clearTimeout(this.idleTimeoutId);
|
|
|
|
|
this.idleTimeoutId = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onUnload = () => {
|
|
|
|
@ -318,6 +340,17 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "IDLE_STATUS": {
|
|
|
|
|
const { userState, socketId, username } = decryptedData.payload;
|
|
|
|
|
const collaborators = new Map(this.collaborators);
|
|
|
|
|
const user = collaborators.get(socketId) || {}!;
|
|
|
|
|
user.userState = userState;
|
|
|
|
|
user.username = username;
|
|
|
|
|
this.excalidrawAPI.updateScene({
|
|
|
|
|
collaborators,
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
@ -330,6 +363,8 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
|
|
|
scenePromise.resolve(null);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.initializeIdleDetector();
|
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
|
activeRoomLink: window.location.href,
|
|
|
|
|
});
|
|
|
|
@ -398,7 +433,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
|
|
|
// Avoid broadcasting to the rest of the collaborators the scene
|
|
|
|
|
// we just received!
|
|
|
|
|
// Note: this needs to be set before updating the scene as it
|
|
|
|
|
// syncronously calls render.
|
|
|
|
|
// synchronously calls render.
|
|
|
|
|
this.setLastBroadcastedOrReceivedSceneVersion(getSceneVersion(newElements));
|
|
|
|
|
|
|
|
|
|
return newElements as ReconciledElements;
|
|
|
|
@ -427,6 +462,58 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
|
|
|
this.excalidrawAPI.history.clear();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private onPointerMove = () => {
|
|
|
|
|
if (this.idleTimeoutId) {
|
|
|
|
|
window.clearTimeout(this.idleTimeoutId);
|
|
|
|
|
this.idleTimeoutId = null;
|
|
|
|
|
}
|
|
|
|
|
this.idleTimeoutId = window.setTimeout(this.reportIdle, IDLE_THRESHOLD);
|
|
|
|
|
if (!this.activeIntervalId) {
|
|
|
|
|
this.activeIntervalId = window.setInterval(
|
|
|
|
|
this.reportActive,
|
|
|
|
|
ACTIVE_THRESHOLD,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private onVisibilityChange = () => {
|
|
|
|
|
if (document.hidden) {
|
|
|
|
|
if (this.idleTimeoutId) {
|
|
|
|
|
window.clearTimeout(this.idleTimeoutId);
|
|
|
|
|
this.idleTimeoutId = null;
|
|
|
|
|
}
|
|
|
|
|
if (this.activeIntervalId) {
|
|
|
|
|
window.clearInterval(this.activeIntervalId);
|
|
|
|
|
this.activeIntervalId = null;
|
|
|
|
|
}
|
|
|
|
|
this.onIdleStateChange(UserIdleState.AWAY);
|
|
|
|
|
} else {
|
|
|
|
|
this.idleTimeoutId = window.setTimeout(this.reportIdle, IDLE_THRESHOLD);
|
|
|
|
|
this.activeIntervalId = window.setInterval(
|
|
|
|
|
this.reportActive,
|
|
|
|
|
ACTIVE_THRESHOLD,
|
|
|
|
|
);
|
|
|
|
|
this.onIdleStateChange(UserIdleState.ACTIVE);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private reportIdle = () => {
|
|
|
|
|
this.onIdleStateChange(UserIdleState.IDLE);
|
|
|
|
|
if (this.activeIntervalId) {
|
|
|
|
|
window.clearInterval(this.activeIntervalId);
|
|
|
|
|
this.activeIntervalId = null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private reportActive = () => {
|
|
|
|
|
this.onIdleStateChange(UserIdleState.ACTIVE);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private initializeIdleDetector = () => {
|
|
|
|
|
document.addEventListener(EVENT.POINTER_MOVE, this.onPointerMove);
|
|
|
|
|
document.addEventListener(EVENT.VISIBILITY_CHANGE, this.onVisibilityChange);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setCollaborators(sockets: string[]) {
|
|
|
|
|
this.setState((state) => {
|
|
|
|
|
const collaborators: InstanceType<
|
|
|
|
@ -466,6 +553,11 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
|
|
|
this.portal.broadcastMouseLocation(payload);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onIdleStateChange = (userState: UserIdleState) => {
|
|
|
|
|
this.setState({ userState });
|
|
|
|
|
this.portal.broadcastIdleChange(userState);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
broadcastElements = (elements: readonly ExcalidrawElement[]) => {
|
|
|
|
|
if (
|
|
|
|
|
getSceneVersion(elements) >
|
|
|
|
|