Skip to content

Commit

Permalink
Merge pull request #34 from rocicorp/resize2
Browse files Browse the repository at this point in the history
Add resize.
  • Loading branch information
aboodman authored Mar 10, 2021
2 parents 7f5675d + 91dea68 commit a138dd5
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 64 deletions.
8 changes: 8 additions & 0 deletions frontend/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Shape,
putShape,
moveShape,
resizeShape,
deleteShape,
} from "../shared/shape";
import {
Expand Down Expand Up @@ -68,6 +69,13 @@ export function createData(rep: Replicache) {
}
),

resizeShape: rep.register(
"resizeShape",
async (tx: WriteTransaction, args: { id: string; ds: number }) => {
await resizeShape(writeStorage(tx), args);
}
),

initClientState: rep.register(
"initClientState",
async (
Expand Down
11 changes: 9 additions & 2 deletions frontend/designer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Data } from "./data";
import { Collaborator } from "./collaborator";
import { RectController } from "./rect-controller";
import { touchToMouse } from "./events";
import { Selection } from "./selection";
import { useDrag } from "./drag";

export function Designer({ data }: { data: Data }) {
const ids = data.useShapeIDs();
Expand Down Expand Up @@ -37,6 +39,11 @@ export function Designer({ data }: { data: Data }) {
}
};

const drag = useDrag({
onDragStart: () => setDragging(true),
onDragEnd: () => setDragging(false),
});

return (
<HotKeys
{...{
Expand All @@ -57,6 +64,7 @@ export function Designer({ data }: { data: Data }) {
},
onMouseMove,
onTouchMove: (e) => touchToMouse(e, onMouseMove),
...drag,
}}
>
{ids.map((id) => (
Expand All @@ -66,7 +74,6 @@ export function Designer({ data }: { data: Data }) {
key: `shape-${id}`,
data,
id,
onDrag: setDragging,
}}
/>
))}
Expand All @@ -88,7 +95,7 @@ export function Designer({ data }: { data: Data }) {
{
// self-selection
selectedID && (
<Rect
<Selection
{...{
key: `selection-${selectedID}`,
data,
Expand Down
79 changes: 79 additions & 0 deletions frontend/drag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useLayoutEffect, useState } from "react";
import { touchToMouse } from "./events";

export type PageCoords = {
pageX: number;
pageY: number;
};

export type DragEvent = PageCoords & {
movementX: number;
movementY: number;
};

export function useDrag({
onDragStart,
onDrag,
onDragEnd,
}: {
onDragStart?: () => void;
onDrag?: (e: DragEvent) => void;
onDragEnd?: () => void;
}) {
const [lastDrag, setLastDrag] = useState<{
pageX: number;
pageY: number;
} | null>(null);

const onTouchStart = (e: any) => touchToMouse(e, onMouseDown);
const onMouseDown = (e: PageCoords) => {
setLastDrag(e);
onDragStart && onDragStart();
};

const onTouchMove = (e: any) => touchToMouse(e, onMouseMove);
const onMouseMove = (e: PageCoords) => {
if (lastDrag) {
const { pageX, pageY } = e;
onDrag &&
onDrag({
pageX,
pageY,
movementX: pageX - lastDrag.pageX,
movementY: pageY - lastDrag.pageY,
});
setLastDrag(e);
}
};

const onMouseUp = () => {
setLastDrag(null);
onDragEnd && onDragEnd();
};

const dragListeners = {
mousemove: (e: any) => onMouseMove(e),
touchmove: (e: any) => onTouchMove(e),
mouseup: onMouseUp,
touchend: onMouseUp,
};

useLayoutEffect(() => {
if (!lastDrag) {
return;
}
Object.entries(dragListeners).forEach(([key, val]) =>
window.addEventListener(key, val)
);
return () => {
Object.entries(dragListeners).forEach(([key, val]) =>
window.removeEventListener(key, val)
);
};
});

return {
onMouseDown,
onTouchStart,
};
}
2 changes: 1 addition & 1 deletion frontend/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function Nav({ data }: { data: Data | null }) {
y: randInt(0, 400),
width: s,
height: s,
rotate: randInt(0, 359),
rotate: 0, // randInt(0, 359),
fill: colors[randInt(0, colors.length - 1)],
},
});
Expand Down
64 changes: 10 additions & 54 deletions frontend/rect-controller.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,21 @@
import { useEffect, useState } from "react";
import { Data } from "./data";
import { touchToMouse } from "./events";
import { Rect } from "./rect";
import { DragEvent, useDrag } from "./drag";

// TODO: In the future I imagine this becoming ShapeController and
// there also be a Shape that wraps Rect and also knows how to draw Circle, etc.
export function RectController({
data,
id,
onDrag,
}: {
data: Data;
id: string;
onDrag: (dragging: boolean) => void;
}) {
const [lastDrag, setLastDrag] = useState<{
pageX: number;
pageY: number;
} | null>(null);
export function RectController({ data, id }: { data: Data; id: string }) {
const shape = data.useShapeByID(id);

const onMouseEnter = () =>
data.overShape({ clientID: data.clientID, shapeID: id });
const onMouseLeave = () =>
data.overShape({ clientID: data.clientID, shapeID: "" });

const onMouseDown = ({ pageX, pageY }: { pageX: number; pageY: number }) => {
const onDragStart = () => {
data.selectShape({ clientID: data.clientID, shapeID: id });
setLastDrag({ pageX, pageY });
onDrag && onDrag(true);
};

const onTouchMove = (e: any) => touchToMouse(e, onMouseMove);
const onMouseMove = ({ pageX, pageY }: { pageX: number; pageY: number }) => {
if (!lastDrag) {
return;
}

const onDrag = (e: DragEvent) => {
// This is subtle, and worth drawing attention to:
// In order to properly resolve conflicts, what we want to capture in
// mutation arguments is the *intent* of the mutation, not the effect.
Expand All @@ -46,36 +25,14 @@ export function RectController({
// then end up with a union of the two vectors, which is what we want!
data.moveShape({
id,
dx: pageX - lastDrag.pageX,
dy: pageY - lastDrag.pageY,
dx: e.movementX,
dy: e.movementY,
});
setLastDrag({ pageX, pageY });
};

const onMouseUp = () => {
setLastDrag(null);
onDrag && onDrag(false);
};

const dragListeners = {
mousemove: (e: any) => onMouseMove(e),
touchmove: (e: any) => onTouchMove(e),
mouseup: onMouseUp,
touchend: onMouseUp,
};

useEffect(() => {
if (!lastDrag) {
return;
}
Object.entries(dragListeners).forEach(([key, val]) =>
window.addEventListener(key, val)
);
return () => {
Object.entries(dragListeners).forEach(([key, val]) =>
window.removeEventListener(key, val)
);
};
const drag = useDrag({
onDragStart,
onDrag,
});

if (!shape) {
Expand All @@ -88,10 +45,9 @@ export function RectController({
data,
id,
highlight: false,
onMouseDown: (e: any) => onMouseDown(e),
onTouchStart: (e: any) => touchToMouse(e, onMouseDown),
onMouseEnter,
onMouseLeave,
...drag,
}}
/>
);
Expand Down
85 changes: 85 additions & 0 deletions frontend/selection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Data } from "./data";
import { DragEvent, useDrag } from "./drag";
import { Rect } from "./rect";

export function Selection({ data, id }: { data: Data; id: string }) {
const shape = data.useShapeByID(id);
const gripSize = 19;

const onDrag = (e: DragEvent) => {
if (!shape) {
return;
}

const shapeCenter = {
x: shape.x + shape.width / 2,
y: shape.y + shape.height / 2,
};

const size = (x1: number, x2: number, y1: number, y2: number) => {
const distanceSqFromCenterToCursor =
Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2);
return Math.sqrt(distanceSqFromCenterToCursor / 2) * 2;
};

const s0 = size(
shapeCenter.x,
e.pageX - e.movementX,
shapeCenter.y,
e.pageY - e.movementY
);
const s1 = size(shapeCenter.x, e.pageX, shapeCenter.y, e.pageY);

data.resizeShape({ id, ds: s1 - s0 });
};

const drag = useDrag({ onDrag });

if (!shape) {
return null;
}

return (
<div>
<Rect
{...{
data,
id,
highlight: true,
}}
/>
<div
style={{
position: "absolute",
transform: `translate3d(${shape.x}px, ${shape.y}px, 0) rotate(${shape.rotate}deg)`,
width: shape.width,
height: shape.height,
pointerEvents: "none",
}}
{...drag}
>
<svg
width={gripSize}
height={gripSize}
style={{
position: "absolute",
transform: `translate3d(${shape.width - gripSize / 2 - 2}px, ${
shape.height - gripSize / 2 - 2
}px, 0)`,
cursor: "grab",
pointerEvents: "all",
...drag,
}}
>
<rect
strokeWidth={2}
stroke="rgb(74,158,255)"
width={gripSize}
height={gripSize}
fill="white"
/>
</svg>
</div>
</div>
);
}
19 changes: 18 additions & 1 deletion pages/api/replicache-push.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import * as t from "io-ts";
import { ExecuteStatementFn, transact } from "../../backend/rds";
import { putShape, moveShape, shape, deleteShape } from "../../shared/shape";
import {
putShape,
moveShape,
resizeShape,
shape,
deleteShape,
} from "../../shared/shape";
import {
initClientState,
overShape,
Expand Down Expand Up @@ -43,6 +49,14 @@ const mutation = t.union([
dy: t.number,
}),
}),
t.type({
id: t.number,
name: t.literal("resizeShape"),
args: t.type({
id: t.string,
ds: t.number,
}),
}),
t.type({
id: t.number,
name: t.literal("initClientState"),
Expand Down Expand Up @@ -155,6 +169,9 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
case "moveShape":
await moveShape(s, mutation.args);
break;
case "resizeShape":
await resizeShape(s, mutation.args);
break;
case "initClientState":
await initClientState(s, mutation.args);
break;
Expand Down
Loading

0 comments on commit a138dd5

Please sign in to comment.