Skip to content

Commit 35f68c0

Browse files
committed
clickWasDragged helper
1 parent 3cb3fb3 commit 35f68c0

10 files changed

Lines changed: 159 additions & 7 deletions

File tree

frontend/three_d_garden/bed/objects/pointer_objects.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
getPlantIconTexture,
4242
getPlantIconTextureUrl,
4343
} from "../../garden/plant_icon_atlas";
44+
import { clickWasDragged } from "../../click_event";
4445

4546
export type PointerPlantRef = React.RefObject<GroupType | null>;
4647
export type RadiusRef = React.RefObject<MeshType | null>;
@@ -178,14 +179,12 @@ export interface SoilClickProps {
178179
getZ(x: number, y: number): number;
179180
}
180181

181-
export const MAX_SOIL_CLICK_DELTA = 2;
182-
183182
export const soilClick = (props: SoilClickProps) =>
184183
(e: ThreeEvent<MouseEvent>) => {
185184
const { config, navigate, addPlantProps, pointerPlantRef } = props;
186185
const getGardenPosition = getGardenPositionFunc(config);
187186
e.stopPropagation();
188-
if ((e.delta || 0) > MAX_SOIL_CLICK_DELTA) { return; }
187+
if (clickWasDragged(e)) { return; }
189188
if (addPlantProps) {
190189
if (getMode() == Mode.clickToAdd) {
191190
dropPlant({
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { ThreeEvent } from "@react-three/fiber";
2+
3+
export const MAX_POINTER_CLICK_DELTA = 1;
4+
5+
export const clickWasDragged = (
6+
event: Pick<ThreeEvent<MouseEvent>, "delta">,
7+
) => (event.delta || 0) > MAX_POINTER_CLICK_DELTA;

frontend/three_d_garden/garden/__tests__/plant_instances_test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,19 @@ describe("<PlantInstances />", () => {
185185
expect(mockNavigate).toHaveBeenCalledWith(Path.plants("1"));
186186
});
187187

188+
it("doesn't navigate after orbiting over a plant icon", () => {
189+
const p = fakeProps();
190+
const dispatch = jest.fn();
191+
p.dispatch = mockDispatch(dispatch);
192+
const wrapper = createRenderer(<PlantInstances {...p} />);
193+
const mesh = wrapper.root.findAll(node =>
194+
(node.type as string) == "instancedMesh")[0];
195+
mesh.props.onClick({ instanceId: 0, delta: 3 });
196+
unmountRenderer(wrapper);
197+
expect(dispatch).not.toHaveBeenCalled();
198+
expect(mockNavigate).not.toHaveBeenCalled();
199+
});
200+
188201
it("doesn't navigate without dispatch", () => {
189202
setMockInstanceId(0);
190203
const p = fakeProps();

frontend/three_d_garden/garden/__tests__/plants_test.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,20 @@ describe("<ThreeDPlantSpread />", () => {
253253
expect(mockNavigate).toHaveBeenCalledWith(Path.plants("1"));
254254
});
255255

256+
it("doesn't navigate after orbiting over a spread sphere", () => {
257+
queueMeshRef();
258+
const p = fakeProps();
259+
const dispatch = jest.fn();
260+
p.dispatch = mockDispatch(dispatch);
261+
const wrapper = createRenderer(<PlantSpreadInstances {...p} />);
262+
const mesh = wrapper.root.findAll(node =>
263+
(node.type as string) == "instancedMesh")[0];
264+
mesh.props.onClick({ instanceId: 0, delta: 3 });
265+
unmountRenderer(wrapper);
266+
expect(dispatch).not.toHaveBeenCalled();
267+
expect(mockNavigate).not.toHaveBeenCalled();
268+
});
269+
256270
it("doesn't navigate on spread click in camera selection mode", () => {
257271
setMockInstanceId(0);
258272
getModeSpy.mockReturnValue(Mode.cameraSelection);

frontend/three_d_garden/garden/__tests__/point_test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,20 @@ describe("<Point />", () => {
7878
expect(mockNavigate).toHaveBeenCalledWith(Path.points("1"));
7979
});
8080

81+
it("doesn't navigate after orbiting over a point", () => {
82+
const p = fakeProps();
83+
const dispatch = jest.fn();
84+
p.dispatch = mockDispatch(dispatch);
85+
p.point.body.id = 1;
86+
const wrapper = createRenderer(<Point {...p} />);
87+
mountedWrappers.push(wrapper);
88+
const point = wrapper.root
89+
.findAll(node => node.props.name == "marker")[0];
90+
point.props.onClick({ delta: 3 });
91+
expect(dispatch).not.toHaveBeenCalled();
92+
expect(mockNavigate).not.toHaveBeenCalled();
93+
});
94+
8195
it("doesn't navigate to point info", () => {
8296
const p = fakeProps();
8397
p.dispatch = undefined;
@@ -119,6 +133,20 @@ describe("<Point />", () => {
119133
});
120134
expect(mockNavigate).toHaveBeenCalledWith(Path.points("1"));
121135
});
136+
137+
it("doesn't navigate after orbiting over a point instance", () => {
138+
const p = fakeInstanceProps();
139+
const dispatch = jest.fn();
140+
p.dispatch = mockDispatch(dispatch);
141+
p.points[0].body.id = 1;
142+
const wrapper = createRenderer(<PointInstances {...p} />);
143+
mountedWrappers.push(wrapper);
144+
const marker = wrapper.root
145+
.findAll(node => node.props.name == "marker")[0];
146+
marker.props.onClick({ instanceId: 0, delta: 3 });
147+
expect(dispatch).not.toHaveBeenCalled();
148+
expect(mockNavigate).not.toHaveBeenCalled();
149+
});
122150
});
123151

124152
describe("<DrawnPoint />", () => {

frontend/three_d_garden/garden/__tests__/weed_test.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,29 @@ import { Actions } from "../../../constants";
99
import { mockDispatch } from "../../../__test_support__/fake_dispatch";
1010
import * as mapUtil from "../../../farm_designer/map/util";
1111
import { Mode } from "../../../farm_designer/map/interfaces";
12+
import { useFrame } from "@react-three/fiber";
13+
import { Quaternion } from "three";
1214
import {
1315
createRenderer,
1416
unmountRenderer,
1517
} from "../../../__test_support__/test_renderer";
1618

1719
describe("<Weed />", () => {
1820
let getModeSpy: jest.SpyInstance;
21+
let reactUseRefSpy: jest.SpyInstance | undefined;
1922
const mountedWrappers: ReturnType<typeof createRenderer>[] = [];
2023

2124
beforeEach(() => {
2225
getModeSpy = jest.spyOn(mapUtil, "getMode").mockReturnValue(Mode.none);
26+
(useFrame as jest.Mock).mockClear();
2327
});
2428

2529
afterEach(() => {
2630
mountedWrappers.splice(0).forEach(wrapper =>
2731
unmountRenderer(wrapper));
2832
getModeSpy.mockRestore();
33+
reactUseRefSpy?.mockRestore();
34+
reactUseRefSpy = undefined;
2935
});
3036

3137
const fakeProps = (): WeedProps => ({
@@ -66,6 +72,20 @@ describe("<Weed />", () => {
6672
expect(mockNavigate).toHaveBeenCalledWith(Path.weeds("1"));
6773
});
6874

75+
it("doesn't navigate after orbiting over a weed", () => {
76+
const p = fakeProps();
77+
const dispatch = jest.fn();
78+
p.dispatch = mockDispatch(dispatch);
79+
p.weed.body.id = 1;
80+
const wrapper = createRenderer(<Weed {...p} />);
81+
mountedWrappers.push(wrapper);
82+
const weed = wrapper.root
83+
.findAll(node => node.props.name == "weed-1")[0];
84+
weed.props.onClick({ delta: 3 });
85+
expect(dispatch).not.toHaveBeenCalled();
86+
expect(mockNavigate).not.toHaveBeenCalled();
87+
});
88+
6989
it("doesn't navigate to weed info", () => {
7090
const p = fakeProps();
7191
p.dispatch = undefined;
@@ -108,4 +128,64 @@ describe("<Weed />", () => {
108128
});
109129
expect(mockNavigate).toHaveBeenCalledWith(Path.weeds("1"));
110130
});
131+
132+
it("navigates from a weed radius instance", () => {
133+
const p = fakeInstanceProps();
134+
const dispatch = jest.fn();
135+
p.dispatch = mockDispatch(dispatch);
136+
p.weeds[0].body.id = 1;
137+
const wrapper = createRenderer(<WeedInstances {...p} />);
138+
mountedWrappers.push(wrapper);
139+
const weedRadius = wrapper.root
140+
.findAll(node => node.props.name == "weed-radius")[0];
141+
weedRadius.props.onClick({ instanceId: 0 });
142+
expect(dispatch).toHaveBeenCalledWith({
143+
type: Actions.SET_PANEL_OPEN, payload: true,
144+
});
145+
expect(mockNavigate).toHaveBeenCalledWith(Path.weeds("1"));
146+
});
147+
148+
it("doesn't navigate after orbiting over weed instances", () => {
149+
const p = fakeInstanceProps();
150+
const dispatch = jest.fn();
151+
p.dispatch = mockDispatch(dispatch);
152+
p.weeds[0].body.id = 1;
153+
const wrapper = createRenderer(<WeedInstances {...p} />);
154+
mountedWrappers.push(wrapper);
155+
const weedIcons = wrapper.root
156+
.findAll(node => node.props.name == "weed-icons")[0];
157+
const weedRadius = wrapper.root
158+
.findAll(node => node.props.name == "weed-radius")[0];
159+
weedIcons.props.onClick({ instanceId: 0, delta: 3 });
160+
weedRadius.props.onClick({ instanceId: 0, delta: 3 });
161+
expect(dispatch).not.toHaveBeenCalled();
162+
expect(mockNavigate).not.toHaveBeenCalled();
163+
});
164+
165+
it("updates weed icon matrices on frame", () => {
166+
const iconRef = {
167+
current: {
168+
setMatrixAt: jest.fn(),
169+
instanceMatrix: { needsUpdate: false },
170+
},
171+
};
172+
const radiusRef = {
173+
current: {
174+
setMatrixAt: jest.fn(),
175+
instanceMatrix: { needsUpdate: false },
176+
},
177+
};
178+
const updateStateRef = { current: {} };
179+
reactUseRefSpy = jest.spyOn(React, "useRef")
180+
.mockImplementationOnce(() => iconRef)
181+
.mockImplementationOnce(() => updateStateRef)
182+
.mockImplementationOnce(() => radiusRef)
183+
.mockImplementation(value => ({ current: value }));
184+
const wrapper = createRenderer(<WeedInstances {...fakeInstanceProps()} />);
185+
mountedWrappers.push(wrapper);
186+
const frameFn = (useFrame as jest.Mock).mock.calls[0][0];
187+
frameFn({ camera: { quaternion: new Quaternion() } });
188+
expect(iconRef.current.setMatrixAt).toHaveBeenCalled();
189+
expect(iconRef.current.instanceMatrix.needsUpdate).toEqual(true);
190+
});
111191
});

frontend/three_d_garden/garden/plant_instances.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { Mode } from "../../farm_designer/map/interfaces";
2929
import {
3030
calcSunCoordinate, calcSunI, getAnimatedSeasonDate,
3131
} from "./sun";
32+
import { clickWasDragged } from "../click_event";
3233

3334
export interface PlantInstancesProps {
3435
plants: ThreeDGardenPlant[];
@@ -162,6 +163,7 @@ const PlantIconInstances = (props: PlantIconInstancesProps) => {
162163
});
163164

164165
const onClick = (event: ThreeEvent<MouseEvent>) => {
166+
if (clickWasDragged(event)) { return; }
165167
const instanceId = event.instanceId;
166168
if (isUndefined(instanceId)) { return; }
167169
const plant = plants[instanceId];

frontend/three_d_garden/garden/plants.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { ActivePositionRef } from "../bed/objects/pointer_objects";
3535
import { Mode } from "../../farm_designer/map/interfaces";
3636
import { findCrop } from "../../crops/find";
3737
import { perfMeasure } from "../../performance/perf";
38+
import { clickWasDragged } from "../click_event";
3839

3940
export interface ThreeDGardenPlant {
4041
id?: number | undefined;
@@ -286,6 +287,7 @@ export const PlantSpreadInstances = React.memo((props: PlantSpreadInstancesProps
286287
});
287288

288289
const onClick = (event: ThreeEvent<MouseEvent>) => {
290+
if (clickWasDragged(event)) { return; }
289291
const instanceId = event.instanceId;
290292
if (isUndefined(instanceId)) { return; }
291293
const plant = plants[instanceId];

frontend/three_d_garden/garden/point.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { HOVER_OBJECT_MODES, RenderOrder } from "../constants";
2828
import {
2929
BillboardRef, ImageRef, RadiusRef, TorusRef,
3030
} from "../bed/objects/pointer_objects";
31+
import { clickWasDragged } from "../click_event";
3132

3233
const POINT_PIN_RADIUS = 12.5;
3334
const POINT_PIN_HEIGHT = 50;
@@ -58,7 +59,8 @@ export const Point = (props: PointProps) => {
5859
y: point.body.y,
5960
z: props.getZ(point.body.x, point.body.y),
6061
}}
61-
onClick={() => {
62+
onClick={(event) => {
63+
if (clickWasDragged(event)) { return; }
6264
if (point.body.id && !isUndefined(props.dispatch) && props.visible &&
6365
!HOVER_OBJECT_MODES.includes(getMode())) {
6466
props.dispatch(setPanelOpen(true));
@@ -186,6 +188,7 @@ const PointBucketInstances = (props: PointInstanceBucketProps) => {
186188

187189
const onClick = (instances: PointInstance[]) =>
188190
(event: ThreeEvent<MouseEvent>) => {
191+
if (clickWasDragged(event)) { return; }
189192
const instanceId = event.instanceId;
190193
if (isUndefined(instanceId)) { return; }
191194
const point = instances[instanceId]?.point;
@@ -292,7 +295,7 @@ export const DrawnPoint = (props: DrawnPointProps) => {
292295
interface PointBaseProps {
293296
pointName: string;
294297
position?: Record<Xyz, number>;
295-
onClick?: () => void;
298+
onClick?: (event: ThreeEvent<MouseEvent>) => void;
296299
color: string | undefined;
297300
radius: number;
298301
alpha: number;

frontend/three_d_garden/garden/weed.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { isUndefined } from "lodash";
2020
import { setPanelOpen } from "../../farm_designer/panel_header";
2121
import { getMode } from "../../farm_designer/map/util";
2222
import { RadiusRef, BillboardRef, ImageRef } from "../bed/objects/pointer_objects";
23+
import { clickWasDragged } from "../click_event";
2324

2425
export const WEED_IMG_SIZE_FRACTION = 0.89;
2526

@@ -37,7 +38,8 @@ export const Weed = (props: WeedProps) => {
3738
return <WeedBase
3839
pointName={"" + weed.body.id}
3940
alpha={1}
40-
onClick={() => {
41+
onClick={(event) => {
42+
if (clickWasDragged(event)) { return; }
4143
if (weed.body.id && !isUndefined(props.dispatch) && props.visible &&
4244
!HOVER_OBJECT_MODES.includes(getMode())) {
4345
props.dispatch(setPanelOpen(true));
@@ -57,7 +59,7 @@ export const Weed = (props: WeedProps) => {
5759
interface WeedBaseProps {
5860
pointName: string;
5961
position?: Record<Xyz, number>;
60-
onClick?: () => void;
62+
onClick?: (event: ThreeEvent<MouseEvent>) => void;
6163
color: string | undefined;
6264
radius: number;
6365
alpha: number;
@@ -241,6 +243,7 @@ const WeedIconInstances = (props: WeedIconInstancesProps) => {
241243
});
242244

243245
const onClick = (event: ThreeEvent<MouseEvent>) => {
246+
if (clickWasDragged(event)) { return; }
244247
const instanceId = event.instanceId;
245248
if (isUndefined(instanceId)) { return; }
246249
navigateToWeed(weedInstances[instanceId]?.weed);
@@ -289,6 +292,7 @@ const WeedRadiusInstances = (props: WeedRadiusInstancesProps) => {
289292
}, [bucket.weeds, noRotation, tempMatrix, tempPosition, tempScale]);
290293

291294
const onClick = (event: ThreeEvent<MouseEvent>) => {
295+
if (clickWasDragged(event)) { return; }
292296
const instanceId = event.instanceId;
293297
if (isUndefined(instanceId)) { return; }
294298
navigateToWeed(bucket.weeds[instanceId]?.weed);

0 commit comments

Comments
 (0)