Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(whiteboard): prevent crashes when grouped shapes are removed #16543

Merged
merged 3 commits into from
Jan 24, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
99 changes: 82 additions & 17 deletions bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { TldrawApp, Tldraw } from "@tldraw/tldraw";
import SlideCalcUtil, {HUNDRED_PERCENT} from '/imports/utils/slideCalcUtils';
import { Utils } from "@tldraw/core";
import Settings from '/imports/ui/services/settings';
import logger from '/imports/startup/client/logger';

function usePrevious(value) {
const ref = React.useRef();
Expand Down Expand Up @@ -163,6 +164,35 @@ export default function Whiteboard(props) {
return !invalidTypes.includes(shape?.type);
}

const filterInvalidShapes = (shapes) => {
const keys = Object.keys(shapes);
const removedChildren = [];

keys.forEach((shape) => {
if (shapes[shape].parentId !== curPageId) {
if(!keys.includes(shapes[shape].parentId)) {
delete shapes[shape];
}
}else{
if (shapes[shape].type === "group") {
const groupChildren = shapes[shape].children;

groupChildren.forEach((child) => {
if (!keys.includes(child)) {
removedChildren.push(child);
}
});
shapes[shape].children = groupChildren.filter((child) => !removedChildren.includes(child));

if (shapes[shape].children.length < 2) {
delete shapes[shape];
}
}
}
});
return shapes;
}

const sendShapeChanges= (app, changedShapes, redo = false) => {
const invalidChange = Object.keys(changedShapes)
.find(id => !hasShapeAccess(id));
Expand Down Expand Up @@ -219,7 +249,20 @@ export default function Whiteboard(props) {
persistShape(shape, whiteboardId);
}
});
removeShapes(deletedShapes, whiteboardId);

//order the ids of shapes being deleted to prevent crash when removing a group shape before its children
const orderedDeletedShapes = [];
deletedShapes.forEach(eid => {
if (shapes[eid]?.type !== 'group') {
orderedDeletedShapes.unshift(eid);
} else {
orderedDeletedShapes.push(eid)
}
});

if (orderedDeletedShapes.length > 0) {
removeShapes(orderedDeletedShapes, whiteboardId);
}
}

React.useEffect(() => {
Expand Down Expand Up @@ -275,26 +318,37 @@ export default function Whiteboard(props) {
}
});

const removed = prevShapes && findRemoved(Object.keys(prevShapes),Object.keys((shapes)))
const removed = prevShapes && findRemoved(Object.keys(prevShapes),Object.keys((shapes)));
if (removed && removed.length > 0) {
tldrawAPI?.patchState(
{
document: {
pageStates: {
[curPageId]: {
selectedIds: tldrawAPI?.selectedIds?.filter(id => !removed.includes(id)) || [],
const patchedShapes = Object.fromEntries(removed.map((id) => [id, undefined]));

try {
tldrawAPI?.patchState(
{
document: {
pageStates: {
[curPageId]: {
selectedIds: tldrawAPI?.selectedIds?.filter(id => !removed.includes(id)) || [],
},
},
},
pages: {
[curPageId]: {
shapes: Object.fromEntries(removed.map((id) => [id, undefined])),
pages: {
[curPageId]: {
shapes: patchedShapes,
},
},
},
},
},
);
);
} catch (error) {
logger.error({
logCode: 'whiteboard_shapes_remove_error',
extraInfo: { error },
}, 'Whiteboard catch error on removing shapes');
}

}
next.pages[curPageId].shapes = shapes;

next.pages[curPageId].shapes = filterInvalidShapes(shapes);
changed = true;
}

Expand All @@ -315,15 +369,26 @@ export default function Whiteboard(props) {
const patch = {
document: {
pages: {
[curPageId]: { shapes: shapes }
[curPageId]: { shapes: filterInvalidShapes(shapes) }
},
},
};
const prevState = tldrawAPI._state;
const nextState = Utils.deepMerge(tldrawAPI._state, patch);
if(nextState.document.pages[curPageId].shapes) {
filterInvalidShapes(nextState.document.pages[curPageId].shapes);
}
const final = tldrawAPI.cleanup(nextState, prevState, patch, '');
tldrawAPI._state = final;
tldrawAPI?.forceUpdate();

try {
tldrawAPI?.forceUpdate();
} catch (e) {
logger.error({
logCode: 'whiteboard_shapes_update_error',
extraInfo: { error },
}, 'Whiteboard catch error on updating shapes');
}
}

// move poll result text to bottom right
Expand Down