update map actions
parent
bb9566345a
commit
240f86c7c1
|
@ -118,9 +118,9 @@ describe("clickMapPlant", () => {
|
|||
const state = fakeState();
|
||||
const dispatch = jest.fn();
|
||||
const getState: GetState = jest.fn(() => state);
|
||||
clickMapPlant("foo", "bar")(dispatch, getState);
|
||||
expect(dispatch).toHaveBeenCalledWith(selectPlant(["foo"]));
|
||||
expect(dispatch).toHaveBeenCalledWith(setHoveredPlant("foo", "bar"));
|
||||
clickMapPlant("fakeUuid", "fakeIcon")(dispatch, getState);
|
||||
expect(dispatch).toHaveBeenCalledWith(selectPlant(["fakeUuid"]));
|
||||
expect(dispatch).toHaveBeenCalledWith(setHoveredPlant("fakeUuid", "fakeIcon"));
|
||||
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
|
@ -133,7 +133,7 @@ describe("clickMapPlant", () => {
|
|||
state.resources = buildResourceIndex([plant]);
|
||||
const dispatch = jest.fn();
|
||||
const getState: GetState = jest.fn(() => state);
|
||||
clickMapPlant(plant.uuid, "bar")(dispatch, getState);
|
||||
clickMapPlant(plant.uuid, "fakeIcon")(dispatch, getState);
|
||||
expect(overwrite).toHaveBeenCalledWith(mockGroup, expect.objectContaining({
|
||||
name: "Fake", point_ids: [1, 23]
|
||||
}));
|
||||
|
@ -149,10 +149,41 @@ describe("clickMapPlant", () => {
|
|||
state.resources = buildResourceIndex([plant]);
|
||||
const dispatch = jest.fn();
|
||||
const getState: GetState = jest.fn(() => state);
|
||||
clickMapPlant(plant.uuid, "bar")(dispatch, getState);
|
||||
clickMapPlant(plant.uuid, "fakeIcon")(dispatch, getState);
|
||||
expect(overwrite).toHaveBeenCalledWith(mockGroup, expect.objectContaining({
|
||||
name: "Fake", point_ids: [1]
|
||||
}));
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("adds a plant to the current selection if plant select is active", () => {
|
||||
mockPath = "/app/designer/plants/select";
|
||||
const state = fakeState();
|
||||
const plant = fakePlant();
|
||||
plant.uuid = "fakePlantUuid";
|
||||
state.resources = buildResourceIndex([plant]);
|
||||
const dispatch = jest.fn();
|
||||
const getState: GetState = jest.fn(() => state);
|
||||
clickMapPlant(plant.uuid, "fakeIcon")(dispatch, getState);
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SELECT_PLANT, payload: [plant.uuid]
|
||||
});
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("removes a plant to the current selection if plant select is active", () => {
|
||||
mockPath = "/app/designer/plants/select";
|
||||
const state = fakeState();
|
||||
const plant = fakePlant();
|
||||
plant.uuid = "fakePlantUuid";
|
||||
state.resources = buildResourceIndex([plant]);
|
||||
state.resources.consumers.farm_designer.selectedPlants = [plant.uuid];
|
||||
const dispatch = jest.fn();
|
||||
const getState: GetState = jest.fn(() => state);
|
||||
clickMapPlant(plant.uuid, "fakeIcon")(dispatch, getState);
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SELECT_PLANT, payload: []
|
||||
});
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -36,6 +36,15 @@ jest.mock("../background/selection_box_actions", () => ({
|
|||
|
||||
jest.mock("../../move_to", () => ({ chooseLocation: jest.fn() }));
|
||||
|
||||
jest.mock("../../../history", () => ({
|
||||
history: {
|
||||
push: jest.fn(),
|
||||
getPathArray: () => [],
|
||||
},
|
||||
push: jest.fn(),
|
||||
getPathArray: () => [],
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { GardenMap } from "../garden_map";
|
||||
import { shallow, mount } from "enzyme";
|
||||
|
@ -56,6 +65,7 @@ import {
|
|||
} from "../../../__test_support__/fake_designer_state";
|
||||
import { fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
||||
import { history } from "../../../history";
|
||||
|
||||
const DEFAULT_EVENT = { preventDefault: jest.fn(), pageX: NaN, pageY: NaN };
|
||||
|
||||
|
@ -144,12 +154,46 @@ describe("<GardenMap/>", () => {
|
|||
expect(dragPlant).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("starts drag: selecting", () => {
|
||||
const wrapper = shallow(<GardenMap {...fakeProps()} />);
|
||||
it("starts drag on background: selecting", () => {
|
||||
const wrapper = mount(<GardenMap {...fakeProps()} />);
|
||||
mockMode = Mode.addPlant;
|
||||
const e = { pageX: 1000, pageY: 2000 };
|
||||
wrapper.find(".drop-area-background").simulate("mouseDown", e);
|
||||
expect(startNewSelectionBox).toHaveBeenCalled();
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/plants");
|
||||
expect(getGardenCoordinates).toHaveBeenCalledWith(
|
||||
expect.objectContaining(e));
|
||||
});
|
||||
|
||||
it("starts drag on background: selecting again", () => {
|
||||
const wrapper = mount(<GardenMap {...fakeProps()} />);
|
||||
mockMode = Mode.boxSelect;
|
||||
const e = { pageX: 1000, pageY: 2000 };
|
||||
wrapper.find(".drop-area-svg").simulate("mouseDown", e);
|
||||
wrapper.find(".drop-area-background").simulate("mouseDown", e);
|
||||
expect(startNewSelectionBox).toHaveBeenCalled();
|
||||
expect(history.push).not.toHaveBeenCalled();
|
||||
expect(getGardenCoordinates).toHaveBeenCalledWith(
|
||||
expect.objectContaining(e));
|
||||
});
|
||||
|
||||
it("starts drag on background: does nothing when adding plants", () => {
|
||||
const wrapper = mount(<GardenMap {...fakeProps()} />);
|
||||
mockMode = Mode.clickToAdd;
|
||||
const e = { pageX: 1000, pageY: 2000 };
|
||||
wrapper.find(".drop-area-background").simulate("mouseDown", e);
|
||||
expect(startNewSelectionBox).not.toHaveBeenCalled();
|
||||
expect(history.push).not.toHaveBeenCalled();
|
||||
expect(getGardenCoordinates).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("starts drag on background: creating points", () => {
|
||||
const wrapper = mount(<GardenMap {...fakeProps()} />);
|
||||
mockMode = Mode.createPoint;
|
||||
const e = { pageX: 1000, pageY: 2000 };
|
||||
wrapper.find(".drop-area-background").simulate("mouseDown", e);
|
||||
expect(startNewPoint).toHaveBeenCalled();
|
||||
expect(startNewSelectionBox).not.toHaveBeenCalled();
|
||||
expect(history.push).not.toHaveBeenCalled();
|
||||
expect(getGardenCoordinates).toHaveBeenCalledWith(
|
||||
expect.objectContaining(e));
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ import { GetState } from "../../redux/interfaces";
|
|||
import { fetchGroupFromUrl } from "../point_groups/group_detail";
|
||||
import { TaggedPoint } from "farmbot";
|
||||
import { getMode } from "../map/util";
|
||||
import { ResourceIndex, UUID } from "../../resources/interfaces";
|
||||
|
||||
export function movePlant(payload: MovePlantProps) {
|
||||
const tr = payload.plant;
|
||||
|
@ -25,36 +26,55 @@ export const selectPlant = (payload: string[] | undefined) => {
|
|||
return { type: Actions.SELECT_PLANT, payload };
|
||||
};
|
||||
|
||||
export const setHoveredPlant =
|
||||
(plantUUID: string | undefined, icon = "") => {
|
||||
return {
|
||||
type: Actions.TOGGLE_HOVERED_PLANT,
|
||||
payload: { plantUUID, icon }
|
||||
};
|
||||
export const setHoveredPlant = (plantUUID: string | undefined, icon = "") => ({
|
||||
type: Actions.TOGGLE_HOVERED_PLANT,
|
||||
payload: { plantUUID, icon }
|
||||
});
|
||||
|
||||
const addOrRemoveFromGroup =
|
||||
(clickedPlantUuid: UUID, resources: ResourceIndex) => {
|
||||
const group = fetchGroupFromUrl(resources);
|
||||
const point =
|
||||
resources.references[clickedPlantUuid] as TaggedPoint | undefined;
|
||||
if (group && point && point.body.id) {
|
||||
type Body = (typeof group)["body"];
|
||||
const nextGroup: Body = ({
|
||||
...group.body,
|
||||
point_ids: [...group.body.point_ids.filter(p => p != point.body.id)]
|
||||
});
|
||||
if (!group.body.point_ids.includes(point.body.id)) {
|
||||
nextGroup.point_ids.push(point.body.id);
|
||||
}
|
||||
nextGroup.point_ids = uniq(nextGroup.point_ids);
|
||||
return overwrite(group, nextGroup);
|
||||
}
|
||||
};
|
||||
|
||||
const addOrRemoveFromSelection =
|
||||
(clickedPlantUuid: UUID, selectedPlants: UUID[] | undefined) => {
|
||||
const nextSelected =
|
||||
(selectedPlants || []).filter(uuid => uuid !== clickedPlantUuid);
|
||||
if (!(selectedPlants && selectedPlants.includes(clickedPlantUuid))) {
|
||||
nextSelected.push(clickedPlantUuid);
|
||||
}
|
||||
return selectPlant(nextSelected);
|
||||
};
|
||||
|
||||
export const clickMapPlant = (clickedPlantUuid: string, icon: string) => {
|
||||
return (dispatch: Function, getState: GetState) => {
|
||||
if (getMode() === Mode.editGroup) {
|
||||
const { resources } = getState();
|
||||
const group = fetchGroupFromUrl(resources.index);
|
||||
const point =
|
||||
resources.index.references[clickedPlantUuid] as TaggedPoint | undefined;
|
||||
if (group && point && point.body.id) {
|
||||
type Body = (typeof group)["body"];
|
||||
const nextGroup: Body = ({
|
||||
...group.body,
|
||||
point_ids: [...group.body.point_ids.filter(p => p != point.body.id)]
|
||||
});
|
||||
if (!group.body.point_ids.includes(point.body.id)) {
|
||||
nextGroup.point_ids.push(point.body.id);
|
||||
}
|
||||
nextGroup.point_ids = uniq(nextGroup.point_ids);
|
||||
dispatch(overwrite(group, nextGroup));
|
||||
}
|
||||
} else {
|
||||
dispatch(selectPlant([clickedPlantUuid]));
|
||||
dispatch(setHoveredPlant(clickedPlantUuid, icon));
|
||||
switch (getMode()) {
|
||||
case Mode.editGroup:
|
||||
const { resources } = getState();
|
||||
dispatch(addOrRemoveFromGroup(clickedPlantUuid, resources.index));
|
||||
break;
|
||||
case Mode.boxSelect:
|
||||
const { selectedPlants } = getState().resources.consumers.farm_designer;
|
||||
dispatch(addOrRemoveFromSelection(clickedPlantUuid, selectedPlants));
|
||||
break;
|
||||
default:
|
||||
dispatch(selectPlant([clickedPlantUuid]));
|
||||
dispatch(setHoveredPlant(clickedPlantUuid, icon));
|
||||
break;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ describe("<Grid/>", () => {
|
|||
return {
|
||||
mapTransformProps: fakeMapTransformProps(),
|
||||
onClick: jest.fn(),
|
||||
onMouseDown: jest.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
import { Mode } from "../../interfaces";
|
||||
let mockMode = Mode.none;
|
||||
jest.mock("../../util", () => ({ getMode: () => mockMode }));
|
||||
|
||||
jest.mock("../../../../history", () => ({ history: { push: jest.fn() } }));
|
||||
|
||||
import { fakePlant } from "../../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
getSelected, resizeBox, startNewSelectionBox
|
||||
getSelected, resizeBox, startNewSelectionBox, ResizeSelectionBoxProps
|
||||
} from "../selection_box_actions";
|
||||
import { Actions } from "../../../../constants";
|
||||
import { history } from "../../../../history";
|
||||
|
||||
describe("getSelected", () => {
|
||||
it("returns some", () => {
|
||||
|
@ -24,7 +31,11 @@ describe("getSelected", () => {
|
|||
});
|
||||
|
||||
describe("resizeBox", () => {
|
||||
const fakeProps = () => ({
|
||||
beforeEach(() => {
|
||||
mockMode = Mode.boxSelect;
|
||||
});
|
||||
|
||||
const fakeProps = (): ResizeSelectionBoxProps => ({
|
||||
selectionBox: { x0: 0, y0: 0, x1: undefined, y1: undefined },
|
||||
plants: [],
|
||||
gardenCoords: { x: 100, y: 200 },
|
||||
|
@ -61,6 +72,24 @@ describe("resizeBox", () => {
|
|||
expect(p.setMapState).not.toHaveBeenCalled();
|
||||
expect(p.dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resizes selection box", () => {
|
||||
mockMode = Mode.none;
|
||||
const p = fakeProps();
|
||||
const plant = fakePlant();
|
||||
plant.body.x = 50;
|
||||
plant.body.y = 50;
|
||||
p.plants = [plant];
|
||||
resizeBox(p);
|
||||
expect(p.setMapState).toHaveBeenCalledWith({
|
||||
selectionBox: { x0: 0, y0: 0, x1: 100, y1: 200 }
|
||||
});
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SELECT_PLANT,
|
||||
payload: [plant.uuid]
|
||||
});
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/plants/select");
|
||||
});
|
||||
});
|
||||
|
||||
describe("startNewSelectionBox", () => {
|
||||
|
|
|
@ -13,7 +13,8 @@ export function Grid(props: GridProps) {
|
|||
const arrowEnd = transformXY(25, 25, mapTransformProps);
|
||||
const xLabel = transformXY(15, -10, mapTransformProps);
|
||||
const yLabel = transformXY(-11, 18, mapTransformProps);
|
||||
return <g className="drop-area-background" onClick={props.onClick}>
|
||||
return <g className="drop-area-background" onClick={props.onClick}
|
||||
onMouseDown={props.onMouseDown}>
|
||||
<defs>
|
||||
<pattern id="minor_grid"
|
||||
width={10} height={10} patternUnits="userSpaceOnUse">
|
||||
|
|
|
@ -26,14 +26,16 @@ export const getSelected = (
|
|||
return arraySelected.length > 0 ? arraySelected : undefined;
|
||||
};
|
||||
|
||||
export interface ResizeSelectionBoxProps {
|
||||
selectionBox: SelectionBoxData | undefined;
|
||||
plants: TaggedPlant[];
|
||||
gardenCoords: AxisNumberProperty | undefined;
|
||||
setMapState: (x: Partial<GardenMapState>) => void;
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
/** Resize a selection box. */
|
||||
export const resizeBox = (props: {
|
||||
selectionBox: SelectionBoxData | undefined,
|
||||
plants: TaggedPlant[],
|
||||
gardenCoords: AxisNumberProperty | undefined,
|
||||
setMapState: (x: Partial<GardenMapState>) => void,
|
||||
dispatch: Function,
|
||||
}) => {
|
||||
export const resizeBox = (props: ResizeSelectionBoxProps) => {
|
||||
if (props.selectionBox) {
|
||||
const current = props.gardenCoords;
|
||||
if (current) {
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
import { chooseLocation } from "../move_to";
|
||||
import { GroupOrder } from "../point_groups/group_order_visual";
|
||||
import { NNPath } from "../point_groups/paths";
|
||||
import { history } from "../../history";
|
||||
|
||||
export class GardenMap extends
|
||||
React.Component<GardenMapProps, Partial<GardenMapState>> {
|
||||
|
@ -95,7 +96,7 @@ export class GardenMap extends
|
|||
|
||||
setMapState = (x: Partial<GardenMapState>) => this.setState(x);
|
||||
|
||||
/** Map drag start actions. */
|
||||
/** Map (anywhere) drag start actions. */
|
||||
startDrag = (e: React.MouseEvent<SVGElement>): void => {
|
||||
switch (getMode()) {
|
||||
case Mode.editPlant:
|
||||
|
@ -108,7 +109,7 @@ export class GardenMap extends
|
|||
selectedPlant: this.props.selectedPlant,
|
||||
});
|
||||
} else { // Actions away from plant exit plant edit mode.
|
||||
closePlantInfo(this.props.dispatch)();
|
||||
this.closePanel()();
|
||||
startNewSelectionBox({
|
||||
gardenCoords,
|
||||
setMapState: this.setMapState,
|
||||
|
@ -125,8 +126,25 @@ export class GardenMap extends
|
|||
break;
|
||||
case Mode.clickToAdd:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Map background drag start actions. */
|
||||
startDragOnBackground = (e: React.MouseEvent<SVGElement>): void => {
|
||||
switch (getMode()) {
|
||||
case Mode.createPoint:
|
||||
case Mode.clickToAdd:
|
||||
case Mode.editPlant:
|
||||
break;
|
||||
case Mode.boxSelect:
|
||||
startNewSelectionBox({
|
||||
gardenCoords: this.getGardenCoordinates(e),
|
||||
setMapState: this.setMapState,
|
||||
dispatch: this.props.dispatch,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
history.push("/app/designer/plants");
|
||||
startNewSelectionBox({
|
||||
gardenCoords: this.getGardenCoordinates(e),
|
||||
setMapState: this.setMapState,
|
||||
|
@ -281,6 +299,7 @@ export class GardenMap extends
|
|||
(this.props.getConfigValue("photo_filter_end") || "").toString()} />
|
||||
Grid = () => <Grid
|
||||
onClick={this.closePanel()}
|
||||
onMouseDown={this.startDragOnBackground}
|
||||
mapTransformProps={this.mapTransformProps} />
|
||||
SensorReadingsLayer = () => <SensorReadingsLayer
|
||||
visible={!!this.props.showSensorReadings}
|
||||
|
|
|
@ -111,6 +111,7 @@ export interface MapBackgroundProps {
|
|||
export interface GridProps {
|
||||
mapTransformProps: MapTransformProps;
|
||||
onClick(): void;
|
||||
onMouseDown(e: React.MouseEvent<SVGElement>): void;
|
||||
}
|
||||
|
||||
export interface VirtualFarmBotProps {
|
||||
|
|
|
@ -60,7 +60,7 @@ describe("<PlantLayer/>", () => {
|
|||
});
|
||||
|
||||
it("is in non-clickable mode", () => {
|
||||
mockPath = "/app/designer/plants/select";
|
||||
mockPath = "/app/designer/plants/crop_search/mint/add";
|
||||
const p = fakeProps();
|
||||
|
||||
const wrapper = svgMount(<PlantLayer {...p} />);
|
||||
|
|
|
@ -44,7 +44,7 @@ export function PlantLayer(props: PlantLayerProps) {
|
|||
style: maybeNoPointer(p.body.id ? {} : { pointerEvents: "none" }),
|
||||
key: p.uuid,
|
||||
};
|
||||
return getMode() === Mode.editGroup
|
||||
return (getMode() === Mode.editGroup || getMode() === Mode.boxSelect)
|
||||
? <g {...wrapperProps}>{plant}</g>
|
||||
: <Link {...wrapperProps}
|
||||
to={`/app/designer/${plantCategory}/${"" + p.body.id}`}>
|
||||
|
|
|
@ -68,7 +68,7 @@ describe("<ToolSlotLayer/>", () => {
|
|||
});
|
||||
|
||||
it("is in non-clickable mode", () => {
|
||||
mockPath = "/app/designer/plants/select";
|
||||
mockPath = "/app/designer/plants/crop_search/mint/add";
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<ToolSlotLayer {...p} />);
|
||||
expect(wrapper.find("g").props().style)
|
||||
|
|
|
@ -340,12 +340,11 @@ export const getGardenCoordinates = (props: {
|
|||
export const maybeNoPointer =
|
||||
(defaultStyle: React.CSSProperties): React.SVGProps<SVGGElement>["style"] => {
|
||||
switch (getMode()) {
|
||||
case Mode.boxSelect:
|
||||
case Mode.clickToAdd:
|
||||
case Mode.moveTo:
|
||||
case Mode.points:
|
||||
case Mode.createPoint:
|
||||
return { "pointerEvents": "none" };
|
||||
return { pointerEvents: "none" };
|
||||
default:
|
||||
return defaultStyle;
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
title={t("{{length}} plants selected",
|
||||
{ length: this.selected.length })}
|
||||
backTo={"/app/designer/plants"}
|
||||
onBack={unselectPlant(dispatch)}
|
||||
description={Content.BOX_SELECT_DESCRIPTION} />
|
||||
<this.ActionButtons />
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const mockId = 123;
|
||||
jest.mock("../../../history", () => ({
|
||||
getPathArray: jest.fn(() => [mockId])
|
||||
getPathArray: jest.fn(() => ["groups", mockId])
|
||||
}));
|
||||
|
||||
import { fetchGroupFromUrl } from "../group_detail";
|
||||
|
|
|
@ -14,7 +14,7 @@ mockGroup.body.point_ids = [23];
|
|||
let mockId = GOOD_ID;
|
||||
jest.mock("../../../history", () => {
|
||||
return {
|
||||
getPathArray: jest.fn(() => [mockId]),
|
||||
getPathArray: jest.fn(() => ["groups", mockId]),
|
||||
push: jest.fn()
|
||||
};
|
||||
});
|
||||
|
|
|
@ -16,12 +16,12 @@ interface GroupDetailProps {
|
|||
}
|
||||
|
||||
export function fetchGroupFromUrl(index: ResourceIndex) {
|
||||
if (!getPathArray().includes("groups")) { return; }
|
||||
/** TODO: Write better selectors. */
|
||||
const groupId = parseInt(getPathArray().pop() || "?", 10);
|
||||
let group: TaggedPointGroup | undefined;
|
||||
try {
|
||||
group =
|
||||
findByKindAndId<TaggedPointGroup>(index, "PointGroup", groupId);
|
||||
group = findByKindAndId<TaggedPointGroup>(index, "PointGroup", groupId);
|
||||
} catch (error) {
|
||||
group = undefined;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue