|
@@ -17,7 +17,7 @@ import {
|
17
|
17
|
getPerfectElementSize,
|
18
|
18
|
getNormalizedDimensions,
|
19
|
19
|
getElementMap,
|
20
|
|
- getDrawingVersion,
|
|
20
|
+ getSceneVersion,
|
21
|
21
|
getSyncableElements,
|
22
|
22
|
newLinearElement,
|
23
|
23
|
transformElements,
|
|
@@ -176,6 +176,7 @@ import {
|
176
|
176
|
import { MaybeTransformHandleType } from "../element/transformHandles";
|
177
|
177
|
import { renderSpreadsheet } from "../charts";
|
178
|
178
|
import { isValidLibrary } from "../data/json";
|
|
179
|
+import { loadFromFirebase, saveToFirebase } from "../data/firebase";
|
179
|
180
|
|
180
|
181
|
/**
|
181
|
182
|
* @param func handler taking at most single parameter (event).
|
|
@@ -468,6 +469,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
468
|
469
|
return false;
|
469
|
470
|
}
|
470
|
471
|
|
|
472
|
+ const roomId = roomMatch[1];
|
|
473
|
+
|
471
|
474
|
let collabForceLoadFlag;
|
472
|
475
|
try {
|
473
|
476
|
collabForceLoadFlag = localStorage?.getItem(
|
|
@@ -485,7 +488,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
485
|
488
|
);
|
486
|
489
|
// if loading same room as the one previously unloaded within 15sec
|
487
|
490
|
// force reload without prompting
|
488
|
|
- if (previousRoom === roomMatch[1] && Date.now() - timestamp < 15000) {
|
|
491
|
+ if (previousRoom === roomId && Date.now() - timestamp < 15000) {
|
489
|
492
|
return true;
|
490
|
493
|
}
|
491
|
494
|
} catch {}
|
|
@@ -902,7 +905,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
902
|
905
|
}
|
903
|
906
|
|
904
|
907
|
if (
|
905
|
|
- getDrawingVersion(this.scene.getElementsIncludingDeleted()) >
|
|
908
|
+ getSceneVersion(this.scene.getElementsIncludingDeleted()) >
|
906
|
909
|
this.lastBroadcastedOrReceivedSceneVersion
|
907
|
910
|
) {
|
908
|
911
|
this.broadcastScene(SCENE.UPDATE, /* syncAll */ false);
|
|
@@ -1210,6 +1213,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
1210
|
1213
|
}
|
1211
|
1214
|
const roomMatch = getCollaborationLinkData(window.location.href);
|
1212
|
1215
|
if (roomMatch) {
|
|
1216
|
+ const roomId = roomMatch[1];
|
|
1217
|
+ const roomSecret = roomMatch[2];
|
|
1218
|
+
|
1213
|
1219
|
const initialize = () => {
|
1214
|
1220
|
this.portal.socketInitialized = true;
|
1215
|
1221
|
clearTimeout(initializationTimer);
|
|
@@ -1226,12 +1232,18 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
1226
|
1232
|
|
1227
|
1233
|
const updateScene = (
|
1228
|
1234
|
decryptedData: SocketUpdateDataSource[SCENE.INIT | SCENE.UPDATE],
|
1229
|
|
- { init = false }: { init?: boolean } = {},
|
|
1235
|
+ {
|
|
1236
|
+ init = false,
|
|
1237
|
+ initFromSnapshot = false,
|
|
1238
|
+ }: { init?: boolean; initFromSnapshot?: boolean } = {},
|
1230
|
1239
|
) => {
|
1231
|
1240
|
const { elements: remoteElements } = decryptedData.payload;
|
1232
|
1241
|
|
1233
|
1242
|
if (init) {
|
1234
|
1243
|
history.resumeRecording();
|
|
1244
|
+ }
|
|
1245
|
+
|
|
1246
|
+ if (init || initFromSnapshot) {
|
1235
|
1247
|
this.setState({
|
1236
|
1248
|
...this.state,
|
1237
|
1249
|
...calculateScrollCenter(
|
|
@@ -1311,7 +1323,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
1311
|
1323
|
// we just received!
|
1312
|
1324
|
// Note: this needs to be set before replaceAllElements as it
|
1313
|
1325
|
// syncronously calls render.
|
1314
|
|
- this.lastBroadcastedOrReceivedSceneVersion = getDrawingVersion(
|
|
1326
|
+ this.lastBroadcastedOrReceivedSceneVersion = getSceneVersion(
|
1315
|
1327
|
newElements,
|
1316
|
1328
|
);
|
1317
|
1329
|
|
|
@@ -1323,7 +1335,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
1323
|
1335
|
// undo, a user makes a change, and then try to redo, your element(s) will be lost. However,
|
1324
|
1336
|
// right now we think this is the right tradeoff.
|
1325
|
1337
|
history.clear();
|
1326
|
|
- if (!this.portal.socketInitialized) {
|
|
1338
|
+ if (!this.portal.socketInitialized && !initFromSnapshot) {
|
1327
|
1339
|
initialize();
|
1328
|
1340
|
}
|
1329
|
1341
|
};
|
|
@@ -1332,11 +1344,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
1332
|
1344
|
/* webpackChunkName: "socketIoClient" */ "socket.io-client"
|
1333
|
1345
|
);
|
1334
|
1346
|
|
1335
|
|
- this.portal.open(
|
1336
|
|
- socketIOClient(SOCKET_SERVER),
|
1337
|
|
- roomMatch[1],
|
1338
|
|
- roomMatch[2],
|
1339
|
|
- );
|
|
1347
|
+ this.portal.open(socketIOClient(SOCKET_SERVER), roomId, roomSecret);
|
1340
|
1348
|
|
1341
|
1349
|
// All socket listeners are moving to Portal
|
1342
|
1350
|
this.portal.socket!.on(
|
|
@@ -1406,6 +1414,19 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
1406
|
1414
|
isCollaborating: true,
|
1407
|
1415
|
isLoading: opts.showLoadingState ? true : this.state.isLoading,
|
1408
|
1416
|
});
|
|
1417
|
+
|
|
1418
|
+ try {
|
|
1419
|
+ const elements = await loadFromFirebase(roomId, roomSecret);
|
|
1420
|
+ if (elements) {
|
|
1421
|
+ updateScene(
|
|
1422
|
+ { type: "SCENE_UPDATE", payload: { elements } },
|
|
1423
|
+ { initFromSnapshot: true },
|
|
1424
|
+ );
|
|
1425
|
+ }
|
|
1426
|
+ } catch (e) {
|
|
1427
|
+ // log the error and move on. other peers will sync us the scene.
|
|
1428
|
+ console.error(e);
|
|
1429
|
+ }
|
1409
|
1430
|
}
|
1410
|
1431
|
};
|
1411
|
1432
|
|
|
@@ -1450,7 +1471,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
1450
|
1471
|
};
|
1451
|
1472
|
|
1452
|
1473
|
// maybe should move to Portal
|
1453
|
|
- broadcastScene = (sceneType: SCENE.INIT | SCENE.UPDATE, syncAll: boolean) => {
|
|
1474
|
+ broadcastScene = async (
|
|
1475
|
+ sceneType: SCENE.INIT | SCENE.UPDATE,
|
|
1476
|
+ syncAll: boolean,
|
|
1477
|
+ ) => {
|
1454
|
1478
|
if (sceneType === SCENE.INIT && !syncAll) {
|
1455
|
1479
|
throw new Error("syncAll must be true when sending SCENE.INIT");
|
1456
|
1480
|
}
|
|
@@ -1479,7 +1503,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
1479
|
1503
|
};
|
1480
|
1504
|
this.lastBroadcastedOrReceivedSceneVersion = Math.max(
|
1481
|
1505
|
this.lastBroadcastedOrReceivedSceneVersion,
|
1482
|
|
- getDrawingVersion(this.scene.getElementsIncludingDeleted()),
|
|
1506
|
+ getSceneVersion(this.scene.getElementsIncludingDeleted()),
|
1483
|
1507
|
);
|
1484
|
1508
|
for (const syncableElement of syncableElements) {
|
1485
|
1509
|
this.broadcastedElementVersions.set(
|
|
@@ -1487,7 +1511,25 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
1487
|
1511
|
syncableElement.version,
|
1488
|
1512
|
);
|
1489
|
1513
|
}
|
1490
|
|
- return this.portal._broadcastSocketData(data as SocketUpdateData);
|
|
1514
|
+
|
|
1515
|
+ const broadcastPromise = this.portal._broadcastSocketData(
|
|
1516
|
+ data as SocketUpdateData,
|
|
1517
|
+ );
|
|
1518
|
+
|
|
1519
|
+ if (syncAll && this.portal.roomID && this.portal.roomKey) {
|
|
1520
|
+ await Promise.all([
|
|
1521
|
+ broadcastPromise,
|
|
1522
|
+ saveToFirebase(
|
|
1523
|
+ this.portal.roomID,
|
|
1524
|
+ this.portal.roomKey,
|
|
1525
|
+ syncableElements,
|
|
1526
|
+ ).catch((e) => {
|
|
1527
|
+ console.error(e);
|
|
1528
|
+ }),
|
|
1529
|
+ ]);
|
|
1530
|
+ } else {
|
|
1531
|
+ await broadcastPromise;
|
|
1532
|
+ }
|
1491
|
1533
|
};
|
1492
|
1534
|
|
1493
|
1535
|
private onSceneUpdated = () => {
|