Forráskód Böngészése

Merge pull request #928 from excalidraw/fix_multiplayer_concurrency

Fix multiplayer concurrency
Pete Hunt 5 éve
szülő
commit
f393486eed
4 módosított fájl, 55 hozzáadás és 19 törlés
  1. 54 14
      src/components/App.tsx
  2. 1 3
      src/data/index.ts
  3. 0 1
      src/data/types.ts
  4. 0 1
      src/types.ts

+ 54 - 14
src/components/App.tsx

@@ -291,7 +291,7 @@ export class App extends React.Component<any, AppState> {
               } else {
                 // create a map of ids so we don't have to iterate
                 // over the array more than once.
-                const elementMap = elements.reduce(
+                const localElementMap = elements.reduce(
                   (
                     acc: { [key: string]: ExcalidrawElement },
                     element: ExcalidrawElement,
@@ -302,15 +302,42 @@ export class App extends React.Component<any, AppState> {
                   {},
                 );
                 // Reconcile
-                elements = restoredState.elements.map(element => {
-                  if (
-                    elementMap.hasOwnProperty(element.id) &&
-                    elementMap[element.id].version > element.version
-                  ) {
-                    return elementMap[element.id];
-                  }
-                  return element;
-                });
+                elements = restoredState.elements
+                  .reduce((elements, element) => {
+                    // if the remote element references one that's currently
+                    //  edited on local, skip it (it'll be added in the next
+                    //  step)
+                    if (
+                      element.id === this.state.editingElement?.id ||
+                      element.id === this.state.resizingElement?.id ||
+                      element.id === this.state.draggingElement?.id
+                    ) {
+                      return elements;
+                    }
+
+                    if (
+                      localElementMap.hasOwnProperty(element.id) &&
+                      localElementMap[element.id].version > element.version
+                    ) {
+                      elements.push(localElementMap[element.id]);
+                    } else {
+                      elements.push(element);
+                    }
+
+                    return elements;
+                  }, [] as any)
+                  // add local elements that are currently being edited
+                  // (can't be done in the step above because the elements may
+                  //  not exist on remote at all)
+                  .concat(
+                    elements.filter(element => {
+                      return (
+                        element.id === this.state.editingElement?.id ||
+                        element.id === this.state.resizingElement?.id ||
+                        element.id === this.state.draggingElement?.id
+                      );
+                    }),
+                  );
               }
               this.setState({});
               if (this.socketInitialized === false) {
@@ -358,7 +385,9 @@ export class App extends React.Component<any, AppState> {
         this.broadcastSocketData({
           type: "SCENE_UPDATE",
           payload: {
-            elements,
+            elements: elements.filter(element => {
+              return element.id !== this.state.editingElement?.id;
+            }),
             appState: this.state,
           },
         });
@@ -1382,6 +1411,7 @@ export class App extends React.Component<any, AppState> {
         elements = [...elements, element];
         this.setState({
           draggingElement: element,
+          editingElement: element,
         });
       }
     } else if (element.type === "selection") {
@@ -1391,7 +1421,11 @@ export class App extends React.Component<any, AppState> {
       });
     } else {
       elements = [...elements, element];
-      this.setState({ multiElement: null, draggingElement: element });
+      this.setState({
+        multiElement: null,
+        draggingElement: element,
+        editingElement: element,
+      });
     }
 
     let resizeArrowFn:
@@ -1860,6 +1894,7 @@ export class App extends React.Component<any, AppState> {
         isResizing: false,
         resizingElement: null,
         selectionElement: null,
+        editingElement: multiElement ? this.state.editingElement : null,
       });
 
       resizeArrowFn = null;
@@ -1883,7 +1918,10 @@ export class App extends React.Component<any, AppState> {
             y - draggingElement.y,
           ]);
           invalidateShapeForElement(draggingElement);
-          this.setState({ multiElement: this.state.draggingElement });
+          this.setState({
+            multiElement: this.state.draggingElement,
+            editingElement: this.state.draggingElement,
+          });
         } else if (draggingOccurred && !multiElement) {
           if (!elementLocked) {
             resetCursor();
@@ -2151,7 +2189,9 @@ export class App extends React.Component<any, AppState> {
       this.broadcastSocketData({
         type: "SCENE_UPDATE",
         payload: {
-          elements,
+          elements: elements.filter(element => {
+            return element.id !== this.state.editingElement?.id;
+          }),
           appState: this.state,
         },
       });

+ 1 - 3
src/data/index.ts

@@ -341,12 +341,10 @@ export async function exportCanvas(
 
 export async function loadScene(id: string | null, privateKey?: string) {
   let data;
-  let selectedId;
   if (id != null) {
     // the private key is used to decrypt the content from the server, take
     // extra care not to leak it
     data = await importFromBackend(id, privateKey);
-    selectedId = id;
     window.history.replaceState({}, "Excalidraw", window.location.origin);
   } else {
     data = restoreFromLocalStorage();
@@ -354,6 +352,6 @@ export async function loadScene(id: string | null, privateKey?: string) {
 
   return {
     elements: data.elements,
-    appState: data.appState && { ...data.appState, selectedId },
+    appState: data.appState && { ...data.appState },
   };
 }

+ 0 - 1
src/data/types.ts

@@ -7,5 +7,4 @@ export interface DataState {
   source?: string;
   elements: readonly ExcalidrawElement[];
   appState: AppState | null;
-  selectedId?: number;
 }

+ 0 - 1
src/types.ts

@@ -28,7 +28,6 @@ export type AppState = {
   cursorY: number;
   scrolledOutside: boolean;
   name: string;
-  selectedId?: string;
   isCollaborating: boolean;
   isResizing: boolean;
   zoom: number;