Merge pull request #928 from excalidraw/fix_multiplayer_concurrency
Fix multiplayer concurrency
This commit is contained in:
commit
f393486eed
@ -291,7 +291,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
} else {
|
} else {
|
||||||
// create a map of ids so we don't have to iterate
|
// create a map of ids so we don't have to iterate
|
||||||
// over the array more than once.
|
// over the array more than once.
|
||||||
const elementMap = elements.reduce(
|
const localElementMap = elements.reduce(
|
||||||
(
|
(
|
||||||
acc: { [key: string]: ExcalidrawElement },
|
acc: { [key: string]: ExcalidrawElement },
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
@ -302,15 +302,42 @@ export class App extends React.Component<any, AppState> {
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
// Reconcile
|
// Reconcile
|
||||||
elements = restoredState.elements.map(element => {
|
elements = restoredState.elements
|
||||||
if (
|
.reduce((elements, element) => {
|
||||||
elementMap.hasOwnProperty(element.id) &&
|
// if the remote element references one that's currently
|
||||||
elementMap[element.id].version > element.version
|
// edited on local, skip it (it'll be added in the next
|
||||||
) {
|
// step)
|
||||||
return elementMap[element.id];
|
if (
|
||||||
}
|
element.id === this.state.editingElement?.id ||
|
||||||
return element;
|
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({});
|
this.setState({});
|
||||||
if (this.socketInitialized === false) {
|
if (this.socketInitialized === false) {
|
||||||
@ -358,7 +385,9 @@ export class App extends React.Component<any, AppState> {
|
|||||||
this.broadcastSocketData({
|
this.broadcastSocketData({
|
||||||
type: "SCENE_UPDATE",
|
type: "SCENE_UPDATE",
|
||||||
payload: {
|
payload: {
|
||||||
elements,
|
elements: elements.filter(element => {
|
||||||
|
return element.id !== this.state.editingElement?.id;
|
||||||
|
}),
|
||||||
appState: this.state,
|
appState: this.state,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -1382,6 +1411,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements = [...elements, element];
|
elements = [...elements, element];
|
||||||
this.setState({
|
this.setState({
|
||||||
draggingElement: element,
|
draggingElement: element,
|
||||||
|
editingElement: element,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (element.type === "selection") {
|
} else if (element.type === "selection") {
|
||||||
@ -1391,7 +1421,11 @@ export class App extends React.Component<any, AppState> {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
elements = [...elements, element];
|
elements = [...elements, element];
|
||||||
this.setState({ multiElement: null, draggingElement: element });
|
this.setState({
|
||||||
|
multiElement: null,
|
||||||
|
draggingElement: element,
|
||||||
|
editingElement: element,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let resizeArrowFn:
|
let resizeArrowFn:
|
||||||
@ -1860,6 +1894,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
isResizing: false,
|
isResizing: false,
|
||||||
resizingElement: null,
|
resizingElement: null,
|
||||||
selectionElement: null,
|
selectionElement: null,
|
||||||
|
editingElement: multiElement ? this.state.editingElement : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
resizeArrowFn = null;
|
resizeArrowFn = null;
|
||||||
@ -1883,7 +1918,10 @@ export class App extends React.Component<any, AppState> {
|
|||||||
y - draggingElement.y,
|
y - draggingElement.y,
|
||||||
]);
|
]);
|
||||||
invalidateShapeForElement(draggingElement);
|
invalidateShapeForElement(draggingElement);
|
||||||
this.setState({ multiElement: this.state.draggingElement });
|
this.setState({
|
||||||
|
multiElement: this.state.draggingElement,
|
||||||
|
editingElement: this.state.draggingElement,
|
||||||
|
});
|
||||||
} else if (draggingOccurred && !multiElement) {
|
} else if (draggingOccurred && !multiElement) {
|
||||||
if (!elementLocked) {
|
if (!elementLocked) {
|
||||||
resetCursor();
|
resetCursor();
|
||||||
@ -2151,7 +2189,9 @@ export class App extends React.Component<any, AppState> {
|
|||||||
this.broadcastSocketData({
|
this.broadcastSocketData({
|
||||||
type: "SCENE_UPDATE",
|
type: "SCENE_UPDATE",
|
||||||
payload: {
|
payload: {
|
||||||
elements,
|
elements: elements.filter(element => {
|
||||||
|
return element.id !== this.state.editingElement?.id;
|
||||||
|
}),
|
||||||
appState: this.state,
|
appState: this.state,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -341,12 +341,10 @@ export async function exportCanvas(
|
|||||||
|
|
||||||
export async function loadScene(id: string | null, privateKey?: string) {
|
export async function loadScene(id: string | null, privateKey?: string) {
|
||||||
let data;
|
let data;
|
||||||
let selectedId;
|
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
// the private key is used to decrypt the content from the server, take
|
// the private key is used to decrypt the content from the server, take
|
||||||
// extra care not to leak it
|
// extra care not to leak it
|
||||||
data = await importFromBackend(id, privateKey);
|
data = await importFromBackend(id, privateKey);
|
||||||
selectedId = id;
|
|
||||||
window.history.replaceState({}, "Excalidraw", window.location.origin);
|
window.history.replaceState({}, "Excalidraw", window.location.origin);
|
||||||
} else {
|
} else {
|
||||||
data = restoreFromLocalStorage();
|
data = restoreFromLocalStorage();
|
||||||
@ -354,6 +352,6 @@ export async function loadScene(id: string | null, privateKey?: string) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
elements: data.elements,
|
elements: data.elements,
|
||||||
appState: data.appState && { ...data.appState, selectedId },
|
appState: data.appState && { ...data.appState },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -7,5 +7,4 @@ export interface DataState {
|
|||||||
source?: string;
|
source?: string;
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
appState: AppState | null;
|
appState: AppState | null;
|
||||||
selectedId?: number;
|
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ export type AppState = {
|
|||||||
cursorY: number;
|
cursorY: number;
|
||||||
scrolledOutside: boolean;
|
scrolledOutside: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
selectedId?: string;
|
|
||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
isResizing: boolean;
|
isResizing: boolean;
|
||||||
zoom: number;
|
zoom: number;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user