history.push(`/app/designer/tool-slots/${id}`)}
+ onClick={() => {
+ if (getMode() == Mode.boxSelect) {
+ mapPointClickAction(props.dispatch, props.toolSlot.uuid)();
+ props.dispatch(setToolHover(undefined));
+ } else {
+ history.push(`/app/designer/tool-slots/${id}`);
+ }
+ }}
onMouseEnter={() => props.dispatch(setToolHover(props.toolSlot.uuid))}
onMouseLeave={() => props.dispatch(setToolHover(undefined))}>
@@ -225,20 +237,24 @@ export const ToolSlotInventoryItem = (props: ToolSlotInventoryItemProps) => {
xySwap={props.xySwap} quadrant={props.quadrant} />
- e.stopPropagation()}>
- tool.body.id == tool_id)[0]}
- onChange={update => {
- props.dispatch(edit(props.toolSlot, update));
- props.dispatch(save(props.toolSlot.uuid));
- }}
- isActive={props.isActive}
- filterSelectedTool={false}
- filterActiveTools={true} />
-
+ {props.hideDropdown
+ ?
+ {toolName || t("Empty")}
+
+ : e.stopPropagation()}>
+ tool.body.id == tool_id)[0]}
+ onChange={update => {
+ props.dispatch(edit(props.toolSlot, update));
+ props.dispatch(save(props.toolSlot.uuid));
+ }}
+ isActive={props.isActive}
+ filterSelectedTool={false}
+ filterActiveTools={true} />
+
}
diff --git a/frontend/farm_designer/tools/interfaces.ts b/frontend/farm_designer/tools/interfaces.ts
index 47013db84..23a188c4f 100644
--- a/frontend/farm_designer/tools/interfaces.ts
+++ b/frontend/farm_designer/tools/interfaces.ts
@@ -19,6 +19,7 @@ export interface AddToolProps {
export interface AddToolState {
toolName: string;
toAdd: string[];
+ uuid: UUID | undefined;
}
export interface EditToolProps {
@@ -26,6 +27,7 @@ export interface EditToolProps {
dispatch: Function;
mountedToolId: number | undefined;
isActive(id: number | undefined): boolean;
+ existingToolNames: string[];
}
export interface EditToolState {
diff --git a/frontend/farm_designer/weeds/__tests__/weed_inventory_item_test.tsx b/frontend/farm_designer/weeds/__tests__/weed_inventory_item_test.tsx
new file mode 100644
index 000000000..125b0e697
--- /dev/null
+++ b/frontend/farm_designer/weeds/__tests__/weed_inventory_item_test.tsx
@@ -0,0 +1,81 @@
+let mockPath = "/app/designer/weeds";
+jest.mock("../../../history", () => ({
+ push: jest.fn(),
+ getPathArray: () => mockPath.split("/"),
+}));
+
+jest.mock("../../map/actions", () => ({
+ mapPointClickAction: jest.fn(() => jest.fn()),
+}));
+
+import * as React from "react";
+import { shallow } from "enzyme";
+import {
+ WeedInventoryItem, WeedInventoryItemProps,
+} from "../weed_inventory_item";
+import { fakeWeed } from "../../../__test_support__/fake_state/resources";
+import { push } from "../../../history";
+import { Actions } from "../../../constants";
+import { mapPointClickAction } from "../../map/actions";
+
+describe(" />", () => {
+ const fakeProps = (): WeedInventoryItemProps => ({
+ tpp: fakeWeed(),
+ dispatch: jest.fn(),
+ hovered: false,
+ });
+
+ it("navigates to weed", () => {
+ const p = fakeProps();
+ p.tpp.body.id = 1;
+ const wrapper = shallow();
+ wrapper.simulate("click");
+ expect(mapPointClickAction).not.toHaveBeenCalled();
+ expect(push).toHaveBeenCalledWith("/app/designer/weeds/1");
+ expect(p.dispatch).toHaveBeenCalledWith({
+ type: Actions.TOGGLE_HOVERED_POINT,
+ payload: [p.tpp.uuid],
+ });
+ });
+
+ it("removes item in box select mode", () => {
+ mockPath = "/app/designer/plants/select";
+ const p = fakeProps();
+ const wrapper = shallow();
+ wrapper.simulate("click");
+ expect(mapPointClickAction).toHaveBeenCalledWith(expect.any(Function),
+ p.tpp.uuid);
+ expect(push).not.toHaveBeenCalled();
+ expect(p.dispatch).toHaveBeenCalledWith({
+ type: Actions.TOGGLE_HOVERED_POINT,
+ payload: undefined,
+ });
+ });
+
+ it("hovers weed", () => {
+ const p = fakeProps();
+ p.tpp.body.id = 1;
+ const wrapper = shallow();
+ wrapper.simulate("mouseEnter");
+ expect(p.dispatch).toHaveBeenCalledWith({
+ type: Actions.TOGGLE_HOVERED_POINT, payload: p.tpp.uuid
+ });
+ });
+
+ it("shows hovered", () => {
+ const p = fakeProps();
+ p.hovered = true;
+ const wrapper = shallow();
+ expect(wrapper.hasClass("hovered")).toBeTruthy();
+ });
+
+ it("un-hovers weed", () => {
+ const p = fakeProps();
+ p.tpp.body.id = 1;
+ const wrapper = shallow();
+ wrapper.simulate("mouseLeave");
+ expect(p.dispatch).toHaveBeenCalledWith({
+ type: Actions.TOGGLE_HOVERED_POINT, payload: undefined
+ });
+ });
+});
diff --git a/frontend/farm_designer/points/__tests__/weeds_edit_test.tsx b/frontend/farm_designer/weeds/__tests__/weeds_edit_test.tsx
similarity index 80%
rename from frontend/farm_designer/points/__tests__/weeds_edit_test.tsx
rename to frontend/farm_designer/weeds/__tests__/weeds_edit_test.tsx
index 9569ac587..fdfdc7516 100644
--- a/frontend/farm_designer/points/__tests__/weeds_edit_test.tsx
+++ b/frontend/farm_designer/weeds/__tests__/weeds_edit_test.tsx
@@ -9,7 +9,7 @@ import { mount, shallow } from "enzyme";
import {
RawEditWeed as EditWeed, EditWeedProps, mapStateToProps,
} from "../weeds_edit";
-import { fakePoint } from "../../../__test_support__/fake_state/resources";
+import { fakeWeed } from "../../../__test_support__/fake_state/resources";
import { fakeState } from "../../../__test_support__/fake_state";
import {
buildResourceIndex,
@@ -32,9 +32,9 @@ describe("", () => {
it("renders", () => {
mockPath = "/app/designer/weeds/1";
const p = fakeProps();
- const point = fakePoint();
- point.body.id = 1;
- p.findPoint = () => point;
+ const weed = fakeWeed();
+ weed.body.id = 1;
+ p.findPoint = () => weed;
const wrapper = mount();
expect(wrapper.text().toLowerCase()).toContain("edit");
});
@@ -42,9 +42,9 @@ describe("", () => {
it("goes back", () => {
mockPath = "/app/designer/weeds/1";
const p = fakeProps();
- const point = fakePoint();
- point.body.id = 1;
- p.findPoint = () => point;
+ const weed = fakeWeed();
+ weed.body.id = 1;
+ p.findPoint = () => weed;
const wrapper = shallow();
wrapper.find(DesignerPanelHeader).simulate("back");
expect(p.dispatch).toHaveBeenCalledWith({
@@ -56,10 +56,10 @@ describe("", () => {
describe("mapStateToProps()", () => {
it("returns props", () => {
const state = fakeState();
- const point = fakePoint();
- point.body.id = 1;
- state.resources = buildResourceIndex([point]);
+ const weed = fakeWeed();
+ weed.body.id = 1;
+ state.resources = buildResourceIndex([weed]);
const props = mapStateToProps(state);
- expect(props.findPoint(1)).toEqual(point);
+ expect(props.findPoint(1)).toEqual(weed);
});
});
diff --git a/frontend/farm_designer/points/__tests__/weeds_inventory_test.tsx b/frontend/farm_designer/weeds/__tests__/weeds_inventory_test.tsx
similarity index 83%
rename from frontend/farm_designer/points/__tests__/weeds_inventory_test.tsx
rename to frontend/farm_designer/weeds/__tests__/weeds_inventory_test.tsx
index 638f79066..b76423aef 100644
--- a/frontend/farm_designer/points/__tests__/weeds_inventory_test.tsx
+++ b/frontend/farm_designer/weeds/__tests__/weeds_inventory_test.tsx
@@ -4,11 +4,11 @@ import {
RawWeeds as Weeds, WeedsProps, mapStateToProps,
} from "../weeds_inventory";
import { fakeState } from "../../../__test_support__/fake_state";
-import { fakePoint } from "../../../__test_support__/fake_state/resources";
+import { fakeWeed } from "../../../__test_support__/fake_state/resources";
describe(" />", () => {
const fakeProps = (): WeedsProps => ({
- genericPoints: [],
+ weeds: [],
dispatch: jest.fn(),
hoveredPoint: undefined,
});
@@ -27,9 +27,9 @@ describe(" />", () => {
it("filters points", () => {
const p = fakeProps();
- p.genericPoints = [fakePoint(), fakePoint()];
- p.genericPoints[0].body.name = "weed 0";
- p.genericPoints[1].body.name = "weed 1";
+ p.weeds = [fakeWeed(), fakeWeed()];
+ p.weeds[0].body.name = "weed 0";
+ p.weeds[1].body.name = "weed 1";
const wrapper = mount();
wrapper.setState({ searchTerm: "0" });
expect(wrapper.text()).toContain("weed 0");
diff --git a/frontend/farm_designer/weeds/weed_inventory_item.tsx b/frontend/farm_designer/weeds/weed_inventory_item.tsx
new file mode 100644
index 000000000..0eb19b476
--- /dev/null
+++ b/frontend/farm_designer/weeds/weed_inventory_item.tsx
@@ -0,0 +1,69 @@
+import * as React from "react";
+import { TaggedWeedPointer } from "farmbot";
+import { Actions } from "../../constants";
+import { push } from "../../history";
+import { t } from "../../i18next_wrapper";
+import { DEFAULT_WEED_ICON } from "../map/layers/weeds/garden_weed";
+import { svgToUrl } from "../../open_farm/icons";
+import { genericWeedIcon } from "../point_groups/point_group_item";
+import { getMode } from "../map/util";
+import { Mode } from "../map/interfaces";
+import { mapPointClickAction } from "../map/actions";
+
+export interface WeedInventoryItemProps {
+ tpp: TaggedWeedPointer;
+ dispatch: Function;
+ hovered: boolean;
+}
+
+export class WeedInventoryItem extends
+ React.Component {
+
+ render() {
+ const weed = this.props.tpp.body;
+ const { tpp, dispatch } = this.props;
+ const weedId = (weed.id || "ERR_NO_POINT_ID").toString();
+
+ const toggle = (action: "enter" | "leave") => {
+ const isEnter = action === "enter";
+ dispatch({
+ type: Actions.TOGGLE_HOVERED_POINT,
+ payload: isEnter ? tpp.uuid : undefined
+ });
+ };
+
+ const click = () => {
+ if (getMode() == Mode.boxSelect) {
+ mapPointClickAction(dispatch, tpp.uuid)();
+ toggle("leave");
+ } else {
+ push(`/app/designer/weeds/${weedId}`);
+ dispatch({ type: Actions.TOGGLE_HOVERED_POINT, payload: [tpp.uuid] });
+ }
+ };
+
+ return toggle("enter")}
+ onMouseLeave={() => toggle("leave")}
+ onClick={click}>
+
+
+
+
+
+ {weed.name || t("Untitled weed")}
+
+
+ {`(${weed.x}, ${weed.y}) ⌀${weed.radius * 2}`}
+
+
;
+ }
+}
diff --git a/frontend/farm_designer/points/weeds_edit.tsx b/frontend/farm_designer/weeds/weeds_edit.tsx
similarity index 87%
rename from frontend/farm_designer/points/weeds_edit.tsx
rename to frontend/farm_designer/weeds/weeds_edit.tsx
index 04c4ac70f..e15cbf5b2 100644
--- a/frontend/farm_designer/points/weeds_edit.tsx
+++ b/frontend/farm_designer/weeds/weeds_edit.tsx
@@ -6,22 +6,22 @@ import {
import { t } from "../../i18next_wrapper";
import { history, getPathArray } from "../../history";
import { Everything } from "../../interfaces";
-import { TaggedGenericPointer } from "farmbot";
-import { maybeFindGenericPointerById } from "../../resources/selectors";
+import { TaggedWeedPointer } from "farmbot";
+import { maybeFindWeedPointerById } from "../../resources/selectors";
import { Panel } from "../panel_header";
import {
EditPointProperties, PointActions, updatePoint,
-} from "./point_edit_actions";
+} from "../points/point_edit_actions";
import { Actions } from "../../constants";
export interface EditWeedProps {
dispatch: Function;
- findPoint(id: number): TaggedGenericPointer | undefined;
+ findPoint(id: number): TaggedWeedPointer | undefined;
}
export const mapStateToProps = (props: Everything): EditWeedProps => ({
dispatch: props.dispatch,
- findPoint: id => maybeFindGenericPointerById(props.resources.index, id),
+ findPoint: id => maybeFindWeedPointerById(props.resources.index, id),
});
export class RawEditWeed extends React.Component {
diff --git a/frontend/farm_designer/points/weeds_inventory.tsx b/frontend/farm_designer/weeds/weeds_inventory.tsx
similarity index 75%
rename from frontend/farm_designer/points/weeds_inventory.tsx
rename to frontend/farm_designer/weeds/weeds_inventory.tsx
index f4517bac2..50b3d1c0d 100644
--- a/frontend/farm_designer/points/weeds_inventory.tsx
+++ b/frontend/farm_designer/weeds/weeds_inventory.tsx
@@ -10,12 +10,12 @@ import {
DesignerPanel, DesignerPanelContent, DesignerPanelTop,
} from "../designer_panel";
import { t } from "../../i18next_wrapper";
-import { TaggedGenericPointer } from "farmbot";
-import { selectAllGenericPointers } from "../../resources/selectors";
-import { PointInventoryItem } from "./point_inventory_item";
+import { TaggedWeedPointer } from "farmbot";
+import { selectAllWeedPointers } from "../../resources/selectors";
+import { WeedInventoryItem } from "./weed_inventory_item";
export interface WeedsProps {
- genericPoints: TaggedGenericPointer[];
+ weeds: TaggedWeedPointer[];
dispatch: Function;
hoveredPoint: string | undefined;
}
@@ -24,13 +24,8 @@ interface WeedsState {
searchTerm: string;
}
-export const isAWeed = (pointName: string, type?: string) =>
- type == "weed" || pointName.toLowerCase().includes("weed");
-
export const mapStateToProps = (props: Everything): WeedsProps => ({
- genericPoints: selectAllGenericPointers(props.resources.index)
- .filter(x => !x.body.discarded_at)
- .filter(x => isAWeed(x.body.name, x.body.meta.type)),
+ weeds: selectAllWeedPointers(props.resources.index),
dispatch: props.dispatch,
hoveredPoint: props.resources.consumers.farm_designer.hoveredPoint,
});
@@ -54,17 +49,16 @@ export class RawWeeds extends React.Component {
0}
+ notEmpty={this.props.weeds.length > 0}
graphic={EmptyStateGraphic.weeds}
title={t("No weeds yet.")}
text={Content.NO_WEEDS}
colorScheme={"weeds"}>
- {this.props.genericPoints
+ {this.props.weeds
.filter(p => p.body.name.toLowerCase()
.includes(this.state.searchTerm.toLowerCase()))
- .map(p => )}
diff --git a/frontend/farmware/weed_detector/__tests__/actions_tests.ts b/frontend/farmware/weed_detector/__tests__/actions_tests.ts
index e321cd544..ab8386018 100644
--- a/frontend/farmware/weed_detector/__tests__/actions_tests.ts
+++ b/frontend/farmware/weed_detector/__tests__/actions_tests.ts
@@ -62,7 +62,8 @@ describe("deletePoints()", () => {
mockDelete = Promise.resolve();
mockData = [{ id: 1 }, { id: 2 }, { id: 3 }];
const dispatch = jest.fn();
- await deletePoints("weeds", { created_by: "plant-detection" })(dispatch, jest.fn());
+ const query = { meta: { created_by: "plant-detection" } };
+ await deletePoints("weeds", query)(dispatch, jest.fn());
expect(axios.post).toHaveBeenCalledWith("http://localhost/api/points/search",
{ meta: { created_by: "plant-detection" } });
await expect(axios.delete).toHaveBeenCalledWith("http://localhost/api/points/1,2,3");
@@ -80,7 +81,8 @@ describe("deletePoints()", () => {
mockDelete = Promise.reject("error");
mockData = [{ id: 1 }, { id: 2 }, { id: 3 }];
const dispatch = jest.fn();
- await deletePoints("weeds", { created_by: "plant-detection" })(dispatch, jest.fn());
+ const query = { meta: { created_by: "plant-detection" } };
+ await deletePoints("weeds", query)(dispatch, jest.fn());
expect(axios.post).toHaveBeenCalledWith("http://localhost/api/points/search",
{ meta: { created_by: "plant-detection" } });
await expect(axios.delete).toHaveBeenCalledWith("http://localhost/api/points/1,2,3");
@@ -98,7 +100,8 @@ describe("deletePoints()", () => {
mockDelete = Promise.resolve();
mockData = times(200, () => ({ id: 1 }));
const dispatch = jest.fn();
- await deletePoints("weeds", { created_by: "plant-detection" })(dispatch, jest.fn());
+ const query = { meta: { created_by: "plant-detection" } };
+ await deletePoints("weeds", query)(dispatch, jest.fn());
expect(axios.post).toHaveBeenCalledWith("http://localhost/api/points/search",
{ meta: { created_by: "plant-detection" } });
await expect(axios.delete).toHaveBeenCalledWith(
diff --git a/frontend/farmware/weed_detector/__tests__/weed_detector_test.tsx b/frontend/farmware/weed_detector/__tests__/weed_detector_test.tsx
index 2dd572f5a..799d561c3 100644
--- a/frontend/farmware/weed_detector/__tests__/weed_detector_test.tsx
+++ b/frontend/farmware/weed_detector/__tests__/weed_detector_test.tsx
@@ -85,7 +85,7 @@ describe("", () => {
expect(wrapper.instance().state.deletionProgress).toBeUndefined();
clickButton(wrapper, 1, "clear weeds");
expect(deletePoints).toHaveBeenCalledWith(
- "weeds", { created_by: "plant-detection" }, expect.any(Function));
+ "weeds", { meta: { created_by: "plant-detection" } }, expect.any(Function));
expect(wrapper.instance().state.deletionProgress).toEqual("Deleting...");
const fakeProgress = { completed: 50, total: 100, isDone: false };
mockDeletePoints.mock.calls[0][2](fakeProgress);
diff --git a/frontend/farmware/weed_detector/actions.tsx b/frontend/farmware/weed_detector/actions.tsx
index 0c1b20626..10511bedd 100644
--- a/frontend/farmware/weed_detector/actions.tsx
+++ b/frontend/farmware/weed_detector/actions.tsx
@@ -5,20 +5,19 @@ import { API } from "../../api";
import { Progress, ProgressCallback, trim } from "../../util";
import { getDevice } from "../../device";
import { noop, chunk } from "lodash";
-import { GenericPointer } from "farmbot/dist/resources/api_resources";
+import { Point } from "farmbot/dist/resources/api_resources";
import { Actions } from "../../constants";
import { t } from "../../i18next_wrapper";
export function deletePoints(
pointName: string,
- metaQuery: { [key: string]: string },
+ query: Partial,
cb?: ProgressCallback): Thunk {
// TODO: Generalize and add to api/crud.ts
return async function (dispatch) {
const URL = API.current.pointSearchPath;
- const QUERY = { meta: metaQuery };
try {
- const resp = await axios.post(URL, QUERY);
+ const resp = await axios.post(URL, query);
const ids = resp.data.map(x => x.id);
// If you delete too many points, you will violate the URL length
// limitation of 2,083. Chunking helps fix that.
diff --git a/frontend/farmware/weed_detector/index.tsx b/frontend/farmware/weed_detector/index.tsx
index 766366ace..ced91eec0 100644
--- a/frontend/farmware/weed_detector/index.tsx
+++ b/frontend/farmware/weed_detector/index.tsx
@@ -39,7 +39,7 @@ export class WeedDetector
this.setState({ deletionProgress: p.isDone ? "" : percentage });
};
this.props.dispatch(deletePoints(t("weeds"),
- { created_by: "plant-detection" }, progress));
+ { meta: { created_by: "plant-detection" } }, progress));
this.setState({ deletionProgress: t("Deleting...") });
}
diff --git a/frontend/resources/__tests__/selectors_test.ts b/frontend/resources/__tests__/selectors_test.ts
index f3041a578..9018d5529 100644
--- a/frontend/resources/__tests__/selectors_test.ts
+++ b/frontend/resources/__tests__/selectors_test.ts
@@ -111,7 +111,7 @@ describe("isKind()", () => {
describe("groupPointsByType()", () => {
it("returns points", () => {
const points = Selector.groupPointsByType(fakeIndex);
- const expectedKeys = ["Plant", "GenericPointer", "ToolSlot"];
+ const expectedKeys = ["Plant", "GenericPointer", "ToolSlot", "Weed"];
expect(expectedKeys.every(key => key in points)).toBeTruthy();
});
});
diff --git a/frontend/resources/selectors.ts b/frontend/resources/selectors.ts
index fbc60d8c3..0f3569422 100644
--- a/frontend/resources/selectors.ts
+++ b/frontend/resources/selectors.ts
@@ -10,15 +10,16 @@ import {
TaggedToolSlotPointer,
TaggedUser,
TaggedDevice,
+ TaggedWeedPointer,
} from "farmbot";
import {
isTaggedPlantPointer,
isTaggedGenericPointer,
isTaggedRegimen,
isTaggedSequence,
- isTaggedTool,
isTaggedToolSlotPointer,
sanityCheck,
+ isTaggedWeedPointer,
} from "./tagged_resources";
import { betterCompact, bail } from "../util";
import { findAllById } from "./selectors_by_id";
@@ -94,6 +95,13 @@ export function selectAllGenericPointers(index: ResourceIndex):
return betterCompact(genericPointers);
}
+export function selectAllWeedPointers(index: ResourceIndex):
+ TaggedWeedPointer[] {
+ const weedPointers = selectAllPoints(index)
+ .map(p => isTaggedWeedPointer(p) ? p : undefined);
+ return betterCompact(weedPointers);
+}
+
export function selectAllPlantPointers(index: ResourceIndex): TaggedPlantPointer[] {
const plantPointers = selectAllActivePoints(index)
.map(p => isTaggedPlantPointer(p) ? p : undefined);
@@ -141,22 +149,6 @@ export function getSequenceByUUID(index: ResourceIndex,
}
}
-/** GIVEN: a slot UUID.
- * FINDS: Tool in that slot (if any) */
-export const currentToolInSlot = (index: ResourceIndex) =>
- (toolSlotUUID: string): TaggedTool | undefined => {
- const currentSlot = selectCurrentToolSlot(index, toolSlotUUID);
- if (currentSlot
- && currentSlot.kind === "Point") {
- const toolUUID = index
- .byKindAndId[joinKindAndId("Tool", currentSlot.body.tool_id)];
- const tool = index.references[toolUUID || "NOPE!"];
- if (tool && isTaggedTool(tool)) {
- return tool;
- }
- }
- };
-
/** FINDS: All tools that are in use. */
export function toolsInUse(index: ResourceIndex): TaggedTool[] {
const ids = betterCompact(selectAllToolSlotPointers(index)
diff --git a/frontend/resources/selectors_by_id.ts b/frontend/resources/selectors_by_id.ts
index 2f42a1d49..361c71059 100644
--- a/frontend/resources/selectors_by_id.ts
+++ b/frontend/resources/selectors_by_id.ts
@@ -11,6 +11,7 @@ import {
isTaggedGenericPointer,
isTaggedSavedGarden,
isTaggedFolder,
+ isTaggedWeedPointer,
} from "./tagged_resources";
import {
ResourceName,
@@ -125,6 +126,13 @@ export function maybeFindGenericPointerById(index: ResourceIndex, id: number) {
if (resource && isTaggedGenericPointer(resource)) { return resource; }
}
+/** Unlike other findById methods, this one allows undefined (missed) values */
+export function maybeFindWeedPointerById(index: ResourceIndex, id: number) {
+ const uuid = index.byKindAndId[joinKindAndId("Point", id)];
+ const resource = index.references[uuid || "nope"];
+ if (resource && isTaggedWeedPointer(resource)) { return resource; }
+}
+
/** Unlike other findById methods, this one allows undefined (missed) values */
export function maybeFindSavedGardenById(index: ResourceIndex, id: number) {
const uuid = index.byKindAndId[joinKindAndId("SavedGarden", id)];
diff --git a/frontend/resources/tagged_resources.ts b/frontend/resources/tagged_resources.ts
index ab83ec9ab..67fc8c430 100644
--- a/frontend/resources/tagged_resources.ts
+++ b/frontend/resources/tagged_resources.ts
@@ -16,6 +16,7 @@ import {
TaggedPlantTemplate,
TaggedSavedGarden,
TaggedPointGroup,
+ TaggedWeedPointer,
} from "farmbot";
export interface TaggedResourceBase {
@@ -102,6 +103,8 @@ export const isTaggedGenericPointer =
(x: object): x is TaggedGenericPointer => {
return isTaggedPoint(x) && (x.body.pointer_type === "GenericPointer");
};
+export const isTaggedWeedPointer = (x: object): x is TaggedWeedPointer =>
+ isTaggedPoint(x) && (x.body.pointer_type === "Weed");
export const isTaggedSavedGarden =
(x: object): x is TaggedSavedGarden => is("SavedGarden")(x);
export const isTaggedPlantTemplate =
diff --git a/frontend/route_config.tsx b/frontend/route_config.tsx
index 77236b088..cd5539edf 100644
--- a/frontend/route_config.tsx
+++ b/frontend/route_config.tsx
@@ -376,7 +376,7 @@ export const UNBOUND_ROUTES = [
$: "/designer/weeds",
getModule,
key,
- getChild: () => import("./farm_designer/points/weeds_inventory"),
+ getChild: () => import("./farm_designer/weeds/weeds_inventory"),
childKey: "Weeds"
}),
route({
@@ -392,7 +392,7 @@ export const UNBOUND_ROUTES = [
$: "/designer/weeds/:point_id",
getModule,
key,
- getChild: () => import("./farm_designer/points/weeds_edit"),
+ getChild: () => import("./farm_designer/weeds/weeds_edit"),
childKey: "EditWeed"
}),
route({
diff --git a/frontend/sequences/locals_list/__tests__/location_form_list_test.ts b/frontend/sequences/locals_list/__tests__/location_form_list_test.ts
index 4606e3efa..4cd988f93 100644
--- a/frontend/sequences/locals_list/__tests__/location_form_list_test.ts
+++ b/frontend/sequences/locals_list/__tests__/location_form_list_test.ts
@@ -30,45 +30,58 @@ describe("locationFormList()", () => {
label: "Generic tool (100, 200, 300)",
value: "1",
});
- const plantHeading = items[3];
- expect(plantHeading).toEqual({
- headingId: "Plant",
- label: "Plants",
- value: 0,
- heading: true,
- });
- const plant = items[4];
- expect(plant).toEqual({
- headingId: "Plant",
- label: "Plant 1 (1, 2, 3)",
- value: "1"
- });
- const pointHeading = items[6];
- expect(pointHeading).toEqual({
- headingId: "GenericPointer",
- label: "Map Points",
- value: 0,
- heading: true,
- });
- const point = items[7];
- expect(point).toEqual({
- headingId: "GenericPointer",
- label: "Point 1 (10, 20, 30)",
- value: "2"
- });
- const groupHeading = items[8];
+ const groupHeading = items[3];
expect(groupHeading).toEqual({
headingId: "PointGroup",
label: "Groups",
value: 0,
heading: true,
});
- const group = items[9];
+ const group = items[4];
expect(group).toEqual({
headingId: "PointGroup",
label: "Fake",
value: "1"
});
+ const plantHeading = items[5];
+ expect(plantHeading).toEqual({
+ headingId: "Plant",
+ label: "Plants",
+ value: 0,
+ heading: true,
+ });
+ const plant = items[6];
+ expect(plant).toEqual({
+ headingId: "Plant",
+ label: "Plant 1 (1, 2, 3)",
+ value: "1"
+ });
+ const pointHeading = items[8];
+ expect(pointHeading).toEqual({
+ headingId: "GenericPointer",
+ label: "Map Points",
+ value: 0,
+ heading: true,
+ });
+ const point = items[9];
+ expect(point).toEqual({
+ headingId: "GenericPointer",
+ label: "Point 1 (10, 20, 30)",
+ value: "2"
+ });
+ const weedHeading = items[10];
+ expect(weedHeading).toEqual({
+ headingId: "Weed",
+ label: "Weeds",
+ value: 0,
+ heading: true,
+ });
+ const weed = items[11];
+ expect(weed).toEqual({
+ headingId: "Weed",
+ label: "Weed 1 (15, 25, 35)",
+ value: "5"
+ });
});
});
diff --git a/frontend/sequences/locals_list/handle_select.ts b/frontend/sequences/locals_list/handle_select.ts
index af2773181..1f03ac1a6 100644
--- a/frontend/sequences/locals_list/handle_select.ts
+++ b/frontend/sequences/locals_list/handle_select.ts
@@ -78,7 +78,7 @@ const toolVar = (value: string | number) =>
});
const pointVar = (
- pointer_type: "Plant" | "GenericPointer",
+ pointer_type: "Plant" | "GenericPointer" | "Weed",
value: string | number,
) => ({ identifierLabel: label, allowedVariableNodes }: NewVarProps): VariableWithAValue =>
createVariableNode(allowedVariableNodes)(label, {
@@ -123,7 +123,9 @@ const createNewVariable = (props: NewVarProps): VariableNode | undefined => {
if (ddi.isNull) { return nothingVar(props); } // Empty form. Nothing selected yet.
switch (ddi.headingId) {
case "Plant":
- case "GenericPointer": return pointVar(ddi.headingId, ddi.value)(props);
+ case "GenericPointer":
+ case "Weed":
+ return pointVar(ddi.headingId, ddi.value)(props);
case "Tool": return toolVar(ddi.value)(props);
case "parameter": return newParameter(props);
case "Coordinate": return manualEntry(ddi.value)(props);
diff --git a/frontend/sequences/locals_list/location_form_list.ts b/frontend/sequences/locals_list/location_form_list.ts
index e778a8e72..39f90e8c5 100644
--- a/frontend/sequences/locals_list/location_form_list.ts
+++ b/frontend/sequences/locals_list/location_form_list.ts
@@ -72,17 +72,20 @@ export function locationFormList(resources: ResourceIndex,
const allPoints = selectAllActivePoints(resources);
const plantDDI = points2ddi(allPoints, "Plant");
const genericPointerDDI = points2ddi(allPoints, "GenericPointer");
+ const weedDDI = points2ddi(allPoints, "Weed");
const toolDDI = activeToolDDIs(resources);
return [COORDINATE_DDI()]
.concat(additionalItems)
.concat(heading("Tool"))
.concat(toolDDI)
+ .concat(displayGroups ? heading("PointGroup") : [])
+ .concat(displayGroups ? groups2Ddi(selectAllPointGroups(resources)) : [])
.concat(heading("Plant"))
.concat(plantDDI)
.concat(heading("GenericPointer"))
.concat(genericPointerDDI)
- .concat(displayGroups ? heading("PointGroup") : [])
- .concat(displayGroups ? groups2Ddi(selectAllPointGroups(resources)) : []);
+ .concat(heading("Weed"))
+ .concat(weedDDI);
}
/** Create drop down item with label; i.e., "Point/Plant (1, 2, 3)" */
@@ -126,15 +129,6 @@ export function dropDownName(name: string, v?: Record,
return capitalize(label);
}
-export const ALL_POINT_LABELS = {
- "Plant": "All plants",
- "GenericPointer": "All map points",
- "Tool": "All tools and seed containers",
- "ToolSlot": "All slots",
-};
-
-export type EveryPointType = keyof typeof ALL_POINT_LABELS;
-
export const COORDINATE_DDI = (vector?: Vector3): DropDownItem => ({
label: vector
? `${t("Coordinate")} (${vector.x}, ${vector.y}, ${vector.z})`
diff --git a/frontend/sequences/locals_list/test_helpers.ts b/frontend/sequences/locals_list/test_helpers.ts
index 135ca28b1..add4ae492 100644
--- a/frontend/sequences/locals_list/test_helpers.ts
+++ b/frontend/sequences/locals_list/test_helpers.ts
@@ -52,6 +52,16 @@ export function fakeResourceIndex(extra: TaggedResource[] = []): ResourceIndex {
"y": 200,
"z": 300,
}),
+ ...newTaggedResource("Point", {
+ id: 5,
+ meta: {},
+ name: "Weed 1",
+ pointer_type: "Weed",
+ radius: 15,
+ x: 15,
+ y: 25,
+ z: 35,
+ }),
...newTaggedResource("Tool", {
"id": 1,
"name": "Generic Tool",
diff --git a/frontend/sequences/step_tiles/mark_as.tsx b/frontend/sequences/step_tiles/mark_as.tsx
index ccc1d507c..598efeb07 100644
--- a/frontend/sequences/step_tiles/mark_as.tsx
+++ b/frontend/sequences/step_tiles/mark_as.tsx
@@ -11,7 +11,7 @@ import { commitStepChanges } from "./mark_as/commit_step_changes";
import { t } from "../../i18next_wrapper";
interface MarkAsState { nextResource: DropDownItem | undefined }
-const NONE: DropDownItem = { value: 0, label: "" };
+const NONE = (): DropDownItem => ({ label: t("Select one"), value: 0 });
export class MarkAs extends React.Component {
state: MarkAsState = { nextResource: undefined };
@@ -57,7 +57,7 @@ export class MarkAs extends React.Component {
list={actionList(this.state.nextResource, step, this.props.resources)}
onChange={this.commitSelection}
key={JSON.stringify(rightSide) + JSON.stringify(this.state)}
- selectedItem={this.state.nextResource ? NONE : rightSide} />
+ selectedItem={this.state.nextResource ? NONE() : rightSide} />
diff --git a/frontend/sequences/step_tiles/mark_as/__tests__/action_list_test.ts b/frontend/sequences/step_tiles/mark_as/__tests__/action_list_test.ts
index 15e0c97aa..04d6d33c8 100644
--- a/frontend/sequences/step_tiles/mark_as/__tests__/action_list_test.ts
+++ b/frontend/sequences/step_tiles/mark_as/__tests__/action_list_test.ts
@@ -10,7 +10,7 @@ describe("actionList()", () => {
const step = resourceUpdate({ resource_type: "Plant" });
const { index } = markAsResourceFixture();
const result = actionList(undefined, step, index);
- expect(result).toEqual(PLANT_OPTIONS);
+ expect(result).toEqual(PLANT_OPTIONS());
});
it("provides a list of tool mount actions", () => {
@@ -35,6 +35,16 @@ describe("actionList()", () => {
expect(labels).toContain("Removed");
});
+ it("provides a list of weed pointer actions", () => {
+ const ddi = { label: "test case", value: 1, headingId: "Weed" };
+ const step = resourceUpdate({});
+ const { index } = markAsResourceFixture();
+ const result = actionList(ddi, step, index);
+ expect(result.length).toBe(1);
+ const labels = result.map(x => x.label);
+ expect(labels).toContain("Removed");
+ });
+
it("returns an empty list for all other options", () => {
const ddi = { label: "test case", value: 1, headingId: "USB Cables" };
const step = resourceUpdate({});
diff --git a/frontend/sequences/step_tiles/mark_as/__tests__/resource_list_test.ts b/frontend/sequences/step_tiles/mark_as/__tests__/resource_list_test.ts
index df63cfd85..41e882a62 100644
--- a/frontend/sequences/step_tiles/mark_as/__tests__/resource_list_test.ts
+++ b/frontend/sequences/step_tiles/mark_as/__tests__/resource_list_test.ts
@@ -10,5 +10,9 @@ describe("resourceList()", () => {
expect(headings).toContain("Device");
expect(headings).toContain("Plants");
expect(headings).toContain("Points");
+ expect(headings).toContain("Weeds");
+ const weeds = result.filter(x => x.headingId == "Weed");
+ expect(weeds.length).toEqual(2);
+ expect(weeds[1].label).toEqual("weed 1 (200, 400, 0)");
});
});
diff --git a/frontend/sequences/step_tiles/mark_as/__tests__/unpack_step_test.ts b/frontend/sequences/step_tiles/mark_as/__tests__/unpack_step_test.ts
index 8c8f45072..cc2375c36 100644
--- a/frontend/sequences/step_tiles/mark_as/__tests__/unpack_step_test.ts
+++ b/frontend/sequences/step_tiles/mark_as/__tests__/unpack_step_test.ts
@@ -7,6 +7,10 @@ import {
selectAllGenericPointers,
} from "../../../../resources/selectors";
import { DropDownPair } from "../interfaces";
+import { fakeTool } from "../../../../__test_support__/fake_state/resources";
+import {
+ buildResourceIndex,
+} from "../../../../__test_support__/resource_index_builder";
describe("unpackStep()", () => {
function assertGoodness(result: DropDownPair,
action_label: string,
@@ -41,6 +45,23 @@ describe("unpackStep()", () => {
assertGoodness(result, actionLabel, "mounted", label, value);
});
+ it("unpacks valid tool_ids with missing names", () => {
+ const tool = fakeTool();
+ tool.body.id = 1;
+ tool.body.name = undefined;
+ const resourceIndex = buildResourceIndex([tool]).index;
+ const { body } = selectAllTools(resourceIndex)[0];
+ expect(body).toBeTruthy();
+
+ const result = unpackStep({
+ step: resourceUpdate({ label: "mounted_tool_id", value: body.id || NaN }),
+ resourceIndex
+ });
+ const actionLabel = "Mounted to: Untitled Tool";
+ const { label, value } = TOOL_MOUNT();
+ assertGoodness(result, actionLabel, "mounted", label, value);
+ });
+
it("unpacks invalid tool_ids (that may have been valid previously)", () => {
const result = unpackStep({
step: resourceUpdate({ label: "mounted_tool_id", value: Infinity }),
diff --git a/frontend/sequences/step_tiles/mark_as/action_list.ts b/frontend/sequences/step_tiles/mark_as/action_list.ts
index a57281cc1..60225c797 100644
--- a/frontend/sequences/step_tiles/mark_as/action_list.ts
+++ b/frontend/sequences/step_tiles/mark_as/action_list.ts
@@ -16,7 +16,7 @@ const allToolsAsDDI = (i: ResourceIndex) => {
.filter(x => !!x.body.id)
.map(x => {
return {
- label: `${MOUNTED_TO} ${x.body.name}`,
+ label: `${MOUNTED_TO()} ${x.body.name}`,
value: x.body.id || 0
};
});
@@ -25,9 +25,10 @@ const allToolsAsDDI = (i: ResourceIndex) => {
const DEFAULT = "Default";
const ACTION_LIST: Dictionary