Skip to content

Commit 43af37f

Browse files
committed
ability to move and set properties for multiple selected elements
1 parent ade1dfe commit 43af37f

File tree

9 files changed

+93
-23
lines changed

9 files changed

+93
-23
lines changed

src/packages/frontend/frame-editors/whiteboard-editor/canvas.tsx

+34-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
useRef,
1515
useState,
1616
} from "react";
17-
import { Element, Point } from "./types";
17+
import { Element, ElementType, Point } from "./types";
1818
import { Tool, TOOLS } from "./tools/spec";
1919
import RenderElement from "./elements/render";
2020
import Focused, { SELECTED_BORDER_COLOR } from "./focused";
@@ -256,6 +256,8 @@ export default function Canvas({
256256
key={id}
257257
canvasScale={canvasScale}
258258
element={element}
259+
allElements={elements}
260+
selectedElements={[element]}
259261
transforms={transforms}
260262
>
261263
{elt}
@@ -289,6 +291,37 @@ export default function Canvas({
289291
v.push(processElement(element));
290292
}
291293

294+
if (selection != null && selection.size > 1) {
295+
// create a virtual selection element that
296+
// contains the region spanned by all elements
297+
// in the selection.
298+
// TODO: This could be optimized with better data structures...
299+
const selectedElements = elements.filter((element) =>
300+
selection.has(element.id)
301+
);
302+
const { xMin, yMin, xMax, yMax } = getPageSpan(selectedElements, 0);
303+
const element = {
304+
type: "selection" as ElementType,
305+
id: "selection",
306+
x: xMin,
307+
y: yMin,
308+
w: xMax - xMin + 1,
309+
h: yMax - yMin + 1,
310+
};
311+
v.push(
312+
<Focused
313+
key={"selection"}
314+
canvasScale={canvasScale}
315+
element={element}
316+
allElements={elements}
317+
selectedElements={selectedElements}
318+
transforms={transforms}
319+
>
320+
<RenderElement element={element} canvasScale={canvasScale} focused />
321+
</Focused>
322+
);
323+
}
324+
292325
if (isNavigator) {
293326
// The navigator rectangle
294327
const visible = frame.desc.get("visibleWindow")?.toJS();

src/packages/frontend/frame-editors/whiteboard-editor/elements/render.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Frame from "./frame";
1010
import Generic from "./generic";
1111
import Pen from "./pen";
1212
import Stopwatch from "./stopwatch";
13+
import Selection from "./selection";
1314

1415
interface Props {
1516
element: Element;
@@ -33,6 +34,8 @@ export default function Render(props: Props) {
3334
return <Pen {...props} />;
3435
case "stopwatch":
3536
return <Stopwatch {...props} />;
37+
case "selection":
38+
return <Selection {...props} />;
3639
default:
3740
return <Generic {...props} />;
3841
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { SELECTED_BORDER_COLOR } from "../focused";
2+
3+
export default function Selection({ canvasScale }) {
4+
return (
5+
<div
6+
style={{
7+
border: `${2 / canvasScale}px solid ${SELECTED_BORDER_COLOR}`,
8+
width: "100%",
9+
height: "100%",
10+
}}
11+
></div>
12+
);
13+
}

src/packages/frontend/frame-editors/whiteboard-editor/focused-resize.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,25 @@ export default function DragHandle({
2929
setOffset,
3030
canvasScale,
3131
element,
32+
selectedElements,
3233
}: {
3334
top: boolean;
3435
left: boolean;
3536
setOffset: (offset: { x: number; y: number; w: number; h: number }) => void;
3637
canvasScale: number;
3738
element: Element;
39+
selectedElements: Element[];
3840
}) {
3941
const [position, setPosition] = useState<{ x: number; y: number }>({
4042
x: 0,
4143
y: 0,
4244
});
4345
const frame = useFrameContext();
46+
47+
if (selectedElements.length > 1) {
48+
return null;
49+
}
50+
4451
const style = {
4552
pointerEvents: "all", // because we turn off pointer events for containing div
4653
cursor: dragHandleCursors[`${top}-${left}`],

src/packages/frontend/frame-editors/whiteboard-editor/focused.tsx

+13-5
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,18 @@ interface Props {
3737
children: ReactNode;
3838
canvasScale: number;
3939
element: Element;
40+
selectedElements: Element[];
41+
allElements: Element[];
4042
transforms;
4143
}
4244

4345
export default function Focused({
4446
children,
4547
canvasScale,
4648
element,
49+
selectedElements,
4750
transforms,
51+
allElements,
4852
}: Props) {
4953
const frame = useFrameContext();
5054
const rectRef = useRef<any>(null);
@@ -72,6 +76,7 @@ export default function Focused({
7276
left={left}
7377
canvasScale={canvasScale}
7478
element={element}
79+
selectedElements={selectedElements}
7580
setOffset={setOffset}
7681
/>
7782
);
@@ -120,6 +125,7 @@ export default function Focused({
120125
}
121126
}
122127
setTimeout(() => {
128+
if (id == "selection") return; // todo
123129
frame.actions.setElement({ id, rotate });
124130
setRotating(undefined);
125131
}, 0);
@@ -198,7 +204,7 @@ export default function Focused({
198204
transformOrigin: "top left",
199205
}}
200206
>
201-
<EditBar elements={[element]} />
207+
<EditBar elements={selectedElements} allElements={allElements} />
202208
</div>
203209
</div>
204210
<Draggable
@@ -210,10 +216,12 @@ export default function Focused({
210216
}}
211217
onStop={(_, data) => {
212218
setDragging(false);
213-
const { id } = element;
214-
const x = element.x + data.x;
215-
const y = element.y + data.y;
216-
frame.actions.setElement({ id, x, y });
219+
for (const elt of selectedElements) {
220+
const { id } = elt;
221+
const x = elt.x + data.x;
222+
const y = elt.y + data.y;
223+
frame.actions.setElement({ id, x, y });
224+
}
217225
}}
218226
>
219227
<div

src/packages/frontend/frame-editors/whiteboard-editor/math.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,16 @@ export function getPageSpan(
5959
zMin: number;
6060
zMax: number;
6161
} {
62-
let xMin = 0,
63-
yMin = 0,
64-
xMax = 0,
65-
yMax = 0,
66-
zMin = 0,
67-
zMax = 0;
62+
let xMin = elements[0]?.x ?? 0,
63+
xMax = elements[0]?.x ?? 0,
64+
yMin = elements[0]?.y ?? 0,
65+
yMax = elements[0]?.y ?? 0,
66+
zMin = elements[0]?.z ?? 0,
67+
zMax = elements[0]?.z ?? 0;
6868
for (const element of elements) {
69-
const x = element.x ?? 0;
70-
const y = element.y ?? 0;
71-
const z = element.z ?? 0;
69+
const x = element.x ?? xMin;
70+
const y = element.y ?? yMin;
71+
const z = element.z ?? zMin;
7272
const w = element.w ?? DEFAULT_WIDTH;
7373
const h = element.h ?? DEFAULT_HEIGHT;
7474
if (x < xMin) xMin = x;

src/packages/frontend/frame-editors/whiteboard-editor/position.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface Props {
1111
}
1212

1313
export default function Position({ children, x, y, z, w, h, style }: Props) {
14+
console.log("z = ", z);
1415
const posStyle: CSSProperties = {
1516
position: "absolute",
1617
left: x,

src/packages/frontend/frame-editors/whiteboard-editor/tools/edit-bar.tsx

+11-7
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ import {
2323
} from "./defaults";
2424

2525
interface Props {
26-
elements: Element[];
26+
elements: Element[]; // selected ones
27+
allElements: Element[]; // all of them
2728
}
2829

29-
export default function EditBar({ elements }: Props) {
30+
export default function EditBar({ elements, allElements }: Props) {
3031
const { actions } = useFrameContext();
3132
if (elements.length == 0) return null;
3233
return (
@@ -44,7 +45,11 @@ export default function EditBar({ elements }: Props) {
4445
<FontSize actions={actions} elements={elements} />
4546
<ColorButton actions={actions} elements={elements} />
4647
<DeleteButton actions={actions} elements={elements} />
47-
<OtherOperations actions={actions} elements={elements} />
48+
<OtherOperations
49+
actions={actions}
50+
elements={elements}
51+
allElements={allElements}
52+
/>
4853
</div>
4954
</div>
5055
);
@@ -225,13 +230,13 @@ function getFontFamily(elements: Element[]): string | undefined {
225230
return DEFAULT_FONT_FAMILY;
226231
}
227232

228-
function OtherOperations({ actions, elements }: ButtonProps) {
233+
function OtherOperations({ actions, elements, allElements }) {
229234
const frame = useFrameContext();
230235
const menu = (
231236
<Menu
232237
onClick={({ key }) => {
233238
if (key == "bring-to-front") {
234-
const { zMax } = getPageSpan(elements);
239+
const { zMax } = getPageSpan(allElements);
235240
let z = zMax + 1;
236241
for (const element of elements) {
237242
actions.setElement({ ...element, z }, false);
@@ -240,9 +245,8 @@ function OtherOperations({ actions, elements }: ButtonProps) {
240245
actions.syncstring_commit();
241246
actions.clearSelection(frame.id);
242247
} else if (key == "send-to-back") {
243-
const { zMin } = getPageSpan(elements);
248+
const { zMin } = getPageSpan(allElements);
244249
let z = zMin - 1;
245-
console.log("zMin = ", zMin, " z = ", z);
246250
for (const element of elements) {
247251
actions.setElement({ ...element, z }, false);
248252
z -= 1;

src/packages/frontend/frame-editors/whiteboard-editor/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export type ElementType =
1717
| "terminal"
1818
| "stopwatch"
1919
| "timer"
20-
| "frame";
20+
| "frame"
21+
| "selection";
2122

2223
export type Point = { x: number; y: number };
2324

0 commit comments

Comments
 (0)