From 22465a555859be1d6ab28321b988c84f9a87bf52 Mon Sep 17 00:00:00 2001 From: gabrielburnworth Date: Fri, 27 Dec 2019 10:37:54 -0800 Subject: [PATCH] add none camera type --- .../fake_sequence_step_data.ts | 17 ++++++ .../sequence_hardware_settings.ts | 11 ---- frontend/__tests__/app_test.tsx | 22 ++++++- frontend/__tests__/controls_popup_test.tsx | 59 ++++++++++++------ frontend/app.tsx | 10 ++- frontend/constants.ts | 6 ++ frontend/controls/__tests__/controls_test.tsx | 1 + .../controls/__tests__/state_to_props_test.ts | 12 +++- frontend/controls/controls.tsx | 1 + frontend/controls/interfaces.ts | 5 +- .../move/__tests__/jog_buttons_test.tsx | 21 ++++++- .../controls/move/__tests__/move_test.tsx | 38 ++++++------ frontend/controls/move/interfaces.ts | 4 +- frontend/controls/move/jog_buttons.tsx | 53 ++++++++-------- frontend/controls/move/jog_controls_group.tsx | 4 +- frontend/controls/move/move.tsx | 1 + frontend/controls/state_to_props.ts | 17 ++---- frontend/controls_popup.tsx | 40 ++++++------ frontend/css/buttons.scss | 1 + .../__tests__/camera_selection_test.tsx | 38 +++++++----- .../fbos_settings/camera_selection.tsx | 61 +++++++++++++------ frontend/devices/interfaces.ts | 4 +- frontend/devices/state_to_props.ts | 26 +++----- .../map_state_to_props_add_edit.ts | 17 +----- frontend/farm_designer/state_to_props.ts | 21 ++----- .../__tests__/farmware_forms_test.tsx | 2 +- frontend/farmware/__tests__/farmware_test.tsx | 2 +- .../set_active_farmware_by_name_test.ts | 17 +++--- .../__tests__/state_to_props_test.tsx | 4 +- .../__tests__/camera_calibration_test.tsx | 23 ++++++- .../camera_calibration/camera_calibration.tsx | 13 ++-- .../farmware/camera_calibration/interfaces.ts | 7 ++- frontend/farmware/farmware_forms.tsx | 6 +- .../images/__tests__/image_flipper_test.tsx | 24 +++++--- .../farmware/images/__tests__/photos_test.tsx | 18 ++++++ frontend/farmware/images/interfaces.ts | 3 + frontend/farmware/images/photos.tsx | 10 ++- frontend/farmware/index.tsx | 24 +++++--- frontend/farmware/state_to_props.ts | 32 ++++++---- .../weed_detector/__tests__/actions_tests.ts | 6 +- .../weed_detector/__tests__/config_test.tsx | 43 ++++++++++--- .../__tests__/weed_detector_test.tsx | 56 +++++++++++------ frontend/farmware/weed_detector/actions.tsx | 2 +- .../weed_detector/image_workspace.tsx | 5 +- frontend/farmware/weed_detector/index.tsx | 30 +++++---- frontend/logs/state_to_props.ts | 22 ++----- frontend/regimens/state_to_props.ts | 17 +----- .../sequence_editor_middle_active_test.tsx | 11 +--- .../__tests__/sequence_editor_middle_test.tsx | 11 +--- .../sequences/__tests__/sequences_test.tsx | 11 +--- .../set_active_sequence_by_name_test.ts | 36 +++++------ .../__tests__/state_to_props_test.ts | 20 ++++-- frontend/sequences/all_steps.tsx | 6 +- frontend/sequences/interfaces.ts | 9 +-- frontend/sequences/sequence_editor_middle.tsx | 2 +- .../sequence_editor_middle_active.tsx | 2 +- frontend/sequences/sequences.tsx | 2 +- frontend/sequences/state_to_props.ts | 24 +++----- .../__tests__/tile_calibrate_test.tsx | 2 +- .../__tests__/tile_execute_script_test.tsx | 30 ++++++--- .../__tests__/tile_find_home_test.tsx | 2 +- ...tile_move_absolute_conflict_check_test.tsx | 2 +- .../__tests__/tile_move_absolute_test.tsx | 2 +- .../__tests__/tile_take_photo_test.tsx | 12 ++++ .../step_tiles/tile_execute_script.tsx | 24 +++++--- .../tile_execute_script_support.tsx | 8 +-- .../sequences/step_tiles/tile_take_photo.tsx | 11 +++- frontend/sequences/step_ui/step_warning.tsx | 8 +-- 68 files changed, 655 insertions(+), 436 deletions(-) create mode 100644 frontend/__test_support__/fake_sequence_step_data.ts delete mode 100644 frontend/__test_support__/sequence_hardware_settings.ts diff --git a/frontend/__test_support__/fake_sequence_step_data.ts b/frontend/__test_support__/fake_sequence_step_data.ts new file mode 100644 index 000000000..451d54392 --- /dev/null +++ b/frontend/__test_support__/fake_sequence_step_data.ts @@ -0,0 +1,17 @@ +import { HardwareFlags, FarmwareData } from "../sequences/interfaces"; + +export const fakeHardwareFlags = (): HardwareFlags => ({ + findHomeEnabled: { x: false, y: false, z: false }, + stopAtHome: { x: false, y: false, z: false }, + stopAtMax: { x: false, y: false, z: false }, + negativeOnly: { x: false, y: false, z: false }, + axisLength: { x: 0, y: 0, z: 0 }, +}); + +export const fakeFarmwareData = (): FarmwareData => ({ + farmwareNames: [], + firstPartyFarmwareNames: [], + showFirstPartyFarmware: false, + farmwareConfigs: {}, + cameraDisabled: false, +}); diff --git a/frontend/__test_support__/sequence_hardware_settings.ts b/frontend/__test_support__/sequence_hardware_settings.ts deleted file mode 100644 index 7fc45fafd..000000000 --- a/frontend/__test_support__/sequence_hardware_settings.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { HardwareFlags } from "../sequences/interfaces"; - -export const fakeHardwareFlags = (): HardwareFlags => { - return { - findHomeEnabled: { x: false, y: false, z: false }, - stopAtHome: { x: false, y: false, z: false }, - stopAtMax: { x: false, y: false, z: false }, - negativeOnly: { x: false, y: false, z: false }, - axisLength: { x: 0, y: 0, z: 0 }, - }; -}; diff --git a/frontend/__tests__/app_test.tsx b/frontend/__tests__/app_test.tsx index c9dba6aeb..a0cafcb17 100644 --- a/frontend/__tests__/app_test.tsx +++ b/frontend/__tests__/app_test.tsx @@ -9,7 +9,7 @@ import { RawApp as App, AppProps, mapStateToProps } from "../app"; import { mount } from "enzyme"; import { bot } from "../__test_support__/fake_state/bot"; import { - fakeUser, fakeWebAppConfig + fakeUser, fakeWebAppConfig, fakeFbosConfig, fakeFarmwareEnv } from "../__test_support__/fake_state/resources"; import { fakeState } from "../__test_support__/fake_state"; import { @@ -40,7 +40,8 @@ const fakeProps = (): AppProps => ({ resources: buildResourceIndex().index, autoSync: false, alertCount: 0, - pings: fakePings() + pings: fakePings(), + env: {}, }); describe(": Controls Pop-Up", () => { @@ -145,7 +146,24 @@ describe("mapStateToProps()", () => { const config = fakeWebAppConfig(); config.body.x_axis_inverted = true; state.resources = buildResourceIndex([config]); + state.bot.hardware.user_env = { fake: "value" }; const result = mapStateToProps(state); expect(result.axisInversion.x).toEqual(true); + expect(result.autoSync).toEqual(false); + expect(result.env).toEqual({ fake: "value" }); + }); + + it("returns api props", () => { + const state = fakeState(); + const config = fakeFbosConfig(); + config.body.auto_sync = true; + config.body.api_migrated = true; + const fakeEnv = fakeFarmwareEnv(); + state.resources = buildResourceIndex([config, fakeEnv]); + state.bot.minOsFeatureData = { api_farmware_env: "8.0.0" }; + state.bot.hardware.informational_settings.controller_version = "8.0.0"; + const result = mapStateToProps(state); + expect(result.autoSync).toEqual(true); + expect(result.env).toEqual({ [fakeEnv.body.key]: fakeEnv.body.value }); }); }); diff --git a/frontend/__tests__/controls_popup_test.tsx b/frontend/__tests__/controls_popup_test.tsx index 92c7153bd..a4852e68a 100644 --- a/frontend/__tests__/controls_popup_test.tsx +++ b/frontend/__tests__/controls_popup_test.tsx @@ -9,38 +9,37 @@ import { ControlsPopup } from "../controls_popup"; import { mount } from "enzyme"; import { bot } from "../__test_support__/fake_state/bot"; import { ControlsPopupProps } from "../controls/move/interfaces"; +import { error } from "../toast/toast"; +import { Content, ToolTips } from "../constants"; describe("", () => { - const fakeProps = (): ControlsPopupProps => { - return { - dispatch: jest.fn(), - axisInversion: { x: false, y: false, z: false }, - botPosition: { x: undefined, y: undefined, z: undefined }, - firmwareSettings: bot.hardware.mcu_params, - xySwap: false, - arduinoBusy: false, - stepSize: 100, - botOnline: true, - }; - }; - - const p = fakeProps(); - p.axisInversion.x = true; - const wrapper = mount(); - - afterAll(wrapper.unmount); + const fakeProps = (): ControlsPopupProps => ({ + dispatch: jest.fn(), + axisInversion: { x: true, y: false, z: false }, + botPosition: { x: undefined, y: undefined, z: undefined }, + firmwareSettings: bot.hardware.mcu_params, + xySwap: false, + arduinoBusy: false, + stepSize: 100, + botOnline: true, + env: {}, + }); it("Has a false initial state", () => { + const wrapper = mount(); expect(wrapper.state("isOpen")).toBeFalsy(); }); it("Toggles state", () => { + const wrapper = mount(); const parent = wrapper.find("i").first(); parent.simulate("click"); expect(wrapper.state("isOpen")).toBeTruthy(); }); it("x axis is inverted", () => { + const wrapper = mount(); + wrapper.setState({ isOpen: true }); const button = wrapper.find("button").at(3); expect(button.props().title).toBe("move x axis (100)"); button.simulate("click"); @@ -49,6 +48,8 @@ describe("", () => { }); it("y axis is not inverted", () => { + const wrapper = mount(); + wrapper.setState({ isOpen: true }); const button = wrapper.find("button").at(1); expect(button.props().title).toBe("move y axis (100)"); button.simulate("click"); @@ -57,6 +58,7 @@ describe("", () => { }); it("disabled when closed", () => { + const wrapper = mount(); wrapper.setState({ isOpen: false }); [0, 1, 2, 3].map((i) => wrapper.find("button").at(i).simulate("click")); expect(mockDevice.moveRelative).not.toHaveBeenCalled(); @@ -65,6 +67,7 @@ describe("", () => { it("swaps axes", () => { const swappedProps = fakeProps(); swappedProps.xySwap = true; + swappedProps.axisInversion.x = false; const swapped = mount(); swapped.setState({ isOpen: true }); expect(swapped.state("isOpen")).toBeTruthy(); @@ -76,7 +79,25 @@ describe("", () => { }); it("takes photo", () => { - wrapper.find("button").at(4).simulate("click"); + const wrapper = mount(); + wrapper.setState({ isOpen: true }); + const btn = wrapper.find("button").at(4); + expect(btn.props().title).not.toEqual(Content.NO_CAMERA_SELECTED); + btn.simulate("click"); expect(mockDevice.takePhoto).toHaveBeenCalled(); + expect(error).not.toHaveBeenCalled(); + }); + + it("shows camera as disabled", () => { + const p = fakeProps(); + p.env = { camera: "NONE" }; + const wrapper = mount(); + wrapper.setState({ isOpen: true }); + const btn = wrapper.find("button").at(4); + expect(btn.props().title).toEqual(Content.NO_CAMERA_SELECTED); + btn.simulate("click"); + expect(error).toHaveBeenCalledWith( + ToolTips.SELECT_A_CAMERA, Content.NO_CAMERA_SELECTED); + expect(mockDevice.takePhoto).not.toHaveBeenCalled(); }); }); diff --git a/frontend/app.tsx b/frontend/app.tsx index 9a6aae89b..c22a3fb5e 100644 --- a/frontend/app.tsx +++ b/frontend/app.tsx @@ -4,7 +4,7 @@ import { init, error } from "./toast/toast"; import { NavBar } from "./nav"; import { Everything, TimeSettings } from "./interfaces"; import { LoadingPlant } from "./loading_plant"; -import { BotState, Xyz } from "./devices/interfaces"; +import { BotState, Xyz, UserEnv } from "./devices/interfaces"; import { ResourceName, TaggedUser, TaggedLog } from "farmbot"; import { maybeFetchUser, @@ -30,6 +30,7 @@ import { isBotOnline } from "./devices/must_be_online"; import { getStatus } from "./connectivity/reducer_support"; import { getAllAlerts } from "./messages/state_to_props"; import { PingDictionary } from "./devices/connectivity/qos"; +import { getEnv, getShouldDisplayFn } from "./farmware/state_to_props"; /** For the logger module */ init(); @@ -52,11 +53,14 @@ export interface AppProps { autoSync: boolean; alertCount: number; pings: PingDictionary; + env: UserEnv; } export function mapStateToProps(props: Everything): AppProps { const webAppConfigValue = getWebAppConfigValue(() => props); const fbosConfig = validFbosConfig(getFbosConfig(props.resources.index)); + const shouldDisplay = getShouldDisplayFn(props.resources.index, props.bot); + const env = getEnv(props.resources.index, shouldDisplay, props.bot); return { timeSettings: maybeGetTimeSettings(props.resources.index), dispatch: props.dispatch, @@ -78,7 +82,8 @@ export function mapStateToProps(props: Everything): AppProps { resources: props.resources.index, autoSync: !!(fbosConfig && fbosConfig.auto_sync), alertCount: getAllAlerts(props.resources).length, - pings: props.bot.connectivity.pings + pings: props.bot.connectivity.pings, + env, }; } /** Time at which the app gives up and asks the user to refresh */ @@ -147,6 +152,7 @@ export class RawApp extends React.Component { xySwap={this.props.xySwap} arduinoBusy={!!this.props.bot.hardware.informational_settings.busy} botOnline={isBotOnline(sync_status, getStatus(bot2mqtt))} + env={this.props.env} stepSize={this.props.bot.stepSize} />} ; } diff --git a/frontend/constants.ts b/frontend/constants.ts index 32ae4e3e9..236ee5f30 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -285,6 +285,9 @@ export namespace ToolTips { trim(`Snaps a photo using the device camera. Select the camera type on the Device page.`); + export const SELECT_A_CAMERA = + trim(`Select a camera on the Device page to take photos.`); + export const MARK_AS = trim(`The Mark As step allows FarmBot to programmatically edit the properties of the UTM, plants, and weeds from within a sequence. @@ -822,6 +825,9 @@ export namespace Content { export const NOT_AVAILABLE_WHEN_OFFLINE = trim(`Not available when device is offline.`); + + export const NO_CAMERA_SELECTED = + trim(`No camera selected`); } export namespace TourContent { diff --git a/frontend/controls/__tests__/controls_test.tsx b/frontend/controls/__tests__/controls_test.tsx index 202db055e..abea6315f 100644 --- a/frontend/controls/__tests__/controls_test.tsx +++ b/frontend/controls/__tests__/controls_test.tsx @@ -24,6 +24,7 @@ describe("", () => { getWebAppConfigVal: jest.fn((key) => (mockConfig[key])), sensorReadings: [], timeSettings: fakeTimeSettings(), + env: {}, }); it("shows webcam widget", () => { diff --git a/frontend/controls/__tests__/state_to_props_test.ts b/frontend/controls/__tests__/state_to_props_test.ts index b3dcb178d..cf2dac026 100644 --- a/frontend/controls/__tests__/state_to_props_test.ts +++ b/frontend/controls/__tests__/state_to_props_test.ts @@ -1,6 +1,6 @@ import { mapStateToProps } from "../state_to_props"; import { buildResourceIndex } from "../../__test_support__/resource_index_builder"; -import { fakeUser } from "../../__test_support__/fake_state/resources"; +import { fakeUser, fakeFarmwareEnv } from "../../__test_support__/fake_state/resources"; import { fakeState } from "../../__test_support__/fake_state"; describe("mapStateToProps()", () => { @@ -10,4 +10,14 @@ describe("mapStateToProps()", () => { const result = mapStateToProps(state); expect(result.timeSettings).toEqual({ utcOffset: 0, hour24: false }); }); + + it("returns api props", () => { + const state = fakeState(); + const fakeEnv = fakeFarmwareEnv(); + state.resources = buildResourceIndex([fakeEnv]); + state.bot.minOsFeatureData = { api_farmware_env: "8.0.0" }; + state.bot.hardware.informational_settings.controller_version = "8.0.0"; + const result = mapStateToProps(state); + expect(result.env).toEqual({ [fakeEnv.body.key]: fakeEnv.body.value }); + }); }); diff --git a/frontend/controls/controls.tsx b/frontend/controls/controls.tsx index 04a86b8a3..7574ccd05 100644 --- a/frontend/controls/controls.tsx +++ b/frontend/controls/controls.tsx @@ -29,6 +29,7 @@ export class RawControls extends React.Component { move = () => ", function () { const jogButtonProps = (): JogMovementControlsProps => { @@ -26,6 +28,7 @@ describe("", function () { firmwareSettings: bot.hardware.mcu_params, xySwap: false, doFindHome: false, + env: {}, }; }; @@ -60,8 +63,12 @@ describe("", function () { it("takes photo", () => { const jogButtons = mount(); - jogButtons.find("button").at(0).simulate("click"); + const cameraBtn = jogButtons.find("button").at(0); + expect(cameraBtn.props().title).not.toEqual(Content.NO_CAMERA_SELECTED); + + cameraBtn.simulate("click"); expect(mockDevice.takePhoto).toHaveBeenCalled(); + expect(error).not.toHaveBeenCalled(); }); it("error taking photo", () => { @@ -71,6 +78,18 @@ describe("", function () { expect(mockDevice.takePhoto).toHaveBeenCalled(); }); + it("shows camera as disabled", () => { + const p = jogButtonProps(); + p.env = { camera: "NONE" }; + const jogButtons = mount(); + const cameraBtn = jogButtons.find("button").at(0); + expect(cameraBtn.props().title).toEqual(Content.NO_CAMERA_SELECTED); + cameraBtn.simulate("click"); + expect(error).toHaveBeenCalledWith( + ToolTips.SELECT_A_CAMERA, Content.NO_CAMERA_SELECTED); + expect(mockDevice.takePhoto).not.toHaveBeenCalled(); + }); + it("has unswapped xy jog buttons", () => { const jogButtons = mount(); const button = jogButtons.find("button").at(6); diff --git a/frontend/controls/move/__tests__/move_test.tsx b/frontend/controls/move/__tests__/move_test.tsx index cd1d22ea1..d9ba41196 100644 --- a/frontend/controls/move/__tests__/move_test.tsx +++ b/frontend/controls/move/__tests__/move_test.tsx @@ -1,16 +1,15 @@ -const mockDevice = { - moveAbsolute: jest.fn(() => { return Promise.resolve(); }), -}; +const mockDevice = { moveAbsolute: jest.fn(() => Promise.resolve()) }; +jest.mock("../../../device", () => ({ getDevice: () => mockDevice })); -jest.mock("../../../device", () => ({ - getDevice: () => (mockDevice) +jest.mock("../../../config_storage/actions", () => ({ + toggleWebAppBool: jest.fn(), })); -jest.mock("../../../config_storage/actions", () => { - return { - toggleWebAppBool: jest.fn() - }; -}); +jest.mock("../../../account/dev/dev_support", () => ({ + DevSettings: { + futureFeaturesEnabled: () => false, + } +})); import * as React from "react"; import { mount, shallow } from "enzyme"; @@ -26,16 +25,15 @@ import { clickButton } from "../../../__test_support__/helpers"; describe("", () => { const mockConfig: Dictionary = {}; - function fakeProps(): MoveProps { - return { - dispatch: jest.fn(), - bot: bot, - arduinoBusy: false, - botToMqttStatus: "up", - firmwareSettings: bot.hardware.mcu_params, - getWebAppConfigVal: jest.fn((key) => (mockConfig[key])), - }; - } + const fakeProps = (): MoveProps => ({ + dispatch: jest.fn(), + bot: bot, + arduinoBusy: false, + botToMqttStatus: "up", + firmwareSettings: bot.hardware.mcu_params, + getWebAppConfigVal: jest.fn((key) => (mockConfig[key])), + env: {}, + }); it("has default elements", () => { const wrapper = mount(); diff --git a/frontend/controls/move/interfaces.ts b/frontend/controls/move/interfaces.ts index 905ba80ca..d3ab61465 100644 --- a/frontend/controls/move/interfaces.ts +++ b/frontend/controls/move/interfaces.ts @@ -1,4 +1,4 @@ -import { BotPosition, BotState } from "../../devices/interfaces"; +import { BotPosition, BotState, UserEnv } from "../../devices/interfaces"; import { McuParams, Xyz } from "farmbot"; import { NetworkState } from "../../connectivity/interfaces"; import { GetWebAppConfigValue } from "../../config_storage/actions"; @@ -14,6 +14,7 @@ export interface MoveProps { botToMqttStatus: NetworkState; firmwareSettings: McuParams; getWebAppConfigVal: GetWebAppConfigValue; + env: UserEnv; } export interface DirectionButtonProps { @@ -47,6 +48,7 @@ interface JogMovementControlsBaseProps extends DirectionAxesProps { stepSize: number; arduinoBusy: boolean; xySwap: boolean; + env: UserEnv; } export interface JogMovementControlsProps extends JogMovementControlsBaseProps { diff --git a/frontend/controls/move/jog_buttons.tsx b/frontend/controls/move/jog_buttons.tsx index a640eae43..dacf00ca4 100644 --- a/frontend/controls/move/jog_buttons.tsx +++ b/frontend/controls/move/jog_buttons.tsx @@ -5,6 +5,9 @@ import { JogMovementControlsProps } from "./interfaces"; import { getDevice } from "../../device"; import { buildDirectionProps } from "./direction_axes_props"; import { t } from "../../i18next_wrapper"; +import { + cameraBtnProps +} from "../../devices/components/fbos_settings/camera_selection"; const DEFAULT_STEP_SIZE = 100; /* @@ -20,35 +23,37 @@ export function JogButtons(props: JogMovementControlsProps) { const directionAxesProps = buildDirectionProps(props); const rightLeft = xySwap ? "y" : "x"; const upDown = xySwap ? "x" : "y"; - + const commonProps = { + steps: stepSize || DEFAULT_STEP_SIZE, + disabled: arduinoBusy + }; + const camDisabled = cameraBtnProps(props.env); return @@ -61,37 +66,29 @@ export function JogButtons(props: JogMovementControlsProps) { diff --git a/frontend/controls/move/jog_controls_group.tsx b/frontend/controls/move/jog_controls_group.tsx index dc9434d38..a1c0b9e3b 100644 --- a/frontend/controls/move/jog_controls_group.tsx +++ b/frontend/controls/move/jog_controls_group.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { McuParams } from "farmbot"; -import { BotPosition } from "../../devices/interfaces"; +import { BotPosition, UserEnv } from "../../devices/interfaces"; import { changeStepSize } from "../../devices/actions"; import { StepSizeSelector } from "./step_size_selector"; import { GetWebAppBool } from "./interfaces"; @@ -15,6 +15,7 @@ interface JogControlsGroupProps { getValue: GetWebAppBool; arduinoBusy: boolean; firmwareSettings: McuParams; + env: UserEnv; } export const JogControlsGroup = (props: JogControlsGroupProps) => { @@ -38,6 +39,7 @@ export const JogControlsGroup = (props: JogControlsGroupProps) => { z: getValue(BooleanSetting.z_axis_inverted) }} arduinoBusy={arduinoBusy} + env={props.env} firmwareSettings={firmwareSettings} xySwap={getValue(BooleanSetting.xy_swap)} doFindHome={getValue(BooleanSetting.home_button_homing)} /> diff --git a/frontend/controls/move/move.tsx b/frontend/controls/move/move.tsx index 8e844a63f..fa9a58155 100644 --- a/frontend/controls/move/move.tsx +++ b/frontend/controls/move/move.tsx @@ -47,6 +47,7 @@ export class Move extends React.Component { botPosition={locationData.position} getValue={this.getValue} arduinoBusy={this.props.arduinoBusy} + env={this.props.env} firmwareSettings={this.props.firmwareSettings} />
- - + - + - + + directionAxisProps={directionAxesProps[rightLeft]} />
; diff --git a/frontend/css/buttons.scss b/frontend/css/buttons.scss index c2fca3df4..7833ffd73 100644 --- a/frontend/css/buttons.scss +++ b/frontend/css/buttons.scss @@ -279,6 +279,7 @@ &.pseudo-disabled { background: $medium_light_gray !important; box-shadow: 0 2px 0px 0px lighten($medium_light_gray, 5%) !important; + border-bottom: none !important; &:focus, &:hover, &.active { diff --git a/frontend/devices/components/fbos_settings/__tests__/camera_selection_test.tsx b/frontend/devices/components/fbos_settings/__tests__/camera_selection_test.tsx index 4a6f7aa10..cb94550bb 100644 --- a/frontend/devices/components/fbos_settings/__tests__/camera_selection_test.tsx +++ b/frontend/devices/components/fbos_settings/__tests__/camera_selection_test.tsx @@ -1,26 +1,20 @@ -const mockDevice = { - setUserEnv: jest.fn(() => { return Promise.resolve(); }), -}; -jest.mock("../../../../device", () => ({ - getDevice: () => mockDevice -})); +const mockDevice = { setUserEnv: jest.fn(() => Promise.resolve()) }; +jest.mock("../../../../device", () => ({ getDevice: () => mockDevice })); import * as React from "react"; import { mount, shallow } from "enzyme"; -import { CameraSelection } from "../camera_selection"; +import { CameraSelection, cameraDisabled } from "../camera_selection"; import { CameraSelectionProps } from "../interfaces"; import { info, error } from "../../../../toast/toast"; describe("", () => { - const fakeProps = (): CameraSelectionProps => { - return { - env: {}, - botOnline: true, - shouldDisplay: () => false, - saveFarmwareEnv: jest.fn(), - dispatch: jest.fn(), - }; - }; + const fakeProps = (): CameraSelectionProps => ({ + env: {}, + botOnline: true, + shouldDisplay: () => false, + saveFarmwareEnv: jest.fn(), + dispatch: jest.fn(), + }); it("doesn't render camera", () => { const cameraSelection = mount(); @@ -66,3 +60,15 @@ describe("", () => { expect(p.saveFarmwareEnv).toHaveBeenCalledWith("camera", "\"mycamera\""); }); }); + +describe("cameraDisabled()", () => { + it("returns enabled", () => { + expect(cameraDisabled({ camera: "USB" })).toEqual(false); + expect(cameraDisabled({ camera: "" })).toEqual(false); + }); + + it("returns disabled", () => { + expect(cameraDisabled({ camera: "none" })).toEqual(true); + expect(cameraDisabled({ camera: "\"NONE\"" })).toEqual(true); + }); +}); diff --git a/frontend/devices/components/fbos_settings/camera_selection.tsx b/frontend/devices/components/fbos_settings/camera_selection.tsx index b848bfc97..46f3d895b 100644 --- a/frontend/devices/components/fbos_settings/camera_selection.tsx +++ b/frontend/devices/components/fbos_settings/camera_selection.tsx @@ -6,25 +6,56 @@ import { import { info, success, error } from "../../../toast/toast"; import { getDevice } from "../../../device"; import { ColWidth } from "../farmbot_os_settings"; -import { Feature } from "../../interfaces"; +import { Feature, UserEnv } from "../../interfaces"; import { t } from "../../../i18next_wrapper"; +import { Content, ToolTips } from "../../../constants"; + +/** Check if the camera has been disabled. */ +export const cameraDisabled = (env: UserEnv): boolean => + parseCameraSelection(env) === Camera.NONE; + +/** `disabled` and `title` props for buttons with actions that use the camera. */ +export const cameraBtnProps = (env: UserEnv) => { + const disabled = cameraDisabled(env); + return disabled + ? { + class: "pseudo-disabled", + click: () => + error(t(ToolTips.SELECT_A_CAMERA), t(Content.NO_CAMERA_SELECTED)), + title: t(Content.NO_CAMERA_SELECTED) + } + : { class: "", click: undefined, title: "" }; +}; + +enum Camera { + USB = "USB", + RPI = "RPI", + NONE = "NONE", +} + +const parseCameraSelection = (env: UserEnv): Camera => { + const camera = env["camera"]?.toUpperCase(); + if (camera?.includes(Camera.NONE)) { + return Camera.NONE; + } else if (camera?.includes(Camera.RPI)) { + return Camera.RPI; + } else { + return Camera.USB; + } +}; const CAMERA_CHOICES = () => ([ - { label: t("USB Camera"), value: "USB" }, - { label: t("Raspberry Pi Camera"), value: "RPI" } + { label: t("USB Camera"), value: Camera.USB }, + { label: t("Raspberry Pi Camera"), value: Camera.RPI }, + { label: t("None"), value: Camera.NONE }, ]); const CAMERA_CHOICES_DDI = () => { const CHOICES = CAMERA_CHOICES(); return { - [CHOICES[0].value]: { - label: CHOICES[0].label, - value: CHOICES[0].value - }, - [CHOICES[1].value]: { - label: CHOICES[1].label, - value: CHOICES[1].value - } + [CHOICES[0].value]: { label: CHOICES[0].label, value: CHOICES[0].value }, + [CHOICES[1].value]: { label: CHOICES[1].label, value: CHOICES[1].value }, + [CHOICES[2].value]: { label: CHOICES[2].label, value: CHOICES[2].value }, }; }; @@ -35,12 +66,8 @@ export class CameraSelection cameraStatus: "" }; - selectedCamera(): DropDownItem { - const camera = this.props.env["camera"]; - return camera - ? CAMERA_CHOICES_DDI()[JSON.parse(camera)] - : CAMERA_CHOICES_DDI()["USB"]; - } + selectedCamera = (): DropDownItem => + CAMERA_CHOICES_DDI()[parseCameraSelection(this.props.env)] sendOffConfig = (selectedCamera: DropDownItem) => { const { props } = this; diff --git a/frontend/devices/interfaces.ts b/frontend/devices/interfaces.ts index 84b9d27f5..972fb6a3b 100644 --- a/frontend/devices/interfaces.ts +++ b/frontend/devices/interfaces.ts @@ -209,8 +209,8 @@ export interface SensorsProps { export interface FarmwareProps { dispatch: Function; - env: Partial; - user_env: UserEnv; + wDEnv: Partial; + env: UserEnv; images: TaggedImage[]; currentImage: TaggedImage | undefined; botToMqttStatus: NetworkState; diff --git a/frontend/devices/state_to_props.ts b/frontend/devices/state_to_props.ts index adfdccc84..20f224dfc 100644 --- a/frontend/devices/state_to_props.ts +++ b/frontend/devices/state_to_props.ts @@ -1,38 +1,28 @@ import { Everything } from "../interfaces"; -import { Props, Feature } from "./interfaces"; +import { Props } from "./interfaces"; import { selectAllImages, getDeviceAccountSettings, - maybeGetDevice, maybeGetTimeSettings, } from "../resources/selectors"; import { sourceFbosConfigValue, sourceFwConfigValue } from "./components/source_config_value"; +import { validFwConfig, validFbosConfig } from "../util"; import { - determineInstalledOsVersion, validFwConfig, validFbosConfig, - createShouldDisplayFn as shouldDisplayFunc -} from "../util"; -import { - saveOrEditFarmwareEnv, reduceFarmwareEnv + saveOrEditFarmwareEnv, getEnv, getShouldDisplayFn } from "../farmware/state_to_props"; -import { getFbosConfig, getFirmwareConfig, getWebAppConfig } from "../resources/getters"; -import { DevSettings } from "../account/dev/dev_support"; +import { + getFbosConfig, getFirmwareConfig, getWebAppConfig +} from "../resources/getters"; import { getAllAlerts } from "../messages/state_to_props"; export function mapStateToProps(props: Everything): Props { const { hardware } = props.bot; const fbosConfig = validFbosConfig(getFbosConfig(props.resources.index)); const firmwareConfig = validFwConfig(getFirmwareConfig(props.resources.index)); - const installedOsVersion = determineInstalledOsVersion( - props.bot, maybeGetDevice(props.resources.index)); - const fbosVersionOverride = DevSettings.overriddenFbosVersion(); - const shouldDisplay = shouldDisplayFunc(installedOsVersion, - props.bot.minOsFeatureData, - fbosVersionOverride); - const env = shouldDisplay(Feature.api_farmware_env) - ? reduceFarmwareEnv(props.resources.index) - : props.bot.hardware.user_env; + const shouldDisplay = getShouldDisplayFn(props.resources.index, props.bot); + const env = getEnv(props.resources.index, shouldDisplay, props.bot); const webAppConfig = getWebAppConfig(props.resources.index); if (!webAppConfig) { throw new Error("Missing web app config"); diff --git a/frontend/farm_designer/farm_events/map_state_to_props_add_edit.ts b/frontend/farm_designer/farm_events/map_state_to_props_add_edit.ts index f5cb98557..8ea513032 100644 --- a/frontend/farm_designer/farm_events/map_state_to_props_add_edit.ts +++ b/frontend/farm_designer/farm_events/map_state_to_props_add_edit.ts @@ -13,7 +13,6 @@ import { findSequenceById, findRegimenById, getDeviceAccountSettings, - maybeGetDevice, maybeGetTimeSettings } from "../../resources/selectors"; import { @@ -22,11 +21,7 @@ import { TaggedRegimen } from "farmbot"; import { DropDownItem } from "../../ui/index"; -import { - validFbosConfig, - createShouldDisplayFn as shouldDisplayFunc, - determineInstalledOsVersion -} from "../../util"; +import { validFbosConfig } from "../../util"; import { sourceFbosConfigValue } from "../../devices/components/source_config_value"; @@ -34,7 +29,7 @@ import { hasId } from "../../resources/util"; import { ExecutableType } from "farmbot/dist/resources/api_resources"; import { getFbosConfig } from "../../resources/getters"; import { t } from "../../i18next_wrapper"; -import { DevSettings } from "../../account/dev/dev_support"; +import { getShouldDisplayFn } from "../../farmware/state_to_props"; export const formatTime = (input: string, timeSettings: TimeSettings) => { const iso = new Date(input).toISOString(); @@ -143,12 +138,6 @@ export function mapStateToPropsAddEdit(props: Everything): AddEditFarmEventProps const autoSyncEnabled = !!sourceFbosConfigValue(fbosConfig, configuration)("auto_sync").value; - const installedOsVersion = determineInstalledOsVersion( - props.bot, maybeGetDevice(props.resources.index)); - const fbosVersionOverride = DevSettings.overriddenFbosVersion(); - const shouldDisplay = shouldDisplayFunc( - installedOsVersion, props.bot.minOsFeatureData, fbosVersionOverride); - return { deviceTimezone: dev .body @@ -167,6 +156,6 @@ export function mapStateToPropsAddEdit(props: Everything): AddEditFarmEventProps timeSettings: maybeGetTimeSettings(props.resources.index), autoSyncEnabled, resources: props.resources.index, - shouldDisplay, + shouldDisplay: getShouldDisplayFn(props.resources.index, props.bot), }; } diff --git a/frontend/farm_designer/state_to_props.ts b/frontend/farm_designer/state_to_props.ts index f788660d7..d9ca4790f 100644 --- a/frontend/farm_designer/state_to_props.ts +++ b/frontend/farm_designer/state_to_props.ts @@ -9,24 +9,17 @@ import { selectAllPlantTemplates, selectAllSensorReadings, selectAllSensors, - maybeGetDevice, maybeGetTimeSettings } from "../resources/selectors"; -import { - validBotLocationData, validFwConfig, unpackUUID, - createShouldDisplayFn as shouldDisplayFunc, - determineInstalledOsVersion -} from "../util"; +import { validBotLocationData, validFwConfig, unpackUUID } from "../util"; import { getWebAppConfigValue } from "../config_storage/actions"; import { Props } from "./interfaces"; import { TaggedPlant } from "./map/interfaces"; import { RestResources } from "../resources/interfaces"; import { isString, uniq, chain } from "lodash"; import { BooleanSetting } from "../session_keys"; -import { Feature } from "../devices/interfaces"; -import { reduceFarmwareEnv } from "../farmware/state_to_props"; +import { getEnv, getShouldDisplayFn } from "../farmware/state_to_props"; import { getFirmwareConfig } from "../resources/getters"; -import { DevSettings } from "../account/dev/dev_support"; import { calcMicrostepsPerMm } from "../controls/move/direction_axes_props"; const plantFinder = (plants: TaggedPlant[]) => @@ -84,14 +77,8 @@ export function mapStateToProps(props: Everything): Props { .reverse() .value(); - const installedOsVersion = determineInstalledOsVersion( - props.bot, maybeGetDevice(props.resources.index)); - const fbosVersionOverride = DevSettings.overriddenFbosVersion(); - const shouldDisplay = shouldDisplayFunc( - installedOsVersion, props.bot.minOsFeatureData, fbosVersionOverride); - const env = shouldDisplay(Feature.api_farmware_env) - ? reduceFarmwareEnv(props.resources.index) - : props.bot.hardware.user_env; + const shouldDisplay = getShouldDisplayFn(props.resources.index, props.bot); + const env = getEnv(props.resources.index, shouldDisplay, props.bot); const cameraCalibrationData = { scale: env["CAMERA_CALIBRATION_coord_scale"], diff --git a/frontend/farmware/__tests__/farmware_forms_test.tsx b/frontend/farmware/__tests__/farmware_forms_test.tsx index f909c8c1c..39f008e98 100644 --- a/frontend/farmware/__tests__/farmware_forms_test.tsx +++ b/frontend/farmware/__tests__/farmware_forms_test.tsx @@ -93,7 +93,7 @@ describe("", () => { const fakeProps = (): FarmwareFormProps => { return { farmware: fakeFarmware(), - user_env: {}, + env: {}, dispatch: jest.fn(), shouldDisplay: () => false, saveFarmwareEnv: jest.fn(), diff --git a/frontend/farmware/__tests__/farmware_test.tsx b/frontend/farmware/__tests__/farmware_test.tsx index e7b6a856e..92758328b 100644 --- a/frontend/farmware/__tests__/farmware_test.tsx +++ b/frontend/farmware/__tests__/farmware_test.tsx @@ -16,8 +16,8 @@ describe("", () => { const fakeProps = (): FarmwareProps => ({ farmwares: fakeFarmwares(), botToMqttStatus: "up", + wDEnv: {}, env: {}, - user_env: {}, dispatch: jest.fn(), currentImage: undefined, images: [], diff --git a/frontend/farmware/__tests__/set_active_farmware_by_name_test.ts b/frontend/farmware/__tests__/set_active_farmware_by_name_test.ts index ca0f4b43f..44119b26f 100644 --- a/frontend/farmware/__tests__/set_active_farmware_by_name_test.ts +++ b/frontend/farmware/__tests__/set_active_farmware_by_name_test.ts @@ -1,14 +1,15 @@ let mockLastUrlChunk = "farmware"; +jest.mock("../../util/urls", () => ({ + urlFriendly: jest.fn(x => x), + lastUrlChunk: jest.fn(() => mockLastUrlChunk) +})); -jest.mock("../../util/urls", () => { - return { - urlFriendly: jest.fn(x => x), - lastUrlChunk: jest.fn(() => mockLastUrlChunk) - }; -}); +jest.mock("../../redux/store", () => ({ store: { dispatch: jest.fn() } })); -jest.mock("../../redux/store", () => ({ - store: { dispatch: jest.fn() } +jest.mock("../../account/dev/dev_support", () => ({ + DevSettings: { + futureFeaturesEnabled: () => false, + } })); import { setActiveFarmwareByName } from "../set_active_farmware_by_name"; diff --git a/frontend/farmware/__tests__/state_to_props_test.tsx b/frontend/farmware/__tests__/state_to_props_test.tsx index 006185610..ec3d7c2af 100644 --- a/frontend/farmware/__tests__/state_to_props_test.tsx +++ b/frontend/farmware/__tests__/state_to_props_test.tsx @@ -43,7 +43,7 @@ describe("mapStateToProps()", () => { const state = fakeState(); state.bot.hardware.user_env = env; const props = mapStateToProps(state); - expect(props.user_env).toEqual(env); + expect(props.env).toEqual(env); }); it("returns API farmware env", () => { @@ -53,7 +53,7 @@ describe("mapStateToProps()", () => { DevSettings.MAX_FBOS_VERSION_OVERRIDE; state.resources = buildResourceIndex([fakeFarmwareEnv()]); const props = mapStateToProps(state); - expect(props.user_env).toEqual({ + expect(props.env).toEqual({ fake_FarmwareEnv_key: "fake_FarmwareEnv_value" }); }); diff --git a/frontend/farmware/camera_calibration/__tests__/camera_calibration_test.tsx b/frontend/farmware/camera_calibration/__tests__/camera_calibration_test.tsx index ef2721f69..577f65133 100644 --- a/frontend/farmware/camera_calibration/__tests__/camera_calibration_test.tsx +++ b/frontend/farmware/camera_calibration/__tests__/camera_calibration_test.tsx @@ -1,6 +1,5 @@ const mockDevice = { setUserEnv: jest.fn(() => Promise.resolve({})) }; jest.mock("../../../device", () => ({ getDevice: () => mockDevice })); - jest.mock("../actions", () => ({ scanImage: jest.fn() })); jest.mock("../../images/actions", () => ({ selectImage: jest.fn() })); @@ -11,12 +10,15 @@ import { CameraCalibrationProps } from "../interfaces"; import { scanImage } from "../actions"; import { selectImage } from "../../images/actions"; import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings"; +import { error } from "../../../toast/toast"; +import { Content, ToolTips } from "../../../constants"; describe("", () => { const fakeProps = (): CameraCalibrationProps => ({ dispatch: jest.fn(), currentImage: undefined, images: [], + wDEnv: {}, env: {}, iteration: 1, morph: 2, @@ -95,4 +97,23 @@ describe("", () => { expect(p.saveFarmwareEnv).toHaveBeenCalledWith( "CAMERA_CALIBRATION_image_bot_origin_location", "\"BOTTOM_LEFT\""); }); + + it("shows calibrate as enabled", () => { + const wrapper = shallow(); + const btn = wrapper.find("button").first(); + expect(btn.text()).toEqual("Calibrate"); + expect(btn.props().title).not.toEqual(Content.NO_CAMERA_SELECTED); + expect(error).not.toHaveBeenCalled(); + }); + + it("shows calibrate as disabled when camera is disabled", () => { + const p = fakeProps(); + p.env = { camera: "NONE" }; + const wrapper = shallow(); + const btn = wrapper.find("button").first(); + expect(btn.props().title).toEqual(Content.NO_CAMERA_SELECTED); + btn.simulate("click"); + expect(error).toHaveBeenCalledWith( + ToolTips.SELECT_A_CAMERA, Content.NO_CAMERA_SELECTED); + }); }); diff --git a/frontend/farmware/camera_calibration/camera_calibration.tsx b/frontend/farmware/camera_calibration/camera_calibration.tsx index a1bd2c1f8..6654eac51 100644 --- a/frontend/farmware/camera_calibration/camera_calibration.tsx +++ b/frontend/farmware/camera_calibration/camera_calibration.tsx @@ -13,6 +13,9 @@ import { Feature } from "../../devices/interfaces"; import { namespace } from "../weed_detector"; import { t } from "../../i18next_wrapper"; import { formatEnvKey } from "../weed_detector/remote_env/translators"; +import { + cameraBtnProps +} from "../../devices/components/fbos_settings/camera_selection"; export class CameraCalibration extends React.Component { @@ -29,6 +32,7 @@ export class CameraCalibration extends : envSave(key, value) render() { + const camDisabled = cameraBtnProps(this.props.env); return
@@ -68,9 +73,9 @@ export class CameraCalibration extends S_HI={this.props.S_HI} V_HI={this.props.V_HI} invertHue={!!envGet(this.namespace("invert_hue_selection"), - this.props.env)} /> + this.props.wDEnv)} /> diff --git a/frontend/farmware/camera_calibration/interfaces.ts b/frontend/farmware/camera_calibration/interfaces.ts index 8f92391b0..98a1b7ad0 100644 --- a/frontend/farmware/camera_calibration/interfaces.ts +++ b/frontend/farmware/camera_calibration/interfaces.ts @@ -1,14 +1,17 @@ import { TaggedImage, SyncStatus } from "farmbot"; import { WD_ENV } from "../weed_detector/remote_env/interfaces"; import { NetworkState } from "../../connectivity/interfaces"; -import { ShouldDisplay, SaveFarmwareEnv } from "../../devices/interfaces"; +import { + ShouldDisplay, SaveFarmwareEnv, UserEnv +} from "../../devices/interfaces"; import { TimeSettings } from "../../interfaces"; export interface CameraCalibrationProps { dispatch: Function; images: TaggedImage[]; currentImage: TaggedImage | undefined; - env: Partial; + wDEnv: Partial; + env: UserEnv; iteration: number; morph: number; blur: number; diff --git a/frontend/farmware/farmware_forms.tsx b/frontend/farmware/farmware_forms.tsx index c77fac4c9..af067ec6d 100644 --- a/frontend/farmware/farmware_forms.tsx +++ b/frontend/farmware/farmware_forms.tsx @@ -11,7 +11,7 @@ import { t } from "../i18next_wrapper"; export interface FarmwareFormProps { farmware: FarmwareManifestInfo; - user_env: UserEnv; + env: UserEnv; shouldDisplay: ShouldDisplay; saveFarmwareEnv: SaveFarmwareEnv; dispatch: Function; @@ -73,7 +73,7 @@ export function FarmwareForm(props: FarmwareFormProps): JSX.Element { /** Get a Farmware input value from FBOS. */ function getValue(farmwareName: string, currentConfig: FarmwareConfig) { - return (user_env[getConfigEnvName(farmwareName, currentConfig.name)] + return (env[getConfigEnvName(farmwareName, currentConfig.name)] || toString(currentConfig.value)); } @@ -87,7 +87,7 @@ export function FarmwareForm(props: FarmwareFormProps): JSX.Element { getDevice().execScript(farmwareName, pairs).catch(() => { }); } - const { farmware, user_env } = props; + const { farmware, env } = props; return
@@ -125,6 +130,7 @@ export class Photos extends React.Component { botToMqttStatus={this.props.botToMqttStatus} takePhoto={this.takePhoto} deletePhoto={this.deletePhoto} + env={this.props.env} imageJobs={this.props.imageJobs} /> this.props.dispatch(selectImage(id))} diff --git a/frontend/farmware/index.tsx b/frontend/farmware/index.tsx index 20a6c3ac2..29f4482b4 100644 --- a/frontend/farmware/index.tsx +++ b/frontend/farmware/index.tsx @@ -24,6 +24,7 @@ import { t } from "../i18next_wrapper"; import { isBotOnline } from "../devices/must_be_online"; import { BooleanSetting } from "../session_keys"; import { Dictionary } from "farmbot"; +import { WDENVKey } from "./weed_detector/remote_env/interfaces"; /** Get the correct help text for the provided Farmware. */ const getToolTipByFarmware = @@ -136,6 +137,7 @@ export class RawFarmwarePage extends React.Component { /** Load Farmware input panel contents for 1st & 3rd party Farmware. */ getPanelByFarmware(farmwareName: string) { + const wDEnvGet = (key: WDENVKey) => envGet(key, this.props.wDEnv); switch (farmwareUrlFriendly(farmwareName)) { case "take_photo": case "photos": @@ -145,6 +147,7 @@ export class RawFarmwarePage extends React.Component { timeSettings={this.props.timeSettings} dispatch={this.props.dispatch} images={this.props.images} + env={this.props.env} currentImage={this.props.currentImage} imageJobs={this.props.imageJobs} />; case "camera_calibration": @@ -153,17 +156,18 @@ export class RawFarmwarePage extends React.Component { dispatch={this.props.dispatch} currentImage={this.props.currentImage} images={this.props.images} + wDEnv={this.props.wDEnv} env={this.props.env} saveFarmwareEnv={this.props.saveFarmwareEnv} - iteration={envGet("CAMERA_CALIBRATION_iteration", this.props.env)} - morph={envGet("CAMERA_CALIBRATION_morph", this.props.env)} - blur={envGet("CAMERA_CALIBRATION_blur", this.props.env)} - H_LO={envGet("CAMERA_CALIBRATION_H_LO", this.props.env)} - S_LO={envGet("CAMERA_CALIBRATION_S_LO", this.props.env)} - V_LO={envGet("CAMERA_CALIBRATION_V_LO", this.props.env)} - H_HI={envGet("CAMERA_CALIBRATION_H_HI", this.props.env)} - S_HI={envGet("CAMERA_CALIBRATION_S_HI", this.props.env)} - V_HI={envGet("CAMERA_CALIBRATION_V_HI", this.props.env)} + iteration={wDEnvGet("CAMERA_CALIBRATION_iteration")} + morph={wDEnvGet("CAMERA_CALIBRATION_morph")} + blur={wDEnvGet("CAMERA_CALIBRATION_blur")} + H_LO={wDEnvGet("CAMERA_CALIBRATION_H_LO")} + S_LO={wDEnvGet("CAMERA_CALIBRATION_S_LO")} + V_LO={wDEnvGet("CAMERA_CALIBRATION_V_LO")} + H_HI={wDEnvGet("CAMERA_CALIBRATION_H_HI")} + S_HI={wDEnvGet("CAMERA_CALIBRATION_S_HI")} + V_HI={wDEnvGet("CAMERA_CALIBRATION_V_HI")} timeSettings={this.props.timeSettings} shouldDisplay={this.props.shouldDisplay} botToMqttStatus={this.props.botToMqttStatus} />; @@ -174,7 +178,7 @@ export class RawFarmwarePage extends React.Component { const farmware = getFarmwareByName(this.props.farmwares, farmwareName); return farmware && needsFarmwareForm(farmware) ? + shouldDisplay(Feature.api_farmware_env) + ? reduceFarmwareEnv(ri) + : bot.hardware.user_env; + +export const getShouldDisplayFn = (ri: ResourceIndex, bot: BotState) => { + const lookupData = bot.minOsFeatureData; + const installed = determineInstalledOsVersion(bot, maybeGetDevice(ri)); + const override = DevSettings.overriddenFbosVersion(); + const shouldDisplay = createShouldDisplayFn(installed, lookupData, override); + return shouldDisplay; +}; + export function mapStateToProps(props: Everything): FarmwareProps { const images = chain(selectAllImages(props.resources.index)) .sortBy(x => x.body.id) @@ -64,14 +78,8 @@ export function mapStateToProps(props: Everything): FarmwareProps { const { currentFarmware, firstPartyFarmwareNames, infoOpen } = props.resources.consumers.farmware; - const installedOsVersion = determineInstalledOsVersion( - props.bot, maybeGetDevice(props.resources.index)); - const fbosVersionOverride = DevSettings.overriddenFbosVersion(); - const shouldDisplay = shouldDisplayFunc( - installedOsVersion, props.bot.minOsFeatureData, fbosVersionOverride); - const env = shouldDisplay(Feature.api_farmware_env) - ? reduceFarmwareEnv(props.resources.index) - : props.bot.hardware.user_env; + const shouldDisplay = getShouldDisplayFn(props.resources.index, props.bot); + const env = getEnv(props.resources.index, shouldDisplay, props.bot); const taggedFarmwareInstallations = selectAllFarmwareInstallations(props.resources.index); @@ -115,8 +123,8 @@ export function mapStateToProps(props: Everything): FarmwareProps { currentFarmware, farmwares, botToMqttStatus, - env: prepopulateEnv(env), - user_env: env, + wDEnv: prepopulateEnv(env), + env, dispatch: props.dispatch, currentImage, images, diff --git a/frontend/farmware/weed_detector/__tests__/actions_tests.ts b/frontend/farmware/weed_detector/__tests__/actions_tests.ts index ca9a94166..e321cd544 100644 --- a/frontend/farmware/weed_detector/__tests__/actions_tests.ts +++ b/frontend/farmware/weed_detector/__tests__/actions_tests.ts @@ -23,7 +23,7 @@ jest.mock("../../../util", () => ({ })); import { deletePoints } from "../actions"; -import { scanImage, test } from "../actions"; +import { scanImage, detectPlants } from "../actions"; import axios from "axios"; import { API } from "../../../api"; import { times } from "lodash"; @@ -44,10 +44,10 @@ describe("scanImage()", () => { }); }); -describe("test()", () => { +describe("detectPlants()", () => { it("calls out to the device", () => { // Run function to invoke side effects - const thunk = test(); + const thunk = detectPlants(); thunk(); // Ensure the side effects were the ones we expected. expect(mockDevice.execScript) diff --git a/frontend/farmware/weed_detector/__tests__/config_test.tsx b/frontend/farmware/weed_detector/__tests__/config_test.tsx index a33b83b9e..19495ebfb 100644 --- a/frontend/farmware/weed_detector/__tests__/config_test.tsx +++ b/frontend/farmware/weed_detector/__tests__/config_test.tsx @@ -1,11 +1,16 @@ import * as React from "react"; import { mount, shallow } from "enzyme"; import { WeedDetectorConfig } from "../config"; +import { SettingsMenuProps } from "../interfaces"; describe("", () => { + const fakeProps = (): SettingsMenuProps => ({ + values: {}, + onChange: jest.fn(), + }); + it("renders", () => { - const wrapper = mount(); + const wrapper = mount(); ["Invert Hue Range Selection", "Calibration Object Separation", "Calibration Object Separation along axis", @@ -15,15 +20,39 @@ describe("", () => { .map(string => expect(wrapper.text()).toContain(string)); }); - it("changes value", () => { - const onChange = jest.fn(); - const wrapper = shallow(); + it("changes axis value", () => { + const p = fakeProps(); + const wrapper = shallow(); const input = wrapper.find("FBSelect").first(); input.simulate("change", { label: "", value: 4 }); - expect(onChange).toHaveBeenCalledWith( + expect(p.onChange).toHaveBeenCalledWith( "CAMERA_CALIBRATION_calibration_along_axis", 4); const badChange = () => input.simulate("change", { label: "", value: "4" }); expect(badChange).toThrow("Weed detector got a non-numeric value"); }); + + it("changes hue invert value", () => { + const p = fakeProps(); + const wrapper = shallow(); + const input = wrapper.find("input").first(); + input.simulate("change", { currentTarget: { checked: true } }); + expect(p.onChange).toHaveBeenCalledWith( + "CAMERA_CALIBRATION_invert_hue_selection", 1); + input.simulate("change", { currentTarget: { checked: false } }); + expect(p.onChange).toHaveBeenCalledWith( + "CAMERA_CALIBRATION_invert_hue_selection", 0); + }); + + it("changes number value", () => { + const p = fakeProps(); + const wrapper = shallow(); + const numBox = wrapper.instance().NumberBox({ + conf: "CAMERA_CALIBRATION_blur", label: "label" + }); + const NumBox = shallow(numBox); + NumBox.find("BlurableInput").first().simulate("commit", { + currentTarget: { value: "1.23" } + }); + expect(p.onChange).toHaveBeenCalledWith("CAMERA_CALIBRATION_blur", 1.23); + }); }); diff --git a/frontend/farmware/weed_detector/__tests__/weed_detector_test.tsx b/frontend/farmware/weed_detector/__tests__/weed_detector_test.tsx index 29ae0cdc5..0f138b360 100644 --- a/frontend/farmware/weed_detector/__tests__/weed_detector_test.tsx +++ b/frontend/farmware/weed_detector/__tests__/weed_detector_test.tsx @@ -1,11 +1,15 @@ -const mockDevice = { - execScript: jest.fn(() => Promise.resolve()), - setUserEnv: jest.fn(() => Promise.resolve()), -}; +const mockDevice = { setUserEnv: jest.fn(() => Promise.resolve()) }; jest.mock("../../../device", () => ({ getDevice: () => mockDevice })); jest.mock("../../images/actions", () => ({ selectImage: jest.fn() })); +const mockDeletePoints = jest.fn(); +jest.mock("../actions", () => ({ + deletePoints: mockDeletePoints, + scanImage: jest.fn(), + detectPlants: jest.fn(), +})); + import * as React from "react"; import { mount, shallow } from "enzyme"; import { WeedDetector, namespace } from "../index"; @@ -14,6 +18,9 @@ import { API } from "../../../api"; import { selectImage } from "../../images/actions"; import { clickButton } from "../../../__test_support__/helpers"; import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings"; +import { deletePoints, detectPlants, scanImage } from "../actions"; +import { error } from "../../../toast/toast"; +import { Content, ToolTips } from "../../../constants"; describe("", () => { API.setBaseUrl("http://localhost:3000"); @@ -22,8 +29,8 @@ describe("", () => { timeSettings: fakeTimeSettings(), farmwares: {}, botToMqttStatus: "up", + wDEnv: {}, env: {}, - user_env: {}, dispatch: jest.fn(), currentImage: undefined, images: [], @@ -54,16 +61,38 @@ describe("", () => { const p = fakeProps(); p.dispatch = jest.fn(x => x()); const wrapper = shallow(); + const btn = wrapper.find("button").first(); + expect(btn.props().title).not.toEqual(Content.NO_CAMERA_SELECTED); clickButton(wrapper, 0, "detect weeds"); - expect(mockDevice.execScript).toHaveBeenCalledWith("plant-detection"); + expect(detectPlants).toHaveBeenCalled(); + expect(error).not.toHaveBeenCalled(); + }); + + it("shows detection button as disabled when camera is disabled", () => { + const p = fakeProps(); + p.env = { camera: "NONE" }; + const wrapper = shallow(); + const btn = wrapper.find("button").first(); + expect(btn.props().title).toEqual(Content.NO_CAMERA_SELECTED); + btn.simulate("click"); + expect(error).toHaveBeenCalledWith( + ToolTips.SELECT_A_CAMERA, Content.NO_CAMERA_SELECTED); + expect(detectPlants).not.toHaveBeenCalled(); }); it("executes clear weeds", () => { - const wrapper = - shallow(); + const wrapper = shallow(); expect(wrapper.instance().state.deletionProgress).toBeUndefined(); clickButton(wrapper, 1, "clear weeds"); + expect(deletePoints).toHaveBeenCalledWith( + "weeds", { 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); + expect(wrapper.instance().state.deletionProgress).toEqual("50 %"); + fakeProgress.isDone = true; + mockDeletePoints.mock.calls[0][2](fakeProgress); + expect(wrapper.instance().state.deletionProgress).toEqual(""); }); it("saves changes", () => { @@ -85,16 +114,9 @@ describe("", () => { }); it("calls scanImage", () => { - const p = fakeProps(); - p.dispatch = jest.fn(x => x()); - const wrapper = shallow(); + const wrapper = shallow(); wrapper.find("ImageWorkspace").simulate("processPhoto", 1); - expect(mockDevice.execScript).toHaveBeenCalledWith( - "historical-plant-detection", - [expect.objectContaining({ - kind: "pair", - args: expect.objectContaining({ value: "1" }) - })]); + expect(scanImage).toHaveBeenCalledWith(1); }); it("calls selectImage", () => { diff --git a/frontend/farmware/weed_detector/actions.tsx b/frontend/farmware/weed_detector/actions.tsx index 2d9717e35..0c1b20626 100644 --- a/frontend/farmware/weed_detector/actions.tsx +++ b/frontend/farmware/weed_detector/actions.tsx @@ -69,7 +69,7 @@ export function scanImage(imageId: number) { }; } -export function test() { +export function detectPlants() { return function () { getDevice().execScript("plant-detection").catch(() => { }); }; diff --git a/frontend/farmware/weed_detector/image_workspace.tsx b/frontend/farmware/weed_detector/image_workspace.tsx index 4ce572e87..45b36de37 100644 --- a/frontend/farmware/weed_detector/image_workspace.tsx +++ b/frontend/farmware/weed_detector/image_workspace.tsx @@ -154,10 +154,9 @@ export class ImageWorkspace extends React.Component {
diff --git a/frontend/farmware/weed_detector/index.tsx b/frontend/farmware/weed_detector/index.tsx index ba704875d..23da0c179 100644 --- a/frontend/farmware/weed_detector/index.tsx +++ b/frontend/farmware/weed_detector/index.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { DetectorState } from "./interfaces"; import { Row, Col } from "../../ui/index"; -import { deletePoints, scanImage, test } from "./actions"; +import { deletePoints, scanImage, detectPlants } from "./actions"; import { selectImage } from "../images/actions"; import { Progress } from "../../util"; import { FarmwareProps, Feature } from "../../devices/interfaces"; @@ -11,6 +11,9 @@ import { envGet } from "./remote_env/selectors"; import { MustBeOnline, isBotOnline } from "../../devices/must_be_online"; import { envSave } from "./remote_env/actions"; import { t } from "../../i18next_wrapper"; +import { + cameraBtnProps +} from "../../devices/components/fbos_settings/camera_selection"; export const namespace = (prefix: string) => (key: string): WDENVKey => { const namespacedKey = prefix + key; @@ -52,6 +55,8 @@ export class WeedDetector : envSave(key, value) render() { + const wDEnvGet = (key: WDENVKey) => envGet(key, this.props.wDEnv); + const camDisabled = cameraBtnProps(this.props.env); return
@@ -86,15 +92,15 @@ export class WeedDetector images={this.props.images} onChange={this.change} timeSettings={this.props.timeSettings} - iteration={envGet(this.namespace("iteration"), this.props.env)} - morph={envGet(this.namespace("morph"), this.props.env)} - blur={envGet(this.namespace("blur"), this.props.env)} - H_LO={envGet(this.namespace("H_LO"), this.props.env)} - H_HI={envGet(this.namespace("H_HI"), this.props.env)} - S_LO={envGet(this.namespace("S_LO"), this.props.env)} - S_HI={envGet(this.namespace("S_HI"), this.props.env)} - V_LO={envGet(this.namespace("V_LO"), this.props.env)} - V_HI={envGet(this.namespace("V_HI"), this.props.env)} /> + iteration={wDEnvGet(this.namespace("iteration"))} + morph={wDEnvGet(this.namespace("morph"))} + blur={wDEnvGet(this.namespace("blur"))} + H_LO={wDEnvGet(this.namespace("H_LO"))} + H_HI={wDEnvGet(this.namespace("H_HI"))} + S_LO={wDEnvGet(this.namespace("S_LO"))} + S_HI={wDEnvGet(this.namespace("S_HI"))} + V_LO={wDEnvGet(this.namespace("V_LO"))} + V_HI={wDEnvGet(this.namespace("V_HI"))} /> diff --git a/frontend/logs/state_to_props.ts b/frontend/logs/state_to_props.ts index fb75b543d..a8d46a8cb 100644 --- a/frontend/logs/state_to_props.ts +++ b/frontend/logs/state_to_props.ts @@ -1,21 +1,16 @@ import { Everything } from "../interfaces"; -import { - selectAllLogs, maybeGetTimeSettings, maybeGetDevice -} from "../resources/selectors"; +import { selectAllLogs, maybeGetTimeSettings } from "../resources/selectors"; import { LogsProps } from "./interfaces"; import { sourceFbosConfigValue } from "../devices/components/source_config_value"; -import { - validFbosConfig, determineInstalledOsVersion, - createShouldDisplayFn as shouldDisplayFunc -} from "../util"; +import { validFbosConfig } from "../util"; import { ResourceIndex } from "../resources/interfaces"; import { TaggedLog } from "farmbot"; import { getWebAppConfigValue } from "../config_storage/actions"; import { getFbosConfig } from "../resources/getters"; import { chain } from "lodash"; -import { DevSettings } from "../account/dev/dev_support"; +import { getShouldDisplayFn } from "../farmware/state_to_props"; /** Take the specified number of logs after sorting by time created. */ export function takeSortedLogs( @@ -30,19 +25,12 @@ export function takeSortedLogs( export function mapStateToProps(props: Everything): LogsProps { const { hardware } = props.bot; const fbosConfig = validFbosConfig(getFbosConfig(props.resources.index)); - const sourceFbosConfig = - sourceFbosConfigValue(fbosConfig, hardware.configuration); - const installedOsVersion = determineInstalledOsVersion( - props.bot, maybeGetDevice(props.resources.index)); - const fbosVersionOverride = DevSettings.overriddenFbosVersion(); - const shouldDisplay = shouldDisplayFunc( - installedOsVersion, props.bot.minOsFeatureData, fbosVersionOverride); return { dispatch: props.dispatch, - sourceFbosConfig, + sourceFbosConfig: sourceFbosConfigValue(fbosConfig, hardware.configuration), logs: takeSortedLogs(250, props.resources.index), timeSettings: maybeGetTimeSettings(props.resources.index), getConfigValue: getWebAppConfigValue(() => props), - shouldDisplay, + shouldDisplay: getShouldDisplayFn(props.resources.index, props.bot), }; } diff --git a/frontend/regimens/state_to_props.ts b/frontend/regimens/state_to_props.ts index d78dcec4e..77c305f93 100644 --- a/frontend/regimens/state_to_props.ts +++ b/frontend/regimens/state_to_props.ts @@ -9,21 +9,16 @@ import { maybeGetRegimen, findId, findSequence, - maybeGetDevice, findSequenceById, maybeGetTimeSettings } from "../resources/selectors"; import { TaggedRegimen, TaggedSequence } from "farmbot"; import moment from "moment"; import { ResourceIndex, UUID, VariableNameSet } from "../resources/interfaces"; -import { - randomColor, determineInstalledOsVersion, - createShouldDisplayFn as shouldDisplayFunc, - timeFormatString -} from "../util"; +import { randomColor, timeFormatString } from "../util"; import { resourceUsageList } from "../resources/in_use"; import { groupBy, chain, sortBy } from "lodash"; -import { DevSettings } from "../account/dev/dev_support"; +import { getShouldDisplayFn } from "../farmware/state_to_props"; export function mapStateToProps(props: Everything): Props { const { resources, dispatch, bot } = props; @@ -36,12 +31,6 @@ export function mapStateToProps(props: Everything): Props { const calendar = current ? generateCalendar(current, index, dispatch, timeSettings) : []; - const installedOsVersion = determineInstalledOsVersion( - props.bot, maybeGetDevice(props.resources.index)); - const fbosVersionOverride = DevSettings.overriddenFbosVersion(); - const shouldDisplay = shouldDisplayFunc( - installedOsVersion, props.bot.minOsFeatureData, fbosVersionOverride); - const calledSequences = (): UUID[] => { if (current) { const sequenceIds = current.body.regimen_items.map(x => x.sequence_id); @@ -70,7 +59,7 @@ export function mapStateToProps(props: Everything): Props { bot, calendar, regimenUsageStats: resourceUsageList(props.resources.index.inUse), - shouldDisplay, + shouldDisplay: getShouldDisplayFn(props.resources.index, props.bot), schedulerOpen, }; } diff --git a/frontend/sequences/__tests__/sequence_editor_middle_active_test.tsx b/frontend/sequences/__tests__/sequence_editor_middle_active_test.tsx index ead20059d..b6d641419 100644 --- a/frontend/sequences/__tests__/sequence_editor_middle_active_test.tsx +++ b/frontend/sequences/__tests__/sequence_editor_middle_active_test.tsx @@ -43,8 +43,8 @@ import { import { fakeSequence } from "../../__test_support__/fake_state/resources"; import { destroy, save, edit } from "../../api/crud"; import { - fakeHardwareFlags -} from "../../__test_support__/sequence_hardware_settings"; + fakeHardwareFlags, fakeFarmwareData as fakeFarmwareData +} from "../../__test_support__/fake_sequence_step_data"; import { SpecialStatus } from "farmbot"; import { move, splice } from "../step_tiles"; import { copySequence, editCurrentSequence } from "../actions"; @@ -66,12 +66,7 @@ describe("", () => { resources: buildResourceIndex(FAKE_RESOURCES).index, syncStatus: "synced", hardwareFlags: fakeHardwareFlags(), - farmwareInfo: { - farmwareNames: [], - firstPartyFarmwareNames: [], - showFirstPartyFarmware: false, - farmwareConfigs: {}, - }, + farmwareData: fakeFarmwareData(), shouldDisplay: jest.fn(), getWebAppConfigValue: jest.fn(), menuOpen: false, diff --git a/frontend/sequences/__tests__/sequence_editor_middle_test.tsx b/frontend/sequences/__tests__/sequence_editor_middle_test.tsx index 3db585711..ce072d771 100644 --- a/frontend/sequences/__tests__/sequence_editor_middle_test.tsx +++ b/frontend/sequences/__tests__/sequence_editor_middle_test.tsx @@ -7,8 +7,8 @@ import { } from "../../__test_support__/resource_index_builder"; import { fakeSequence } from "../../__test_support__/fake_state/resources"; import { - fakeHardwareFlags -} from "../../__test_support__/sequence_hardware_settings"; + fakeHardwareFlags, fakeFarmwareData +} from "../../__test_support__/fake_sequence_step_data"; describe("", () => { function fakeProps(): SequenceEditorMiddleProps { @@ -18,12 +18,7 @@ describe("", () => { resources: buildResourceIndex(FAKE_RESOURCES).index, syncStatus: "synced", hardwareFlags: fakeHardwareFlags(), - farmwareInfo: { - farmwareNames: [], - firstPartyFarmwareNames: [], - showFirstPartyFarmware: false, - farmwareConfigs: {}, - }, + farmwareData: fakeFarmwareData(), shouldDisplay: jest.fn(), getWebAppConfigValue: jest.fn(), menuOpen: false, diff --git a/frontend/sequences/__tests__/sequences_test.tsx b/frontend/sequences/__tests__/sequences_test.tsx index a116c66fe..d1b916b98 100644 --- a/frontend/sequences/__tests__/sequences_test.tsx +++ b/frontend/sequences/__tests__/sequences_test.tsx @@ -15,8 +15,8 @@ import { import { fakeSequence } from "../../__test_support__/fake_state/resources"; import { ToolTips, Actions } from "../../constants"; import { - fakeHardwareFlags -} from "../../__test_support__/sequence_hardware_settings"; + fakeHardwareFlags, fakeFarmwareData +} from "../../__test_support__/fake_sequence_step_data"; import { push } from "../../history"; import { mapStateToFolderProps } from "../../folders/map_state_to_props"; import { fakeState } from "../../__test_support__/fake_state"; @@ -29,12 +29,7 @@ describe("", () => { resources: buildResourceIndex(FAKE_RESOURCES).index, syncStatus: "synced", hardwareFlags: fakeHardwareFlags(), - farmwareInfo: { - farmwareNames: [], - firstPartyFarmwareNames: [], - showFirstPartyFarmware: false, - farmwareConfigs: {}, - }, + farmwareData: fakeFarmwareData(), shouldDisplay: jest.fn(), getWebAppConfigValue: jest.fn(), menuOpen: false, diff --git a/frontend/sequences/__tests__/set_active_sequence_by_name_test.ts b/frontend/sequences/__tests__/set_active_sequence_by_name_test.ts index ef717a56d..8c14df61c 100644 --- a/frontend/sequences/__tests__/set_active_sequence_by_name_test.ts +++ b/frontend/sequences/__tests__/set_active_sequence_by_name_test.ts @@ -4,31 +4,25 @@ const mockData = { fakeSequences: [fakeSequence()] }; -jest.mock("../../util/urls", () => { - return { - urlFriendly: jest.fn(x => x), - lastUrlChunk: jest.fn(() => mockData.lastUrlChunk) - }; -}); +jest.mock("../../util/urls", () => ({ + urlFriendly: jest.fn(x => x), + lastUrlChunk: jest.fn(() => mockData.lastUrlChunk) +})); jest.mock("../actions", () => ({ selectSequence: jest.fn() })); -jest.mock("../../resources/selectors", () => { - return { - selectAllSequences: jest.fn(() => { - return mockData.fakeSequences || []; - }) - }; -}); +jest.mock("../../resources/selectors", () => ({ + selectAllSequences: jest.fn(() => mockData.fakeSequences || []), +})); -jest.mock("../../redux/store", () => { - return { - store: { - dispatch: jest.fn(), - getState: jest.fn(() => ({ resources: { index: {} } })) - } - }; -}); +jest.mock("../../redux/store", () => ({ + store: { + dispatch: jest.fn(), + getState: jest.fn(() => ({ resources: { index: {} } })) + } +})); + +jest.mock("../../account/dev/dev_support", () => ({})); import { setActiveSequenceByName } from "../set_active_sequence_by_name"; import { selectSequence } from "../actions"; diff --git a/frontend/sequences/__tests__/state_to_props_test.ts b/frontend/sequences/__tests__/state_to_props_test.ts index 5cf1d94cd..f5d06d388 100644 --- a/frontend/sequences/__tests__/state_to_props_test.ts +++ b/frontend/sequences/__tests__/state_to_props_test.ts @@ -3,7 +3,7 @@ import { fakeState } from "../../__test_support__/fake_state"; import { Feature } from "../../devices/interfaces"; import { fakeFarmwareManifestV1 } from "../../__test_support__/fake_farmwares"; import { - fakeSequence, fakeWebAppConfig + fakeSequence, fakeWebAppConfig, fakeFarmwareEnv } from "../../__test_support__/fake_state/resources"; import { buildResourceIndex @@ -67,12 +67,24 @@ describe("mapStateToProps()", () => { "My Fake Farmware": fakeFarmwareManifestV1() }; const props = mapStateToProps(state); - expect(props.farmwareInfo.farmwareNames).toEqual(["My Fake Farmware"]); - expect(props.farmwareInfo.showFirstPartyFarmware).toEqual(true); - expect(props.farmwareInfo.farmwareConfigs).toEqual({ + expect(props.farmwareData.farmwareNames).toEqual(["My Fake Farmware"]); + expect(props.farmwareData.showFirstPartyFarmware).toEqual(true); + expect(props.farmwareData.farmwareConfigs).toEqual({ "My Fake Farmware": [{ name: "config_1", label: "Config 1", value: "4" }] }); }); + + it("returns api props", () => { + const state = fakeState(); + const fakeEnv = fakeFarmwareEnv(); + fakeEnv.body.key = "camera"; + fakeEnv.body.value = "NONE"; + state.resources = buildResourceIndex([fakeEnv]); + state.bot.minOsFeatureData = { api_farmware_env: "8.0.0" }; + state.bot.hardware.informational_settings.controller_version = "8.0.0"; + const props = mapStateToProps(state); + expect(props.farmwareData.cameraDisabled).toEqual(true); + }); }); diff --git a/frontend/sequences/all_steps.tsx b/frontend/sequences/all_steps.tsx index 68758e2a8..bfd50b5e1 100644 --- a/frontend/sequences/all_steps.tsx +++ b/frontend/sequences/all_steps.tsx @@ -6,7 +6,7 @@ import { StepDragger } from "../draggable/step_dragger"; import { renderCeleryNode } from "./step_tiles/index"; import { ResourceIndex } from "../resources/interfaces"; import { getStepTag } from "../resources/sequence_tagging"; -import { HardwareFlags, FarmwareInfo } from "./interfaces"; +import { HardwareFlags, FarmwareData } from "./interfaces"; import { ShouldDisplay } from "../devices/interfaces"; import { AddCommandButton } from "./sequence_editor_middle_active"; import { ErrorBoundary } from "../error_boundary"; @@ -18,7 +18,7 @@ export interface AllStepsProps { dispatch: Function; resources: ResourceIndex; hardwareFlags?: HardwareFlags; - farmwareInfo?: FarmwareInfo; + farmwareData?: FarmwareData; shouldDisplay?: ShouldDisplay; confirmStepDeletion: boolean; showPins?: boolean; @@ -43,7 +43,7 @@ export class AllSteps extends React.Component { currentSequence: sequence, resources: this.props.resources, hardwareFlags: this.props.hardwareFlags, - farmwareInfo: this.props.farmwareInfo, + farmwareData: this.props.farmwareData, shouldDisplay: this.props.shouldDisplay, confirmStepDeletion: this.props.confirmStepDeletion, showPins: this.props.showPins, diff --git a/frontend/sequences/interfaces.ts b/frontend/sequences/interfaces.ts index 3ec45506b..82be8ae71 100644 --- a/frontend/sequences/interfaces.ts +++ b/frontend/sequences/interfaces.ts @@ -44,7 +44,7 @@ export interface Props { resources: ResourceIndex; syncStatus: SyncStatus; hardwareFlags: HardwareFlags; - farmwareInfo: FarmwareInfo; + farmwareData: FarmwareData; shouldDisplay: ShouldDisplay; getWebAppConfigValue: GetWebAppConfigValue; menuOpen: boolean; @@ -58,7 +58,7 @@ export interface SequenceEditorMiddleProps { resources: ResourceIndex; syncStatus: SyncStatus; hardwareFlags: HardwareFlags; - farmwareInfo: FarmwareInfo; + farmwareData: FarmwareData; shouldDisplay: ShouldDisplay; getWebAppConfigValue: GetWebAppConfigValue; menuOpen: boolean; @@ -189,11 +189,12 @@ export type dispatcher = (a: Function | { type: string }) => DataXferObj; export type FarmwareConfigs = { [x: string]: FarmwareConfig[] }; -export interface FarmwareInfo { +export interface FarmwareData { farmwareNames: string[]; firstPartyFarmwareNames: string[]; showFirstPartyFarmware: boolean; farmwareConfigs: FarmwareConfigs; + cameraDisabled: boolean; } export interface StepParams { @@ -203,7 +204,7 @@ export interface StepParams { index: number; resources: ResourceIndex; hardwareFlags?: HardwareFlags; - farmwareInfo?: FarmwareInfo; + farmwareData?: FarmwareData; shouldDisplay?: ShouldDisplay; confirmStepDeletion: boolean; showPins?: boolean; diff --git a/frontend/sequences/sequence_editor_middle.tsx b/frontend/sequences/sequence_editor_middle.tsx index 29507a9c1..9ca320bfc 100644 --- a/frontend/sequences/sequence_editor_middle.tsx +++ b/frontend/sequences/sequence_editor_middle.tsx @@ -23,7 +23,7 @@ export class SequenceEditorMiddle resources={this.props.resources} syncStatus={this.props.syncStatus} hardwareFlags={this.props.hardwareFlags} - farmwareInfo={this.props.farmwareInfo} + farmwareData={this.props.farmwareData} shouldDisplay={this.props.shouldDisplay} getWebAppConfigValue={this.props.getWebAppConfigValue} menuOpen={this.props.menuOpen} />} diff --git a/frontend/sequences/sequence_editor_middle_active.tsx b/frontend/sequences/sequence_editor_middle_active.tsx index 6e2073c32..8a8c18886 100644 --- a/frontend/sequences/sequence_editor_middle_active.tsx +++ b/frontend/sequences/sequence_editor_middle_active.tsx @@ -249,7 +249,7 @@ export class SequenceEditorMiddleActive extends dispatch: this.props.dispatch, resources: this.props.resources, hardwareFlags: this.props.hardwareFlags, - farmwareInfo: this.props.farmwareInfo, + farmwareData: this.props.farmwareData, shouldDisplay: this.props.shouldDisplay, confirmStepDeletion: !!getConfig(BooleanSetting.confirm_step_deletion), showPins: !!getConfig(BooleanSetting.show_pins), diff --git a/frontend/sequences/sequences.tsx b/frontend/sequences/sequences.tsx index 594cc43a8..05cf74823 100644 --- a/frontend/sequences/sequences.tsx +++ b/frontend/sequences/sequences.tsx @@ -58,7 +58,7 @@ export class RawSequences extends React.Component { sequence={this.props.sequence} resources={this.props.resources} hardwareFlags={this.props.hardwareFlags} - farmwareInfo={this.props.farmwareInfo} + farmwareData={this.props.farmwareData} shouldDisplay={this.props.shouldDisplay} getWebAppConfigValue={this.props.getWebAppConfigValue} menuOpen={this.props.menuOpen} /> diff --git a/frontend/sequences/state_to_props.ts b/frontend/sequences/state_to_props.ts index 3f14e9415..7a4902cf3 100644 --- a/frontend/sequences/state_to_props.ts +++ b/frontend/sequences/state_to_props.ts @@ -1,22 +1,20 @@ import { Everything } from "../interfaces"; import { Props, HardwareFlags, FarmwareConfigs } from "./interfaces"; -import { - selectAllSequences, findSequence, maybeGetDevice -} from "../resources/selectors"; +import { selectAllSequences, findSequence } from "../resources/selectors"; import { getStepTag } from "../resources/sequence_tagging"; import { enabledAxisMap } from "../devices/components/axis_tracking_status"; -import { - createShouldDisplayFn as shouldDisplayFunc, - determineInstalledOsVersion, validFwConfig -} from "../util"; +import { validFwConfig } from "../util"; import { BooleanSetting } from "../session_keys"; import { getWebAppConfigValue } from "../config_storage/actions"; import { getFirmwareConfig } from "../resources/getters"; import { Farmwares } from "../farmware/interfaces"; import { manifestInfo } from "../farmware/generate_manifest_info"; -import { DevSettings } from "../account/dev/dev_support"; import { calculateAxialLengths } from "../controls/move/direction_axes_props"; import { mapStateToFolderProps } from "../folders/map_state_to_props"; +import { getEnv, getShouldDisplayFn } from "../farmware/state_to_props"; +import { + cameraDisabled +} from "../devices/components/fbos_settings/camera_selection"; export function mapStateToProps(props: Everything): Props { const uuid = props.resources.consumers.sequences.current; @@ -62,11 +60,8 @@ export function mapStateToProps(props: Everything): Props { const farmwareConfigs: FarmwareConfigs = {}; Object.values(farmwares).map(fw => farmwareConfigs[fw.name] = fw.config); - const installedOsVersion = determineInstalledOsVersion( - props.bot, maybeGetDevice(props.resources.index)); - const fbosVersionOverride = DevSettings.overriddenFbosVersion(); - const shouldDisplay = shouldDisplayFunc( - installedOsVersion, props.bot.minOsFeatureData, fbosVersionOverride); + const shouldDisplay = getShouldDisplayFn(props.resources.index, props.bot); + const env = getEnv(props.resources.index, shouldDisplay, props.bot); return { dispatch: props.dispatch, @@ -79,11 +74,12 @@ export function mapStateToProps(props: Everything): Props { .informational_settings .sync_status || "unknown"), hardwareFlags: hardwareFlags(), - farmwareInfo: { + farmwareData: { farmwareNames, firstPartyFarmwareNames, showFirstPartyFarmware, farmwareConfigs, + cameraDisabled: cameraDisabled(env), }, shouldDisplay, getWebAppConfigValue: getConfig, diff --git a/frontend/sequences/step_tiles/__tests__/tile_calibrate_test.tsx b/frontend/sequences/step_tiles/__tests__/tile_calibrate_test.tsx index 213adb798..5a87fcece 100644 --- a/frontend/sequences/step_tiles/__tests__/tile_calibrate_test.tsx +++ b/frontend/sequences/step_tiles/__tests__/tile_calibrate_test.tsx @@ -6,7 +6,7 @@ import { emptyState } from "../../../resources/reducer"; import { HardwareFlags } from "../../interfaces"; import { fakeHardwareFlags -} from "../../../__test_support__/sequence_hardware_settings"; +} from "../../../__test_support__/fake_sequence_step_data"; describe("", () => { const fakeProps = (): CalibrateParams => ({ diff --git a/frontend/sequences/step_tiles/__tests__/tile_execute_script_test.tsx b/frontend/sequences/step_tiles/__tests__/tile_execute_script_test.tsx index d6ccda3f9..e94b49fc7 100644 --- a/frontend/sequences/step_tiles/__tests__/tile_execute_script_test.tsx +++ b/frontend/sequences/step_tiles/__tests__/tile_execute_script_test.tsx @@ -4,8 +4,11 @@ import { mount, shallow } from "enzyme"; import { fakeSequence } from "../../../__test_support__/fake_state/resources"; import { ExecuteScript } from "farmbot/dist"; import { StepParams } from "../../interfaces"; -import { Actions } from "../../../constants"; +import { Actions, Content } from "../../../constants"; import { emptyState } from "../../../resources/reducer"; +import { + fakeFarmwareData +} from "../../../__test_support__/fake_sequence_step_data"; describe("", () => { const fakeProps = (): StepParams => { @@ -15,18 +18,17 @@ describe("", () => { label: "farmware-to-execute" } }; + const farmwareData = fakeFarmwareData(); + farmwareData.farmwareNames = ["one", "two", "three"]; + farmwareData.firstPartyFarmwareNames = ["one"]; + farmwareData.farmwareConfigs = { "farmware-to-execute": [] }; return { currentSequence: fakeSequence(), currentStep, dispatch: jest.fn(), index: 0, resources: emptyState().index, - farmwareInfo: { - farmwareNames: ["one", "two", "three"], - firstPartyFarmwareNames: ["one"], - showFirstPartyFarmware: false, - farmwareConfigs: { "farmware-to-execute": [] }, - }, + farmwareData, confirmStepDeletion: false, }; }; @@ -61,7 +63,7 @@ describe("", () => { it("shows 1st party in list", () => { const p = fakeProps(); - p.farmwareInfo && (p.farmwareInfo.showFirstPartyFarmware = true); + p.farmwareData && (p.farmwareData.showFirstPartyFarmware = true); const wrapper = shallow(); expect(wrapper.find("FBSelect").props().list).toEqual([ { label: "one", value: "one" }, @@ -81,7 +83,7 @@ describe("", () => { it("shows special 1st-party Farmware name", () => { const p = fakeProps(); (p.currentStep as ExecuteScript).args.label = "plant-detection"; - p.farmwareInfo && p.farmwareInfo.farmwareNames.push("plant-detection"); + p.farmwareData && p.farmwareData.farmwareNames.push("plant-detection"); const wrapper = mount(); expect(wrapper.find("label").length).toEqual(1); expect(wrapper.text()).toContain("Weed Detector"); @@ -89,7 +91,7 @@ describe("", () => { it("renders manual input", () => { const p = fakeProps(); - p.farmwareInfo = undefined; + p.farmwareData = undefined; const wrapper = mount(); expect(wrapper.find("button").text()).toEqual("Manual Input"); expect(wrapper.find("label").at(1).text()).toEqual("Manual input"); @@ -131,4 +133,12 @@ describe("", () => { type: Actions.OVERWRITE_RESOURCE }); }); + + it("displays warning when camera is disabled", () => { + const p = fakeProps(); + (p.currentStep as ExecuteScript).args.label = "plant-detection"; + p.farmwareData && (p.farmwareData.cameraDisabled = true); + const wrapper = mount(); + expect(wrapper.text()).toContain(Content.NO_CAMERA_SELECTED); + }); }); diff --git a/frontend/sequences/step_tiles/__tests__/tile_find_home_test.tsx b/frontend/sequences/step_tiles/__tests__/tile_find_home_test.tsx index 163d146a2..75dd58696 100644 --- a/frontend/sequences/step_tiles/__tests__/tile_find_home_test.tsx +++ b/frontend/sequences/step_tiles/__tests__/tile_find_home_test.tsx @@ -4,7 +4,7 @@ import { mount } from "enzyme"; import { fakeSequence } from "../../../__test_support__/fake_state/resources"; import { fakeHardwareFlags -} from "../../../__test_support__/sequence_hardware_settings"; +} from "../../../__test_support__/fake_sequence_step_data"; import { HardwareFlags } from "../../interfaces"; import { emptyState } from "../../../resources/reducer"; diff --git a/frontend/sequences/step_tiles/__tests__/tile_move_absolute_conflict_check_test.tsx b/frontend/sequences/step_tiles/__tests__/tile_move_absolute_conflict_check_test.tsx index 676f7422b..6bbf3cea9 100644 --- a/frontend/sequences/step_tiles/__tests__/tile_move_absolute_conflict_check_test.tsx +++ b/frontend/sequences/step_tiles/__tests__/tile_move_absolute_conflict_check_test.tsx @@ -3,7 +3,7 @@ import { mount } from "enzyme"; import { MoveAbsoluteWarningProps } from "../../interfaces"; import { fakeHardwareFlags -} from "../../../__test_support__/sequence_hardware_settings"; +} from "../../../__test_support__/fake_sequence_step_data"; import { MoveAbsoluteWarning } from "../tile_move_absolute_conflict_check"; describe("", () => { diff --git a/frontend/sequences/step_tiles/__tests__/tile_move_absolute_test.tsx b/frontend/sequences/step_tiles/__tests__/tile_move_absolute_test.tsx index 28f3480d3..29af5199a 100644 --- a/frontend/sequences/step_tiles/__tests__/tile_move_absolute_test.tsx +++ b/frontend/sequences/step_tiles/__tests__/tile_move_absolute_test.tsx @@ -13,7 +13,7 @@ import { } from "farmbot"; import { fakeHardwareFlags -} from "../../../__test_support__/sequence_hardware_settings"; +} from "../../../__test_support__/fake_sequence_step_data"; import { emptyState } from "../../../resources/reducer"; import { inputEvent } from "../../../__test_support__/fake_input_event"; import { StepParams } from "../../interfaces"; diff --git a/frontend/sequences/step_tiles/__tests__/tile_take_photo_test.tsx b/frontend/sequences/step_tiles/__tests__/tile_take_photo_test.tsx index 29c1a3451..8f6ca531d 100644 --- a/frontend/sequences/step_tiles/__tests__/tile_take_photo_test.tsx +++ b/frontend/sequences/step_tiles/__tests__/tile_take_photo_test.tsx @@ -5,6 +5,10 @@ import { fakeSequence } from "../../../__test_support__/fake_state/resources"; import { TakePhoto } from "farmbot/dist"; import { StepParams } from "../../interfaces"; import { emptyState } from "../../../resources/reducer"; +import { + fakeFarmwareData +} from "../../../__test_support__/fake_sequence_step_data"; +import { Content } from "../../../constants"; describe("", () => { const currentStep: TakePhoto = { @@ -19,6 +23,7 @@ describe("", () => { index: 0, resources: emptyState().index, confirmStepDeletion: false, + farmwareData: fakeFarmwareData(), }); it("renders step", () => { @@ -34,4 +39,11 @@ describe("", () => { expect(inputs.first().props().placeholder).toEqual("Take a Photo"); expect(wrapper.text()).toContain("farmware page"); }); + + it("displays warning when camera is disabled", () => { + const p = fakeProps(); + p.farmwareData && (p.farmwareData.cameraDisabled = true); + const wrapper = mount(); + expect(wrapper.text()).toContain(Content.NO_CAMERA_SELECTED); + }); }); diff --git a/frontend/sequences/step_tiles/tile_execute_script.tsx b/frontend/sequences/step_tiles/tile_execute_script.tsx index 96978b72c..5d764b23f 100644 --- a/frontend/sequences/step_tiles/tile_execute_script.tsx +++ b/frontend/sequences/step_tiles/tile_execute_script.tsx @@ -1,8 +1,10 @@ import * as React from "react"; import { StepParams } from "../interfaces"; -import { ToolTips } from "../../constants"; +import { ToolTips, Content } from "../../constants"; import { StepInputBox } from "../inputs/step_input_box"; -import { StepWrapper, StepHeader, StepContent } from "../step_ui/index"; +import { + StepWrapper, StepHeader, StepContent, StepWarning +} from "../step_ui/index"; import { Row, Col, FBSelect, DropDownItem } from "../../ui/index"; import { editStep } from "../../api/crud"; import { ExecuteScript, FarmwareConfig } from "farmbot"; @@ -10,14 +12,14 @@ import { FarmwareInputs, farmwareList } from "./tile_execute_script_support"; import { t } from "../../i18next_wrapper"; export function TileExecuteScript(props: StepParams) { - const { dispatch, currentStep, index, currentSequence, farmwareInfo } = props; + const { dispatch, currentStep, index, currentSequence, farmwareData } = props; if (currentStep.kind === "execute_script") { const farmwareName = currentStep.args.label; /** Selected Farmware is installed on connected bot. */ const isInstalled = (name: string): boolean => { - return !!(farmwareInfo && farmwareInfo.farmwareNames.includes(name)); + return !!(farmwareData && farmwareData.farmwareNames.includes(name)); }; const selectedFarmwareDDI = (name: string): DropDownItem => { @@ -52,8 +54,8 @@ export function TileExecuteScript(props: StepParams) { /** Configs (inputs) from Farmware manifest for . */ const currentFarmwareConfigDefaults = (fwName: string): FarmwareConfig[] => { - return farmwareInfo && farmwareInfo.farmwareConfigs[fwName] - ? farmwareInfo.farmwareConfigs[fwName] + return farmwareData && farmwareData.farmwareConfigs[fwName] + ? farmwareData.farmwareConfigs[fwName] : []; }; @@ -66,14 +68,20 @@ export function TileExecuteScript(props: StepParams) { currentStep={currentStep} dispatch={dispatch} index={index} - confirmStepDeletion={props.confirmStepDeletion} /> + confirmStepDeletion={props.confirmStepDeletion}> + {props.farmwareData && props.farmwareData.cameraDisabled && + (farmwareName === "plant-detection") && + } +
{ - if (farmwareInfo) { + (farmwareData: FarmwareData | undefined): DropDownItem[] => { + if (farmwareData) { const { farmwareNames, showFirstPartyFarmware, firstPartyFarmwareNames - } = farmwareInfo; + } = farmwareData; return farmwareNames .filter(x => (firstPartyFarmwareNames && !showFirstPartyFarmware) ? !firstPartyFarmwareNames.includes(x) : x) diff --git a/frontend/sequences/step_tiles/tile_take_photo.tsx b/frontend/sequences/step_tiles/tile_take_photo.tsx index 1fd674d65..26cdd8101 100644 --- a/frontend/sequences/step_tiles/tile_take_photo.tsx +++ b/frontend/sequences/step_tiles/tile_take_photo.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { StepParams } from "../interfaces"; -import { ToolTips } from "../../constants"; -import { StepWrapper, StepHeader, StepContent } from "../step_ui"; +import { ToolTips, Content } from "../../constants"; +import { StepWrapper, StepHeader, StepContent, StepWarning } from "../step_ui"; import { Col, Row } from "../../ui/index"; import { Link } from "../../link"; import { t } from "../../i18next_wrapper"; @@ -17,7 +17,12 @@ export function TileTakePhoto(props: StepParams) { currentStep={currentStep} dispatch={dispatch} index={index} - confirmStepDeletion={props.confirmStepDeletion} /> + confirmStepDeletion={props.confirmStepDeletion}> + {props.farmwareData && props.farmwareData.cameraDisabled && + } + diff --git a/frontend/sequences/step_ui/step_warning.tsx b/frontend/sequences/step_ui/step_warning.tsx index 0cc276740..a93a2f430 100644 --- a/frontend/sequences/step_ui/step_warning.tsx +++ b/frontend/sequences/step_ui/step_warning.tsx @@ -6,6 +6,7 @@ import { t } from "../../i18next_wrapper"; interface StepWarningProps { warning: string; conflicts?: Record; + titleBase?: string; } const TITLE_BASE = t("Hardware setting conflict"); @@ -20,11 +21,10 @@ export const conflictsString = (conflicts: Record) => { }; export function StepWarning(props: StepWarningProps) { - const { conflicts, warning } = props; + const { conflicts, warning, titleBase } = props; const warningTitle = () => { - return conflicts - ? TITLE_BASE + ": " + conflictsString(conflicts) - : TITLE_BASE; + return (titleBase || TITLE_BASE) + + (conflicts ? ": " + conflictsString(conflicts) : ""); }; return
- + directionAxisProps={directionAxesProps[upDown]} /> - + directionAxisProps={directionAxesProps.z} />
- + directionAxisProps={directionAxesProps[rightLeft]} /> - + directionAxisProps={directionAxesProps[upDown]} /> - + directionAxisProps={directionAxesProps[rightLeft]} /> - + directionAxisProps={directionAxesProps.z} />