diff --git a/app/models/in_use_point.rb b/app/models/in_use_point.rb
index 820684516..4c29f3894 100644
--- a/app/models/in_use_point.rb
+++ b/app/models/in_use_point.rb
@@ -6,7 +6,7 @@ class InUsePoint < ApplicationRecord
DEFAULT_NAME = "point"
FANCY_NAMES = {
GenericPointer.name => DEFAULT_NAME,
- ToolSlot.name => "tool slot",
+ ToolSlot.name => "slot",
Plant.name => "plant",
}
diff --git a/app/models/tool_slot.rb b/app/models/tool_slot.rb
index 22a0abd90..e416dadbd 100644
--- a/app/models/tool_slot.rb
+++ b/app/models/tool_slot.rb
@@ -11,7 +11,7 @@ class ToolSlot < Point
MIN_PULLOUT = PULLOUT_DIRECTIONS.min
PULLOUT_ERR = "must be a value between #{MIN_PULLOUT} and #{MAX_PULLOUT}. "\
"%{value} is not valid."
- IN_USE = "already in use by another tool slot. "\
+ IN_USE = "already in use by another slot. "\
"Please un-assign the tool from its current slot"\
" before reassigning."
diff --git a/app/mutations/tools/destroy.rb b/app/mutations/tools/destroy.rb
index f60fb3c29..3618a5d98 100644
--- a/app/mutations/tools/destroy.rb
+++ b/app/mutations/tools/destroy.rb
@@ -1,9 +1,9 @@
module Tools
class Destroy < Mutations::Command
- STILL_IN_USE = "Can't delete tool because the following sequences are " \
- "still using it: %s"
- STILL_IN_SLOT = "Can't delete tool because it is still in a tool slot. " \
- "Please remove it from the tool slot first."
+ STILL_IN_USE = "Can't delete tool or seed container because the " \
+ "following sequences are still using it: %s"
+ STILL_IN_SLOT = "Can't delete tool or seed container because it is " \
+ "still in a slot. Please remove it from the slot first."
required do
model :tool, class: Tool
diff --git a/frontend/constants.ts b/frontend/constants.ts
index 7adb0a80f..2c9f6449d 100644
--- a/frontend/constants.ts
+++ b/frontend/constants.ts
@@ -648,8 +648,8 @@ export namespace Content {
trim(`Restart the Farmduino or Arduino firmware.`);
export const OS_AUTO_UPDATE =
- trim(`When enabled, FarmBot OS will periodically check for, download,
- and install updates automatically.`);
+ trim(`When enabled, FarmBot OS will automatically download and install
+ software updates at the chosen time.`);
export const AUTO_SYNC =
trim(`When enabled, device resources such as sequences and regimens
@@ -663,7 +663,7 @@ export namespace Content {
back on, unplug FarmBot and plug it back in.`);
export const OS_BETA_RELEASES =
- trim(`Warning! Opting in to FarmBot OS beta releases may reduce
+ trim(`Warning! Leaving the stable FarmBot OS release channel may reduce
FarmBot system stability. Are you sure?`);
export const DIAGNOSTIC_CHECK =
@@ -897,16 +897,16 @@ export namespace TourContent {
export const ADD_TOOLS_AND_SLOTS =
trim(`Press the + button to add tools and seed containers. Then create
- tool slots for them to by pressing the tool slot + button.`);
+ slots for them to by pressing the slot + button.`);
export const ADD_SEED_CONTAINERS_AND_SLOTS =
trim(`Press the + button to add seed containers. Then create
- slots for them to by pressing the seed container slot + button.`);
+ slots for them to by pressing the slot + button.`);
export const ADD_TOOLS_SLOTS =
trim(`Add the newly created tools and seed containers to the
- corresponding tool slots on FarmBot:
- press the + button to create a tool slot.`);
+ corresponding slots on FarmBot:
+ press the + button to create a slot.`);
export const ADD_PERIPHERALS =
trim(`Press edit and then the + button to add peripherals.`);
@@ -998,7 +998,7 @@ export enum DeviceSetting {
pinGuard = `Pin Guard`,
// Danger Zone
- dangerZone = `dangerZone`,
+ dangerZone = `Danger Zone`,
resetHardwareParams = `Reset hardware parameter defaults`,
// Pin Bindings
@@ -1009,7 +1009,8 @@ export enum DeviceSetting {
timezone = `timezone`,
camera = `camera`,
firmware = `firmware`,
- farmbotOSAutoUpdate = `Farmbot OS Auto Update`,
+ applySoftwareUpdates = `update time`,
+ farmbotOSAutoUpdate = `auto update`,
farmbotOS = `Farmbot OS`,
autoSync = `Auto Sync`,
bootSequence = `Boot Sequence`,
diff --git a/frontend/css/farm_designer/farm_designer.scss b/frontend/css/farm_designer/farm_designer.scss
index 74d11b8af..bb34b097a 100644
--- a/frontend/css/farm_designer/farm_designer.scss
+++ b/frontend/css/farm_designer/farm_designer.scss
@@ -30,6 +30,9 @@
padding: 35rem 2rem 2rem 2rem; // at zoom = 1.0: 350px 20px 20px 20px
}
transition: 0.2s ease;
+ &::-webkit-scrollbar {
+ display: none;
+ }
}
.drop-area {
diff --git a/frontend/css/farm_designer/farm_designer_panels.scss b/frontend/css/farm_designer/farm_designer_panels.scss
index fea4106db..a915f1f5e 100644
--- a/frontend/css/farm_designer/farm_designer_panels.scss
+++ b/frontend/css/farm_designer/farm_designer_panels.scss
@@ -552,8 +552,12 @@
}
.tool-slots-panel-content,
.tools-panel-content {
+ max-height: calc(100vh - 15rem);
+ overflow-y: auto;
+ overflow-x: hidden;
.tool-search-item,
.tool-slot-search-item {
+ line-height: 4rem;
cursor: pointer;
margin-left: -15px;
margin-right: -15px;
@@ -562,11 +566,32 @@
margin-right: 0;
}
p {
- line-height: 3rem;
+ font-size: 1.2rem;
+ line-height: 4rem;
+ &.tool-status,
&.tool-slot-position {
float: right;
}
}
+ .filter-search {
+ .bp3-button {
+ min-height: 2.5rem;
+ max-height: 2.5rem;
+ span {
+ line-height: 1.5rem;
+ }
+ }
+ i {
+ line-height: 2.5rem;
+ }
+ }
+ svg {
+ vertical-align: middle;
+ }
+ .tool-slot-position-info {
+ padding: 0;
+ padding-right: 1rem;
+ }
}
.mounted-tool-header {
display: flex;
@@ -624,6 +649,13 @@
float: left;
}
}
+ svg {
+ display: block;
+ margin: auto;
+ width: 10rem;
+ height: 10rem;
+ margin-top: 2rem;
+ }
.add-stock-tools {
.filter-search {
margin-bottom: 1rem;
@@ -634,6 +666,25 @@
ul {
font-size: 1.2rem;
padding-left: 1rem;
+ li {
+ margin-top: 0.5rem;
+ line-height: 2rem;
+ cursor: pointer;
+ width: 50%;
+ &:hover {
+ font-weight: bold;
+ }
+ .fb-checkbox {
+ display: inline;
+ }
+ p {
+ display: inline;
+ line-height: 2.25rem;
+ font-size: 1.2rem;
+ vertical-align: top;
+ margin-left: 1rem;
+ }
+ }
}
button {
.fa-plus {
@@ -645,6 +696,13 @@
.add-tool-slot-panel-content,
.edit-tool-slot-panel-content {
+ svg {
+ display: block;
+ margin: auto;
+ width: 10rem;
+ height: 10rem;
+ margin-top: 2rem;
+ }
label {
margin-top: 0 !important;
}
@@ -657,12 +715,24 @@
.direction-icon {
margin-left: 1rem;
}
- .use-current-location-input {
+ .help-icon {
+ color: $dark_gray;
+ }
+ .tool-slot-location-input {
+ .axis-inputs {
+ padding-left: 0;
+ }
+ .use-current-location {
+ padding: 0;
+ margin-left: -1rem;
+ }
button {
- margin: 0;
- float: none;
- margin-left: 1rem;
- vertical-align: middle;
+ margin-top: 0.5rem;
+ margin-right: 0.5rem;
+ height: 2.5rem;
+ .fa {
+ font-size: 1.5rem;
+ }
}
}
.gantry-mounted-input {
diff --git a/frontend/css/global.scss b/frontend/css/global.scss
index 02ff86b62..e8530d2c6 100644
--- a/frontend/css/global.scss
+++ b/frontend/css/global.scss
@@ -226,7 +226,7 @@ fieldset {
.percent-bar {
position: absolute;
top: 2px;
- left: 12rem;
+ right: 0;
height: 1rem;
width: 25%;
clip-path: polygon(0 85%, 100% 0, 100% 100%, 0% 100%);
@@ -1543,16 +1543,21 @@ textarea:focus {
cursor: pointer;
margin-top: 0.25rem;
margin-bottom: 0.25rem;
- border: 2px solid $panel_light_blue;
+ border: 2px solid darken($panel_light_blue, 30%);
+ border-radius: 5px;
&:hover, &.selected {
- border: 2px solid $medium_gray;
- border-radius: 2px;
.sort-path-info-bar {
- background: darken($light_gray, 10%);
+ background: darken($panel_light_blue, 40%);
}
}
+ &:hover {
+ border: 2px solid darken($panel_light_blue, 40%);
+ }
+ &.selected {
+ border: 2px solid $medium_gray;
+ }
.sort-path-info-bar {
- background: $light_gray;
+ background: darken($panel_light_blue, 30%);
font-size: 1.2rem;
padding-left: 0.5rem;
white-space: nowrap;
diff --git a/frontend/css/inputs.scss b/frontend/css/inputs.scss
index e31b27443..9326c1a44 100644
--- a/frontend/css/inputs.scss
+++ b/frontend/css/inputs.scss
@@ -154,4 +154,12 @@ select {
}
}
}
+ &.disabled {
+ input[type="checkbox"] {
+ cursor: not-allowed;
+ &:checked:after {
+ border-color: $gray;
+ }
+ }
+ }
}
diff --git a/frontend/devices/__tests__/actions_test.ts b/frontend/devices/__tests__/actions_test.ts
index 2d0e5a1cd..2f69f5bc3 100644
--- a/frontend/devices/__tests__/actions_test.ts
+++ b/frontend/devices/__tests__/actions_test.ts
@@ -353,9 +353,10 @@ describe("fetchReleases()", () => {
it("fails to fetches latest OS release version", async () => {
mockGetRelease = Promise.reject("error");
const dispatch = jest.fn();
+ console.error = jest.fn();
await actions.fetchReleases("url")(dispatch);
await expect(axios.get).toHaveBeenCalledWith("url");
- expect(error).toHaveBeenCalledWith(
+ expect(console.error).toHaveBeenCalledWith(
"Could not download FarmBot OS update information.");
expect(dispatch).toHaveBeenCalledWith({
payload: "error",
diff --git a/frontend/devices/actions.ts b/frontend/devices/actions.ts
index edea6ce0f..c1a9565ea 100644
--- a/frontend/devices/actions.ts
+++ b/frontend/devices/actions.ts
@@ -212,7 +212,7 @@ export const fetchReleases =
})
.catch((ferror) => {
!options.beta &&
- error(t("Could not download FarmBot OS update information."));
+ console.error(t("Could not download FarmBot OS update information."));
dispatch({
type: options.beta
? "FETCH_BETA_OS_UPDATE_INFO_ERROR"
diff --git a/frontend/devices/components/fbos_settings/__tests__/fbos_details_test.tsx b/frontend/devices/components/fbos_settings/__tests__/fbos_details_test.tsx
index 0c4cdb602..55042b2b4 100644
--- a/frontend/devices/components/fbos_settings/__tests__/fbos_details_test.tsx
+++ b/frontend/devices/components/fbos_settings/__tests__/fbos_details_test.tsx
@@ -89,6 +89,7 @@ describe("", () => {
const p = fakeProps();
const commit = "abcdefgh";
p.botInfoSettings.firmware_commit = commit;
+ p.botInfoSettings.firmware_version = "1.0.0";
const wrapper = mount();
expect(wrapper.find("a").last().text()).toEqual(commit);
expect(wrapper.find("a").last().props().href?.split("/").slice(-1)[0])
@@ -115,6 +116,7 @@ describe("", () => {
it("doesn't display link without commit", () => {
const p = fakeProps();
+ p.botInfoSettings.firmware_version = undefined;
p.botInfoSettings.commit = "---";
p.botInfoSettings.firmware_commit = "---";
const wrapper = mount();
diff --git a/frontend/devices/components/fbos_settings/fbos_details.tsx b/frontend/devices/components/fbos_settings/fbos_details.tsx
index c06a4b627..c4b138cbd 100644
--- a/frontend/devices/components/fbos_settings/fbos_details.tsx
+++ b/frontend/devices/components/fbos_settings/fbos_details.tsx
@@ -69,7 +69,7 @@ export function WiFiStrengthDisplay(
return
{t("WiFi strength")}:
- {wifiStrength ? dbString : "N/A"}
+ {wifiStrength ? `${dbString} (${percentString})` : "N/A"}
{wifiStrength &&
@@ -261,8 +261,8 @@ export function FbosDetails(props: FbosDetailsProps) {
wifi_level_percent, cpu_usage, private_ip,
} = props.botInfoSettings;
const { last_ota, last_ota_checkup } = props.deviceAccount.body;
- const firmwareCommit = [firmware_commit, firmware_version].includes("---")
- ? firmware_commit : firmware_version?.split("-")[1] || firmware_commit;
+ const infoFwCommit = firmware_version?.includes(".") ? firmware_commit : "---";
+ const firmwareCommit = firmware_version?.split("-")[1] || infoFwCommit;
return
;
type EveryTimeTable = Record;
+const ASAP = () => t("As soon as possible");
const TIME_TABLE_12H = (): TimeTable => ({
0: { label: t("Midnight"), value: 0 },
1: { label: "1:00 AM", value: 1 },
@@ -62,7 +65,7 @@ const TIME_TABLE_12H = (): TimeTable => ({
21: { label: "9:00 PM", value: 21 },
22: { label: "10:00 PM", value: 22 },
23: { label: "11:00 PM", value: 23 },
- [IMMEDIATELY]: { label: t("as soon as possible"), value: IMMEDIATELY },
+ [IMMEDIATELY]: { label: ASAP(), value: IMMEDIATELY },
});
const TIME_TABLE_24H = (): TimeTable => ({
0: { label: "00:00", value: 0 },
@@ -89,7 +92,7 @@ const TIME_TABLE_24H = (): TimeTable => ({
21: { label: "21:00", value: 21 },
22: { label: "22:00", value: 22 },
23: { label: "23:00", value: 23 },
- [IMMEDIATELY]: { label: t("as soon as possible"), value: IMMEDIATELY },
+ [IMMEDIATELY]: { label: ASAP(), value: IMMEDIATELY },
});
const DEFAULT_HOUR: keyof TimeTable = IMMEDIATELY;
@@ -144,17 +147,19 @@ export const OtaTimeSelector = (props: OtaTimeSelectorProps): JSX.Element => {
const selectedItem = (typeof value == "number") ?
theTimeTable[value as HOUR] : theTimeTable[DEFAULT_HOUR];
return
-
-
-
-
-
-
+
+
+
+
+
+
+
+
;
};
diff --git a/frontend/devices/components/maybe_highlight.tsx b/frontend/devices/components/maybe_highlight.tsx
index eab806b0f..7381534fa 100644
--- a/frontend/devices/components/maybe_highlight.tsx
+++ b/frontend/devices/components/maybe_highlight.tsx
@@ -3,6 +3,7 @@ import { ControlPanelState } from "../interfaces";
import { toggleControlPanel } from "../actions";
import { urlFriendly } from "../../util";
import { DeviceSetting } from "../../constants";
+import { trim } from "lodash";
const HOMING_PANEL = [
DeviceSetting.homingAndCalibration,
@@ -86,10 +87,15 @@ DANGER_ZONE_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "danger_zone");
PIN_BINDINGS_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "pin_bindings");
POWER_AND_RESET_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "power_and_reset");
+/** Keep string up until first `(` character (trailing whitespace removed). */
+const stripUnits = (settingName: string) => trim(settingName.split("(")[0]);
+
/** Look up parent panels for settings using URL-friendly names. */
const URL_FRIENDLY_LOOKUP: Record = {};
-Object.entries(SETTING_PANEL_LOOKUP).map(([setting, panel]) =>
- URL_FRIENDLY_LOOKUP[urlFriendly(setting)] = panel);
+Object.entries(SETTING_PANEL_LOOKUP).map(([setting, panel]) => {
+ URL_FRIENDLY_LOOKUP[urlFriendly(setting)] = panel;
+ URL_FRIENDLY_LOOKUP[urlFriendly(stripUnits(setting))] = panel;
+});
/** Look up all relevant names for the same setting. */
const ALTERNATE_NAMES =
@@ -100,7 +106,9 @@ ALTERNATE_NAMES[DeviceSetting.stallDetection].push(DeviceSetting.encoders);
/** Generate array of names for the same setting. Most only have one. */
const compareValues = (settingName: DeviceSetting) =>
- (ALTERNATE_NAMES[settingName]).map(s => urlFriendly(s));
+ (ALTERNATE_NAMES[settingName] as string[])
+ .concat(stripUnits(settingName))
+ .map(s => urlFriendly(s));
/** Retrieve a highlight search term. */
const getHighlightName = () => location.search.split("?highlight=").pop();
diff --git a/frontend/farm_designer/__tests__/farm_designer_test.tsx b/frontend/farm_designer/__tests__/farm_designer_test.tsx
index 82249de16..4b7251dcc 100644
--- a/frontend/farm_designer/__tests__/farm_designer_test.tsx
+++ b/frontend/farm_designer/__tests__/farm_designer_test.tsx
@@ -62,6 +62,7 @@ describe("", () => {
sensors: [],
groups: [],
shouldDisplay: () => false,
+ mountedToolName: undefined,
});
it("loads default map settings", () => {
diff --git a/frontend/farm_designer/__tests__/state_to_props_test.tsx b/frontend/farm_designer/__tests__/state_to_props_test.tsx
index 68dc494d8..5e48b7ef3 100644
--- a/frontend/farm_designer/__tests__/state_to_props_test.tsx
+++ b/frontend/farm_designer/__tests__/state_to_props_test.tsx
@@ -1,7 +1,7 @@
import { mapStateToProps, getPlants } from "../state_to_props";
import { fakeState } from "../../__test_support__/fake_state";
import {
- buildResourceIndex
+ buildResourceIndex, fakeDevice
} from "../../__test_support__/resource_index_builder";
import {
fakePlant,
@@ -49,7 +49,7 @@ describe("mapStateToProps()", () => {
it("returns selected plant", () => {
const state = fakeState();
- state.resources = buildResourceIndex([fakePlant()]);
+ state.resources = buildResourceIndex([fakePlant(), fakeDevice()]);
const plantUuid = Object.keys(state.resources.index.byKind["Point"])[0];
state.resources.consumers.farm_designer.selectedPlants = [plantUuid];
expect(mapStateToProps(state).selectedPlant).toEqual(
@@ -66,7 +66,9 @@ describe("mapStateToProps()", () => {
point2.body.discarded_at = DISCARDED_AT;
const point3 = fakePoint();
point3.body.discarded_at = DISCARDED_AT;
- state.resources = buildResourceIndex([webAppConfig, point1, point2, point3]);
+ state.resources = buildResourceIndex([
+ webAppConfig, point1, point2, point3, fakeDevice()
+ ]);
expect(mapStateToProps(state).genericPoints.length).toEqual(3);
});
@@ -80,7 +82,9 @@ describe("mapStateToProps()", () => {
point2.body.discarded_at = DISCARDED_AT;
const point3 = fakePoint();
point3.body.discarded_at = DISCARDED_AT;
- state.resources = buildResourceIndex([webAppConfig, point1, point2, point3]);
+ state.resources = buildResourceIndex([
+ webAppConfig, point1, point2, point3, fakeDevice()
+ ]);
expect(mapStateToProps(state).genericPoints.length).toEqual(1);
});
@@ -90,7 +94,7 @@ describe("mapStateToProps()", () => {
sr1.body.created_at = "2018-01-14T20:20:38.362Z";
const sr2 = fakeSensorReading();
sr2.body.created_at = "2018-01-11T20:20:38.362Z";
- state.resources = buildResourceIndex([sr1, sr2]);
+ state.resources = buildResourceIndex([sr1, sr2, fakeDevice()]);
const uuid1 = Object.keys(state.resources.index.byKind["SensorReading"])[0];
const uuid2 = Object.keys(state.resources.index.byKind["SensorReading"])[1];
expect(mapStateToProps(state).sensorReadings).toEqual([
@@ -112,7 +116,8 @@ describe("getPlants()", () => {
const template2 = fakePlantTemplate();
template2.body.saved_garden_id = 2;
return buildResourceIndex([
- savedGarden, plant1, plant2, template1, template2]);
+ savedGarden, plant1, plant2, template1, template2, fakeDevice()
+ ]);
};
it("returns plants", () => {
expect(getPlants(fakeResources()).length).toEqual(2);
@@ -133,7 +138,7 @@ describe("getPlants()", () => {
const fwEnv = fakeFarmwareEnv();
fwEnv.body.key = "CAMERA_CALIBRATION_total_rotation_angle";
fwEnv.body.value = 15;
- state.resources = buildResourceIndex([fwEnv]);
+ state.resources = buildResourceIndex([fwEnv, fakeDevice()]);
const props = mapStateToProps(state);
expect(props.cameraCalibrationData).toEqual(
expect.objectContaining({ rotation: "15" }));
diff --git a/frontend/farm_designer/farm_events/__tests__/add_farm_event_test.tsx b/frontend/farm_designer/farm_events/__tests__/add_farm_event_test.tsx
index 04dfb8a2d..85d74e786 100644
--- a/frontend/farm_designer/farm_events/__tests__/add_farm_event_test.tsx
+++ b/frontend/farm_designer/farm_events/__tests__/add_farm_event_test.tsx
@@ -55,7 +55,7 @@ describe("", () => {
const wrapper = mount();
wrapper.setState({ uuid: "FarmEvent" });
["Add Event", "Sequence or Regimen", "fake", "Save"].map(string =>
- expect(wrapper.text()).toContain(string));
+ expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase()));
const deleteBtn = wrapper.find("button").last();
expect(deleteBtn.text()).toEqual("Delete");
expect(deleteBtn.props().hidden).toBeTruthy();
diff --git a/frontend/farm_designer/farm_events/add_farm_event.tsx b/frontend/farm_designer/farm_events/add_farm_event.tsx
index 95e11f9af..878f6dabb 100644
--- a/frontend/farm_designer/farm_events/add_farm_event.tsx
+++ b/frontend/farm_designer/farm_events/add_farm_event.tsx
@@ -102,7 +102,7 @@ export class RawAddFarmEvent
this.props.dispatch(destroyOK(farmEvent))
: undefined} />
@@ -115,7 +115,7 @@ export class RawAddFarmEvent
executableOptions={this.props.executableOptions}
dispatch={this.props.dispatch}
findExecutable={this.props.findExecutable}
- title={t("Add Event")}
+ title={t("Add event")}
timeSettings={this.props.timeSettings}
autoSyncEnabled={this.props.autoSyncEnabled}
resources={this.props.resources}
diff --git a/frontend/farm_designer/index.tsx b/frontend/farm_designer/index.tsx
index 746bb48cb..ab6721152 100755
--- a/frontend/farm_designer/index.tsx
+++ b/frontend/farm_designer/index.tsx
@@ -211,6 +211,7 @@ export class RawFarmDesigner extends React.Component> {
timeSettings={this.props.timeSettings}
sensors={this.props.sensors}
groups={this.props.groups}
+ mountedToolName={this.props.mountedToolName}
shouldDisplay={this.props.shouldDisplay} />
diff --git a/frontend/farm_designer/interfaces.ts b/frontend/farm_designer/interfaces.ts
index d265e5fd9..2f243ae78 100644
--- a/frontend/farm_designer/interfaces.ts
+++ b/frontend/farm_designer/interfaces.ts
@@ -80,6 +80,7 @@ export interface Props {
sensors: TaggedSensor[];
groups: TaggedPointGroup[];
shouldDisplay: ShouldDisplay;
+ mountedToolName: string | undefined;
}
export interface MovePlantProps {
@@ -210,6 +211,7 @@ export interface GardenMapProps {
timeSettings: TimeSettings;
groups: TaggedPointGroup[];
shouldDisplay: ShouldDisplay;
+ mountedToolName: string | undefined;
}
export interface GardenMapState {
diff --git a/frontend/farm_designer/map/__tests__/garden_map_test.tsx b/frontend/farm_designer/map/__tests__/garden_map_test.tsx
index f2d6b6ac7..61b48f64f 100644
--- a/frontend/farm_designer/map/__tests__/garden_map_test.tsx
+++ b/frontend/farm_designer/map/__tests__/garden_map_test.tsx
@@ -124,6 +124,7 @@ const fakeProps = (): GardenMapProps => ({
timeSettings: fakeTimeSettings(),
groups: [],
shouldDisplay: () => false,
+ mountedToolName: undefined,
});
describe("
", () => {
@@ -200,6 +201,16 @@ describe("
", () => {
expect(getGardenCoordinates).not.toHaveBeenCalled();
});
+ it("starts drag on background: does nothing when in move mode", () => {
+ const wrapper = mount(
);
+ mockMode = Mode.moveTo;
+ 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(
);
mockMode = Mode.createPoint;
@@ -348,7 +359,7 @@ describe("
", () => {
expect(closePlantInfo).toHaveBeenCalled();
});
- it("doesn't close panel", () => {
+ it("doesn't close panel: box select", () => {
mockMode = Mode.boxSelect;
const p = fakeProps();
p.designer.selectedPlants = [fakePlant().uuid];
@@ -357,6 +368,15 @@ describe("
", () => {
expect(closePlantInfo).not.toHaveBeenCalled();
});
+ it("doesn't close panel: move mode", () => {
+ mockMode = Mode.moveTo;
+ const p = fakeProps();
+ p.designer.selectedPlants = [fakePlant().uuid];
+ const wrapper = mount
();
+ wrapper.instance().closePanel()();
+ expect(closePlantInfo).not.toHaveBeenCalled();
+ });
+
it("calls unselectPlant on unmount", () => {
const wrapper = shallow();
wrapper.unmount();
@@ -405,7 +425,7 @@ describe("", () => {
const point = fakePoint();
point.body.id = 1;
p.allPoints = [point];
- const wrapper = shallow();
+ const wrapper = mount();
expect(wrapper.instance().pointsSelectedByGroup).toEqual([point]);
});
});
diff --git a/frontend/farm_designer/map/garden_map.tsx b/frontend/farm_designer/map/garden_map.tsx
index 063c5ccbf..fa2694827 100644
--- a/frontend/farm_designer/map/garden_map.tsx
+++ b/frontend/farm_designer/map/garden_map.tsx
@@ -160,6 +160,8 @@ export class GardenMap extends
/** Map background drag start actions. */
startDragOnBackground = (e: React.MouseEvent): void => {
switch (getMode()) {
+ case Mode.moveTo:
+ break;
case Mode.createPoint:
case Mode.clickToAdd:
case Mode.editPlant:
@@ -301,6 +303,8 @@ export class GardenMap extends
/** Return to garden (unless selecting more plants). */
closePanel = () => {
switch (getMode()) {
+ case Mode.moveTo:
+ return () => { };
case Mode.boxSelect:
return this.props.designer.selectedPlants
? () => { }
@@ -410,6 +414,7 @@ export class GardenMap extends
plantAreaOffset={this.props.gridOffset}
peripherals={this.props.peripherals}
eStopStatus={this.props.eStopStatus}
+ mountedToolName={this.props.mountedToolName}
getConfigValue={this.props.getConfigValue} />
HoveredPlant = () => ", () => {
p.position.x = 100;
const wrapper = shallow();
expect(wrapper.instance().state.hovered).toBeFalsy();
- const utm = wrapper.find("#UTM");
+ const utm = wrapper.find("#UTM-wrapper");
utm.simulate("mouseOver");
expect(wrapper.instance().state.hovered).toBeTruthy();
expect(wrapper.find("text").props()).toEqual(expect.objectContaining({
@@ -105,7 +105,7 @@ describe("", () => {
p.position.x = 100;
p.mapTransformProps.xySwap = true;
const wrapper = shallow();
- const utm = wrapper.find("#UTM");
+ const utm = wrapper.find("#UTM-wrapper");
utm.simulate("mouseOver");
expect(wrapper.instance().state.hovered).toBeTruthy();
expect(wrapper.find("text").props()).toEqual(expect.objectContaining({
@@ -114,4 +114,12 @@ describe("", () => {
}));
expect(wrapper.text()).toEqual("(100, 0, 0)");
});
+
+ it("shows mounted tool", () => {
+ const p = fakeProps();
+ p.mountedToolName = "Seeder";
+ const wrapper = shallow();
+ expect(wrapper.find("#UTM-wrapper").find("#mounted-tool").length)
+ .toEqual(1);
+ });
});
diff --git a/frontend/farm_designer/map/layers/farmbot/__tests__/farmbot_layer_test.tsx b/frontend/farm_designer/map/layers/farmbot/__tests__/farmbot_layer_test.tsx
index 8649870e5..c61de5f08 100644
--- a/frontend/farm_designer/map/layers/farmbot/__tests__/farmbot_layer_test.tsx
+++ b/frontend/farm_designer/map/layers/farmbot/__tests__/farmbot_layer_test.tsx
@@ -25,6 +25,7 @@ describe("", () => {
peripherals: [],
eStopStatus: false,
getConfigValue: jest.fn(),
+ mountedToolName: undefined,
};
}
diff --git a/frontend/farm_designer/map/layers/farmbot/__tests__/index_test.tsx b/frontend/farm_designer/map/layers/farmbot/__tests__/index_test.tsx
index 305d29d8c..3ea77f5b4 100644
--- a/frontend/farm_designer/map/layers/farmbot/__tests__/index_test.tsx
+++ b/frontend/farm_designer/map/layers/farmbot/__tests__/index_test.tsx
@@ -19,6 +19,7 @@ describe("", () => {
peripherals: [],
eStopStatus: false,
getConfigValue: () => true,
+ mountedToolName: undefined,
};
}
diff --git a/frontend/farm_designer/map/layers/farmbot/bot_figure.tsx b/frontend/farm_designer/map/layers/farmbot/bot_figure.tsx
index 59d699041..9ceef1efa 100644
--- a/frontend/farm_designer/map/layers/farmbot/bot_figure.tsx
+++ b/frontend/farm_designer/map/layers/farmbot/bot_figure.tsx
@@ -4,6 +4,9 @@ import { getMapSize, transformXY } from "../../util";
import { BotPosition } from "../../../../devices/interfaces";
import { Color } from "../../../../ui/index";
import { botPositionLabel } from "./bot_position_label";
+import { Tool } from "../tool_slots/tool_graphics";
+import { reduceToolName } from "../tool_slots/tool_slot_point";
+import { noop } from "lodash";
export interface BotFigureProps {
name: string;
@@ -11,6 +14,7 @@ export interface BotFigureProps {
mapTransformProps: MapTransformProps;
plantAreaOffset: AxisNumberProperty;
eStopStatus?: boolean;
+ mountedToolName?: string | undefined;
}
interface BotFigureState {
@@ -24,7 +28,8 @@ export class BotFigure extends
setHover = (state: boolean) => { this.setState({ hovered: state }); };
render() {
- const { name, position, plantAreaOffset, eStopStatus, mapTransformProps
+ const {
+ name, position, plantAreaOffset, eStopStatus, mapTransformProps,
} = this.props;
const { xySwap } = mapTransformProps;
const mapSize = getMapSize(mapTransformProps, plantAreaOffset);
@@ -32,6 +37,14 @@ export class BotFigure extends
(position.x || 0), (position.y || 0), mapTransformProps);
const color = eStopStatus ? Color.virtualRed : Color.darkGray;
const opacity = name.includes("encoder") ? 0.25 : 0.5;
+ const toolProps = {
+ x: positionQ.qx,
+ y: positionQ.qy,
+ hovered: this.state.hovered,
+ dispatch: noop,
+ uuid: "utm",
+ xySwap,
+ };
return
- this.setHover(true)}
onMouseLeave={() => this.setHover(false)}
- cx={positionQ.qx}
- cy={positionQ.qy}
- r={35}
fillOpacity={opacity}
- fill={color} />
+ fill={color}>
+ {this.props.mountedToolName
+ ?
+
+
+
+ : }
+
{encoderFigure &&
", () => {
const fakeProps = (): ToolSlotGraphicProps => ({
@@ -15,6 +19,7 @@ describe("", () => {
pulloutDirection: 0,
quadrant: 2,
xySwap: false,
+ occupied: true,
});
it.each<[number, BotOriginQuadrant, boolean, string]>([
@@ -89,6 +94,29 @@ describe("", () => {
});
};
+ it("renders empty tool slot styling", () => {
+ const p = fakeProps();
+ p.tool = ToolNames.emptyToolSlot;
+ const wrapper = svgMount();
+ const props = wrapper.find("circle").last().props();
+ expect(props.r).toEqual(34);
+ expect(props.fill).toEqual("none");
+ expect(props.strokeDasharray).toEqual("10 5");
+ });
+
+ it("renders empty tool slot hover styling", () => {
+ const p = fakeProps();
+ p.tool = ToolNames.emptyToolSlot;
+ p.toolProps.hovered = true;
+ const wrapper = svgMount();
+ const props = wrapper.find("circle").first().props();
+ expect(props.fill).toEqual(Color.darkGray);
+ });
+
+ it("sets hover state for empty tool slot", () => {
+ testHoverActions(ToolNames.emptyToolSlot);
+ });
+
it("renders standard tool styling", () => {
const wrapper = svgMount();
const props = wrapper.find("circle").last().props();
@@ -107,12 +135,96 @@ describe("", () => {
});
it("sets hover state for tool", () => {
- testHoverActions("tool");
+ testHoverActions(ToolNames.tool);
+ });
+
+ it("renders special tool styling: weeder", () => {
+ const p = fakeProps();
+ p.tool = ToolNames.weeder;
+ const wrapper = svgMount();
+ const elements = wrapper.find("#weeder").find("line");
+ expect(elements.length).toEqual(2);
+ });
+
+ it("renders weeder hover styling", () => {
+ const p = fakeProps();
+ p.tool = ToolNames.weeder;
+ p.toolProps.hovered = true;
+ const wrapper = svgMount();
+ expect(wrapper.find("#weeder").find("circle").props().fill)
+ .toEqual(Color.darkGray);
+ });
+
+ it("sets hover state for weeder", () => {
+ testHoverActions(ToolNames.weeder);
+ });
+
+ it("renders special tool styling: watering nozzle", () => {
+ const p = fakeProps();
+ p.tool = ToolNames.wateringNozzle;
+ const wrapper = svgMount();
+ const elements = wrapper.find("#watering-nozzle").find("circle");
+ expect(elements.length).toEqual(3);
+ });
+
+ it("renders watering nozzle hover styling", () => {
+ const p = fakeProps();
+ p.tool = ToolNames.wateringNozzle;
+ p.toolProps.hovered = true;
+ const wrapper = svgMount();
+ expect(wrapper.find("#watering-nozzle").find("circle").at(1).props().fill)
+ .toEqual(Color.darkGray);
+ });
+
+ it("sets hover state for watering nozzle", () => {
+ testHoverActions(ToolNames.wateringNozzle);
+ });
+
+ it("renders special tool styling: seeder", () => {
+ const p = fakeProps();
+ p.tool = ToolNames.seeder;
+ const wrapper = svgMount();
+ const elements = wrapper.find("#seeder").find("circle");
+ expect(elements.length).toEqual(2);
+ });
+
+ it("renders seeder hover styling", () => {
+ const p = fakeProps();
+ p.tool = ToolNames.seeder;
+ p.toolProps.hovered = true;
+ const wrapper = svgMount();
+ expect(wrapper.find("#seeder").find("circle").first().props().fill)
+ .toEqual(Color.darkGray);
+ });
+
+ it("sets hover state for seeder", () => {
+ testHoverActions(ToolNames.seeder);
+ });
+
+ it("renders special tool styling: soil sensor", () => {
+ const p = fakeProps();
+ p.tool = ToolNames.soilSensor;
+ const wrapper = svgMount();
+ const elements = wrapper.find("#soil-sensor").find("line");
+ expect(elements.length).toEqual(2);
+ });
+
+ it("renders soil sensor hover styling", () => {
+ const p = fakeProps();
+ p.tool = ToolNames.soilSensor;
+ p.toolProps.hovered = true;
+ const wrapper = svgMount();
+ expect(wrapper.find("#soil-sensor").find("circle").props().fill)
+ .toEqual(Color.darkGray);
+ });
+
+ it("sets hover state for soil sensor", () => {
+ testHoverActions(ToolNames.soilSensor);
});
it("renders special tool styling: bin", () => {
const p = fakeProps();
- p.tool = "seedBin";
+ p.tool = ToolNames.seedBin;
const wrapper = svgMount();
const elements = wrapper.find("#seed-bin").find("circle");
expect(elements.length).toEqual(2);
@@ -121,20 +233,19 @@ describe("", () => {
it("renders bin hover styling", () => {
const p = fakeProps();
- p.tool = "seedBin";
+ p.tool = ToolNames.seedBin;
p.toolProps.hovered = true;
const wrapper = svgMount();
- p.toolProps.hovered = true;
expect(wrapper.find("#seed-bin").find("circle").length).toEqual(3);
});
it("sets hover state for bin", () => {
- testHoverActions("seedBin");
+ testHoverActions(ToolNames.seedBin);
});
it("renders special tool styling: tray", () => {
const p = fakeProps();
- p.tool = "seedTray";
+ p.tool = ToolNames.seedTray;
const wrapper = svgMount();
const elements = wrapper.find("#seed-tray");
expect(elements.find("circle").length).toEqual(2);
@@ -144,20 +255,19 @@ describe("", () => {
it("renders tray hover styling", () => {
const p = fakeProps();
- p.tool = "seedTray";
+ p.tool = ToolNames.seedTray;
p.toolProps.hovered = true;
const wrapper = svgMount();
- p.toolProps.hovered = true;
expect(wrapper.find("#seed-tray").find("circle").length).toEqual(3);
});
it("sets hover state for tray", () => {
- testHoverActions("seedTray");
+ testHoverActions(ToolNames.seedTray);
});
it("renders special tool styling: trough", () => {
const p = fakeProps();
- p.tool = "seedTrough";
+ p.tool = ToolNames.seedTrough;
const wrapper = svgMount();
const elements = wrapper.find("#seed-trough");
expect(elements.find("circle").length).toEqual(0);
@@ -166,15 +276,49 @@ describe("", () => {
it("renders trough hover styling", () => {
const p = fakeProps();
- p.tool = "seedTrough";
+ p.tool = ToolNames.seedTrough;
p.toolProps.hovered = true;
const wrapper = svgMount();
- p.toolProps.hovered = true;
expect(wrapper.find("#seed-trough").find("circle").length).toEqual(0);
expect(wrapper.find("#seed-trough").find("rect").length).toEqual(1);
});
it("sets hover state for trough", () => {
- testHoverActions("seedTrough");
+ testHoverActions(ToolNames.seedTrough);
+ });
+});
+
+describe("", () => {
+ const fakeProps = (): ToolSVGProps => ({
+ toolName: "seed trough",
+ });
+
+ it("renders trough", () => {
+ const wrapper = shallow();
+ expect(wrapper.find("svg").props().viewBox).toEqual("-25 0 50 1");
+ });
+});
+
+describe("", () => {
+ const fakeProps = (): ToolSlotSVGProps => ({
+ toolSlot: fakeToolSlot(),
+ toolName: "seeder",
+ renderRotation: false,
+ xySwap: false,
+ quadrant: 2,
+ });
+
+ it("renders slot", () => {
+ const p = fakeProps();
+ p.toolSlot.body.pullout_direction = ToolPulloutDirection.POSITIVE_X;
+ const wrapper = shallow();
+ expect(wrapper.find(ToolbaySlot).length).toEqual(1);
+ });
+
+ it("doesn't render slot", () => {
+ const p = fakeProps();
+ p.toolSlot.body.pullout_direction = ToolPulloutDirection.NONE;
+ const wrapper = shallow();
+ expect(wrapper.find(ToolbaySlot).length).toEqual(0);
});
});
diff --git a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_label_test.ts b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_label_test.ts
index 41323925d..7b72f45ae 100644
--- a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_label_test.ts
+++ b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_label_test.ts
@@ -7,50 +7,61 @@ describe("textAnchorPosition()", () => {
const MIDDLE_BOTTOM = { anchor: "middle", x: 0, y: -40 };
it("returns correct label position: positive x", () => {
- expect(textAnchorPosition(1, 1, false)).toEqual(END);
- expect(textAnchorPosition(1, 2, false)).toEqual(START);
- expect(textAnchorPosition(1, 3, false)).toEqual(START);
- expect(textAnchorPosition(1, 4, false)).toEqual(END);
- expect(textAnchorPosition(1, 1, true)).toEqual(MIDDLE_TOP);
- expect(textAnchorPosition(1, 2, true)).toEqual(MIDDLE_TOP);
- expect(textAnchorPosition(1, 3, true)).toEqual(MIDDLE_BOTTOM);
- expect(textAnchorPosition(1, 4, true)).toEqual(MIDDLE_BOTTOM);
+ expect(textAnchorPosition(1, 1, false, false)).toEqual(END);
+ expect(textAnchorPosition(1, 2, false, false)).toEqual(START);
+ expect(textAnchorPosition(1, 3, false, false)).toEqual(START);
+ expect(textAnchorPosition(1, 4, false, false)).toEqual(END);
+ expect(textAnchorPosition(1, 1, true, false)).toEqual(MIDDLE_TOP);
+ expect(textAnchorPosition(1, 2, true, false)).toEqual(MIDDLE_TOP);
+ expect(textAnchorPosition(1, 3, true, false)).toEqual(MIDDLE_BOTTOM);
+ expect(textAnchorPosition(1, 4, true, false)).toEqual(MIDDLE_BOTTOM);
});
it("returns correct label position: negative x", () => {
- expect(textAnchorPosition(2, 1, false)).toEqual(START);
- expect(textAnchorPosition(2, 2, false)).toEqual(END);
- expect(textAnchorPosition(2, 3, false)).toEqual(END);
- expect(textAnchorPosition(2, 4, false)).toEqual(START);
- expect(textAnchorPosition(2, 1, true)).toEqual(MIDDLE_BOTTOM);
- expect(textAnchorPosition(2, 2, true)).toEqual(MIDDLE_BOTTOM);
- expect(textAnchorPosition(2, 3, true)).toEqual(MIDDLE_TOP);
- expect(textAnchorPosition(2, 4, true)).toEqual(MIDDLE_TOP);
+ expect(textAnchorPosition(2, 1, false, false)).toEqual(START);
+ expect(textAnchorPosition(2, 2, false, false)).toEqual(END);
+ expect(textAnchorPosition(2, 3, false, false)).toEqual(END);
+ expect(textAnchorPosition(2, 4, false, false)).toEqual(START);
+ expect(textAnchorPosition(2, 1, true, false)).toEqual(MIDDLE_BOTTOM);
+ expect(textAnchorPosition(2, 2, true, false)).toEqual(MIDDLE_BOTTOM);
+ expect(textAnchorPosition(2, 3, true, false)).toEqual(MIDDLE_TOP);
+ expect(textAnchorPosition(2, 4, true, false)).toEqual(MIDDLE_TOP);
});
it("returns correct label position: positive y", () => {
- expect(textAnchorPosition(3, 1, false)).toEqual(MIDDLE_TOP);
- expect(textAnchorPosition(3, 2, false)).toEqual(MIDDLE_TOP);
- expect(textAnchorPosition(3, 3, false)).toEqual(MIDDLE_BOTTOM);
- expect(textAnchorPosition(3, 4, false)).toEqual(MIDDLE_BOTTOM);
- expect(textAnchorPosition(3, 1, true)).toEqual(END);
- expect(textAnchorPosition(3, 2, true)).toEqual(START);
- expect(textAnchorPosition(3, 3, true)).toEqual(START);
- expect(textAnchorPosition(3, 4, true)).toEqual(END);
+ expect(textAnchorPosition(3, 1, false, false)).toEqual(MIDDLE_TOP);
+ expect(textAnchorPosition(3, 2, false, false)).toEqual(MIDDLE_TOP);
+ expect(textAnchorPosition(3, 3, false, false)).toEqual(MIDDLE_BOTTOM);
+ expect(textAnchorPosition(3, 4, false, false)).toEqual(MIDDLE_BOTTOM);
+ expect(textAnchorPosition(3, 1, true, false)).toEqual(END);
+ expect(textAnchorPosition(3, 2, true, false)).toEqual(START);
+ expect(textAnchorPosition(3, 3, true, false)).toEqual(START);
+ expect(textAnchorPosition(3, 4, true, false)).toEqual(END);
});
it("returns correct label position: negative y", () => {
- expect(textAnchorPosition(4, 1, false)).toEqual(MIDDLE_BOTTOM);
- expect(textAnchorPosition(4, 2, false)).toEqual(MIDDLE_BOTTOM);
- expect(textAnchorPosition(4, 3, false)).toEqual(MIDDLE_TOP);
- expect(textAnchorPosition(4, 4, false)).toEqual(MIDDLE_TOP);
- expect(textAnchorPosition(4, 1, true)).toEqual(START);
- expect(textAnchorPosition(4, 2, true)).toEqual(END);
- expect(textAnchorPosition(4, 3, true)).toEqual(END);
- expect(textAnchorPosition(4, 4, true)).toEqual(START);
+ expect(textAnchorPosition(4, 1, false, false)).toEqual(MIDDLE_BOTTOM);
+ expect(textAnchorPosition(4, 2, false, false)).toEqual(MIDDLE_BOTTOM);
+ expect(textAnchorPosition(4, 3, false, false)).toEqual(MIDDLE_TOP);
+ expect(textAnchorPosition(4, 4, false, false)).toEqual(MIDDLE_TOP);
+ expect(textAnchorPosition(4, 1, true, false)).toEqual(START);
+ expect(textAnchorPosition(4, 2, true, false)).toEqual(END);
+ expect(textAnchorPosition(4, 3, true, false)).toEqual(END);
+ expect(textAnchorPosition(4, 4, true, false)).toEqual(START);
+ });
+
+ it("returns correct label position: no pullout direction", () => {
+ expect(textAnchorPosition(0, 1, false, false)).toEqual(END);
+ expect(textAnchorPosition(1, 1, false, true)).toEqual(END);
+ expect(textAnchorPosition(0, 1, true, false)).toEqual(MIDDLE_TOP);
+ expect(textAnchorPosition(1, 1, true, true)).toEqual(MIDDLE_TOP);
+ expect(textAnchorPosition(0, 2, false, false)).toEqual(START);
+ expect(textAnchorPosition(1, 2, false, true)).toEqual(START);
+ expect(textAnchorPosition(0, 2, true, false)).toEqual(MIDDLE_TOP);
+ expect(textAnchorPosition(1, 2, true, true)).toEqual(MIDDLE_TOP);
});
it("handles bad data", () => {
- expect(textAnchorPosition(1.1, 1.1, false)).toEqual(START);
+ expect(textAnchorPosition(1.1, 1.1, false, false)).toEqual(START);
});
});
diff --git a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx
index f47a485eb..a53c1508f 100644
--- a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx
+++ b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx
@@ -71,7 +71,7 @@ describe("", () => {
p.slot.tool = undefined;
p.hoveredToolSlot = p.slot.toolSlot.uuid;
const wrapper = svgMount();
- expect(wrapper.find("text").text()).toEqual("empty");
+ expect(wrapper.find("text").text()).toEqual("Empty");
expect(wrapper.find("text").props().dx).toEqual(40);
});
@@ -80,6 +80,34 @@ describe("", () => {
expect(wrapper.find("text").props().visibility).toEqual("hidden");
});
+ it("renders weeder", () => {
+ const p = fakeProps();
+ if (p.slot.tool) { p.slot.tool.body.name = "weeder"; }
+ const wrapper = svgMount();
+ expect(wrapper.find("#weeder").length).toEqual(1);
+ });
+
+ it("renders watering nozzle", () => {
+ const p = fakeProps();
+ if (p.slot.tool) { p.slot.tool.body.name = "watering nozzle"; }
+ const wrapper = svgMount();
+ expect(wrapper.find("#watering-nozzle").length).toEqual(1);
+ });
+
+ it("renders seeder", () => {
+ const p = fakeProps();
+ if (p.slot.tool) { p.slot.tool.body.name = "seeder"; }
+ const wrapper = svgMount();
+ expect(wrapper.find("#seeder").length).toEqual(1);
+ });
+
+ it("renders soil sensor", () => {
+ const p = fakeProps();
+ if (p.slot.tool) { p.slot.tool.body.name = "soil sensor"; }
+ const wrapper = svgMount();
+ expect(wrapper.find("#soil-sensor").length).toEqual(1);
+ });
+
it("renders bin", () => {
const p = fakeProps();
if (p.slot.tool) { p.slot.tool.body.name = "seed bin"; }
diff --git a/frontend/farm_designer/map/layers/tool_slots/tool_graphics.tsx b/frontend/farm_designer/map/layers/tool_slots/tool_graphics.tsx
index b87288589..51dc35f5d 100644
--- a/frontend/farm_designer/map/layers/tool_slots/tool_graphics.tsx
+++ b/frontend/farm_designer/map/layers/tool_slots/tool_graphics.tsx
@@ -5,6 +5,9 @@ import { BotOriginQuadrant } from "../../../interfaces";
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
import { Actions } from "../../../../constants";
import { UUID } from "../../../../resources/interfaces";
+import { TaggedToolSlotPointer } from "farmbot";
+import { reduceToolName } from "./tool_slot_point";
+import { noop } from "lodash";
export interface ToolGraphicProps {
x: number;
@@ -27,6 +30,7 @@ export interface ToolSlotGraphicProps {
pulloutDirection: ToolPulloutDirection;
quadrant: BotOriginQuadrant;
xySwap: boolean;
+ occupied: boolean;
}
const toolbaySlotAngle = (
@@ -57,10 +61,15 @@ const toolbaySlotAngle = (
};
export enum ToolNames {
+ weeder = "weeder",
+ wateringNozzle = "wateringNozzle",
+ seeder = "seeder",
+ soilSensor = "soilSensor",
seedBin = "seedBin",
seedTray = "seedTray",
seedTrough = "seedTrough",
tool = "tool",
+ emptyToolSlot = "emptyToolSlot",
}
export const ToolbaySlot = (props: ToolSlotGraphicProps) => {
@@ -82,7 +91,7 @@ export const ToolbaySlot = (props: ToolSlotGraphicProps) => {
-
@@ -91,9 +100,14 @@ export const ToolbaySlot = (props: ToolSlotGraphicProps) => {
export const Tool = (props: ToolProps) => {
switch (props.tool) {
+ case ToolNames.weeder: return ;
+ case ToolNames.wateringNozzle: return ;
+ case ToolNames.seeder: return ;
+ case ToolNames.soilSensor: return ;
case ToolNames.seedBin: return ;
case ToolNames.seedTray: return ;
case ToolNames.seedTrough: return ;
+ case ToolNames.emptyToolSlot: return ;
default: return ;
}
};
@@ -115,6 +129,115 @@ const StandardTool = (props: ToolGraphicProps) => {
;
};
+const EmptySlot = (props: ToolGraphicProps) => {
+ const { x, y, hovered, dispatch, uuid } = props;
+ return dispatch(setToolHover(uuid))}
+ onMouseLeave={() => dispatch(setToolHover(undefined))}>
+
+
+ ;
+};
+
+const Weeder = (props: ToolGraphicProps) => {
+ const { x, y, hovered, dispatch, uuid } = props;
+ const size = 10;
+ return dispatch(setToolHover(uuid))}
+ onMouseLeave={() => dispatch(setToolHover(undefined))}>
+
+
+
+ ;
+};
+
+const WateringNozzle = (props: ToolGraphicProps) => {
+ const { x, y, hovered, dispatch, uuid } = props;
+ return dispatch(setToolHover(uuid))}
+ onMouseLeave={() => dispatch(setToolHover(undefined))}>
+
+
+
+
+
+
+
+
+
+ ;
+};
+
+const Seeder = (props: ToolGraphicProps) => {
+ const { x, y, hovered, dispatch, uuid } = props;
+ const size = 10;
+ return dispatch(setToolHover(uuid))}
+ onMouseLeave={() => dispatch(setToolHover(undefined))}>
+
+
+ ;
+};
+
+const SoilSensor = (props: ToolGraphicProps) => {
+ const { x, y, hovered, dispatch, uuid } = props;
+ const size = 20;
+ return dispatch(setToolHover(uuid))}
+ onMouseLeave={() => dispatch(setToolHover(undefined))}>
+
+
+
+ ;
+};
+
const seedBinGradient =
@@ -214,3 +337,62 @@ const SeedTrough = (props: ToolGraphicProps) => {
fill={hovered ? Color.darkGray : Color.mediumGray} />
;
};
+
+export interface ToolSlotSVGProps {
+ toolSlot: TaggedToolSlotPointer;
+ toolName: string | undefined;
+ renderRotation: boolean;
+ xySwap?: boolean;
+ quadrant?: BotOriginQuadrant;
+}
+
+export const ToolSlotSVG = (props: ToolSlotSVGProps) => {
+ const xySwap = props.renderRotation ? !!props.xySwap : false;
+ const toolProps = {
+ x: 0, y: 0,
+ hovered: false,
+ dispatch: noop,
+ uuid: props.toolSlot.uuid,
+ xySwap,
+ };
+ const pulloutDirection = props.renderRotation
+ ? props.toolSlot.body.pullout_direction
+ : ToolPulloutDirection.POSITIVE_X;
+ const quadrant = props.renderRotation && props.quadrant ? props.quadrant : 2;
+ const viewBox = props.renderRotation ? "-25 0 50 1" : "-25 0 50 1";
+ return props.toolSlot.body.gantry_mounted
+ ?
+ : ;
+};
+
+export interface ToolSVGProps {
+ toolName: string | undefined;
+}
+
+export const ToolSVG = (props: ToolSVGProps) => {
+ const toolProps = {
+ x: 0, y: 0, hovered: false, dispatch: noop, uuid: "", xySwap: false,
+ };
+ const viewBox = reduceToolName(props.toolName) === ToolNames.seedTrough
+ ? "-25 0 50 1" : "-40 0 80 1";
+ return ;
+};
diff --git a/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx b/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx
index 18d9ff713..93bfcf04f 100644
--- a/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx
+++ b/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx
@@ -13,9 +13,17 @@ enum Anchor {
export const textAnchorPosition = (
pulloutDirection: ToolPulloutDirection,
quadrant: BotOriginQuadrant,
- xySwap: boolean): { x: number, y: number, anchor: string } => {
+ xySwap: boolean,
+ gantryMounted: boolean,
+): { x: number, y: number, anchor: string } => {
const rawAnchor = () => {
- const direction = pulloutDirection + (xySwap ? 2 : 0);
+ const noDirection = !pulloutDirection || gantryMounted;
+ const noDirectionXY = xySwap
+ ? ToolPulloutDirection.POSITIVE_Y
+ : ToolPulloutDirection.POSITIVE_X;
+ const direction = noDirection
+ ? noDirectionXY
+ : pulloutDirection + (xySwap ? 2 : 0);
switch (direction > 4 ? direction % 4 : direction) {
case ToolPulloutDirection.POSITIVE_X: return Anchor.start;
case ToolPulloutDirection.NEGATIVE_X: return Anchor.end;
@@ -51,12 +59,15 @@ interface ToolLabelProps {
pulloutDirection: ToolPulloutDirection;
quadrant: BotOriginQuadrant;
xySwap: boolean;
+ gantryMounted: boolean;
}
export const ToolLabel = (props: ToolLabelProps) => {
- const { toolName, hovered, x, y, pulloutDirection, quadrant, xySwap } = props;
- const labelAnchor = textAnchorPosition(pulloutDirection, quadrant, xySwap);
-
+ const {
+ toolName, hovered, x, y, pulloutDirection, quadrant, xySwap, gantryMounted,
+ } = props;
+ const labelAnchor = textAnchorPosition
+ (pulloutDirection, quadrant, xySwap, gantryMounted);
return {
+export const reduceToolName = (raw: string | undefined) => {
const lower = (raw || "").toLowerCase();
+ if (raw == "Empty") { return ToolNames.emptyToolSlot; }
+ if (includes(lower, "weeder")) { return ToolNames.weeder; }
+ if (includes(lower, "watering nozzle")) { return ToolNames.wateringNozzle; }
+ if (includes(lower, "seeder")) { return ToolNames.seeder; }
+ if (includes(lower, "soil sensor")) { return ToolNames.soilSensor; }
if (includes(lower, "seed bin")) { return ToolNames.seedBin; }
if (includes(lower, "seed tray")) { return ToolNames.seedTray; }
if (includes(lower, "seed trough")) { return ToolNames.seedTrough; }
@@ -32,7 +38,7 @@ export const ToolSlotPoint = (props: TSPProps) => {
const { quadrant, xySwap } = mapTransformProps;
const xPosition = gantry_mounted ? (botPositionX || 0) : x;
const { qx, qy } = transformXY(xPosition, y, props.mapTransformProps);
- const toolName = props.slot.tool ? props.slot.tool.body.name : "empty";
+ const toolName = props.slot.tool ? props.slot.tool.body.name : t("Empty");
const hovered = props.slot.toolSlot.uuid === props.hoveredToolSlot;
const toolProps = {
x: qx,
@@ -45,13 +51,14 @@ export const ToolSlotPoint = (props: TSPProps) => {
return !DevSettings.futureFeaturesEnabled() &&
history.push(`/app/designer/tool-slots/${id}`)}>
- {pullout_direction &&
+ {pullout_direction && !gantry_mounted &&
}
{gantry_mounted && }
@@ -67,6 +74,7 @@ export const ToolSlotPoint = (props: TSPProps) => {
x={qx}
y={qy}
pulloutDirection={pullout_direction}
+ gantryMounted={gantry_mounted}
quadrant={quadrant}
xySwap={xySwap} />
;
diff --git a/frontend/farm_designer/point_groups/__tests__/group_detail_active_test.tsx b/frontend/farm_designer/point_groups/__tests__/group_detail_active_test.tsx
index 1054d9806..d987e4db7 100644
--- a/frontend/farm_designer/point_groups/__tests__/group_detail_active_test.tsx
+++ b/frontend/farm_designer/point_groups/__tests__/group_detail_active_test.tsx
@@ -6,13 +6,6 @@ jest.mock("../../../api/crud", () => ({
jest.mock("../../map/actions", () => ({ setHoveredPlant: jest.fn() }));
-let mockDev = false;
-jest.mock("../../../account/dev/dev_support", () => ({
- DevSettings: {
- futureFeaturesEnabled: () => mockDev,
- }
-}));
-
import React from "react";
import {
GroupDetailActive, GroupDetailActiveProps
@@ -107,19 +100,11 @@ describe("", () => {
});
it("shows paths", () => {
- mockDev = false;
const p = fakeProps();
const wrapper = mount();
expect(wrapper.text().toLowerCase()).toContain("0m");
});
- it("doesn't show paths", () => {
- mockDev = true;
- const p = fakeProps();
- const wrapper = mount();
- expect(wrapper.text().toLowerCase()).not.toContain("0m");
- });
-
it("shows random warning text", () => {
const p = fakeProps();
p.group.body.sort_type = "random";
diff --git a/frontend/farm_designer/point_groups/__tests__/paths_test.tsx b/frontend/farm_designer/point_groups/__tests__/paths_test.tsx
index 31cecd0ba..4ad44cabf 100644
--- a/frontend/farm_designer/point_groups/__tests__/paths_test.tsx
+++ b/frontend/farm_designer/point_groups/__tests__/paths_test.tsx
@@ -22,7 +22,7 @@ import { Actions } from "../../../constants";
import { edit } from "../../../api/crud";
import { error } from "../../../toast/toast";
import { svgMount } from "../../../__test_support__/svg_mount";
-import { SORT_OPTIONS } from "../point_group_sort_selector";
+import { SORT_OPTIONS } from "../point_group_sort";
import { PointGroupSortType } from "farmbot/dist/resources/api_resources";
/**
diff --git a/frontend/farm_designer/point_groups/__tests__/point_group_sort_selector_test.tsx b/frontend/farm_designer/point_groups/__tests__/point_group_sort_test.ts
similarity index 59%
rename from frontend/farm_designer/point_groups/__tests__/point_group_sort_selector_test.tsx
rename to frontend/farm_designer/point_groups/__tests__/point_group_sort_test.ts
index bfd1f1d60..096e36b7d 100644
--- a/frontend/farm_designer/point_groups/__tests__/point_group_sort_selector_test.tsx
+++ b/frontend/farm_designer/point_groups/__tests__/point_group_sort_test.ts
@@ -1,45 +1,8 @@
-import {
- isSortType, sortTypeChange, SORT_OPTIONS
-} from "../point_group_sort_selector";
-import { DropDownItem } from "../../../ui";
+import { SORT_OPTIONS } from "../point_group_sort";
import { PointGroupSortType } from "farmbot/dist/resources/api_resources";
import { TaggedPoint } from "farmbot";
import { fakePlant } from "../../../__test_support__/fake_state/resources";
-const tests: [string, boolean][] = [
- ["", false],
- ["nope", false],
- ["random", true],
- ["xy_ascending", true],
- ["xy_descending", true],
- ["yx_ascending", true],
- ["yx_descending", true]
-];
-
-describe("isSortType", () => {
- it("identifies malformed sort types", () => {
- tests.map(([sortType, valid]) => {
- expect(isSortType(sortType)).toBe(valid);
- });
- });
-});
-
-describe("sortTypeChange", () => {
- it("selectively triggers the callback", () => {
- tests.map(([value, valid]) => {
- const cb = jest.fn();
- const ddi: DropDownItem = { value, label: "TEST" };
- if (valid) {
- sortTypeChange(cb)(ddi);
- expect(cb).toHaveBeenCalledWith(value);
- } else {
- sortTypeChange(cb)(ddi);
- expect(cb).not.toHaveBeenCalled();
- }
- });
- });
-});
-
describe("sort()", () => {
const phony = (name: string, x: number, y: number): TaggedPoint => {
const plant = fakePlant();
diff --git a/frontend/farm_designer/point_groups/criteria/add.tsx b/frontend/farm_designer/point_groups/criteria/add.tsx
index d4f9275b2..a24826756 100644
--- a/frontend/farm_designer/point_groups/criteria/add.tsx
+++ b/frontend/farm_designer/point_groups/criteria/add.tsx
@@ -70,7 +70,7 @@ export const CRITERIA_TYPE_LIST = () => [
export const POINTER_TYPE_DDI_LOOKUP = (): { [x: string]: DropDownItem } => ({
Plant: { label: t("Plants"), value: "Plant" },
GenericPointer: { label: t("Points"), value: "GenericPointer" },
- ToolSlot: { label: t("Tool Slots"), value: "ToolSlot" },
+ ToolSlot: { label: t("Slots"), value: "ToolSlot" },
});
export const POINTER_TYPE_LIST = () => [
POINTER_TYPE_DDI_LOOKUP().Plant,
diff --git a/frontend/farm_designer/point_groups/group_detail_active.tsx b/frontend/farm_designer/point_groups/group_detail_active.tsx
index 5c50e92a3..66ae340e7 100644
--- a/frontend/farm_designer/point_groups/group_detail_active.tsx
+++ b/frontend/farm_designer/point_groups/group_detail_active.tsx
@@ -7,11 +7,10 @@ import {
import { TaggedPointGroup, TaggedPoint } from "farmbot";
import { DeleteButton } from "../../ui/delete_button";
import { save, edit } from "../../api/crud";
-import { PointGroupSortSelector, sortGroupBy } from "./point_group_sort_selector";
+import { sortGroupBy } from "./point_group_sort";
import { PointGroupSortType } from "farmbot/dist/resources/api_resources";
import { PointGroupItem } from "./point_group_item";
import { Paths } from "./paths";
-import { DevSettings } from "../../account/dev/dev_support";
import { Feature, ShouldDisplay } from "../../devices/interfaces";
import { ErrorBoundary } from "../../error_boundary";
import {
@@ -103,16 +102,12 @@ export class GroupDetailActive
- {!DevSettings.futureFeaturesEnabled()
- ? p.body.id))}
- pathPoints={this.pointsSelectedByGroup}
- dispatch={dispatch}
- group={group} />
- : }
+ p.body.id))}
+ pathPoints={this.pointsSelectedByGroup}
+ dispatch={dispatch}
+ group={group} />
{group.body.sort_type == "random" && t(Content.SORT_DESCRIPTION)}
diff --git a/frontend/farm_designer/point_groups/group_list_panel.tsx b/frontend/farm_designer/point_groups/group_list_panel.tsx
index 6f7949970..93f7884d8 100644
--- a/frontend/farm_designer/point_groups/group_list_panel.tsx
+++ b/frontend/farm_designer/point_groups/group_list_panel.tsx
@@ -48,7 +48,7 @@ export class RawGroupListPanel extends React.Component
+ title={t("Add group")}>
diff --git a/frontend/farm_designer/point_groups/group_order_visual.tsx b/frontend/farm_designer/point_groups/group_order_visual.tsx
index cdf9f21cc..800df2c13 100644
--- a/frontend/farm_designer/point_groups/group_order_visual.tsx
+++ b/frontend/farm_designer/point_groups/group_order_visual.tsx
@@ -2,7 +2,7 @@ import * as React from "react";
import { store } from "../../redux/store";
import { MapTransformProps } from "../map/interfaces";
import { isUndefined } from "lodash";
-import { sortGroupBy } from "./point_group_sort_selector";
+import { sortGroupBy } from "./point_group_sort";
import { Color } from "../../ui";
import { transformXY } from "../map/util";
import { nn } from "./paths";
diff --git a/frontend/farm_designer/point_groups/paths.tsx b/frontend/farm_designer/point_groups/paths.tsx
index e2368e967..9c9b5b134 100644
--- a/frontend/farm_designer/point_groups/paths.tsx
+++ b/frontend/farm_designer/point_groups/paths.tsx
@@ -1,6 +1,6 @@
import * as React from "react";
import { MapTransformProps } from "../map/interfaces";
-import { sortGroupBy, sortOptionsTable } from "./point_group_sort_selector";
+import { sortGroupBy, sortOptionsTable } from "./point_group_sort";
import { sortBy, isNumber } from "lodash";
import { PointsPathLine } from "./group_order_visual";
import { Color } from "../../ui";
diff --git a/frontend/farm_designer/point_groups/point_group_sort_selector.tsx b/frontend/farm_designer/point_groups/point_group_sort.ts
similarity index 58%
rename from frontend/farm_designer/point_groups/point_group_sort_selector.tsx
rename to frontend/farm_designer/point_groups/point_group_sort.ts
index e1c335dd9..36803a2d2 100644
--- a/frontend/farm_designer/point_groups/point_group_sort_selector.tsx
+++ b/frontend/farm_designer/point_groups/point_group_sort.ts
@@ -1,6 +1,4 @@
-import * as React from "react";
import { PointGroupSortType } from "farmbot/dist/resources/api_resources";
-import { FBSelect, DropDownItem } from "../../ui";
import { t } from "../../i18next_wrapper";
import { shuffle, sortBy } from "lodash";
import { TaggedPoint } from "farmbot";
@@ -18,36 +16,6 @@ export const sortOptionsTable = (): Record => ({
"yx_descending": t("Y/X, Descending"),
}); // Typechecker will remind us when this needs an update. Don't simplify - RC
-const optionPlusDescriptions = () =>
- (Object
- .entries(sortOptionsTable()) as [PointGroupSortType, string][])
- .map(x => ({ label: x[1], value: x[0] }));
-
-const optionList =
- optionPlusDescriptions().map(x => x.value);
-
-export const isSortType = (x: unknown): x is PointGroupSortType => {
- return optionList.includes(x as PointGroupSortType);
-};
-
-const selected = (value: PointGroupSortType) => ({
- label: t(sortOptionsTable()[value] || value),
- value: value
-});
-
-export const sortTypeChange = (cb: Function) => (ddi: DropDownItem) => {
- const { value } = ddi;
- isSortType(value) && cb(value);
-};
-
-export function PointGroupSortSelector(p: PointGroupSortSelectorProps) {
- return ;
-}
-
type Sorter = (p: TaggedPoint[]) => TaggedPoint[];
type SortDictionary = Record;
diff --git a/frontend/farm_designer/points/__tests__/create_points_test.tsx b/frontend/farm_designer/points/__tests__/create_points_test.tsx
index 669958c6e..1d66b188c 100644
--- a/frontend/farm_designer/points/__tests__/create_points_test.tsx
+++ b/frontend/farm_designer/points/__tests__/create_points_test.tsx
@@ -70,14 +70,14 @@ describe("", () => {
it("renders for points", () => {
mockPath = "/app/designer";
const wrapper = mount();
- ["create point", "delete", "x", "y", "radius", "color"]
+ ["add point", "delete", "x", "y", "radius", "color"]
.map(string => expect(wrapper.text().toLowerCase()).toContain(string));
});
it("renders for weeds", () => {
mockPath = "/app/designer/weeds/add";
const wrapper = mount();
- ["create weed", "delete", "x", "y", "radius", "color"]
+ ["add weed", "delete", "x", "y", "radius", "color"]
.map(string => expect(wrapper.text().toLowerCase()).toContain(string));
});
diff --git a/frontend/farm_designer/points/create_points.tsx b/frontend/farm_designer/points/create_points.tsx
index 227ab3051..a0ebd2143 100644
--- a/frontend/farm_designer/points/create_points.tsx
+++ b/frontend/farm_designer/points/create_points.tsx
@@ -274,7 +274,7 @@ export class RawCreatePoints
diff --git a/frontend/farm_designer/saved_gardens/__tests__/garden_add_test.tsx b/frontend/farm_designer/saved_gardens/__tests__/garden_add_test.tsx
index 883600f9c..aeaa2a0b1 100644
--- a/frontend/farm_designer/saved_gardens/__tests__/garden_add_test.tsx
+++ b/frontend/farm_designer/saved_gardens/__tests__/garden_add_test.tsx
@@ -15,7 +15,7 @@ describe("", () => {
it("renders add garden panel", () => {
const wrapper = mount();
- expect(wrapper.text()).toContain("create new garden");
+ expect(wrapper.text().toLowerCase()).toContain("add garden");
});
});
diff --git a/frontend/farm_designer/saved_gardens/garden_add.tsx b/frontend/farm_designer/saved_gardens/garden_add.tsx
index b0a1b3daa..255f43dde 100644
--- a/frontend/farm_designer/saved_gardens/garden_add.tsx
+++ b/frontend/farm_designer/saved_gardens/garden_add.tsx
@@ -29,7 +29,7 @@ export class RawAddGarden extends React.Component {
diff --git a/frontend/farm_designer/saved_gardens/garden_snapshot.tsx b/frontend/farm_designer/saved_gardens/garden_snapshot.tsx
index 6687f10e0..2c906ae38 100644
--- a/frontend/farm_designer/saved_gardens/garden_snapshot.tsx
+++ b/frontend/farm_designer/saved_gardens/garden_snapshot.tsx
@@ -49,7 +49,7 @@ export class GardenSnapshot
;
}
diff --git a/frontend/farm_designer/state_to_props.ts b/frontend/farm_designer/state_to_props.ts
index b5f76e15b..c1e32879b 100644
--- a/frontend/farm_designer/state_to_props.ts
+++ b/frontend/farm_designer/state_to_props.ts
@@ -11,7 +11,9 @@ import {
selectAllSensors,
maybeGetTimeSettings,
selectAllPoints,
- selectAllPointGroups
+ selectAllPointGroups,
+ getDeviceAccountSettings,
+ maybeFindToolById
} from "../resources/selectors";
import { validBotLocationData, validFwConfig, unpackUUID } from "../util";
import { getWebAppConfigValue } from "../config_storage/actions";
@@ -64,6 +66,11 @@ export function mapStateToProps(props: Everything): Props {
y: calcMicrostepsPerMm(fw.movement_step_per_mm_y, fw.movement_microsteps_y),
};
+ const mountedToolId =
+ getDeviceAccountSettings(props.resources.index).body.mounted_tool_id;
+ const mountedToolName =
+ maybeFindToolById(props.resources.index, mountedToolId)?.body.name;
+
const peripherals = uniq(selectAllPeripherals(props.resources.index))
.map(x => {
const label = x.body.label;
@@ -123,5 +130,6 @@ export function mapStateToProps(props: Everything): Props {
sensors: selectAllSensors(props.resources.index),
groups: selectAllPointGroups(props.resources.index),
shouldDisplay,
+ mountedToolName,
};
}
diff --git a/frontend/farm_designer/tools/__tests__/add_tool_slot_test.tsx b/frontend/farm_designer/tools/__tests__/add_tool_slot_test.tsx
index 76a5c991b..3c631136f 100644
--- a/frontend/farm_designer/tools/__tests__/add_tool_slot_test.tsx
+++ b/frontend/farm_designer/tools/__tests__/add_tool_slot_test.tsx
@@ -9,12 +9,10 @@ jest.mock("../../../history", () => ({ history: { push: jest.fn() } }));
import * as React from "react";
import { mount, shallow } from "enzyme";
-import {
- RawAddToolSlot as AddToolSlot, AddToolSlotProps, mapStateToProps
-} from "../add_tool_slot";
+import { RawAddToolSlot as AddToolSlot } from "../add_tool_slot";
import { fakeState } from "../../../__test_support__/fake_state";
import {
- fakeTool, fakeToolSlot
+ fakeTool, fakeToolSlot, fakeWebAppConfig
} from "../../../__test_support__/fake_state/resources";
import {
buildResourceIndex
@@ -23,6 +21,7 @@ import { init, save, edit, destroy } from "../../../api/crud";
import { history } from "../../../history";
import { SpecialStatus } from "farmbot";
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
+import { AddToolSlotProps, mapStateToPropsAdd } from "../map_to_props_add_edit";
describe("
", () => {
const fakeProps = (): AddToolSlotProps => ({
@@ -32,15 +31,18 @@ describe("
", () => {
dispatch: jest.fn(),
findToolSlot: fakeToolSlot,
firmwareHardware: undefined,
+ xySwap: false,
+ quadrant: 2,
+ isActive: jest.fn(),
});
it("renders", () => {
const wrapper = mount(
);
- ["add new tool slot", "x (mm)", "y (mm)", "z (mm)", "tool or seed container",
- "change slot direction", "use current location", "gantry-mounted"
+ ["add new slot", "x (mm)", "y (mm)", "z (mm)", "tool or seed container",
+ "change direction", "gantry-mounted"
].map(string => expect(wrapper.text().toLowerCase()).toContain(string));
expect(init).toHaveBeenCalledWith("Point", {
- pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {},
+ pointer_type: "ToolSlot", name: "Slot", radius: 0, meta: {},
x: 0, y: 0, z: 0, tool_id: undefined,
pullout_direction: ToolPulloutDirection.NONE,
gantry_mounted: false,
@@ -116,7 +118,7 @@ describe("
", () => {
const wrapper = mount(
);
expect(wrapper.text().toLowerCase()).not.toContain("tool");
expect(init).toHaveBeenCalledWith("Point", {
- pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {},
+ pointer_type: "ToolSlot", name: "Slot", radius: 0, meta: {},
x: 0, y: 0, z: 0, tool_id: undefined,
pullout_direction: ToolPulloutDirection.NONE,
gantry_mounted: true,
@@ -124,14 +126,17 @@ describe("
", () => {
});
});
-describe("mapStateToProps()", () => {
+describe("mapStateToPropsAdd()", () => {
it("returns props", () => {
+ const webAppConfig = fakeWebAppConfig();
+ webAppConfig.body.bot_origin_quadrant = 1;
const tool = fakeTool();
tool.body.id = 1;
const toolSlot = fakeToolSlot();
const state = fakeState();
- state.resources = buildResourceIndex([tool, toolSlot]);
- const props = mapStateToProps(state);
+ state.resources = buildResourceIndex([tool, toolSlot, webAppConfig]);
+ const props = mapStateToPropsAdd(state);
+ expect(props.quadrant).toEqual(1);
expect(props.findTool(1)).toEqual(tool);
expect(props.findToolSlot(toolSlot.uuid)).toEqual(toolSlot);
});
diff --git a/frontend/farm_designer/tools/__tests__/add_tool_test.tsx b/frontend/farm_designer/tools/__tests__/add_tool_test.tsx
index c8d06b478..91b6f2d1f 100644
--- a/frontend/farm_designer/tools/__tests__/add_tool_test.tsx
+++ b/frontend/farm_designer/tools/__tests__/add_tool_test.tsx
@@ -22,7 +22,7 @@ describe("
", () => {
it("renders", () => {
const wrapper = mount(
);
- expect(wrapper.text()).toContain("Add new tool");
+ expect(wrapper.text()).toContain("Add new");
});
it("edits tool name", () => {
@@ -60,11 +60,36 @@ describe("
", () => {
p.firmwareHardware = "express_k10";
p.existingToolNames = ["Seed Trough 1"];
const wrapper = mount(
);
- wrapper.setState({ model: "express" });
wrapper.find("button").last().simulate("click");
expect(initSave).toHaveBeenCalledTimes(1);
expect(history.push).toHaveBeenCalledWith("/app/designer/tools");
});
+
+ it("copies a tool name", () => {
+ const p = fakeProps();
+ p.firmwareHardware = "express_k10";
+ const wrapper = mount
();
+ wrapper.find("p").last().simulate("click");
+ expect(wrapper.state().toolName).toEqual("Seed Trough 2");
+ });
+
+ it("deselects a tool", () => {
+ const p = fakeProps();
+ p.firmwareHardware = "express_k10";
+ const wrapper = mount();
+ expect(wrapper.state().toAdd).toEqual(["Seed Trough 1", "Seed Trough 2"]);
+ wrapper.find("input").last().simulate("change");
+ expect(wrapper.state().toAdd).toEqual(["Seed Trough 1"]);
+ });
+
+ it("selects a tool", () => {
+ const p = fakeProps();
+ p.firmwareHardware = "express_k10";
+ const wrapper = mount();
+ wrapper.setState({ toAdd: [] });
+ wrapper.find("input").last().simulate("change");
+ expect(wrapper.state().toAdd).toEqual(["Seed Trough 2"]);
+ });
});
describe("mapStateToProps()", () => {
diff --git a/frontend/farm_designer/tools/__tests__/edit_tool_slot_test.tsx b/frontend/farm_designer/tools/__tests__/edit_tool_slot_test.tsx
index 7057f20dd..d1938eed9 100644
--- a/frontend/farm_designer/tools/__tests__/edit_tool_slot_test.tsx
+++ b/frontend/farm_designer/tools/__tests__/edit_tool_slot_test.tsx
@@ -9,9 +9,7 @@ jest.mock("../../../device", () => ({ getDevice: () => mockDevice }));
import * as React from "react";
import { mount, shallow } from "enzyme";
-import {
- RawEditToolSlot as EditToolSlot, EditToolSlotProps, mapStateToProps
-} from "../edit_tool_slot";
+import { RawEditToolSlot as EditToolSlot } from "../edit_tool_slot";
import { fakeState } from "../../../__test_support__/fake_state";
import {
fakeToolSlot, fakeTool
@@ -20,6 +18,10 @@ import {
buildResourceIndex
} from "../../../__test_support__/resource_index_builder";
import { destroy, edit, save } from "../../../api/crud";
+import {
+ EditToolSlotProps, mapStateToPropsEdit
+} from "../map_to_props_add_edit";
+import { SlotEditRows } from "../tool_slot_edit_components";
describe("", () => {
const fakeProps = (): EditToolSlotProps => ({
@@ -29,6 +31,9 @@ describe("", () => {
botPosition: { x: undefined, y: undefined, z: undefined },
dispatch: jest.fn(),
firmwareHardware: undefined,
+ xySwap: false,
+ quadrant: 2,
+ isActive: jest.fn(),
});
it("redirects", () => {
@@ -40,8 +45,8 @@ describe("", () => {
const p = fakeProps();
p.findToolSlot = () => fakeToolSlot();
const wrapper = mount();
- ["edit tool slot", "x (mm)", "y (mm)", "z (mm)", "tool or seed container",
- "change slot direction", "use current location", "gantry-mounted"
+ ["edit slot", "x (mm)", "y (mm)", "z (mm)", "tool or seed container",
+ "change direction", "gantry-mounted"
].map(string => expect(wrapper.text().toLowerCase()).toContain(string));
});
@@ -65,6 +70,34 @@ describe("", () => {
expect(mockDevice.moveAbsolute).toHaveBeenCalledWith({ x: 1, y: 2, z: 3 });
});
+ it("moves to gantry-mounted tool slot", () => {
+ const p = fakeProps();
+ p.botPosition = { x: 10, y: 20, z: 30 };
+ const toolSlot = fakeToolSlot();
+ toolSlot.body.gantry_mounted = true;
+ toolSlot.body.x = 1;
+ toolSlot.body.y = 2;
+ toolSlot.body.z = 3;
+ p.findToolSlot = () => toolSlot;
+ const wrapper = shallow();
+ wrapper.find(".gray").last().simulate("click");
+ expect(mockDevice.moveAbsolute).toHaveBeenCalledWith({ x: 10, y: 2, z: 3 });
+ });
+
+ it("falls back to tool slot when moving to gantry-mounted tool slot", () => {
+ const p = fakeProps();
+ p.botPosition = { x: undefined, y: undefined, z: undefined };
+ const toolSlot = fakeToolSlot();
+ toolSlot.body.gantry_mounted = true;
+ toolSlot.body.x = 1;
+ toolSlot.body.y = 2;
+ toolSlot.body.z = 3;
+ p.findToolSlot = () => toolSlot;
+ const wrapper = shallow();
+ wrapper.find(".gray").last().simulate("click");
+ expect(mockDevice.moveAbsolute).toHaveBeenCalledWith({ x: 1, y: 2, z: 3 });
+ });
+
it("removes tool slot", () => {
const p = fakeProps();
const toolSlot = fakeToolSlot();
@@ -73,9 +106,19 @@ describe("", () => {
wrapper.find("button").last().simulate("click");
expect(destroy).toHaveBeenCalledWith(toolSlot.uuid);
});
+
+ it("finds tool", () => {
+ const p = fakeProps();
+ const toolSlot = fakeToolSlot();
+ p.findToolSlot = () => toolSlot;
+ const tool = fakeTool();
+ p.findTool = () => tool;
+ const wrapper = mount();
+ expect(wrapper.find(SlotEditRows).props().tool).toEqual(tool);
+ });
});
-describe("mapStateToProps()", () => {
+describe("mapStateToPropsEdit()", () => {
it("returns props", () => {
const tool = fakeTool();
tool.body.id = 1;
@@ -83,7 +126,7 @@ describe("mapStateToProps()", () => {
toolSlot.body.id = 1;
const state = fakeState();
state.resources = buildResourceIndex([tool, toolSlot]);
- const props = mapStateToProps(state);
+ const props = mapStateToPropsEdit(state);
expect(props.findTool(1)).toEqual(tool);
expect(props.findToolSlot("1")).toEqual(toolSlot);
});
@@ -91,7 +134,7 @@ describe("mapStateToProps()", () => {
it("doesn't find tool slot", () => {
const state = fakeState();
state.resources = buildResourceIndex([]);
- const props = mapStateToProps(state);
+ const props = mapStateToPropsEdit(state);
expect(props.findToolSlot("1")).toEqual(undefined);
});
});
diff --git a/frontend/farm_designer/tools/__tests__/edit_tool_test.tsx b/frontend/farm_designer/tools/__tests__/edit_tool_test.tsx
index 88193b14c..2bef0eae0 100644
--- a/frontend/farm_designer/tools/__tests__/edit_tool_test.tsx
+++ b/frontend/farm_designer/tools/__tests__/edit_tool_test.tsx
@@ -13,16 +13,17 @@ jest.mock("../../../history", () => ({
import * as React from "react";
import { mount, shallow } from "enzyme";
import {
- RawEditTool as EditTool, EditToolProps, mapStateToProps
+ RawEditTool as EditTool, EditToolProps, mapStateToProps, isActive
} from "../edit_tool";
-import { fakeTool } from "../../../__test_support__/fake_state/resources";
+import { fakeTool, fakeToolSlot } from "../../../__test_support__/fake_state/resources";
import { fakeState } from "../../../__test_support__/fake_state";
import {
- buildResourceIndex
+ buildResourceIndex, fakeDevice
} from "../../../__test_support__/resource_index_builder";
import { SaveBtn } from "../../../ui";
import { history } from "../../../history";
import { edit, destroy } from "../../../api/crud";
+import { clickButton } from "../../../__test_support__/helpers";
describe("", () => {
beforeEach(() => {
@@ -32,6 +33,8 @@ describe("", () => {
const fakeProps = (): EditToolProps => ({
findTool: jest.fn(() => fakeTool()),
dispatch: jest.fn(),
+ mountedToolId: undefined,
+ isActive: jest.fn(),
});
it("renders", () => {
@@ -75,11 +78,38 @@ describe("", () => {
it("removes tool", () => {
const p = fakeProps();
const tool = fakeTool();
+ tool.body.id = 1;
p.findTool = () => tool;
+ p.isActive = () => false;
+ p.mountedToolId = undefined;
const wrapper = shallow();
- wrapper.find("button").last().simulate("click");
+ clickButton(wrapper, 0, "delete");
expect(destroy).toHaveBeenCalledWith(tool.uuid);
});
+
+ it("doesn't remove tool: active", () => {
+ const p = fakeProps();
+ const tool = fakeTool();
+ tool.body.id = 1;
+ p.findTool = () => tool;
+ p.isActive = () => true;
+ p.mountedToolId = undefined;
+ const wrapper = shallow();
+ clickButton(wrapper, 0, "delete");
+ expect(destroy).not.toHaveBeenCalledWith(tool.uuid);
+ });
+
+ it("doesn't remove tool: mounted", () => {
+ const p = fakeProps();
+ const tool = fakeTool();
+ tool.body.id = 1;
+ p.findTool = () => tool;
+ p.isActive = () => false;
+ p.mountedToolId = tool.body.id;
+ const wrapper = shallow();
+ clickButton(wrapper, 0, "delete");
+ expect(destroy).not.toHaveBeenCalledWith(tool.uuid);
+ });
});
describe("mapStateToProps()", () => {
@@ -87,8 +117,19 @@ describe("mapStateToProps()", () => {
const state = fakeState();
const tool = fakeTool();
tool.body.id = 123;
- state.resources = buildResourceIndex([tool]);
+ state.resources = buildResourceIndex([tool, fakeDevice()]);
const props = mapStateToProps(state);
expect(props.findTool("" + tool.body.id)).toEqual(tool);
});
});
+
+describe("isActive()", () => {
+ it("returns tool state", () => {
+ const toolSlot = fakeToolSlot();
+ toolSlot.body.tool_id = 1;
+ const active = isActive([toolSlot]);
+ expect(active(1)).toEqual(true);
+ expect(active(2)).toEqual(false);
+ expect(active(undefined)).toEqual(false);
+ });
+});
diff --git a/frontend/farm_designer/tools/__tests__/index_test.tsx b/frontend/farm_designer/tools/__tests__/index_test.tsx
index c4584f781..22f074c0e 100644
--- a/frontend/farm_designer/tools/__tests__/index_test.tsx
+++ b/frontend/farm_designer/tools/__tests__/index_test.tsx
@@ -13,7 +13,10 @@ jest.mock("../../../device", () => ({ getDevice: () => mockDevice }));
import * as React from "react";
import { mount, shallow } from "enzyme";
-import { RawTools as Tools, ToolsProps, mapStateToProps } from "../index";
+import {
+ RawTools as Tools, ToolsProps, mapStateToProps,
+ ToolSlotInventoryItem, ToolSlotInventoryItemProps,
+} from "../index";
import {
fakeTool, fakeToolSlot, fakeSensor
} from "../../../__test_support__/fake_state/resources";
@@ -40,6 +43,7 @@ describe("", () => {
botToMqttStatus: "down",
hoveredToolSlot: undefined,
firmwareHardware: undefined,
+ isActive: jest.fn(),
});
it("renders with no tools", () => {
@@ -182,6 +186,62 @@ describe("", () => {
const wrapper = mount();
expect(wrapper.text().toLowerCase()).not.toContain("mounted tool");
});
+
+ it("displays tool as active", () => {
+ const p = fakeProps();
+ p.tools = [fakeTool()];
+ p.isActive = () => true;
+ p.device.body.mounted_tool_id = undefined;
+ const wrapper = mount();
+ expect(wrapper.text().toLowerCase()).toContain("active");
+ });
+
+ it("displays tool as mounted", () => {
+ const p = fakeProps();
+ const tool = fakeTool();
+ tool.body.id = 1;
+ p.findTool = () => tool;
+ p.tools = [tool];
+ p.device.body.mounted_tool_id = 1;
+ const wrapper = mount();
+ expect(wrapper.find("p").last().text().toLowerCase()).toContain("mounted");
+ });
+
+ it("handles missing tools", () => {
+ const p = fakeProps();
+ const tool = fakeTool();
+ tool.body.id = 1;
+ p.findTool = () => undefined;
+ p.tools = [tool];
+ p.device.body.mounted_tool_id = 1;
+ const wrapper = mount();
+ expect(wrapper.find("p").last().text().toLowerCase()).not.toContain("mounted");
+ });
+});
+
+describe("", () => {
+ const fakeProps = (): ToolSlotInventoryItemProps => ({
+ toolSlot: fakeToolSlot(),
+ tools: [],
+ hovered: false,
+ dispatch: jest.fn(),
+ isActive: jest.fn(),
+ });
+
+ it("changes tool", () => {
+ const p = fakeProps();
+ const wrapper = shallow();
+ wrapper.find(ToolSelection).simulate("change", { tool_id: 1 });
+ expect(edit).toHaveBeenCalledWith(p.toolSlot, { tool_id: 1 });
+ expect(save).toHaveBeenCalledWith(p.toolSlot.uuid);
+ });
+
+ it("doesn't open tool slot", () => {
+ const wrapper = shallow();
+ const e = { stopPropagation: jest.fn() };
+ wrapper.find(".tool-selection-wrapper").first().simulate("click", e);
+ expect(e.stopPropagation).toHaveBeenCalled();
+ });
});
describe("mapStateToProps()", () => {
diff --git a/frontend/farm_designer/tools/__tests__/tool_slot_edit_components_test.tsx b/frontend/farm_designer/tools/__tests__/tool_slot_edit_components_test.tsx
index 861c26b3f..e75dbf479 100644
--- a/frontend/farm_designer/tools/__tests__/tool_slot_edit_components_test.tsx
+++ b/frontend/farm_designer/tools/__tests__/tool_slot_edit_components_test.tsx
@@ -2,14 +2,13 @@ import * as React from "react";
import { shallow, mount } from "enzyme";
import {
GantryMountedInput, GantryMountedInputProps,
- UseCurrentLocationInputRow, UseCurrentLocationInputRowProps,
SlotDirectionInputRow, SlotDirectionInputRowProps,
ToolInputRow, ToolInputRowProps,
SlotLocationInputRow, SlotLocationInputRowProps,
- ToolSelection, ToolSelectionProps,
+ ToolSelection, ToolSelectionProps, SlotEditRows, SlotEditRowsProps,
} from "../tool_slot_edit_components";
-import { fakeTool } from "../../../__test_support__/fake_state/resources";
-import { FBSelect } from "../../../ui";
+import { fakeTool, fakeToolSlot } from "../../../__test_support__/fake_state/resources";
+import { FBSelect, NULL_CHOICE } from "../../../ui";
describe("", () => {
const fakeProps = (): GantryMountedInputProps => ({
@@ -30,33 +29,6 @@ describe("", () => {
});
});
-describe("", () => {
- const fakeProps = (): UseCurrentLocationInputRowProps => ({
- botPosition: { x: undefined, y: undefined, z: undefined },
- onChange: jest.fn(),
- });
-
- it("renders", () => {
- const wrapper = mount();
- expect(wrapper.text().toLowerCase()).toContain("use current location");
- });
-
- it("doesn't change value", () => {
- const p = fakeProps();
- const wrapper = shallow();
- wrapper.find("button").simulate("click");
- expect(p.onChange).not.toHaveBeenCalled();
- });
-
- it("changes value", () => {
- const p = fakeProps();
- p.botPosition = { x: 0, y: 1, z: 2 };
- const wrapper = shallow();
- wrapper.find("button").simulate("click");
- expect(p.onChange).toHaveBeenCalledWith(p.botPosition);
- });
-});
-
describe("", () => {
const fakeProps = (): SlotDirectionInputRowProps => ({
toolPulloutDirection: 0,
@@ -65,7 +37,7 @@ describe("", () => {
it("renders", () => {
const wrapper = mount();
- expect(wrapper.text().toLowerCase()).toContain("change slot direction");
+ expect(wrapper.text().toLowerCase()).toContain("change direction");
});
it("changes value by click", () => {
@@ -89,6 +61,7 @@ describe("", () => {
selectedTool: undefined,
onChange: jest.fn(),
filterSelectedTool: false,
+ isActive: jest.fn(),
});
it("renders", () => {
@@ -98,12 +71,13 @@ describe("", () => {
it("handles missing tool data", () => {
const p = fakeProps();
+ p.filterSelectedTool = true;
const tool = fakeTool();
tool.body.name = undefined;
tool.body.id = undefined;
p.tools = [tool];
const wrapper = shallow();
- expect(wrapper.find("FBSelect").props().list).toEqual([]);
+ expect(wrapper.find("FBSelect").props().list).toEqual([NULL_CHOICE]);
});
it("handles missing selected tool data", () => {
@@ -137,6 +111,7 @@ describe("", () => {
selectedTool: undefined,
onChange: jest.fn(),
isExpress: false,
+ isActive: jest.fn(),
});
it("renders", () => {
@@ -164,6 +139,7 @@ describe("", () => {
slotLocation: { x: 0, y: 0, z: 0 },
gantryMounted: false,
onChange: jest.fn(),
+ botPosition: { x: undefined, y: undefined, z: undefined },
});
it("renders", () => {
@@ -195,4 +171,40 @@ describe("", () => {
expect(p.onChange).toHaveBeenCalledWith({ y: 2 });
expect(p.onChange).toHaveBeenCalledWith({ z: 3 });
});
+
+ it("doesn't use current coordinates", () => {
+ const p = fakeProps();
+ const wrapper = shallow();
+ wrapper.find("button").simulate("click");
+ expect(p.onChange).not.toHaveBeenCalled();
+ });
+
+ it("uses current coordinates", () => {
+ const p = fakeProps();
+ p.botPosition = { x: 0, y: 1, z: 2 };
+ const wrapper = shallow();
+ wrapper.find("button").simulate("click");
+ expect(p.onChange).toHaveBeenCalledWith(p.botPosition);
+ });
+});
+
+describe("", () => {
+ const fakeProps = (): SlotEditRowsProps => ({
+ toolSlot: fakeToolSlot(),
+ tools: [],
+ tool: undefined,
+ botPosition: { x: undefined, y: undefined, z: undefined },
+ updateToolSlot: jest.fn(),
+ isExpress: false,
+ xySwap: false,
+ quadrant: 2,
+ isActive: () => false,
+ });
+
+ it("handles missing tool", () => {
+ const p = fakeProps();
+ p.tool = undefined;
+ const wrapper = mount();
+ expect(wrapper.text()).toContain("None");
+ });
});
diff --git a/frontend/farm_designer/tools/add_tool.tsx b/frontend/farm_designer/tools/add_tool.tsx
index 0eb08d86d..4078e7957 100644
--- a/frontend/farm_designer/tools/add_tool.tsx
+++ b/frontend/farm_designer/tools/add_tool.tsx
@@ -13,9 +13,10 @@ import { history } from "../../history";
import { selectAllTools } from "../../resources/selectors";
import { betterCompact } from "../../util";
import {
- isExpressBoard, getFwHardwareValue
+ getFwHardwareValue
} from "../../devices/components/firmware_hardware_support";
import { getFbosConfig } from "../../resources/getters";
+import { ToolSVG } from "../map/layers/tool_slots/tool_graphics";
export interface AddToolProps {
dispatch: Function;
@@ -25,6 +26,7 @@ export interface AddToolProps {
export interface AddToolState {
toolName: string;
+ toAdd: string[];
}
export const mapStateToProps = (props: Everything): AddToolProps => ({
@@ -35,7 +37,19 @@ export const mapStateToProps = (props: Everything): AddToolProps => ({
});
export class RawAddTool extends React.Component {
- state: AddToolState = { toolName: "" };
+ state: AddToolState = { toolName: "", toAdd: [] };
+
+ filterExisting = (n: string) => !this.props.existingToolNames.includes(n);
+
+ add = (n: string) => this.filterExisting(n) && !this.state.toAdd.includes(n) &&
+ this.setState({ toAdd: this.state.toAdd.concat([n]) });
+
+ remove = (n: string) =>
+ this.setState({ toAdd: this.state.toAdd.filter(name => name != n) });
+
+ componentDidMount = () => this.setState({
+ toAdd: this.stockToolNames().filter(this.filterExisting)
+ });
newTool = (name: string) => {
this.props.dispatch(initSave("Tool", { name }));
@@ -79,22 +93,38 @@ export class RawAddTool extends React.Component {
}
}
+ StockToolCheckbox = ({ toolName }: { toolName: string }) => {
+ const alreadyAdded = !this.filterExisting(toolName);
+ const checked = this.state.toAdd.includes(toolName) || alreadyAdded;
+ return
+ checked
+ ? this.remove(toolName)
+ : this.add(toolName)} />
+
;
+ }
+
AddStockTools = () =>
-
+
@@ -103,16 +133,16 @@ export class RawAddTool extends React.Component {
return
+
-
- this.setState({ toolName: e.currentTarget.value })} />
+
+ this.setState({ toolName: e.currentTarget.value })} />
diff --git a/frontend/farm_designer/tools/add_tool_slot.tsx b/frontend/farm_designer/tools/add_tool_slot.tsx
index ffd4062d8..381abcfdc 100644
--- a/frontend/farm_designer/tools/add_tool_slot.tsx
+++ b/frontend/farm_designer/tools/add_tool_slot.tsx
@@ -3,58 +3,31 @@ import { connect } from "react-redux";
import {
DesignerPanel, DesignerPanelContent, DesignerPanelHeader
} from "../designer_panel";
-import { Everything } from "../../interfaces";
import { t } from "../../i18next_wrapper";
import { SaveBtn } from "../../ui";
-import {
- SpecialStatus, TaggedTool, TaggedToolSlotPointer, FirmwareHardware
-} from "farmbot";
+import { SpecialStatus, TaggedToolSlotPointer } from "farmbot";
import { init, save, edit, destroy } from "../../api/crud";
import { Panel } from "../panel_header";
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
-import {
- selectAllTools, maybeFindToolById, maybeGetToolSlot
-} from "../../resources/selectors";
-import { BotPosition } from "../../devices/interfaces";
-import { validBotLocationData } from "../../util";
import { history } from "../../history";
import { SlotEditRows } from "./tool_slot_edit_components";
import { UUID } from "../../resources/interfaces";
import {
- isExpressBoard, getFwHardwareValue
+ isExpressBoard
} from "../../devices/components/firmware_hardware_support";
-import { getFbosConfig } from "../../resources/getters";
-
-export interface AddToolSlotProps {
- tools: TaggedTool[];
- dispatch: Function;
- botPosition: BotPosition;
- findTool(id: number): TaggedTool | undefined;
- findToolSlot(uuid: UUID | undefined): TaggedToolSlotPointer | undefined;
- firmwareHardware: FirmwareHardware | undefined;
-}
+import { AddToolSlotProps, mapStateToPropsAdd } from "./map_to_props_add_edit";
export interface AddToolSlotState {
uuid: UUID | undefined;
}
-export const mapStateToProps = (props: Everything): AddToolSlotProps => ({
- tools: selectAllTools(props.resources.index),
- dispatch: props.dispatch,
- botPosition: validBotLocationData(props.bot.hardware.location_data).position,
- findTool: (id: number) => maybeFindToolById(props.resources.index, id),
- findToolSlot: (uuid: UUID | undefined) =>
- maybeGetToolSlot(props.resources.index, uuid),
- firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)),
-});
-
export class RawAddToolSlot
extends React.Component {
state: AddToolSlotState = { uuid: undefined };
componentDidMount() {
const action = init("Point", {
- pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {},
+ pointer_type: "ToolSlot", name: t("Slot"), radius: 0, meta: {},
x: 0, y: 0, z: 0, tool_id: undefined,
pullout_direction: ToolPulloutDirection.NONE,
gantry_mounted: isExpressBoard(this.props.firmwareHardware) ? true : false,
@@ -95,9 +68,7 @@ export class RawAddToolSlot
return
@@ -108,6 +79,9 @@ export class RawAddToolSlot
tools={this.props.tools}
tool={this.tool}
botPosition={this.props.botPosition}
+ xySwap={this.props.xySwap}
+ quadrant={this.props.quadrant}
+ isActive={this.props.isActive}
updateToolSlot={this.updateSlot(this.toolSlot)} />
: "initializing"}
@@ -116,4 +90,4 @@ export class RawAddToolSlot
}
}
-export const AddToolSlot = connect(mapStateToProps)(RawAddToolSlot);
+export const AddToolSlot = connect(mapStateToPropsAdd)(RawAddToolSlot);
diff --git a/frontend/farm_designer/tools/edit_tool.tsx b/frontend/farm_designer/tools/edit_tool.tsx
index 41b0fe387..27cfbbca7 100644
--- a/frontend/farm_designer/tools/edit_tool.tsx
+++ b/frontend/farm_designer/tools/edit_tool.tsx
@@ -6,16 +6,26 @@ import {
import { Everything } from "../../interfaces";
import { t } from "../../i18next_wrapper";
import { getPathArray } from "../../history";
-import { TaggedTool, SpecialStatus } from "farmbot";
-import { maybeFindToolById } from "../../resources/selectors";
+import { TaggedTool, SpecialStatus, TaggedToolSlotPointer } from "farmbot";
+import {
+ maybeFindToolById, getDeviceAccountSettings, selectAllToolSlotPointers
+} from "../../resources/selectors";
import { SaveBtn } from "../../ui";
import { edit, destroy } from "../../api/crud";
import { history } from "../../history";
import { Panel } from "../panel_header";
+import { ToolSVG } from "../map/layers/tool_slots/tool_graphics";
+import { error } from "../../toast/toast";
+
+export const isActive = (toolSlots: TaggedToolSlotPointer[]) =>
+ (toolId: number | undefined) =>
+ !!(toolId && toolSlots.map(x => x.body.tool_id).includes(toolId));
export interface EditToolProps {
findTool(id: string): TaggedTool | undefined;
dispatch: Function;
+ mountedToolId: number | undefined;
+ isActive(id: number | undefined): boolean;
}
export interface EditToolState {
@@ -26,6 +36,9 @@ export const mapStateToProps = (props: Everything): EditToolProps => ({
findTool: (id: string) =>
maybeFindToolById(props.resources.index, parseInt(id)),
dispatch: props.dispatch,
+ mountedToolId: getDeviceAccountSettings(props.resources.index)
+ .body.mounted_tool_id,
+ isActive: isActive(selectAllToolSlotPointers(props.resources.index)),
});
export class RawEditTool extends React.Component {
@@ -44,6 +57,11 @@ export class RawEditTool extends React.Component {
const { dispatch } = this.props;
const { toolName } = this.state;
const panelName = "edit-tool";
+ const isMounted = this.props.mountedToolId == tool.body.id;
+ const message = isMounted
+ ? t("Cannot delete while mounted.")
+ : t("Cannot delete while in a slot.");
+ const activeOrMounted = this.props.isActive(tool.body.id) || isMounted;
return
{
backTo={"/app/designer/tools"}
panel={Panel.Tools} />
+
{
}}
status={SpecialStatus.DIRTY} />
diff --git a/frontend/farm_designer/tools/edit_tool_slot.tsx b/frontend/farm_designer/tools/edit_tool_slot.tsx
index 210b283d3..7bf2bc05e 100644
--- a/frontend/farm_designer/tools/edit_tool_slot.tsx
+++ b/frontend/farm_designer/tools/edit_tool_slot.tsx
@@ -3,43 +3,18 @@ import { connect } from "react-redux";
import {
DesignerPanel, DesignerPanelContent, DesignerPanelHeader
} from "../designer_panel";
-import { Everything } from "../../interfaces";
import { t } from "../../i18next_wrapper";
import { getPathArray } from "../../history";
-import { TaggedToolSlotPointer, TaggedTool, FirmwareHardware } from "farmbot";
+import { TaggedToolSlotPointer } from "farmbot";
import { edit, save, destroy } from "../../api/crud";
import { history } from "../../history";
import { Panel } from "../panel_header";
-import {
- maybeFindToolSlotById, selectAllTools, maybeFindToolById
-} from "../../resources/selectors";
-import { BotPosition } from "../../devices/interfaces";
-import { validBotLocationData } from "../../util";
import { SlotEditRows } from "./tool_slot_edit_components";
import { moveAbs } from "../../devices/actions";
import {
- getFwHardwareValue, isExpressBoard
+ isExpressBoard
} from "../../devices/components/firmware_hardware_support";
-import { getFbosConfig } from "../../resources/getters";
-
-export interface EditToolSlotProps {
- findToolSlot(id: string): TaggedToolSlotPointer | undefined;
- tools: TaggedTool[];
- findTool(id: number): TaggedTool | undefined;
- dispatch: Function;
- botPosition: BotPosition;
- firmwareHardware: FirmwareHardware | undefined;
-}
-
-export const mapStateToProps = (props: Everything): EditToolSlotProps => ({
- findToolSlot: (id: string) =>
- maybeFindToolSlotById(props.resources.index, parseInt(id)),
- tools: selectAllTools(props.resources.index),
- findTool: (id: number) => maybeFindToolById(props.resources.index, id),
- dispatch: props.dispatch,
- botPosition: validBotLocationData(props.bot.hardware.location_data).position,
- firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)),
-});
+import { EditToolSlotProps, mapStateToPropsEdit } from "./map_to_props_add_edit";
export class RawEditToolSlot extends React.Component {
@@ -65,7 +40,7 @@ export class RawEditToolSlot extends React.Component {
return
@@ -75,14 +50,20 @@ export class RawEditToolSlot extends React.Component {
tools={this.props.tools}
tool={this.tool}
botPosition={this.props.botPosition}
+ xySwap={this.props.xySwap}
+ quadrant={this.props.quadrant}
+ isActive={this.props.isActive}
updateToolSlot={this.updateSlot(toolSlot)} />