diff --git a/src/packages/frontend/frame-editors/whiteboard-editor/canvas.tsx b/src/packages/frontend/frame-editors/whiteboard-editor/canvas.tsx
index 58a09cf0d99..625e98eb784 100644
--- a/src/packages/frontend/frame-editors/whiteboard-editor/canvas.tsx
+++ b/src/packages/frontend/frame-editors/whiteboard-editor/canvas.tsx
@@ -14,7 +14,7 @@ import {
useRef,
useState,
} from "react";
-import { Element, Point } from "./types";
+import { Element, ElementType, Point } from "./types";
import { Tool, TOOLS } from "./tools/spec";
import RenderElement from "./elements/render";
import Focused, { SELECTED_BORDER_COLOR } from "./focused";
@@ -256,6 +256,8 @@ export default function Canvas({
key={id}
canvasScale={canvasScale}
element={element}
+ allElements={elements}
+ selectedElements={[element]}
transforms={transforms}
>
{elt}
@@ -289,6 +291,37 @@ export default function Canvas({
v.push(processElement(element));
}
+ if (selection != null && selection.size > 1) {
+ // create a virtual selection element that
+ // contains the region spanned by all elements
+ // in the selection.
+ // TODO: This could be optimized with better data structures...
+ const selectedElements = elements.filter((element) =>
+ selection.has(element.id)
+ );
+ const { xMin, yMin, xMax, yMax } = getPageSpan(selectedElements, 0);
+ const element = {
+ type: "selection" as ElementType,
+ id: "selection",
+ x: xMin,
+ y: yMin,
+ w: xMax - xMin + 1,
+ h: yMax - yMin + 1,
+ };
+ v.push(
+
+
+
+ );
+ }
+
if (isNavigator) {
// The navigator rectangle
const visible = frame.desc.get("visibleWindow")?.toJS();
diff --git a/src/packages/frontend/frame-editors/whiteboard-editor/elements/render.tsx b/src/packages/frontend/frame-editors/whiteboard-editor/elements/render.tsx
index a70ab709175..acd9c772da9 100644
--- a/src/packages/frontend/frame-editors/whiteboard-editor/elements/render.tsx
+++ b/src/packages/frontend/frame-editors/whiteboard-editor/elements/render.tsx
@@ -10,6 +10,7 @@ import Frame from "./frame";
import Generic from "./generic";
import Pen from "./pen";
import Stopwatch from "./stopwatch";
+import Selection from "./selection";
interface Props {
element: Element;
@@ -33,6 +34,8 @@ export default function Render(props: Props) {
return ;
case "stopwatch":
return ;
+ case "selection":
+ return ;
default:
return ;
}
diff --git a/src/packages/frontend/frame-editors/whiteboard-editor/elements/selection.tsx b/src/packages/frontend/frame-editors/whiteboard-editor/elements/selection.tsx
new file mode 100644
index 00000000000..6cfd3c62720
--- /dev/null
+++ b/src/packages/frontend/frame-editors/whiteboard-editor/elements/selection.tsx
@@ -0,0 +1,13 @@
+import { SELECTED_BORDER_COLOR } from "../focused";
+
+export default function Selection({ canvasScale }) {
+ return (
+
+ );
+}
diff --git a/src/packages/frontend/frame-editors/whiteboard-editor/focused-resize.tsx b/src/packages/frontend/frame-editors/whiteboard-editor/focused-resize.tsx
index e3409aa5947..aafccf4fbaf 100644
--- a/src/packages/frontend/frame-editors/whiteboard-editor/focused-resize.tsx
+++ b/src/packages/frontend/frame-editors/whiteboard-editor/focused-resize.tsx
@@ -29,18 +29,25 @@ export default function DragHandle({
setOffset,
canvasScale,
element,
+ selectedElements,
}: {
top: boolean;
left: boolean;
setOffset: (offset: { x: number; y: number; w: number; h: number }) => void;
canvasScale: number;
element: Element;
+ selectedElements: Element[];
}) {
const [position, setPosition] = useState<{ x: number; y: number }>({
x: 0,
y: 0,
});
const frame = useFrameContext();
+
+ if (selectedElements.length > 1) {
+ return null;
+ }
+
const style = {
pointerEvents: "all", // because we turn off pointer events for containing div
cursor: dragHandleCursors[`${top}-${left}`],
diff --git a/src/packages/frontend/frame-editors/whiteboard-editor/focused.tsx b/src/packages/frontend/frame-editors/whiteboard-editor/focused.tsx
index f817cf65002..8238dd77df2 100644
--- a/src/packages/frontend/frame-editors/whiteboard-editor/focused.tsx
+++ b/src/packages/frontend/frame-editors/whiteboard-editor/focused.tsx
@@ -37,6 +37,8 @@ interface Props {
children: ReactNode;
canvasScale: number;
element: Element;
+ selectedElements: Element[];
+ allElements: Element[];
transforms;
}
@@ -44,7 +46,9 @@ export default function Focused({
children,
canvasScale,
element,
+ selectedElements,
transforms,
+ allElements,
}: Props) {
const frame = useFrameContext();
const rectRef = useRef(null);
@@ -72,6 +76,7 @@ export default function Focused({
left={left}
canvasScale={canvasScale}
element={element}
+ selectedElements={selectedElements}
setOffset={setOffset}
/>
);
@@ -120,6 +125,7 @@ export default function Focused({
}
}
setTimeout(() => {
+ if (id == "selection") return; // todo
frame.actions.setElement({ id, rotate });
setRotating(undefined);
}, 0);
@@ -198,7 +204,7 @@ export default function Focused({
transformOrigin: "top left",
}}
>
-
+
{
setDragging(false);
- const { id } = element;
- const x = element.x + data.x;
- const y = element.y + data.y;
- frame.actions.setElement({ id, x, y });
+ for (const elt of selectedElements) {
+ const { id } = elt;
+ const x = elt.x + data.x;
+ const y = elt.y + data.y;
+ frame.actions.setElement({ id, x, y });
+ }
}}
>
-
+
);
@@ -225,13 +230,13 @@ function getFontFamily(elements: Element[]): string | undefined {
return DEFAULT_FONT_FAMILY;
}
-function OtherOperations({ actions, elements }: ButtonProps) {
+function OtherOperations({ actions, elements, allElements }) {
const frame = useFrameContext();
const menu = (