From a04ec59ba55ad8f1c3f6b745265735e40c3caa6e Mon Sep 17 00:00:00 2001 From: gabrielburnworth Date: Tue, 18 Feb 2020 11:21:09 -0800 Subject: [PATCH] model and version updates part 2 --- frontend/__tests__/attach_app_to_dom_test.ts | 13 +- frontend/__tests__/external_urls_test.ts | 10 +- frontend/constants.ts | 107 +++++++++++- frontend/controls/controls.tsx | 2 + .../peripherals/__tests__/index_test.tsx | 18 +- frontend/controls/peripherals/index.tsx | 38 +++- .../controls/sensors/__tests__/index_test.tsx | 11 +- .../sensors/__tests__/sensor_list_test.tsx | 7 + frontend/controls/sensors/index.tsx | 18 +- frontend/css/_blueprint_overrides.scss | 1 + frontend/css/global.scss | 20 +++ frontend/demo/demo_iframe.tsx | 4 +- .../boolean_mcu_input_group_test.tsx | 3 +- .../__tests__/farmbot_os_settings_test.tsx | 20 ++- .../firmware_hardware_support_test.ts | 18 +- .../__tests__/maybe_highlight_test.tsx | 81 +++++++++ .../components/boolean_mcu_input_group.tsx | 76 ++++---- .../components/farmbot_os_settings.tsx | 59 ++++--- .../fbos_settings/auto_sync_row.tsx | 41 +++-- .../fbos_settings/auto_update_row.tsx | 39 +++-- .../components/fbos_settings/board_type.tsx | 52 +++--- .../fbos_settings/boot_sequence_selector.tsx | 28 +-- .../fbos_settings/camera_selection.tsx | 35 ++-- .../fbos_settings/factory_reset_row.tsx | 121 +++++++------ .../fbos_settings/farmbot_os_row.tsx | 88 +++++----- .../fbos_settings/fbos_button_row.tsx | 44 ++--- .../fbos_settings/power_and_reset.tsx | 35 ++-- .../components/firmware_hardware_support.ts | 8 +- .../devices/components/hardware_settings.tsx | 4 + .../__tests__/calibration_row_test.tsx | 3 +- .../__tests__/header_test.tsx | 7 +- .../hardware_settings/calibration_row.tsx | 41 +++-- .../hardware_settings/danger_zone.tsx | 50 +++--- .../components/hardware_settings/encoders.tsx | 33 ++-- .../components/hardware_settings/endstops.tsx | 19 +- .../hardware_settings/error_handling.tsx | 19 +- .../components/hardware_settings/header.tsx | 12 +- .../homing_and_calibration.tsx | 28 +-- .../components/hardware_settings/motors.tsx | 35 ++-- .../hardware_settings/pin_bindings.tsx | 11 +- .../hardware_settings/pin_guard.tsx | 12 +- .../hardware_settings/single_setting_row.tsx | 33 ++-- frontend/devices/components/interfaces.ts | 7 +- .../devices/components/maybe_highlight.tsx | 162 ++++++++++++++++++ .../components/numeric_mcu_input_group.tsx | 78 +++++---- frontend/devices/interfaces.ts | 8 +- .../pin_bindings/list_and_label_support.tsx | 18 +- frontend/external_urls.ts | 10 +- frontend/farm_designer/map/garden_map.tsx | 1 + frontend/farm_designer/map/interfaces.ts | 3 + .../farmbot/__tests__/bot_figure_test.tsx | 18 +- .../map/layers/farmbot/bot_figure.tsx | 2 +- .../plants/__tests__/garden_plant_test.tsx | 21 +++ .../plants/__tests__/plant_layer_test.tsx | 16 +- .../map/layers/plants/garden_plant.tsx | 12 +- .../map/layers/plants/plant_layer.tsx | 10 +- .../__tests__/tool_slot_point_test.tsx | 4 +- .../map/layers/tool_slots/tool_slot_point.tsx | 2 +- .../__tests__/group_detail_active_test.tsx | 1 + .../point_groups/group_detail.tsx | 3 + .../point_groups/group_detail_active.tsx | 4 +- .../point_groups/group_order_visual.tsx | 2 +- .../point_groups/point_group_item.tsx | 1 + .../tools/__tests__/add_tool_slot_test.tsx | 24 ++- .../tools/__tests__/add_tool_test.tsx | 30 +++- .../tools/__tests__/edit_tool_slot_test.tsx | 3 +- .../tools/__tests__/index_test.tsx | 11 +- .../tool_slot_edit_components_test.tsx | 8 + frontend/farm_designer/tools/add_tool.tsx | 75 ++++---- .../farm_designer/tools/add_tool_slot.tsx | 20 ++- frontend/farm_designer/tools/edit_tool.tsx | 2 +- .../farm_designer/tools/edit_tool_slot.tsx | 9 +- frontend/farm_designer/tools/index.tsx | 55 ++++-- .../tools/tool_slot_edit_components.tsx | 23 ++- frontend/folders/actions.ts | 8 +- frontend/front_page/laptop_splash.tsx | 2 +- frontend/help/__tests__/tour_test.tsx | 2 +- frontend/help/__tests__/tours_test.ts | 56 +++++- frontend/help/tour.tsx | 21 ++- frontend/help/tours.ts | 80 +++++++-- frontend/messages/__tests__/alerts_test.tsx | 4 +- frontend/messages/alerts.tsx | 1 + frontend/redux/upgrade_reminder.ts | 3 +- .../mark_as/__tests__/unpack_step_test.ts | 6 +- .../step_tiles/mark_as/unpack_step.ts | 27 +-- public/app-resources/languages/_helper.js | 3 +- .../languages/translation_metrics.md | 22 +-- 87 files changed, 1481 insertions(+), 701 deletions(-) create mode 100644 frontend/devices/components/__tests__/maybe_highlight_test.tsx create mode 100644 frontend/devices/components/maybe_highlight.tsx diff --git a/frontend/__tests__/attach_app_to_dom_test.ts b/frontend/__tests__/attach_app_to_dom_test.ts index 1a2d69daa..2c18907d9 100644 --- a/frontend/__tests__/attach_app_to_dom_test.ts +++ b/frontend/__tests__/attach_app_to_dom_test.ts @@ -1,10 +1,9 @@ -jest.mock("../util", () => { - return { - attachToRoot: jest.fn(), - // Incidental mock. Can be removed if errors go away. - trim: jest.fn(x => x) - }; -}); +jest.mock("../util", () => ({ + attachToRoot: jest.fn(), + // Incidental mock. Can be removed if errors go away. + trim: jest.fn(x => x), + urlFriendly: jest.fn(), +})); jest.mock("../redux/store", () => { return { store: { dispatch: jest.fn() } }; diff --git a/frontend/__tests__/external_urls_test.ts b/frontend/__tests__/external_urls_test.ts index 156e1932b..99f23cea5 100644 --- a/frontend/__tests__/external_urls_test.ts +++ b/frontend/__tests__/external_urls_test.ts @@ -6,9 +6,9 @@ import { ExternalUrl } from "../external_urls"; describe("ExternalUrl", () => { it("returns urls", () => { expect(ExternalUrl.featureMinVersions) - .toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/FEATURE_MIN_VERSIONS.json"); + .toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/staging/FEATURE_MIN_VERSIONS.json"); expect(ExternalUrl.osReleaseNotes) - .toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/RELEASE_NOTES.md"); + .toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/staging/RELEASE_NOTES.md"); expect(ExternalUrl.latestRelease) .toEqual("https://api.github.com/repos/FarmBot/farmbot_os/releases/latest"); expect(ExternalUrl.webAppRepo) @@ -18,16 +18,16 @@ describe("ExternalUrl", () => { expect(ExternalUrl.softwareDocs) .toEqual("https://software.farm.bot/docs"); expect(ExternalUrl.softwareForum) - .toEqual("http://forum.farmbot.org/c/software"); + .toEqual("https://forum.farmbot.org/c/software"); expect(ExternalUrl.OpenFarm.cropApi) .toEqual("https://openfarm.cc/api/v1/crops/"); expect(ExternalUrl.OpenFarm.cropBrowse) .toEqual("https://openfarm.cc/crops/"); expect(ExternalUrl.OpenFarm.newCrop) .toEqual("https://openfarm.cc/en/crops/new"); - expect(ExternalUrl.Videos.desktop) + expect(ExternalUrl.Video.desktop) .toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Farm_Designer_Loop.mp4?9552037556691879018"); - expect(ExternalUrl.Videos.mobile) + expect(ExternalUrl.Video.mobile) .toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Controls.png?9668345515035078097"); }); }); diff --git a/frontend/constants.ts b/frontend/constants.ts index c76339cb7..7adb0a80f 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -702,9 +702,9 @@ export namespace Content { trim(`FarmBot sent a malformed message. You may need to upgrade FarmBot OS. Please upgrade FarmBot OS and log back in.`); - export const OLD_FBOS_REC_UPGRADE = trim(`Your version of FarmBot OS is - outdated and will soon no longer be supported. Please update your device as - soon as possible.`); + export const OLD_FBOS_REC_UPGRADE = + trim(`Your version of FarmBot OS is outdated and will soon no longer + be supported. Please update your device as soon as possible.`); export const EXPERIMENTAL_WARNING = trim(`Warning! This is an EXPERIMENTAL feature. This feature may be @@ -812,7 +812,10 @@ export namespace Content { trim(`add this crop on OpenFarm?`); export const NO_TOOLS = - trim(`Press "+" to add a new tool.`); + trim(`Press "+" to add a new tool or seed container.`); + + export const NO_SEED_CONTAINERS = + trim(`Press "+" to add a seed container.`); export const MOUNTED_TOOL = trim(`The tool currently mounted to the UTM can be set here or by using @@ -887,12 +890,23 @@ export namespace TourContent { selecting one, and dragging it into the garden.`); export const ADD_TOOLS = - trim(`Press edit and then the + button to add tools and seed containers.`); + trim(`Press the + button to add tools and seed containers.`); + + export const ADD_SEED_CONTAINERS = + trim(`Press the + button to add seed containers.`); + + 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.`); + + 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.`); export const ADD_TOOLS_SLOTS = trim(`Add the newly created tools and seed containers to the corresponding tool slots on FarmBot: - press edit and then + to create a tool slot.`); + press the + button to create a tool slot.`); export const ADD_PERIPHERALS = trim(`Press edit and then the + button to add peripherals.`); @@ -930,6 +944,87 @@ export namespace TourContent { trim(`Toggle various settings to customize your web app experience.`); } +export enum DeviceSetting { + // Homing and calibration + homingAndCalibration = `Homing and Calibration`, + homing = `Homing`, + calibration = `Calibration`, + setZeroPosition = `Set Zero Position`, + findHomeOnBoot = `Find Home on Boot`, + stopAtHome = `Stop at Home`, + stopAtMax = `Stop at Max`, + negativeCoordinatesOnly = `Negative Coordinates Only`, + axisLength = `Axis Length (mm)`, + + // Motors + motors = `Motors`, + maxSpeed = `Max Speed (mm/s)`, + homingSpeed = `Homing Speed (mm/s)`, + minimumSpeed = `Minimum Speed (mm/s)`, + accelerateFor = `Accelerate for (mm)`, + stepsPerMm = `Steps per MM`, + microstepsPerStep = `Microsteps per step`, + alwaysPowerMotors = `Always Power Motors`, + invertMotors = `Invert Motors`, + motorCurrent = `Motor Current`, + enable2ndXMotor = `Enable 2nd X Motor`, + invert2ndXMotor = `Invert 2nd X Motor`, + + // Encoders / Stall Detection + encoders = `Encoders`, + stallDetection = `Stall Detection`, + enableEncoders = `Enable Encoders`, + enableStallDetection = `Enable Stall Detection`, + stallSensitivity = `Stall Sensitivity`, + useEncodersForPositioning = `Use Encoders for Positioning`, + invertEncoders = `Invert Encoders`, + maxMissedSteps = `Max Missed Steps`, + missedStepDecay = `Missed Step Decay`, + encoderScaling = `Encoder Scaling`, + + // Endstops + endstops = `Endstops`, + enableEndstops = `Enable Endstops`, + swapEndstops = `Swap Endstops`, + invertEndstops = `Invert Endstops`, + + // Error handling + errorHandling = `Error Handling`, + timeoutAfter = `Timeout after (seconds)`, + maxRetries = `Max Retries`, + estopOnMovementError = `E-Stop on Movement Error`, + + // Pin Guard + pinGuard = `Pin Guard`, + + // Danger Zone + dangerZone = `dangerZone`, + resetHardwareParams = `Reset hardware parameter defaults`, + + // Pin Bindings + pinBindings = `Pin Bindings`, + + // FarmBot OS + name = `name`, + timezone = `timezone`, + camera = `camera`, + firmware = `firmware`, + farmbotOSAutoUpdate = `Farmbot OS Auto Update`, + farmbotOS = `Farmbot OS`, + autoSync = `Auto Sync`, + bootSequence = `Boot Sequence`, + + // Power and Reset + powerAndReset = `Power and Reset`, + restartFarmbot = `Restart Farmbot`, + shutdownFarmbot = `Shutdown Farmbot`, + restartFirmware = `Restart Firmware`, + factoryReset = `Factory Reset`, + autoFactoryReset = `Automatic Factory Reset`, + connectionAttemptPeriod = `Connection Attempt Period`, + changeOwnership = `Change Ownership`, +} + export namespace DiagnosticMessages { export const OK = trim(`All systems nominal.`); diff --git a/frontend/controls/controls.tsx b/frontend/controls/controls.tsx index bcd98afa0..5195c58ba 100644 --- a/frontend/controls/controls.tsx +++ b/frontend/controls/controls.tsx @@ -38,6 +38,7 @@ export class RawControls extends React.Component { getWebAppConfigVal={this.props.getWebAppConfigVal} /> peripherals = () => { sensors = () => this.hideSensors ?
: ", () => { @@ -14,7 +14,8 @@ describe("", () => { bot, peripherals: [fakePeripheral()], dispatch: jest.fn(), - disabled: false + disabled: false, + firmwareHardware: undefined, }; } @@ -73,11 +74,18 @@ describe("", () => { expect(p.dispatch).toHaveBeenCalled(); }); - it("adds farmduino peripherals", () => { + it.each<[FirmwareHardware, number]>([ + ["arduino", 2], + ["farmduino", 5], + ["farmduino_k14", 5], + ["farmduino_k15", 5], + ["express_k10", 3], + ])("adds peripherals: %s", (firmware, expectedAdds) => { const p = fakeProps(); + p.firmwareHardware = firmware; const wrapper = mount(); wrapper.setState({ isEditing: true }); - clickButton(wrapper, 3, "farmduino"); - expect(p.dispatch).toHaveBeenCalledTimes(5); + clickButton(wrapper, 3, "stock"); + expect(p.dispatch).toHaveBeenCalledTimes(expectedAdds); }); }); diff --git a/frontend/controls/peripherals/index.tsx b/frontend/controls/peripherals/index.tsx index cad85b669..69decd905 100644 --- a/frontend/controls/peripherals/index.tsx +++ b/frontend/controls/peripherals/index.tsx @@ -56,12 +56,31 @@ export class Peripherals this.props.dispatch(init("Peripheral", { pin, label })); }; - farmduinoPeripherals = () => { - this.newPeripheral(7, t("Lighting")); - this.newPeripheral(8, t("Water")); - this.newPeripheral(9, t("Vacuum")); - this.newPeripheral(10, t("Peripheral ") + "4"); - this.newPeripheral(12, t("Peripheral ") + "5"); + get stockPeripherals() { + switch (this.props.firmwareHardware) { + case "arduino": + return [ + { pin: 8, label: t("Water") }, + { pin: 9, label: t("Vacuum") }, + ]; + case "farmduino": + case "farmduino_k14": + case "farmduino_k15": + default: + return [ + { pin: 7, label: t("Lighting") }, + { pin: 8, label: t("Water") }, + { pin: 9, label: t("Vacuum") }, + { pin: 10, label: t("Peripheral ") + "4" }, + { pin: 12, label: t("Peripheral ") + "5" }, + ]; + case "express_k10": + return [ + { pin: 7, label: t("Lighting") }, + { pin: 8, label: t("Water") }, + { pin: 9, label: t("Vacuum") }, + ]; + } } render() { @@ -92,10 +111,11 @@ export class Peripherals hidden={!isEditing} className="fb-button green" type="button" - onClick={this.farmduinoPeripherals}> + onClick={() => this.stockPeripherals.map(p => + this.newPeripheral(p.pin, p.label))}> - Farmduino - + {t("Stock")} + {this.showPins()} diff --git a/frontend/controls/sensors/__tests__/index_test.tsx b/frontend/controls/sensors/__tests__/index_test.tsx index 23d13d778..569beee99 100644 --- a/frontend/controls/sensors/__tests__/index_test.tsx +++ b/frontend/controls/sensors/__tests__/index_test.tsx @@ -18,7 +18,8 @@ describe("", () => { bot, sensors: [fakeSensor1, fakeSensor2], dispatch: jest.fn(), - disabled: false + disabled: false, + firmwareHardware: undefined, }; } @@ -68,8 +69,16 @@ describe("", () => { it("adds stock sensors", () => { const p = fakeProps(); const wrapper = mount(); + expect(wrapper.text().toLowerCase()).toContain("stock sensors"); wrapper.setState({ isEditing: true }); clickButton(wrapper, 3, "stock sensors"); expect(p.dispatch).toHaveBeenCalledTimes(2); }); + + it("doesn't display + stock button", () => { + const p = fakeProps(); + p.firmwareHardware = "express_k10"; + const wrapper = mount(); + expect(wrapper.text().toLowerCase()).not.toContain("stock sensors"); + }); }); diff --git a/frontend/controls/sensors/__tests__/sensor_list_test.tsx b/frontend/controls/sensors/__tests__/sensor_list_test.tsx index 112280990..791f9fe81 100644 --- a/frontend/controls/sensors/__tests__/sensor_list_test.tsx +++ b/frontend/controls/sensors/__tests__/sensor_list_test.tsx @@ -100,4 +100,11 @@ describe("", function () { readSensorBtn.last().simulate("click"); expect(mockDevice.readPin).not.toHaveBeenCalled(); }); + + it("renders analog reading", () => { + const p = fakeProps(); + p.pins[50] && (p.pins[50].value = 600); + const wrapper = mount(); + expect(wrapper.html()).toContain("margin-left: -3.5rem"); + }); }); diff --git a/frontend/controls/sensors/index.tsx b/frontend/controls/sensors/index.tsx index 3ed6e9c82..77bb35249 100644 --- a/frontend/controls/sensors/index.tsx +++ b/frontend/controls/sensors/index.tsx @@ -10,6 +10,7 @@ import { saveAll, init } from "../../api/crud"; import { ToolTips } from "../../constants"; import { uniq } from "lodash"; import { t } from "../../i18next_wrapper"; +import { isExpressBoard } from "../../devices/components/firmware_hardware_support"; export class Sensors extends React.Component { constructor(props: SensorsProps) { @@ -79,14 +80,15 @@ export class Sensors extends React.Component { onClick={() => this.newSensor()}> - + {!isExpressBoard(this.props.firmwareHardware) && + } {this.showPins()} diff --git a/frontend/css/_blueprint_overrides.scss b/frontend/css/_blueprint_overrides.scss index 391d11aa9..039e5add0 100644 --- a/frontend/css/_blueprint_overrides.scss +++ b/frontend/css/_blueprint_overrides.scss @@ -1,5 +1,6 @@ // Padding for the popups. .bp3-popover-content { + z-index: 999; padding: 1rem; } diff --git a/frontend/css/global.scss b/frontend/css/global.scss index b719f3d1c..02ff86b62 100644 --- a/frontend/css/global.scss +++ b/frontend/css/global.scss @@ -1629,3 +1629,23 @@ textarea:focus { } } } + +.section { + display: block !important; +} + +.highlight, +.unhighlight { + display: flex; +} + +.highlight { + background-color: $light_yellow; + box-shadow: 0px 0px 7px 4px $light_yellow; +} + +.unhighlight { + transition: background-color 10s linear, box-shadow 10s linear; + background-color: transparent; + box-shadow: none; +} diff --git a/frontend/demo/demo_iframe.tsx b/frontend/demo/demo_iframe.tsx index 16681552f..1fe9181ed 100644 --- a/frontend/demo/demo_iframe.tsx +++ b/frontend/demo/demo_iframe.tsx @@ -60,9 +60,9 @@ export class DemoIframe extends React.Component<{}, State> { return
- + diff --git a/frontend/devices/components/__tests__/boolean_mcu_input_group_test.tsx b/frontend/devices/components/__tests__/boolean_mcu_input_group_test.tsx index eaf610a0b..505650d38 100644 --- a/frontend/devices/components/__tests__/boolean_mcu_input_group_test.tsx +++ b/frontend/devices/components/__tests__/boolean_mcu_input_group_test.tsx @@ -7,13 +7,14 @@ import { ToggleButton } from "../../../controls/toggle_button"; import { settingToggle } from "../../actions"; import { bot } from "../../../__test_support__/fake_state/bot"; import { BooleanMCUInputGroupProps } from "../interfaces"; +import { DeviceSetting } from "../../../constants"; describe("BooleanMCUInputGroup", () => { const fakeProps = (): BooleanMCUInputGroupProps => ({ sourceFwConfig: x => ({ value: bot.hardware.mcu_params[x], consistent: true }), dispatch: jest.fn(), tooltip: "Tooltip", - name: "Name", + label: DeviceSetting.invertEncoders, x: "encoder_invert_x", y: "encoder_invert_y", z: "encoder_invert_z", diff --git a/frontend/devices/components/__tests__/farmbot_os_settings_test.tsx b/frontend/devices/components/__tests__/farmbot_os_settings_test.tsx index 917caaead..757cc3b56 100644 --- a/frontend/devices/components/__tests__/farmbot_os_settings_test.tsx +++ b/frontend/devices/components/__tests__/farmbot_os_settings_test.tsx @@ -22,6 +22,8 @@ import axios from "axios"; import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings"; import { edit } from "../../../api/crud"; import { fakeWebAppConfig } from "../../../__test_support__/fake_state/resources"; +import { formEvent } from "../../../__test_support__/fake_html_events"; +import { Content } from "../../../constants"; describe("", () => { beforeEach(() => { @@ -54,8 +56,8 @@ describe("", () => { const osSettings = mount(); expect(osSettings.find("input").length).toBe(1); expect(osSettings.find("button").length).toBe(7); - ["NAME", "TIME ZONE", "FARMBOT OS", "CAMERA", "FIRMWARE"] - .map(string => expect(osSettings.text()).toContain(string)); + ["name", "time zone", "farmbot os", "camera", "firmware"] + .map(string => expect(osSettings.text().toLowerCase()).toContain(string)); }); it("fetches OS release notes", async () => { @@ -115,4 +117,18 @@ describe("", () => { const osSettings = shallow(); expect(osSettings.find("BootSequenceSelector").length).toEqual(1); }); + + it("prevents default form submit action", () => { + const osSettings = shallow(); + const e = formEvent(); + osSettings.find("form").simulate("submit", e); + expect(e.preventDefault).toHaveBeenCalled(); + }); + + it("warns about timezone mismatch", () => { + const p = fakeProps(); + p.deviceAccount.body.timezone = "different"; + const osSettings = mount(); + expect(osSettings.text()).toContain(Content.DIFFERENT_TZ_WARNING); + }); }); diff --git a/frontend/devices/components/__tests__/firmware_hardware_support_test.ts b/frontend/devices/components/__tests__/firmware_hardware_support_test.ts index e7f5e2a4a..46ddb33f8 100644 --- a/frontend/devices/components/__tests__/firmware_hardware_support_test.ts +++ b/frontend/devices/components/__tests__/firmware_hardware_support_test.ts @@ -1,4 +1,5 @@ -import { boardType } from "../firmware_hardware_support"; +import { boardType, getFwHardwareValue } from "../firmware_hardware_support"; +import { fakeFbosConfig } from "../../../__test_support__/fake_state/resources"; describe("boardType()", () => { it("returns Farmduino", () => { @@ -32,3 +33,18 @@ describe("boardType()", () => { expect(boardType("none")).toEqual("none"); }); }); + +describe("getFwHardwareValue()", () => { + it("returns undefined", () => { + const fbosConfig = fakeFbosConfig(); + fbosConfig.body.firmware_hardware = "wrong"; + expect(getFwHardwareValue(fbosConfig)).toEqual(undefined); + expect(getFwHardwareValue(undefined)).toEqual(undefined); + }); + + it("returns real value", () => { + const fbosConfig = fakeFbosConfig(); + fbosConfig.body.firmware_hardware = "express_k10"; + expect(getFwHardwareValue(fbosConfig)).toEqual("express_k10"); + }); +}); diff --git a/frontend/devices/components/__tests__/maybe_highlight_test.tsx b/frontend/devices/components/__tests__/maybe_highlight_test.tsx new file mode 100644 index 000000000..e748d7574 --- /dev/null +++ b/frontend/devices/components/__tests__/maybe_highlight_test.tsx @@ -0,0 +1,81 @@ +jest.mock("../../actions", () => ({ + toggleControlPanel: jest.fn(), +})); + +import * as React from "react"; +import { mount } from "enzyme"; +import { + Highlight, HighlightProps, maybeHighlight, maybeOpenPanel, highlight +} from "../maybe_highlight"; +import { DeviceSetting } from "../../../constants"; +import { panelState } from "../../../__test_support__/control_panel_state"; +import { toggleControlPanel } from "../../actions"; + +describe("", () => { + const fakeProps = (): HighlightProps => ({ + settingName: DeviceSetting.motors, + children:
, + className: "section", + }); + + it("fades highlight", () => { + const p = fakeProps(); + const wrapper = mount(); + wrapper.setState({ className: "highlight" }); + wrapper.instance().componentDidMount(); + expect(wrapper.state().className).toEqual("unhighlight"); + }); +}); + +describe("maybeHighlight()", () => { + beforeEach(() => { + highlight.opened = false; + highlight.highlighted = false; + }); + + it("highlights only once", () => { + location.search = "?highlight=motors"; + expect(maybeHighlight(DeviceSetting.motors)).toEqual("highlight"); + expect(maybeHighlight(DeviceSetting.motors)).toEqual(""); + }); + + it("doesn't highlight: different setting", () => { + location.search = "?highlight=name"; + expect(maybeHighlight(DeviceSetting.motors)).toEqual(""); + }); + + it("doesn't highlight: no matches", () => { + location.search = "?highlight=na"; + expect(maybeHighlight(DeviceSetting.motors)).toEqual(""); + }); +}); + +describe("maybeOpenPanel()", () => { + beforeEach(() => { + highlight.opened = false; + highlight.highlighted = false; + }); + + it("opens panel only once", () => { + location.search = "?highlight=motors"; + maybeOpenPanel(panelState())(jest.fn()); + expect(toggleControlPanel).toHaveBeenCalledWith("motors"); + jest.resetAllMocks(); + maybeOpenPanel(panelState())(jest.fn()); + expect(toggleControlPanel).not.toHaveBeenCalled(); + }); + + it("doesn't open panel: already open", () => { + location.search = "?highlight=motors"; + const panels = panelState(); + panels.motors = true; + maybeOpenPanel(panels)(jest.fn()); + expect(toggleControlPanel).not.toHaveBeenCalled(); + }); + + it("doesn't open panel: no search term", () => { + location.search = ""; + maybeOpenPanel(panelState())(jest.fn()); + expect(toggleControlPanel).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/devices/components/boolean_mcu_input_group.tsx b/frontend/devices/components/boolean_mcu_input_group.tsx index cd223864c..710fafa31 100644 --- a/frontend/devices/components/boolean_mcu_input_group.tsx +++ b/frontend/devices/components/boolean_mcu_input_group.tsx @@ -4,12 +4,14 @@ import { settingToggle } from "../actions"; import { Row, Col, Help } from "../../ui/index"; import { BooleanMCUInputGroupProps } from "./interfaces"; import { Position } from "@blueprintjs/core"; +import { t } from "../../i18next_wrapper"; +import { Highlight } from "./maybe_highlight"; export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) { const { tooltip, - name, + label, x, y, z, @@ -26,40 +28,42 @@ export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) { const zParam = sourceFwConfig(z); return - - - - - - - dispatch(settingToggle(x, sourceFwConfig, displayAlert))} /> - - - - dispatch(settingToggle(y, sourceFwConfig, displayAlert))} /> - - - - dispatch(settingToggle(z, sourceFwConfig, displayAlert))} /> - + + + + + + + + dispatch(settingToggle(x, sourceFwConfig, displayAlert))} /> + + + + dispatch(settingToggle(y, sourceFwConfig, displayAlert))} /> + + + + dispatch(settingToggle(z, sourceFwConfig, displayAlert))} /> + + ; } diff --git a/frontend/devices/components/farmbot_os_settings.tsx b/frontend/devices/components/farmbot_os_settings.tsx index cbfa9d10f..1498f5868 100644 --- a/frontend/devices/components/farmbot_os_settings.tsx +++ b/frontend/devices/components/farmbot_os_settings.tsx @@ -5,7 +5,7 @@ import { FarmbotOsProps, FarmbotOsState, Feature } from "../interfaces"; import { Widget, WidgetHeader, WidgetBody, Row, Col } from "../../ui"; import { save, edit } from "../../api/crud"; import { isBotOnline } from "../must_be_online"; -import { Content } from "../../constants"; +import { Content, DeviceSetting } from "../../constants"; import { TimezoneSelector } from "../timezones/timezone_selector"; import { timezoneMismatch } from "../timezones/guess_timezone"; import { CameraSelection } from "./fbos_settings/camera_selection"; @@ -16,6 +16,7 @@ import { AutoSyncRow } from "./fbos_settings/auto_sync_row"; import { PowerAndReset } from "./fbos_settings/power_and_reset"; import { BootSequenceSelector } from "./fbos_settings/boot_sequence_selector"; import { ExternalUrl } from "../../external_urls"; +import { Highlight } from "./maybe_highlight"; export enum ColWidth { label = 3, @@ -85,34 +86,38 @@ export class FarmbotOsSettings - - - - - - + + + + + + + + - - - - -
- {this.maybeWarnTz()} -
-
- -
- + + + + + +
+ {this.maybeWarnTz()} +
+
+ +
+ +
- - - - -

- {t(Content.AUTO_SYNC)} -

- - - { - props.dispatch(updateConfig({ auto_sync: !autoSync.value })); - }} /> - + + + + + +

+ {t(Content.AUTO_SYNC)} +

+ + + { + props.dispatch(updateConfig({ auto_sync: !autoSync.value })); + }} /> + +
; } diff --git a/frontend/devices/components/fbos_settings/auto_update_row.tsx b/frontend/devices/components/fbos_settings/auto_update_row.tsx index f99e553f1..2540daecf 100644 --- a/frontend/devices/components/fbos_settings/auto_update_row.tsx +++ b/frontend/devices/components/fbos_settings/auto_update_row.tsx @@ -3,10 +3,11 @@ import { Row, Col } from "../../../ui/index"; import { ColWidth } from "../farmbot_os_settings"; import { ToggleButton } from "../../../controls/toggle_button"; import { updateConfig } from "../../actions"; -import { Content } from "../../../constants"; +import { Content, DeviceSetting } from "../../../constants"; import { AutoUpdateRowProps } from "./interfaces"; import { t } from "../../../i18next_wrapper"; import { OtaTimeSelector, changeOtaHour } from "./ota_time_selector"; +import { Highlight } from "../maybe_highlight"; export function AutoUpdateRow(props: AutoUpdateRowProps) { const osAutoUpdate = props.sourceFbosConfig("os_auto_update"); @@ -18,23 +19,25 @@ export function AutoUpdateRow(props: AutoUpdateRowProps) { value={props.device.body.ota_hour} onChange={changeOtaHour(props.dispatch, props.device)} /> - - - - -

- {t(Content.OS_AUTO_UPDATE)} -

- - - props.dispatch(updateConfig({ - os_auto_update: !osAutoUpdate.value - }))} /> - + + + + + +

+ {t(Content.OS_AUTO_UPDATE)} +

+ + + props.dispatch(updateConfig({ + os_auto_update: !osAutoUpdate.value + }))} /> + +
; } diff --git a/frontend/devices/components/fbos_settings/board_type.tsx b/frontend/devices/components/fbos_settings/board_type.tsx index f5fc2fe23..9334f2a0d 100644 --- a/frontend/devices/components/fbos_settings/board_type.tsx +++ b/frontend/devices/components/fbos_settings/board_type.tsx @@ -10,6 +10,8 @@ import { FirmwareHardwareStatus } from "./firmware_hardware_status"; import { isFwHardwareValue, getFirmwareChoices, FIRMWARE_CHOICES_DDI } from "../firmware_hardware_support"; +import { Highlight } from "../maybe_highlight"; +import { DeviceSetting } from "../../../constants"; interface BoardTypeState { sending: boolean } @@ -47,30 +49,32 @@ export class BoardType extends React.Component { render() { return - - - - -
- -
- - - - + + + + + +
+ +
+ + + + +
; } } diff --git a/frontend/devices/components/fbos_settings/boot_sequence_selector.tsx b/frontend/devices/components/fbos_settings/boot_sequence_selector.tsx index 6c3c9342b..1a0f4d460 100644 --- a/frontend/devices/components/fbos_settings/boot_sequence_selector.tsx +++ b/frontend/devices/components/fbos_settings/boot_sequence_selector.tsx @@ -9,6 +9,8 @@ import { selectAllSequences, findSequenceById } from "../../../resources/selecto import { betterCompact } from "../../../util"; import { ColWidth } from "../farmbot_os_settings"; import { t } from "../../../i18next_wrapper"; +import { Highlight } from "../maybe_highlight"; +import { DeviceSetting } from "../../../constants"; interface Props { list: DropDownItem[]; @@ -56,18 +58,20 @@ export class RawBootSequenceSelector extends React.Component { render() { return - - - - - - + + + + + + + + ; } } diff --git a/frontend/devices/components/fbos_settings/camera_selection.tsx b/frontend/devices/components/fbos_settings/camera_selection.tsx index 46f3d895b..34adca94a 100644 --- a/frontend/devices/components/fbos_settings/camera_selection.tsx +++ b/frontend/devices/components/fbos_settings/camera_selection.tsx @@ -8,7 +8,8 @@ import { getDevice } from "../../../device"; import { ColWidth } from "../farmbot_os_settings"; import { Feature, UserEnv } from "../../interfaces"; import { t } from "../../../i18next_wrapper"; -import { Content, ToolTips } from "../../../constants"; +import { Content, ToolTips, DeviceSetting } from "../../../constants"; +import { Highlight } from "../maybe_highlight"; /** Check if the camera has been disabled. */ export const cameraDisabled = (env: UserEnv): boolean => @@ -84,21 +85,23 @@ export class CameraSelection render() { return - - - - -
- -
- + + + + + +
+ +
+ +
; } } diff --git a/frontend/devices/components/fbos_settings/factory_reset_row.tsx b/frontend/devices/components/fbos_settings/factory_reset_row.tsx index 0a42a8f3a..273313502 100644 --- a/frontend/devices/components/fbos_settings/factory_reset_row.tsx +++ b/frontend/devices/components/fbos_settings/factory_reset_row.tsx @@ -1,12 +1,13 @@ import * as React from "react"; import { Row, Col } from "../../../ui/index"; -import { Content } from "../../../constants"; +import { Content, DeviceSetting } from "../../../constants"; import { factoryReset, updateConfig } from "../../actions"; import { ToggleButton } from "../../../controls/toggle_button"; import { BotConfigInputBox } from "../bot_config_input_box"; import { FactoryResetRowProps } from "./interfaces"; import { ColWidth } from "../farmbot_os_settings"; import { t } from "../../../i18next_wrapper"; +import { Highlight } from "../maybe_highlight"; export function FactoryResetRow(props: FactoryResetRowProps) { const { dispatch, sourceFbosConfig, botOnline } = props; @@ -14,66 +15,72 @@ export function FactoryResetRow(props: FactoryResetRowProps) { const maybeDisableTimer = disableFactoryReset.value ? { color: "grey" } : {}; return
- - - - -

- {t(Content.FACTORY_RESET_WARNING)} -

- - - - + + + + + +

+ {t(Content.FACTORY_RESET_WARNING)} +

+ + + + +
- - - - -

- {t(Content.AUTO_FACTORY_RESET)} -

- - - { - dispatch(updateConfig({ - disable_factory_reset: !disableFactoryReset.value - })); - }} /> - + + + + + +

+ {t(Content.AUTO_FACTORY_RESET)} +

+ + + { + dispatch(updateConfig({ + disable_factory_reset: !disableFactoryReset.value + })); + }} /> + +
- - - - -

- {t(Content.AUTO_FACTORY_RESET_PERIOD)} -

- - - - + + + + + +

+ {t(Content.AUTO_FACTORY_RESET_PERIOD)} +

+ + + + +
; } diff --git a/frontend/devices/components/fbos_settings/farmbot_os_row.tsx b/frontend/devices/components/fbos_settings/farmbot_os_row.tsx index 2f1cc8e04..2bcc6fd15 100644 --- a/frontend/devices/components/fbos_settings/farmbot_os_row.tsx +++ b/frontend/devices/components/fbos_settings/farmbot_os_row.tsx @@ -7,6 +7,8 @@ import { FarmbotOsRowProps } from "./interfaces"; import { FbosDetails } from "./fbos_details"; import { t } from "../../../i18next_wrapper"; import { ErrorBoundary } from "../../../error_boundary"; +import { Highlight } from "../maybe_highlight"; +import { DeviceSetting } from "../../../constants"; const getVersionString = (fbosVersion: string | undefined, onBeta: boolean | undefined): string => { @@ -21,48 +23,50 @@ export function FarmbotOsRow(props: FarmbotOsRowProps) { } = bot.hardware.informational_settings; const version = getVersionString(controller_version, currently_on_beta); return - - - - - -

- {t("Version {{ version }}", { version })} -

- - - -
- - - -

- {t("Release Notes")}  + + + + + + +

+ {t("Version {{ version }}", { version })} +

+ + + +
+ + + +

+ {t("Release Notes")}  -

-
-

{props.osReleaseNotesHeading}

- - {osReleaseNotes} - -
-
- - - - +

+
+

{props.osReleaseNotesHeading}

+ + {osReleaseNotes} + +
+ + + + + +
; } diff --git a/frontend/devices/components/fbos_settings/fbos_button_row.tsx b/frontend/devices/components/fbos_settings/fbos_button_row.tsx index 1cf1d3a58..5ef9ec8f9 100644 --- a/frontend/devices/components/fbos_settings/fbos_button_row.tsx +++ b/frontend/devices/components/fbos_settings/fbos_button_row.tsx @@ -2,10 +2,12 @@ import * as React from "react"; import { Row, Col } from "../../../ui"; import { ColWidth } from "../farmbot_os_settings"; import { t } from "../../../i18next_wrapper"; +import { Highlight } from "../maybe_highlight"; +import { DeviceSetting } from "../../../constants"; export interface FbosButtonRowProps { botOnline: boolean; - label: string; + label: DeviceSetting; description: string; buttonText: string; color: string; @@ -14,24 +16,26 @@ export interface FbosButtonRowProps { export const FbosButtonRow = (props: FbosButtonRowProps) => { return - - - - -

- {t(props.description)} -

- - - - + + + + + +

+ {t(props.description)} +

+ + + + +
; }; diff --git a/frontend/devices/components/fbos_settings/power_and_reset.tsx b/frontend/devices/components/fbos_settings/power_and_reset.tsx index e6ac2d437..1a33fa116 100644 --- a/frontend/devices/components/fbos_settings/power_and_reset.tsx +++ b/frontend/devices/components/fbos_settings/power_and_reset.tsx @@ -5,37 +5,39 @@ import { FactoryResetRow } from "./factory_reset_row"; import { PowerAndResetProps } from "./interfaces"; import { ChangeOwnershipForm } from "./change_ownership_form"; import { FbosButtonRow } from "./fbos_button_row"; -import { Content } from "../../../constants"; +import { Content, DeviceSetting } from "../../../constants"; import { reboot, powerOff, restartFirmware } from "../../actions"; import { t } from "../../../i18next_wrapper"; +import { Highlight } from "../maybe_highlight"; export function PowerAndReset(props: PowerAndResetProps) { const { dispatch, sourceFbosConfig, botOnline } = props; const { power_and_reset } = props.controlPanelState; - return
+ return
{botOnline && - -

- {t("Change Ownership")}  - -

- -
- } + + +

+ {t(DeviceSetting.changeOwnership)}  + +

+ +
+
}
-
; + ; } diff --git a/frontend/devices/components/firmware_hardware_support.ts b/frontend/devices/components/firmware_hardware_support.ts index 56e111276..71756e221 100644 --- a/frontend/devices/components/firmware_hardware_support.ts +++ b/frontend/devices/components/firmware_hardware_support.ts @@ -1,4 +1,4 @@ -import { FirmwareHardware } from "farmbot"; +import { FirmwareHardware, TaggedFbosConfig } from "farmbot"; export const isFwHardwareValue = (x?: unknown): x is FirmwareHardware => { const values: FirmwareHardware[] = [ @@ -10,6 +10,12 @@ export const isFwHardwareValue = (x?: unknown): x is FirmwareHardware => { return !!values.includes(x as FirmwareHardware); }; +export const getFwHardwareValue = + (fbosConfig: TaggedFbosConfig | undefined) => { + const value = fbosConfig?.body.firmware_hardware; + return isFwHardwareValue(value) ? value : undefined; + }; + const TMC_BOARDS = ["express_k10", "farmduino_k15"]; const EXPRESS_BOARDS = ["express_k10"]; diff --git a/frontend/devices/components/hardware_settings.tsx b/frontend/devices/components/hardware_settings.tsx index 0ad58b3cd..1f12f68fe 100644 --- a/frontend/devices/components/hardware_settings.tsx +++ b/frontend/devices/components/hardware_settings.tsx @@ -18,10 +18,14 @@ import { FwParamExportMenu } from "./hardware_settings/export_menu"; import { t } from "../../i18next_wrapper"; import { PinBindings } from "./hardware_settings/pin_bindings"; import { ErrorHandling } from "./hardware_settings/error_handling"; +import { maybeOpenPanel } from "./maybe_highlight"; export class HardwareSettings extends React.Component { + componentDidMount = () => + this.props.dispatch(maybeOpenPanel(this.props.controlPanelState)); + render() { const { bot, dispatch, sourceFwConfig, controlPanelState, firmwareConfig, diff --git a/frontend/devices/components/hardware_settings/__tests__/calibration_row_test.tsx b/frontend/devices/components/hardware_settings/__tests__/calibration_row_test.tsx index f1475c44a..e7c47b59b 100644 --- a/frontend/devices/components/hardware_settings/__tests__/calibration_row_test.tsx +++ b/frontend/devices/components/hardware_settings/__tests__/calibration_row_test.tsx @@ -3,6 +3,7 @@ import { mount } from "enzyme"; import { CalibrationRow } from "../calibration_row"; import { bot } from "../../../../__test_support__/fake_state/bot"; import { CalibrationRowProps } from "../../interfaces"; +import { DeviceSetting } from "../../../../constants"; describe("", () => { const fakeProps = (): CalibrationRowProps => ({ @@ -11,7 +12,7 @@ describe("", () => { botDisconnected: false, action: jest.fn(), toolTip: "calibrate", - title: "calibrate", + title: DeviceSetting.calibration, axisTitle: "calibrate", }); diff --git a/frontend/devices/components/hardware_settings/__tests__/header_test.tsx b/frontend/devices/components/hardware_settings/__tests__/header_test.tsx index 902fee1b6..8b63d6d11 100644 --- a/frontend/devices/components/hardware_settings/__tests__/header_test.tsx +++ b/frontend/devices/components/hardware_settings/__tests__/header_test.tsx @@ -1,16 +1,17 @@ import * as React from "react"; import { Header } from "../header"; import { mount } from "enzyme"; +import { DeviceSetting } from "../../../../constants"; describe("
", () => { it("renders", () => { const fn = jest.fn(); const el = mount(
); - expect(el.text()).toContain("FOO"); + expect(el.text().toLowerCase()).toContain("motors"); expect(el.find(".fa-minus").length).toBe(1); }); }); diff --git a/frontend/devices/components/hardware_settings/calibration_row.tsx b/frontend/devices/components/hardware_settings/calibration_row.tsx index ab986afec..5be2448c2 100644 --- a/frontend/devices/components/hardware_settings/calibration_row.tsx +++ b/frontend/devices/components/hardware_settings/calibration_row.tsx @@ -5,30 +5,33 @@ import { Row, Col, Help } from "../../../ui/index"; import { CalibrationRowProps } from "../interfaces"; import { t } from "../../../i18next_wrapper"; import { Position } from "@blueprintjs/core"; +import { Highlight } from "../maybe_highlight"; export function CalibrationRow(props: CalibrationRowProps) { const { hardware, botDisconnected } = props; return - - - - - {axisTrackingStatus(hardware) - .map(row => { - const { axis } = row; - const hardwareDisabled = props.type == "zero" ? false : row.disabled; - return - props.action(axis)}> - {`${t(props.axisTitle)} ${axis}`} - - ; - })} + + + + + + {axisTrackingStatus(hardware) + .map(row => { + const { axis } = row; + const hardwareDisabled = props.type == "zero" ? false : row.disabled; + return + props.action(axis)}> + {`${t(props.axisTitle)} ${axis}`} + + ; + })} + ; } diff --git a/frontend/devices/components/hardware_settings/danger_zone.tsx b/frontend/devices/components/hardware_settings/danger_zone.tsx index cd78e7f23..a7ce7940c 100644 --- a/frontend/devices/components/hardware_settings/danger_zone.tsx +++ b/frontend/devices/components/hardware_settings/danger_zone.tsx @@ -3,41 +3,45 @@ import { DangerZoneProps } from "../interfaces"; import { Row, Col } from "../../../ui/index"; import { Header } from "./header"; import { Collapse } from "@blueprintjs/core"; -import { Content } from "../../../constants"; +import { Content, DeviceSetting } from "../../../constants"; import { t } from "../../../i18next_wrapper"; +import { Highlight } from "../maybe_highlight"; export function DangerZone(props: DangerZoneProps) { const { dispatch, onReset, botDisconnected } = props; const { danger_zone } = props.controlPanelState; - return
+ return
- - - - -

- {t(Content.RESTORE_DEFAULT_HARDWARE_SETTINGS)} -

- - - - + + + + + +

+ {t(Content.RESTORE_DEFAULT_HARDWARE_SETTINGS)} +

+ + + + +
-
; + ; } diff --git a/frontend/devices/components/hardware_settings/encoders.tsx b/frontend/devices/components/hardware_settings/encoders.tsx index 7addae3ae..2f5d817aa 100644 --- a/frontend/devices/components/hardware_settings/encoders.tsx +++ b/frontend/devices/components/hardware_settings/encoders.tsx @@ -1,12 +1,12 @@ import * as React from "react"; import { BooleanMCUInputGroup } from "../boolean_mcu_input_group"; -import { ToolTips } from "../../../constants"; +import { ToolTips, DeviceSetting } from "../../../constants"; import { NumericMCUInputGroup } from "../numeric_mcu_input_group"; import { EncodersProps } from "../interfaces"; import { Header } from "./header"; import { Collapse } from "@blueprintjs/core"; -import { t } from "../../../i18next_wrapper"; import { isExpressBoard } from "../firmware_hardware_support"; +import { Highlight } from "../maybe_highlight"; export function Encoders(props: EncodersProps) { @@ -20,19 +20,20 @@ export function Encoders(props: EncodersProps) { }; const isExpress = isExpressBoard(firmwareHardware); - return
+ return
{isExpress && } {!isExpress && } {!isExpress && } {!isExpress && } -
; + ; } diff --git a/frontend/devices/components/hardware_settings/endstops.tsx b/frontend/devices/components/hardware_settings/endstops.tsx index ffa1b5f3d..c2b55ed65 100644 --- a/frontend/devices/components/hardware_settings/endstops.tsx +++ b/frontend/devices/components/hardware_settings/endstops.tsx @@ -1,25 +1,26 @@ import * as React from "react"; import { BooleanMCUInputGroup } from "../boolean_mcu_input_group"; -import { ToolTips } from "../../../constants"; +import { ToolTips, DeviceSetting } from "../../../constants"; import { EndStopsProps } from "../interfaces"; import { Header } from "./header"; import { Collapse } from "@blueprintjs/core"; -import { t } from "../../../i18next_wrapper"; +import { Highlight } from "../maybe_highlight"; export function EndStops(props: EndStopsProps) { const { endstops } = props.controlPanelState; const { dispatch, sourceFwConfig } = props; - return
+ return
-
; + ; } diff --git a/frontend/devices/components/hardware_settings/error_handling.tsx b/frontend/devices/components/hardware_settings/error_handling.tsx index e8d4142f6..c0dccc7a0 100644 --- a/frontend/devices/components/hardware_settings/error_handling.tsx +++ b/frontend/devices/components/hardware_settings/error_handling.tsx @@ -1,14 +1,14 @@ import * as React from "react"; import { NumericMCUInputGroup } from "../numeric_mcu_input_group"; -import { ToolTips } from "../../../constants"; +import { ToolTips, DeviceSetting } from "../../../constants"; import { ErrorHandlingProps } from "../interfaces"; import { Header } from "./header"; import { Collapse } from "@blueprintjs/core"; -import { t } from "../../../i18next_wrapper"; import { McuInputBox } from "../mcu_input_box"; import { settingToggle } from "../../actions"; import { SingleSettingRow } from "./single_setting_row"; import { ToggleButton } from "../../../controls/toggle_button"; +import { Highlight } from "../maybe_highlight"; export function ErrorHandling(props: ErrorHandlingProps) { @@ -16,15 +16,16 @@ export function ErrorHandling(props: ErrorHandlingProps) { const { dispatch, sourceFwConfig } = props; const eStopOnMoveError = sourceFwConfig("param_e_stop_on_mov_err"); - return
+ return
-
; + ; } diff --git a/frontend/devices/components/hardware_settings/header.tsx b/frontend/devices/components/hardware_settings/header.tsx index 3536d115a..f072cad13 100644 --- a/frontend/devices/components/hardware_settings/header.tsx +++ b/frontend/devices/components/hardware_settings/header.tsx @@ -2,18 +2,20 @@ import * as React from "react"; import { ControlPanelState } from "../../interfaces"; import { toggleControlPanel } from "../../actions"; import { ExpandableHeader } from "../../../ui/expandable_header"; +import { t } from "../../../i18next_wrapper"; +import { DeviceSetting } from "../../../constants"; interface Props { dispatch: Function; - name: keyof ControlPanelState; - title: string; + panel: keyof ControlPanelState; + title: DeviceSetting; expanded: boolean; } export const Header = (props: Props) => { - const { dispatch, name, title, expanded } = props; + const { dispatch, panel, title, expanded } = props; return dispatch(toggleControlPanel(name))} />; + title={t(title)} + onClick={() => dispatch(toggleControlPanel(panel))} />; }; diff --git a/frontend/devices/components/hardware_settings/homing_and_calibration.tsx b/frontend/devices/components/hardware_settings/homing_and_calibration.tsx index 1602fa351..0fa2c4374 100644 --- a/frontend/devices/components/hardware_settings/homing_and_calibration.tsx +++ b/frontend/devices/components/hardware_settings/homing_and_calibration.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { BooleanMCUInputGroup } from "../boolean_mcu_input_group"; -import { ToolTips } from "../../../constants"; +import { ToolTips, DeviceSetting } from "../../../constants"; import { NumericMCUInputGroup } from "../numeric_mcu_input_group"; import { CalibrationRow } from "./calibration_row"; import { disabledAxisMap } from "../axis_tracking_status"; @@ -13,6 +13,7 @@ import { isExpressBoard } from "../firmware_hardware_support"; import { getDevice } from "../../../device"; import { commandErr } from "../../actions"; import { CONFIG_DEFAULTS } from "farmbot/dist/config"; +import { Highlight } from "../maybe_highlight"; export function HomingAndCalibration(props: HomingAndCalibrationProps) { @@ -31,16 +32,17 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) { const scale = calculateScale(sourceFwConfig); - return
+ return
getDevice().setZero(axis) @@ -71,7 +73,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) { hardware={hardware} botDisconnected={botDisconnected} /> -
; + ; } diff --git a/frontend/devices/components/hardware_settings/motors.tsx b/frontend/devices/components/hardware_settings/motors.tsx index b6281e751..39cdbabd6 100644 --- a/frontend/devices/components/hardware_settings/motors.tsx +++ b/frontend/devices/components/hardware_settings/motors.tsx @@ -1,18 +1,18 @@ import * as React from "react"; import { BooleanMCUInputGroup } from "../boolean_mcu_input_group"; -import { ToolTips } from "../../../constants"; +import { ToolTips, DeviceSetting } from "../../../constants"; import { ToggleButton } from "../../../controls/toggle_button"; import { settingToggle } from "../../actions"; import { NumericMCUInputGroup } from "../numeric_mcu_input_group"; import { MotorsProps } from "../interfaces"; import { Header } from "./header"; import { Collapse } from "@blueprintjs/core"; -import { t } from "../../../i18next_wrapper"; import { Xyz, McuParamName } from "farmbot"; import { SourceFwConfig } from "../../interfaces"; import { calcMicrostepsPerMm } from "../../../controls/move/direction_axes_props"; import { isTMCBoard } from "../firmware_hardware_support"; import { SingleSettingRow } from "./single_setting_row"; +import { Highlight } from "../maybe_highlight"; export const calculateScale = (sourceFwConfig: SourceFwConfig): Record => { @@ -35,15 +35,16 @@ export function Motors(props: MotorsProps) { const invert2ndXMotor = sourceFwConfig("movement_secondary_motor_invert_x"); const scale = calculateScale(sourceFwConfig); - return
+ return
{isTMCBoard(firmwareHardware) && } -
; + ; } diff --git a/frontend/devices/components/hardware_settings/pin_bindings.tsx b/frontend/devices/components/hardware_settings/pin_bindings.tsx index e7e716c0c..fa258ce42 100644 --- a/frontend/devices/components/hardware_settings/pin_bindings.tsx +++ b/frontend/devices/components/hardware_settings/pin_bindings.tsx @@ -3,20 +3,23 @@ import { PinBindingsProps } from "../interfaces"; import { Header } from "./header"; import { Collapse } from "@blueprintjs/core"; import { PinBindingsContent } from "../../pin_bindings/pin_bindings"; +import { DeviceSetting } from "../../../constants"; +import { Highlight } from "../maybe_highlight"; export function PinBindings(props: PinBindingsProps) { const { pin_bindings } = props.controlPanelState; const { dispatch, resources } = props; - return
+ return
-
; + ; } diff --git a/frontend/devices/components/hardware_settings/pin_guard.tsx b/frontend/devices/components/hardware_settings/pin_guard.tsx index 9bd49c83b..1886382fc 100644 --- a/frontend/devices/components/hardware_settings/pin_guard.tsx +++ b/frontend/devices/components/hardware_settings/pin_guard.tsx @@ -4,19 +4,21 @@ import { PinGuardProps } from "../interfaces"; import { Header } from "./header"; import { Collapse, Position } from "@blueprintjs/core"; import { Row, Col, Help } from "../../../ui/index"; -import { ToolTips } from "../../../constants"; +import { ToolTips, DeviceSetting } from "../../../constants"; import { t } from "../../../i18next_wrapper"; +import { Highlight } from "../maybe_highlight"; export function PinGuard(props: PinGuardProps) { const { pin_guard } = props.controlPanelState; const { dispatch, sourceFwConfig, resources } = props; - return
+ return
@@ -79,5 +81,5 @@ export function PinGuard(props: PinGuardProps) { resources={resources} sourceFwConfig={sourceFwConfig} /> -
; + ; } diff --git a/frontend/devices/components/hardware_settings/single_setting_row.tsx b/frontend/devices/components/hardware_settings/single_setting_row.tsx index 02075c813..0ab1c4bba 100644 --- a/frontend/devices/components/hardware_settings/single_setting_row.tsx +++ b/frontend/devices/components/hardware_settings/single_setting_row.tsx @@ -1,20 +1,27 @@ import * as React from "react"; import { Row, Col, Help } from "../../../ui/index"; import { Position } from "@blueprintjs/core"; +import { DeviceSetting } from "../../../constants"; +import { Highlight } from "../maybe_highlight"; +import { t } from "../../../i18next_wrapper"; + +export interface SingleSettingRowProps { + label: DeviceSetting; + tooltip: string; + children: React.ReactChild; + settingType: "button" | "input"; +} export const SingleSettingRow = - ({ label, tooltip, settingType, children }: { - label: string, - tooltip: string, - children: React.ReactChild, - settingType: "button" | "input", - }) => + ({ label, tooltip, settingType, children }: SingleSettingRowProps) => - - - - - {settingType === "button" - ? {children} - : {children}} + + + + + + {settingType === "button" + ? {children} + : {children}} + ; diff --git a/frontend/devices/components/interfaces.ts b/frontend/devices/components/interfaces.ts index a76dd30ec..4142d3284 100644 --- a/frontend/devices/components/interfaces.ts +++ b/frontend/devices/components/interfaces.ts @@ -6,6 +6,7 @@ import { McuParamName, McuParams, FirmwareHardware } from "farmbot/dist"; import { IntegerSize } from "../../util"; import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware"; import { ResourceIndex } from "../../resources/interfaces"; +import { DeviceSetting } from "../../constants"; export interface ZeroRowProps { botDisconnected: boolean; @@ -25,7 +26,7 @@ export interface BooleanMCUInputGroupProps { sourceFwConfig: SourceFwConfig; dispatch: Function; tooltip: string; - name: string; + label: DeviceSetting; x: McuParamName; y: McuParamName; z: McuParamName; @@ -41,7 +42,7 @@ export interface CalibrationRowProps { botDisconnected: boolean; action(axis: Axis): void; toolTip: string; - title: string; + title: DeviceSetting; axisTitle: string; } @@ -49,7 +50,7 @@ export interface NumericMCUInputGroupProps { sourceFwConfig: SourceFwConfig; dispatch: Function; tooltip: string; - name: string; + label: DeviceSetting; x: McuParamName; xScale?: number; y: McuParamName; diff --git a/frontend/devices/components/maybe_highlight.tsx b/frontend/devices/components/maybe_highlight.tsx new file mode 100644 index 000000000..eab806b0f --- /dev/null +++ b/frontend/devices/components/maybe_highlight.tsx @@ -0,0 +1,162 @@ +import * as React from "react"; +import { ControlPanelState } from "../interfaces"; +import { toggleControlPanel } from "../actions"; +import { urlFriendly } from "../../util"; +import { DeviceSetting } from "../../constants"; + +const HOMING_PANEL = [ + DeviceSetting.homingAndCalibration, + DeviceSetting.homing, + DeviceSetting.calibration, + DeviceSetting.setZeroPosition, + DeviceSetting.findHomeOnBoot, + DeviceSetting.stopAtHome, + DeviceSetting.stopAtMax, + DeviceSetting.negativeCoordinatesOnly, + DeviceSetting.axisLength, +]; +const MOTORS_PANEL = [ + DeviceSetting.motors, + DeviceSetting.maxSpeed, + DeviceSetting.homingSpeed, + DeviceSetting.minimumSpeed, + DeviceSetting.accelerateFor, + DeviceSetting.stepsPerMm, + DeviceSetting.microstepsPerStep, + DeviceSetting.alwaysPowerMotors, + DeviceSetting.invertMotors, + DeviceSetting.motorCurrent, + DeviceSetting.enable2ndXMotor, + DeviceSetting.invert2ndXMotor, +]; +const ENCODERS_PANEL = [ + DeviceSetting.encoders, + DeviceSetting.stallDetection, + DeviceSetting.enableEncoders, + DeviceSetting.enableStallDetection, + DeviceSetting.stallSensitivity, + DeviceSetting.useEncodersForPositioning, + DeviceSetting.invertEncoders, + DeviceSetting.maxMissedSteps, + DeviceSetting.missedStepDecay, + DeviceSetting.encoderScaling, +]; +const ENDSTOPS_PANEL = [ + DeviceSetting.endstops, + DeviceSetting.enableEndstops, + DeviceSetting.swapEndstops, + DeviceSetting.invertEndstops, +]; +const ERROR_HANDLING_PANEL = [ + DeviceSetting.errorHandling, + DeviceSetting.timeoutAfter, + DeviceSetting.maxRetries, + DeviceSetting.estopOnMovementError, +]; +const PIN_GUARD_PANEL = [ + DeviceSetting.pinGuard, +]; +const DANGER_ZONE_PANEL = [ + DeviceSetting.dangerZone, + DeviceSetting.resetHardwareParams, +]; +const PIN_BINDINGS_PANEL = [ + DeviceSetting.pinBindings, +]; +const POWER_AND_RESET_PANEL = [ + DeviceSetting.powerAndReset, + DeviceSetting.restartFarmbot, + DeviceSetting.shutdownFarmbot, + DeviceSetting.restartFirmware, + DeviceSetting.factoryReset, + DeviceSetting.autoFactoryReset, + DeviceSetting.connectionAttemptPeriod, + DeviceSetting.changeOwnership, +]; + +/** Look up parent panels for settings. */ +const SETTING_PANEL_LOOKUP = {} as Record; +HOMING_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "homing_and_calibration"); +MOTORS_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "motors"); +ENCODERS_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "encoders"); +ENDSTOPS_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "endstops"); +ERROR_HANDLING_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "error_handling"); +PIN_GUARD_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "pin_guard"); +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"); + +/** 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); + +/** Look up all relevant names for the same setting. */ +const ALTERNATE_NAMES = + Object.values(DeviceSetting).reduce((acc, s) => { acc[s] = [s]; return acc; }, + {} as Record); +ALTERNATE_NAMES[DeviceSetting.encoders].push(DeviceSetting.stallDetection); +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)); + +/** Retrieve a highlight search term. */ +const getHighlightName = () => location.search.split("?highlight=").pop(); + +/** Only open panel and highlight once per app load. Exported for tests. */ +export const highlight = { opened: false, highlighted: false }; + +/** Open a panel if a setting in that panel is highlighted. */ +export const maybeOpenPanel = (panelState: ControlPanelState) => + (dispatch: Function) => { + if (highlight.opened) { return; } + const urlFriendlySettingName = urlFriendly(getHighlightName() || ""); + if (!urlFriendlySettingName) { return; } + const panel = URL_FRIENDLY_LOOKUP[urlFriendlySettingName]; + const panelIsOpen = panelState[panel]; + if (panelIsOpen) { return; } + dispatch(toggleControlPanel(panel)); + highlight.opened = true; + }; + +/** Highlight a setting if provided as a search term. */ +export const maybeHighlight = (settingName: DeviceSetting) => { + const item = getHighlightName(); + if (highlight.highlighted || !item) { return ""; } + const isCurrentSetting = compareValues(settingName).includes(item); + if (!isCurrentSetting) { return ""; } + highlight.highlighted = true; + return "highlight"; +}; + +export interface HighlightProps { + settingName: DeviceSetting; + children: React.ReactChild + | React.ReactChild[] + | (React.ReactChild | React.ReactChild[])[]; + className?: string; +} + +interface HighlightState { + className: string; +} + +/** Wrap highlight-able settings. */ +export class Highlight extends React.Component { + state: HighlightState = { className: maybeHighlight(this.props.settingName) }; + + componentDidMount = () => { + if (this.state.className == "highlight") { + /** Slowly fades highlight. */ + this.setState({ className: "unhighlight" }); + } + } + + render() { + return
+ {this.props.children} +
; + } +} diff --git a/frontend/devices/components/numeric_mcu_input_group.tsx b/frontend/devices/components/numeric_mcu_input_group.tsx index 13397af32..c76b94041 100644 --- a/frontend/devices/components/numeric_mcu_input_group.tsx +++ b/frontend/devices/components/numeric_mcu_input_group.tsx @@ -3,48 +3,52 @@ import { McuInputBox } from "./mcu_input_box"; import { NumericMCUInputGroupProps } from "./interfaces"; import { Row, Col, Help } from "../../ui/index"; import { Position } from "@blueprintjs/core"; +import { Highlight } from "./maybe_highlight"; +import { t } from "../../i18next_wrapper"; export function NumericMCUInputGroup(props: NumericMCUInputGroupProps) { const { - sourceFwConfig, dispatch, tooltip, name, x, y, z, intSize, gray, float, + sourceFwConfig, dispatch, tooltip, label, x, y, z, intSize, gray, float, } = props; return - - - - - - - - - - - - - + + + + + + + + + + + + + + + ; } diff --git a/frontend/devices/interfaces.ts b/frontend/devices/interfaces.ts index 767ea8bd5..c74ff5e9f 100644 --- a/frontend/devices/interfaces.ts +++ b/frontend/devices/interfaces.ts @@ -201,6 +201,7 @@ export interface PeripheralsProps { peripherals: TaggedPeripheral[]; dispatch: Function; disabled: boolean | undefined; + firmwareHardware: FirmwareHardware | undefined; } export interface SensorsProps { @@ -208,6 +209,7 @@ export interface SensorsProps { sensors: TaggedSensor[]; dispatch: Function; disabled: boolean | undefined; + firmwareHardware: FirmwareHardware | undefined; } export interface FarmwareProps { @@ -248,8 +250,8 @@ export interface ControlPanelState { encoders: boolean; endstops: boolean; error_handling: boolean; - pin_bindings: boolean; - danger_zone: boolean; - power_and_reset: boolean; pin_guard: boolean; + danger_zone: boolean; + pin_bindings: boolean; + power_and_reset: boolean; } diff --git a/frontend/devices/pin_bindings/list_and_label_support.tsx b/frontend/devices/pin_bindings/list_and_label_support.tsx index 09ea49a71..ddc7be7c6 100644 --- a/frontend/devices/pin_bindings/list_and_label_support.tsx +++ b/frontend/devices/pin_bindings/list_and_label_support.tsx @@ -89,17 +89,17 @@ export const piSpi1Pins = [16, 17, 18, 19, 20, 21]; /** Pin numbers used for special purposes by the RPi. (internal pullup, etc.) */ export const reservedPiGPIO = piI2c0Pins; -const LabeledGpioPins: { [x: number]: string } = { - [ButtonPin.estop]: "Button 1: E-STOP", - [ButtonPin.unlock]: "Button 2: UNLOCK", - [ButtonPin.btn3]: "Button 3", - [ButtonPin.btn4]: "Button 4", - [ButtonPin.btn5]: "Button 5", -}; +const GPIO_PIN_LABELS = (): { [x: number]: string } => ({ + [ButtonPin.estop]: t("Button {{ num }}: E-STOP", { num: 1 }), + [ButtonPin.unlock]: t("Button {{ num }}: UNLOCK", { num: 2 }), + [ButtonPin.btn3]: t("Button {{ num }})", { num: 3 }), + [ButtonPin.btn4]: t("Button {{ num }}", { num: 4 }), + [ButtonPin.btn5]: t("Button {{ num }}", { num: 5 }), +}); export const generatePinLabel = (pin: number) => - LabeledGpioPins[pin] - ? `${LabeledGpioPins[pin]} (Pi ${pin})` + GPIO_PIN_LABELS()[pin] + ? `${t(GPIO_PIN_LABELS()[pin])} (Pi ${pin})` : `Pi GPIO ${pin}`; /** Raspberry Pi GPIO pin numbers. */ diff --git a/frontend/external_urls.ts b/frontend/external_urls.ts index 47e123e98..6caa90822 100644 --- a/frontend/external_urls.ts +++ b/frontend/external_urls.ts @@ -20,10 +20,11 @@ export namespace ExternalUrl { const GITHUB_API = "https://api.github.com"; const OPENFARM = "https://openfarm.cc"; const SOFTWARE_DOCS = "https://software.farm.bot"; - const FORUM = "http://forum.farmbot.org"; + const FORUM = "https://forum.farmbot.org"; const SHOPIFY_CDN = "https://cdn.shopify.com/s/files/1/2040/0289/files"; - const FBOS_RAW = `${GITHUB_RAW}/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}`; + const FBOS_RAW = + `${GITHUB_RAW}/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}/staging`; export const featureMinVersions = `${FBOS_RAW}/${FbosFile.featureMinVersions}`; export const osReleaseNotes = `${FBOS_RAW}/${FbosFile.osReleaseNotes}`; @@ -31,8 +32,7 @@ export namespace ExternalUrl { `${GITHUB_API}/repos/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}/releases/latest`; export const gitHubFarmBot = `${GITHUB}/${Org.FarmBot}`; - export const webAppRepo = - `${GITHUB}/${Org.FarmBot}/${FarmBotRepo.FarmBotWebApp}`; + export const webAppRepo = `${gitHubFarmBot}/${FarmBotRepo.FarmBotWebApp}`; export const softwareDocs = `${SOFTWARE_DOCS}/docs`; export const softwareForum = `${FORUM}/c/software`; @@ -43,7 +43,7 @@ export namespace ExternalUrl { export const newCrop = `${OPENFARM}/en/crops/new`; } - export namespace Videos { + export namespace Video { export const desktop = `${SHOPIFY_CDN}/Farm_Designer_Loop.mp4?9552037556691879018`; export const mobile = `${SHOPIFY_CDN}/Controls.png?9668345515035078097`; diff --git a/frontend/farm_designer/map/garden_map.tsx b/frontend/farm_designer/map/garden_map.tsx index 90c76eafb..063c5ccbf 100644 --- a/frontend/farm_designer/map/garden_map.tsx +++ b/frontend/farm_designer/map/garden_map.tsx @@ -386,6 +386,7 @@ export class GardenMap extends visible={!!this.props.showPlants} plants={this.props.plants} currentPlant={this.getPlant()} + hoveredPlant={this.props.hoveredPlant} dragging={!!this.state.isDragging} editing={this.isEditing} boxSelected={this.props.designer.selectedPlants} diff --git a/frontend/farm_designer/map/interfaces.ts b/frontend/farm_designer/map/interfaces.ts index 8b647c2fc..aca542188 100644 --- a/frontend/farm_designer/map/interfaces.ts +++ b/frontend/farm_designer/map/interfaces.ts @@ -14,6 +14,7 @@ export type TaggedPlant = TaggedPlantPointer | TaggedPlantTemplate; export interface PlantLayerProps { plants: TaggedPlant[]; currentPlant: TaggedPlant | undefined; + hoveredPlant: TaggedPlant | undefined; dragging: boolean; editing: boolean; visible: boolean; @@ -57,12 +58,14 @@ export interface GardenPlantProps { dispatch: Function; plant: Readonly; selected: boolean; + current: boolean; editing: boolean; dragging: boolean; zoomLvl: number; activeDragXY: BotPosition | undefined; uuid: string; animate: boolean; + hovered: boolean; } export interface GardenPlantState { diff --git a/frontend/farm_designer/map/layers/farmbot/__tests__/bot_figure_test.tsx b/frontend/farm_designer/map/layers/farmbot/__tests__/bot_figure_test.tsx index b17fa5345..d523f8f1e 100644 --- a/frontend/farm_designer/map/layers/farmbot/__tests__/bot_figure_test.tsx +++ b/frontend/farm_designer/map/layers/farmbot/__tests__/bot_figure_test.tsx @@ -15,17 +15,19 @@ describe("", () => { plantAreaOffset: { x: 100, y: 100 }, }); + const EXPECTED_MOTORS_OPACITY = 0.5; + it.each<[ string, BotOriginQuadrant, Record<"x" | "y", number>, boolean, number ]>([ - ["motors", 1, { x: 3000, y: 0 }, false, 0.75], - ["motors", 2, { x: 0, y: 0 }, false, 0.75], - ["motors", 3, { x: 0, y: 1500 }, false, 0.75], - ["motors", 4, { x: 3000, y: 1500 }, false, 0.75], - ["motors", 1, { x: 0, y: 1500 }, true, 0.75], - ["motors", 2, { x: 0, y: 0 }, true, 0.75], - ["motors", 3, { x: 3000, y: 0 }, true, 0.75], - ["motors", 4, { x: 3000, y: 1500 }, true, 0.75], + ["motors", 1, { x: 3000, y: 0 }, false, EXPECTED_MOTORS_OPACITY], + ["motors", 2, { x: 0, y: 0 }, false, EXPECTED_MOTORS_OPACITY], + ["motors", 3, { x: 0, y: 1500 }, false, EXPECTED_MOTORS_OPACITY], + ["motors", 4, { x: 3000, y: 1500 }, false, EXPECTED_MOTORS_OPACITY], + ["motors", 1, { x: 0, y: 1500 }, true, EXPECTED_MOTORS_OPACITY], + ["motors", 2, { x: 0, y: 0 }, true, EXPECTED_MOTORS_OPACITY], + ["motors", 3, { x: 3000, y: 0 }, true, EXPECTED_MOTORS_OPACITY], + ["motors", 4, { x: 3000, y: 1500 }, true, EXPECTED_MOTORS_OPACITY], ["encoders", 2, { x: 0, y: 0 }, false, 0.25], ])("shows %s in correct location for quadrant %i", (name, quadrant, expected, xySwap, opacity) => { diff --git a/frontend/farm_designer/map/layers/farmbot/bot_figure.tsx b/frontend/farm_designer/map/layers/farmbot/bot_figure.tsx index bf07f7de2..59d699041 100644 --- a/frontend/farm_designer/map/layers/farmbot/bot_figure.tsx +++ b/frontend/farm_designer/map/layers/farmbot/bot_figure.tsx @@ -31,7 +31,7 @@ export class BotFigure extends const positionQ = transformXY( (position.x || 0), (position.y || 0), mapTransformProps); const color = eStopStatus ? Color.virtualRed : Color.darkGray; - const opacity = name.includes("encoder") ? 0.25 : 0.75; + const opacity = name.includes("encoder") ? 0.25 : 0.5; return ", () => { return { mapTransformProps: fakeMapTransformProps(), plant: fakePlant(), + current: false, selected: false, editing: false, dragging: false, @@ -21,6 +22,7 @@ describe("", () => { activeDragXY: { x: undefined, y: undefined, z: undefined }, uuid: "plantUuid", animate: false, + hovered: false, }; } @@ -31,6 +33,8 @@ describe("", () => { const wrapper = shallow(); expect(wrapper.find("image").length).toEqual(1); expect(wrapper.find("image").props().opacity).toEqual(1); + expect(wrapper.find("image").props().visibility).toEqual("visible"); + expect(wrapper.find("image").props().opacity).toEqual(1.0); expect(wrapper.find("text").length).toEqual(0); expect(wrapper.find("rect").length).toBeLessThanOrEqual(1); expect(wrapper.find("use").length).toEqual(0); @@ -88,4 +92,21 @@ describe("", () => { expect(wrapper.find(".plant-indicator").length).toEqual(1); expect(wrapper.find("Circle").length).toEqual(1); }); + + it("doesn't render indicator circle twice", () => { + const p = fakeProps(); + p.selected = true; + p.hovered = true; + const wrapper = shallow(); + expect(wrapper.find(".plant-indicator").length).toEqual(0); + expect(wrapper.find("Circle").length).toEqual(0); + }); + + it("renders while dragging", () => { + const p = fakeProps(); + p.dragging = true; + const wrapper = shallow(); + expect(wrapper.find("image").props().visibility).toEqual("hidden"); + expect(wrapper.find("image").props().opacity).toEqual(0.4); + }); }); diff --git a/frontend/farm_designer/map/layers/plants/__tests__/plant_layer_test.tsx b/frontend/farm_designer/map/layers/plants/__tests__/plant_layer_test.tsx index b981856b5..09dc7f1c1 100644 --- a/frontend/farm_designer/map/layers/plants/__tests__/plant_layer_test.tsx +++ b/frontend/farm_designer/map/layers/plants/__tests__/plant_layer_test.tsx @@ -8,11 +8,13 @@ import { PlantLayer } from "../plant_layer"; import { fakePlant, fakePlantTemplate } from "../../../../../__test_support__/fake_state/resources"; -import { PlantLayerProps, GardenPlantProps } from "../../../interfaces"; +import { PlantLayerProps } from "../../../interfaces"; import { fakeMapTransformProps } from "../../../../../__test_support__/map_transform_props"; import { svgMount } from "../../../../../__test_support__/svg_mount"; +import { shallow } from "enzyme"; +import { GardenPlant } from "../garden_plant"; describe("", () => { const fakeProps = (): PlantLayerProps => ({ @@ -28,6 +30,7 @@ describe("", () => { zoomLvl: 1, activeDragXY: { x: undefined, y: undefined, z: undefined }, animate: true, + hoveredPlant: undefined, }); it("shows plants", () => { @@ -88,14 +91,14 @@ describe("", () => { .toEqual("/app/designer/gardens/templates/5"); }); - it("has selected plant", () => { + it("has hovered plant", () => { mockPath = "/app/designer/plants"; const p = fakeProps(); const plant = fakePlant(); p.plants = [plant]; - p.currentPlant = plant; - const wrapper = svgMount(); - expect(wrapper.find("GardenPlant").props().selected).toEqual(true); + p.hoveredPlant = plant; + const wrapper = shallow(); + expect(wrapper.find(GardenPlant).props().hovered).toEqual(true); }); it("has plant selected by selection box", () => { @@ -105,8 +108,7 @@ describe("", () => { p.plants = [plant]; p.boxSelected = [plant.uuid]; const wrapper = svgMount(); - expect((wrapper.find("GardenPlant").props() as GardenPlantProps).selected) - .toEqual(true); + expect(wrapper.find("GardenPlant").props().selected).toEqual(true); }); it("allows clicking of unsaved plants", () => { diff --git a/frontend/farm_designer/map/layers/plants/garden_plant.tsx b/frontend/farm_designer/map/layers/plants/garden_plant.tsx index cc3a2b112..8bdec572e 100644 --- a/frontend/farm_designer/map/layers/plants/garden_plant.tsx +++ b/frontend/farm_designer/map/layers/plants/garden_plant.tsx @@ -36,22 +36,22 @@ export class GardenPlant extends }; get radius() { - const { selected, plant } = this.props; + const { plant } = this.props; const { hover } = this.state; const { radius } = plant.body; - return (hover && !selected) ? radius * 1.1 : radius; + return hover ? radius * 1.1 : radius; } render() { - const { selected, dragging, plant, mapTransformProps, - activeDragXY, zoomLvl, animate, editing } = this.props; + const { current, selected, dragging, plant, mapTransformProps, + activeDragXY, zoomLvl, animate, editing, hovered } = this.props; const { id, radius, x, y } = plant.body; const { icon } = this.state; const { qx, qy } = transformXY(round(x), round(y), mapTransformProps); const alpha = dragging ? 0.4 : 1.0; const className = [ - "plant-image", `is-chosen-${selected}`, animate ? "animate" : "" + "plant-image", `is-chosen-${current || selected}`, animate ? "animate" : "" ].join(" "); return @@ -65,7 +65,7 @@ export class GardenPlant extends fill={Color.soilCloud} fillOpacity={0} />} - {selected && !editing && + {(current || selected) && !editing && !hovered && {visible && plants.map(p => { - const selected = !!(p.uuid === currentPlant?.uuid); + const current = p.uuid === currentPlant?.uuid; + const hovered = p.uuid === hoveredPlant?.uuid; const selectedByBox = !!boxSelected?.includes(p.uuid); const selectedByGroup = groupSelected.includes(p.uuid); const plantCategory = unpackUUID(p.uuid).kind === "PlantTemplate" @@ -33,12 +35,14 @@ export function PlantLayer(props: PlantLayerProps) { uuid={p.uuid} mapTransformProps={mapTransformProps} plant={p} - selected={selected || selectedByBox || selectedByGroup} + selected={selectedByBox || selectedByGroup} + current={current} editing={editing} - dragging={selected && dragging && editing} + dragging={current && dragging && editing} dispatch={dispatch} zoomLvl={zoomLvl} activeDragXY={activeDragXY} + hovered={hovered} animate={animate} />; const wrapperProps = { className: "plant-link-wrapper", 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 75451aefc..f47a485eb 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 @@ -66,12 +66,12 @@ describe("", () => { expect(wrapper.find("text").props().dx).toEqual(-40); }); - it("displays 'no tool'", () => { + it("displays 'empty'", () => { const p = fakeProps(); p.slot.tool = undefined; p.hoveredToolSlot = p.slot.toolSlot.uuid; const wrapper = svgMount(); - expect(wrapper.find("text").text()).toEqual("no tool"); + expect(wrapper.find("text").text()).toEqual("empty"); expect(wrapper.find("text").props().dx).toEqual(40); }); diff --git a/frontend/farm_designer/map/layers/tool_slots/tool_slot_point.tsx b/frontend/farm_designer/map/layers/tool_slots/tool_slot_point.tsx index ddb889b00..e34858e6d 100644 --- a/frontend/farm_designer/map/layers/tool_slots/tool_slot_point.tsx +++ b/frontend/farm_designer/map/layers/tool_slots/tool_slot_point.tsx @@ -32,7 +32,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 : "no tool"; + const toolName = props.slot.tool ? props.slot.tool.body.name : "empty"; const hovered = props.slot.toolSlot.uuid === props.hoveredToolSlot; const toolProps = { x: qx, 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 2d7909c5b..1054d9806 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 @@ -40,6 +40,7 @@ describe("", () => { allPoints: [], shouldDisplay: () => true, slugs: [], + hovered: undefined, }; }; diff --git a/frontend/farm_designer/point_groups/group_detail.tsx b/frontend/farm_designer/point_groups/group_detail.tsx index c9bf393d9..506c5a0ed 100644 --- a/frontend/farm_designer/point_groups/group_detail.tsx +++ b/frontend/farm_designer/point_groups/group_detail.tsx @@ -10,6 +10,7 @@ import { GroupDetailActive } from "./group_detail_active"; import { ShouldDisplay } from "../../devices/interfaces"; import { getShouldDisplayFn } from "../../farmware/state_to_props"; import { uniq } from "lodash"; +import { UUID } from "../../resources/interfaces"; interface GroupDetailProps { dispatch: Function; @@ -17,6 +18,7 @@ interface GroupDetailProps { allPoints: TaggedPoint[]; shouldDisplay: ShouldDisplay; slugs: string[]; + hovered: UUID | undefined; } /** Find a group from a URL-provided ID. */ @@ -35,6 +37,7 @@ function mapStateToProps(props: Everything): GroupDetailProps { shouldDisplay: getShouldDisplayFn(props.resources.index, props.bot), slugs: uniq(selectAllPlantPointers(props.resources.index) .map(p => p.body.openfarm_slug)), + hovered: props.resources.consumers.farm_designer.hoveredPlantListItem, }; } diff --git a/frontend/farm_designer/point_groups/group_detail_active.tsx b/frontend/farm_designer/point_groups/group_detail_active.tsx index b56ed3e71..5c50e92a3 100644 --- a/frontend/farm_designer/point_groups/group_detail_active.tsx +++ b/frontend/farm_designer/point_groups/group_detail_active.tsx @@ -18,6 +18,7 @@ import { GroupCriteria, GroupPointCountBreakdown, pointsSelectedByGroup } from "./criteria"; import { Content } from "../../constants"; +import { UUID } from "../../resources/interfaces"; export interface GroupDetailActiveProps { dispatch: Function; @@ -25,6 +26,7 @@ export interface GroupDetailActiveProps { allPoints: TaggedPoint[]; shouldDisplay: ShouldDisplay; slugs: string[]; + hovered: UUID | undefined; } type State = { timerId?: ReturnType }; @@ -47,7 +49,7 @@ export class GroupDetailActive return sortedPoints.map(point => { return ; diff --git a/frontend/farm_designer/point_groups/group_order_visual.tsx b/frontend/farm_designer/point_groups/group_order_visual.tsx index 7f4aead09..cdf9f21cc 100644 --- a/frontend/farm_designer/point_groups/group_order_visual.tsx +++ b/frontend/farm_designer/point_groups/group_order_visual.tsx @@ -36,7 +36,7 @@ export interface PointsPathLineProps { } export const PointsPathLine = (props: PointsPathLineProps) => - diff --git a/frontend/farm_designer/point_groups/point_group_item.tsx b/frontend/farm_designer/point_groups/point_group_item.tsx index cee43c163..830c25bbf 100644 --- a/frontend/farm_designer/point_groups/point_group_item.tsx +++ b/frontend/farm_designer/point_groups/point_group_item.tsx @@ -93,6 +93,7 @@ export class PointGroupItem style={{ border: this.criteriaIcon ? "1px solid gray" : "none", borderRadius: "5px", + background: this.props.hovered ? "lightgray" : "none", }} src={DEFAULT_ICON} onLoad={this.maybeGetCachedIcon} 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 616f081d9..76a5c991b 100644 --- a/frontend/farm_designer/tools/__tests__/add_tool_slot_test.tsx +++ b/frontend/farm_designer/tools/__tests__/add_tool_slot_test.tsx @@ -22,6 +22,7 @@ import { import { init, save, edit, destroy } from "../../../api/crud"; import { history } from "../../../history"; import { SpecialStatus } from "farmbot"; +import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources"; describe("", () => { const fakeProps = (): AddToolSlotProps => ({ @@ -30,14 +31,20 @@ describe("", () => { botPosition: { x: undefined, y: undefined, z: undefined }, dispatch: jest.fn(), findToolSlot: fakeToolSlot, + firmwareHardware: undefined, }); it("renders", () => { const wrapper = mount(); - ["add new tool slot", "x (mm)", "y (mm)", "z (mm)", "toolnone", + ["add new tool slot", "x (mm)", "y (mm)", "z (mm)", "tool or seed container", "change slot direction", "use current location", "gantry-mounted" ].map(string => expect(wrapper.text().toLowerCase()).toContain(string)); - expect(init).toHaveBeenCalled(); + expect(init).toHaveBeenCalledWith("Point", { + pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {}, + x: 0, y: 0, z: 0, tool_id: undefined, + pullout_direction: ToolPulloutDirection.NONE, + gantry_mounted: false, + }); }); it("renders while loading", () => { @@ -102,6 +109,19 @@ describe("", () => { const wrapper = mount(); expect(wrapper.instance().tool).toEqual(undefined); }); + + it("renders for express bots", () => { + const p = fakeProps(); + p.firmwareHardware = "express_k10"; + const wrapper = mount(); + expect(wrapper.text().toLowerCase()).not.toContain("tool"); + expect(init).toHaveBeenCalledWith("Point", { + pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {}, + x: 0, y: 0, z: 0, tool_id: undefined, + pullout_direction: ToolPulloutDirection.NONE, + gantry_mounted: true, + }); + }); }); describe("mapStateToProps()", () => { diff --git a/frontend/farm_designer/tools/__tests__/add_tool_test.tsx b/frontend/farm_designer/tools/__tests__/add_tool_test.tsx index e982afc55..c8d06b478 100644 --- a/frontend/farm_designer/tools/__tests__/add_tool_test.tsx +++ b/frontend/farm_designer/tools/__tests__/add_tool_test.tsx @@ -11,11 +11,13 @@ import { fakeState } from "../../../__test_support__/fake_state"; import { SaveBtn } from "../../../ui"; import { initSave } from "../../../api/crud"; import { history } from "../../../history"; -import { error } from "../../../toast/toast"; +import { FirmwareHardware } from "farmbot"; describe("", () => { const fakeProps = (): AddToolProps => ({ dispatch: jest.fn(), + existingToolNames: [], + firmwareHardware: undefined, }); it("renders", () => { @@ -38,19 +40,29 @@ describe("", () => { expect(initSave).toHaveBeenCalledWith("Tool", { name: "Foo" }); }); - it("doesn't add stock tools", () => { - const wrapper = mount(); + it.each<[FirmwareHardware, number]>([ + ["arduino", 6], + ["farmduino", 6], + ["farmduino_k14", 6], + ["farmduino_k15", 8], + ["express_k10", 2], + ])("adds peripherals: %s", (firmware, expectedAdds) => { + const p = fakeProps(); + p.firmwareHardware = firmware; + const wrapper = mount(); wrapper.find("button").last().simulate("click"); - expect(error).toHaveBeenCalledWith("Please choose a FarmBot model."); - expect(initSave).not.toHaveBeenCalledTimes(6); - expect(history.push).not.toHaveBeenCalledWith("/app/designer/tools"); + expect(initSave).toHaveBeenCalledTimes(expectedAdds); + expect(history.push).toHaveBeenCalledWith("/app/designer/tools"); }); - it("adds stock tools", () => { - const wrapper = mount(); + it("doesn't add stock tools twice", () => { + const p = fakeProps(); + 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(2); + expect(initSave).toHaveBeenCalledTimes(1); expect(history.push).toHaveBeenCalledWith("/app/designer/tools"); }); }); 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 f251c62e0..7057f20dd 100644 --- a/frontend/farm_designer/tools/__tests__/edit_tool_slot_test.tsx +++ b/frontend/farm_designer/tools/__tests__/edit_tool_slot_test.tsx @@ -28,6 +28,7 @@ describe("", () => { findTool: jest.fn(), botPosition: { x: undefined, y: undefined, z: undefined }, dispatch: jest.fn(), + firmwareHardware: undefined, }); it("redirects", () => { @@ -39,7 +40,7 @@ describe("", () => { const p = fakeProps(); p.findToolSlot = () => fakeToolSlot(); const wrapper = mount(); - ["edit tool slot", "x (mm)", "y (mm)", "z (mm)", "toolnone", + ["edit tool slot", "x (mm)", "y (mm)", "z (mm)", "tool or seed container", "change slot direction", "use current location", "gantry-mounted" ].map(string => expect(wrapper.text().toLowerCase()).toContain(string)); }); diff --git a/frontend/farm_designer/tools/__tests__/index_test.tsx b/frontend/farm_designer/tools/__tests__/index_test.tsx index 39e83f38a..c4584f781 100644 --- a/frontend/farm_designer/tools/__tests__/index_test.tsx +++ b/frontend/farm_designer/tools/__tests__/index_test.tsx @@ -39,6 +39,7 @@ describe("", () => { bot, botToMqttStatus: "down", hoveredToolSlot: undefined, + firmwareHardware: undefined, }); it("renders with no tools", () => { @@ -64,7 +65,7 @@ describe("", () => { p.toolSlots[1].body.y = 2; const wrapper = mount(); [ - "foo", "my tool", "unnamed tool", "(1, 0, 0)", "unknown", "(gantry, 2, 0)" + "foo", "my tool", "unnamed", "(1, 0, 0)", "unknown", "(gantry, 2, 0)" ].map(string => expect(wrapper.text().toLowerCase()).toContain(string)); }); @@ -158,6 +159,7 @@ describe("", () => { p.bot.hardware.informational_settings.sync_status = "synced"; p.botToMqttStatus = "up"; const wrapper = mount(); + expect(wrapper.text().toLowerCase()).toContain("mounted tool"); wrapper.find(".yellow").first().simulate("click"); expect(mockDevice.readPin).toHaveBeenCalledWith({ label: "pin63", pin_mode: 0, pin_number: 63 @@ -173,6 +175,13 @@ describe("", () => { expect(mockDevice.readPin).not.toHaveBeenCalled(); expect(error).toHaveBeenCalledWith(Content.NOT_AVAILABLE_WHEN_OFFLINE); }); + + it("doesn't display mounted tool on express models", () => { + const p = fakeProps(); + p.firmwareHardware = "express_k10"; + const wrapper = mount(); + expect(wrapper.text().toLowerCase()).not.toContain("mounted tool"); + }); }); 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 7a58f2c93..861c26b3f 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 @@ -136,6 +136,7 @@ describe("", () => { tools: [], selectedTool: undefined, onChange: jest.fn(), + isExpress: false, }); it("renders", () => { @@ -149,6 +150,13 @@ describe("", () => { const wrapper = mount(); expect(wrapper.text().toLowerCase()).toContain("foo"); }); + + it("renders for express bots", () => { + const p = fakeProps(); + p.isExpress = true; + const wrapper = mount(); + expect(wrapper.text().toLowerCase()).toContain("seed container"); + }); }); describe("", () => { diff --git a/frontend/farm_designer/tools/add_tool.tsx b/frontend/farm_designer/tools/add_tool.tsx index 2eba033c9..0eb08d86d 100644 --- a/frontend/farm_designer/tools/add_tool.tsx +++ b/frontend/farm_designer/tools/add_tool.tsx @@ -5,36 +5,37 @@ import { } from "../designer_panel"; import { Everything } from "../../interfaces"; import { t } from "../../i18next_wrapper"; -import { SaveBtn, FBSelect, DropDownItem } from "../../ui"; -import { SpecialStatus } from "farmbot"; +import { SaveBtn } from "../../ui"; +import { SpecialStatus, FirmwareHardware } from "farmbot"; import { initSave } from "../../api/crud"; import { Panel } from "../panel_header"; import { history } from "../../history"; -import { error } from "../../toast/toast"; - -enum Model { genesis14 = "genesis14", genesis15 = "genesis15", express = "express" } - -const MODEL_DDI_LOOKUP = (): { [x: string]: DropDownItem } => ({ - [Model.genesis14]: { label: t("Genesis v1.2-v1.4"), value: Model.genesis14 }, - [Model.genesis15]: { label: t("Genesis v1.5+"), value: Model.genesis15 }, - [Model.express]: { label: t("Express"), value: Model.express }, -}); +import { selectAllTools } from "../../resources/selectors"; +import { betterCompact } from "../../util"; +import { + isExpressBoard, getFwHardwareValue +} from "../../devices/components/firmware_hardware_support"; +import { getFbosConfig } from "../../resources/getters"; export interface AddToolProps { dispatch: Function; + existingToolNames: string[]; + firmwareHardware: FirmwareHardware | undefined; } export interface AddToolState { toolName: string; - model: Model | undefined; } export const mapStateToProps = (props: Everything): AddToolProps => ({ dispatch: props.dispatch, + existingToolNames: betterCompact(selectAllTools(props.resources.index) + .map(tool => tool.body.name)), + firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)), }); export class RawAddTool extends React.Component { - state: AddToolState = { toolName: "", model: undefined }; + state: AddToolState = { toolName: "" }; newTool = (name: string) => { this.props.dispatch(initSave("Tool", { name })); @@ -45,9 +46,12 @@ export class RawAddTool extends React.Component { history.push("/app/designer/tools"); } - stockToolNames = (model: Model) => { - switch (model) { - case Model.genesis14: + stockToolNames = () => { + switch (this.props.firmwareHardware) { + case "arduino": + case "farmduino": + case "farmduino_k14": + default: return [ t("Seeder"), t("Watering Nozzle"), @@ -56,7 +60,7 @@ export class RawAddTool extends React.Component { t("Seed Bin"), t("Seed Tray"), ]; - case Model.genesis15: + case "farmduino_k15": return [ t("Seeder"), t("Watering Nozzle"), @@ -67,7 +71,7 @@ export class RawAddTool extends React.Component { t("Seed Trough 1"), t("Seed Trough 2"), ]; - case Model.express: + case "express_k10": return [ t("Seed Trough 1"), t("Seed Trough 2"), @@ -77,31 +81,20 @@ export class RawAddTool extends React.Component { AddStockTools = () =>
- - this.setState({ model: ddi.value as Model })} - /> - {this.state.model && -
    - {this.stockToolNames(this.state.model).map(n =>
  • {n}
  • )} -
} + +
    + {this.stockToolNames().map(n =>
  • {n}
  • )} +
@@ -110,12 +103,14 @@ export class RawAddTool extends React.Component { return
- + 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 23de61eb3..ffd4062d8 100644 --- a/frontend/farm_designer/tools/add_tool_slot.tsx +++ b/frontend/farm_designer/tools/add_tool_slot.tsx @@ -6,7 +6,9 @@ import { import { Everything } from "../../interfaces"; import { t } from "../../i18next_wrapper"; import { SaveBtn } from "../../ui"; -import { SpecialStatus, TaggedTool, TaggedToolSlotPointer } from "farmbot"; +import { + SpecialStatus, TaggedTool, TaggedToolSlotPointer, FirmwareHardware +} from "farmbot"; import { init, save, edit, destroy } from "../../api/crud"; import { Panel } from "../panel_header"; import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources"; @@ -18,6 +20,10 @@ import { validBotLocationData } from "../../util"; import { history } from "../../history"; import { SlotEditRows } from "./tool_slot_edit_components"; import { UUID } from "../../resources/interfaces"; +import { + isExpressBoard, getFwHardwareValue +} from "../../devices/components/firmware_hardware_support"; +import { getFbosConfig } from "../../resources/getters"; export interface AddToolSlotProps { tools: TaggedTool[]; @@ -25,6 +31,7 @@ export interface AddToolSlotProps { botPosition: BotPosition; findTool(id: number): TaggedTool | undefined; findToolSlot(uuid: UUID | undefined): TaggedToolSlotPointer | undefined; + firmwareHardware: FirmwareHardware | undefined; } export interface AddToolSlotState { @@ -38,6 +45,7 @@ export const mapStateToProps = (props: Everything): AddToolSlotProps => ({ 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 @@ -48,7 +56,8 @@ export class RawAddToolSlot const action = init("Point", { pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {}, x: 0, y: 0, z: 0, tool_id: undefined, - pullout_direction: ToolPulloutDirection.NONE, gantry_mounted: false + pullout_direction: ToolPulloutDirection.NONE, + gantry_mounted: isExpressBoard(this.props.firmwareHardware) ? true : false, }); this.setState({ uuid: action.payload.uuid }); this.props.dispatch(action); @@ -57,7 +66,7 @@ export class RawAddToolSlot componentWillUnmount() { if (this.state.uuid && this.toolSlot && this.toolSlot.specialStatus == SpecialStatus.DIRTY) { - confirm(t("Save new tool?")) + confirm(t("Save new slot?")) ? this.props.dispatch(save(this.state.uuid)) : this.props.dispatch(destroy(this.state.uuid, true)); } @@ -86,12 +95,15 @@ export class RawAddToolSlot return {this.toolSlot ? { backTo={"/app/designer/tools"} panel={Panel.Tools} /> - + this.setState({ toolName: e.currentTarget.value })} /> diff --git a/frontend/farm_designer/tools/edit_tool_slot.tsx b/frontend/farm_designer/tools/edit_tool_slot.tsx index 21a114be3..210b283d3 100644 --- a/frontend/farm_designer/tools/edit_tool_slot.tsx +++ b/frontend/farm_designer/tools/edit_tool_slot.tsx @@ -6,7 +6,7 @@ import { import { Everything } from "../../interfaces"; import { t } from "../../i18next_wrapper"; import { getPathArray } from "../../history"; -import { TaggedToolSlotPointer, TaggedTool } from "farmbot"; +import { TaggedToolSlotPointer, TaggedTool, FirmwareHardware } from "farmbot"; import { edit, save, destroy } from "../../api/crud"; import { history } from "../../history"; import { Panel } from "../panel_header"; @@ -17,6 +17,10 @@ import { BotPosition } from "../../devices/interfaces"; import { validBotLocationData } from "../../util"; import { SlotEditRows } from "./tool_slot_edit_components"; import { moveAbs } from "../../devices/actions"; +import { + getFwHardwareValue, isExpressBoard +} from "../../devices/components/firmware_hardware_support"; +import { getFbosConfig } from "../../resources/getters"; export interface EditToolSlotProps { findToolSlot(id: string): TaggedToolSlotPointer | undefined; @@ -24,6 +28,7 @@ export interface EditToolSlotProps { findTool(id: number): TaggedTool | undefined; dispatch: Function; botPosition: BotPosition; + firmwareHardware: FirmwareHardware | undefined; } export const mapStateToProps = (props: Everything): EditToolSlotProps => ({ @@ -33,6 +38,7 @@ export const mapStateToProps = (props: Everything): EditToolSlotProps => ({ 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)), }); export class RawEditToolSlot extends React.Component { @@ -64,6 +70,7 @@ export class RawEditToolSlot extends React.Component { panel={Panel.Tools} /> ({ bot: props.bot, botToMqttStatus: getStatus(props.bot.connectivity.uptime["bot.mqtt"]), hoveredToolSlot: props.resources.consumers.farm_designer.hoveredToolSlot, + firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)), }); const toolStatus = (value: number | undefined): string => { @@ -108,6 +115,8 @@ export class RawTools extends React.Component { this.props.botToMqttStatus); } + get isExpress() { return isExpressBoard(this.props.firmwareHardware); } + MountedToolInfo = () =>
@@ -141,10 +150,10 @@ export class RawTools extends React.Component { ToolSlots = () =>
- +
- +
@@ -162,10 +171,10 @@ export class RawTools extends React.Component { Tools = () =>
- +
- +
@@ -176,9 +185,32 @@ export class RawTools extends React.Component { .map(tool => )} + toolName={tool.body.name || t("Unnamed")} />)}
+ get strings() { + return { + placeholder: this.isExpress + ? t("Search your seed containers...") + : t("Search your tools..."), + titleText: this.isExpress + ? t("Add a seed container") + : t("Add a tool or seed container"), + emptyStateText: this.isExpress + ? Content.NO_SEED_CONTAINERS + : Content.NO_TOOLS, + tools: this.isExpress + ? t("seed containers") + : t("tools and seed containers"), + toolSlots: this.isExpress + ? t("seed container slots") + : t("tool slots"), + addSlot: this.isExpress + ? t("Add slot") + : t("Add tool slot"), + }; + } + render() { const panelName = "tools"; const hasTools = this.props.tools.length > 0; @@ -187,18 +219,19 @@ export class RawTools extends React.Component { + title={!hasTools ? this.strings.titleText : undefined}> + placeholder={this.strings.placeholder} /> - + {!this.isExpress && + } @@ -223,7 +256,7 @@ const ToolSlotInventoryItem = (props: ToolSlotInventoryItemProps) => { onMouseLeave={() => props.dispatch(setToolHover(undefined))}> -

{props.getToolName(tool_id) || t("No tool")}

+

{props.getToolName(tool_id) || t("Empty")}

diff --git a/frontend/farm_designer/tools/tool_slot_edit_components.tsx b/frontend/farm_designer/tools/tool_slot_edit_components.tsx index 9a2fc0f86..c1e58ccd0 100644 --- a/frontend/farm_designer/tools/tool_slot_edit_components.tsx +++ b/frontend/farm_designer/tools/tool_slot_edit_components.tsx @@ -97,13 +97,18 @@ export interface ToolInputRowProps { tools: TaggedTool[]; selectedTool: TaggedTool | undefined; onChange(update: { tool_id: number }): void; + isExpress: boolean; } export const ToolInputRow = (props: ToolInputRowProps) =>

- + ): void; + isExpress: boolean; } export const SlotEditRows = (props: SlotEditRowsProps) => @@ -153,16 +159,19 @@ export const SlotEditRows = (props: SlotEditRowsProps) => gantryMounted={props.toolSlot.body.gantry_mounted} onChange={props.updateToolSlot} /> - + {!props.toolSlot.body.gantry_mounted && + } - + {!props.isExpress && + }
; diff --git a/frontend/folders/actions.ts b/frontend/folders/actions.ts index 449501dfb..eac3583da 100644 --- a/frontend/folders/actions.ts +++ b/frontend/folders/actions.ts @@ -40,11 +40,11 @@ export const setFolderName = (id: number, name: string) => { return d(save(folder.uuid)) as Promise<{}>; }; -const DEFAULTS: Folder = { - name: "New Folder", +const DEFAULTS = (): Folder => ({ + name: t("New Folder"), color: "gray", parent_id: 0, -}; +}); export const addNewSequenceToFolder = (folder_id?: number) => { const uuidMap = store.getState().resources.index.byKind["Sequence"]; @@ -67,7 +67,7 @@ export const addNewSequenceToFolder = (folder_id?: number) => { export const createFolder = (config: DeepPartial = {}) => { const d: Function = store.dispatch; - const folder: Folder = { ...DEFAULTS, ...config }; + const folder: Folder = { ...DEFAULTS(), ...config }; const action = initSave("Folder", folder); // tslint:disable-next-line:no-any const p: Promise<{}> = d(action); diff --git a/frontend/front_page/laptop_splash.tsx b/frontend/front_page/laptop_splash.tsx index bc9ddb94f..dc81416fa 100644 --- a/frontend/front_page/laptop_splash.tsx +++ b/frontend/front_page/laptop_splash.tsx @@ -7,7 +7,7 @@ export const LaptopSplash = ({ className }: { className: string }) =>
diff --git a/frontend/help/__tests__/tour_test.tsx b/frontend/help/__tests__/tour_test.tsx index a3c5908ad..38be5e14e 100644 --- a/frontend/help/__tests__/tour_test.tsx +++ b/frontend/help/__tests__/tour_test.tsx @@ -56,7 +56,7 @@ describe("", () => { expect(wrapper.state()).toEqual({ run: true, index: 1, returnPath: "/app/messages" }); - expect(history.push).toHaveBeenCalledWith("/app/tools"); + expect(history.push).toHaveBeenCalledWith("/app/designer/tools"); }); it("navigates through tour: other", () => { diff --git a/frontend/help/__tests__/tours_test.ts b/frontend/help/__tests__/tours_test.ts index 2ab27cf74..98be2687a 100644 --- a/frontend/help/__tests__/tours_test.ts +++ b/frontend/help/__tests__/tours_test.ts @@ -1,7 +1,22 @@ jest.mock("../../history", () => ({ history: { push: jest.fn() } })); -import { tourPageNavigation } from "../tours"; +let mockDev = false; +jest.mock("../../account/dev/dev_support", () => ({ + DevSettings: { + futureFeaturesEnabled: () => mockDev, + } +})); + +import { fakeState } from "../../__test_support__/fake_state"; +const mockState = fakeState(); +jest.mock("../../redux/store", () => ({ + store: { getState: () => mockState }, +})); + +import { tourPageNavigation, TOUR_STEPS, Tours } from "../tours"; import { history } from "../../history"; +import { fakeTool, fakeFbosConfig } from "../../__test_support__/fake_state/resources"; +import { buildResourceIndex } from "../../__test_support__/resource_index_builder"; describe("tourPageNavigation()", () => { const testCase = (el: string) => { @@ -20,8 +35,47 @@ describe("tourPageNavigation()", () => { testCase(".regimen-list-panel"); testCase(".tool-list"); testCase(".toolbay-list"); + testCase(".tools"); + testCase(".tool-slots"); + testCase(".tools-panel"); testCase(".photos"); testCase(".logs-table"); testCase(".app-settings-widget"); }); + + it("includes steps based on tool count", () => { + const getTargets = () => + Object.values(TOUR_STEPS()[Tours.gettingStarted]).map(t => t.target); + mockDev = false; + mockState.resources = buildResourceIndex([]); + expect(getTargets()).not.toContain(".tool-slots"); + mockState.resources = buildResourceIndex([fakeTool()]); + expect(getTargets()).toContain(".tool-slots"); + }); + + it("has correct content based on board version", () => { + const getTitles = () => + Object.values(TOUR_STEPS()[Tours.gettingStarted]).map(t => t.title); + mockDev = false; + mockState.resources = buildResourceIndex([]); + expect(getTitles()).toContain("Add tools and tool slots"); + expect(getTitles()).not.toContain("Add seed containers"); + const fbosConfig = fakeFbosConfig(); + fbosConfig.body.firmware_hardware = "express_k10"; + mockState.resources = buildResourceIndex([fbosConfig]); + expect(getTitles()).toContain("Add seed containers and slots"); + expect(getTitles()).not.toContain("Add seed containers"); + mockState.resources = buildResourceIndex([fbosConfig, fakeTool()]); + expect(getTitles()).not.toContain("Add seed containers and slots"); + expect(getTitles()).toContain("Add seed containers"); + }); + + it("includes correct tour steps", () => { + mockDev = true; + const targets = + Object.values(TOUR_STEPS()[Tours.gettingStarted]).map(t => t.target); + expect(targets).not.toContain(".tools"); + expect(targets).toContain(".tool-list"); + expect(targets).toContain(".toolbay-list"); + }); }); diff --git a/frontend/help/tour.tsx b/frontend/help/tour.tsx index ece8f5064..3f2e6408f 100644 --- a/frontend/help/tour.tsx +++ b/frontend/help/tour.tsx @@ -6,6 +6,7 @@ import { TOUR_STEPS, tourPageNavigation } from "./tours"; import { t } from "../i18next_wrapper"; import { Actions } from "../constants"; import { store } from "../redux/store"; +import { ErrorBoundary } from "../error_boundary"; const strings = () => ({ back: t("Back"), @@ -65,15 +66,17 @@ export class Tour extends React.Component { return step; }); return
- + + +
; } } diff --git a/frontend/help/tours.ts b/frontend/help/tours.ts index f01fe0dec..fc4339da5 100644 --- a/frontend/help/tours.ts +++ b/frontend/help/tours.ts @@ -2,6 +2,13 @@ import { history } from "../history"; import { Step as TourStep } from "react-joyride"; import { TourContent } from "../constants"; import { t } from "../i18next_wrapper"; +import { DevSettings } from "../account/dev/dev_support"; +import { selectAllTools } from "../resources/selectors"; +import { store } from "../redux/store"; +import { getFbosConfig } from "../resources/getters"; +import { + isExpressBoard, getFwHardwareValue +} from "../devices/components/firmware_hardware_support"; export enum Tours { gettingStarted = "gettingStarted", @@ -15,70 +22,105 @@ export const tourNames = () => [ { name: Tours.funStuff, description: t("find new features") }, ]; +const hasTools = () => + selectAllTools(store.getState().resources.index).length > 0; + +const isExpress = () => + isExpressBoard(getFwHardwareValue( + getFbosConfig(store.getState().resources.index))); + +const toolsStep = () => hasTools() + ? [{ + target: ".tools", + content: isExpress() + ? t(TourContent.ADD_SEED_CONTAINERS) + : t(TourContent.ADD_TOOLS), + title: isExpress() + ? t("Add seed containers") + : t("Add tools and seed containers"), + }] + : [{ + target: ".tools", + content: isExpress() + ? t(TourContent.ADD_SEED_CONTAINERS_AND_SLOTS) + : t(TourContent.ADD_TOOLS_AND_SLOTS), + title: isExpress() + ? t("Add seed containers and slots") + : t("Add tools and tool slots"), + }]; + +const toolSlotsStep = () => hasTools() + ? [{ + target: ".tool-slots", + content: t(TourContent.ADD_TOOLS_AND_SLOTS), + title: t("Add tool slots"), + }] + : []; + export const TOUR_STEPS = (): { [x: string]: TourStep[] } => ({ [Tours.gettingStarted]: [ { target: ".plant-inventory-panel", - content: TourContent.ADD_PLANTS, + content: t(TourContent.ADD_PLANTS), title: t("Add plants"), }, - { + ...(DevSettings.futureFeaturesEnabled() ? [{ target: ".tool-list", - content: TourContent.ADD_TOOLS, - title: t("Add tools"), - }, - { + content: t(TourContent.ADD_TOOLS), + title: t("Add tools and seed containers"), + }] : toolsStep()), + ...(DevSettings.futureFeaturesEnabled() ? [{ target: ".toolbay-list", - content: TourContent.ADD_TOOLS_SLOTS, + content: t(TourContent.ADD_TOOLS_SLOTS), title: t("Add tools to tool bay"), - }, + }] : toolSlotsStep()), { target: ".peripherals-widget", - content: TourContent.ADD_PERIPHERALS, + content: t(TourContent.ADD_PERIPHERALS), title: t("Add peripherals"), }, { target: ".sequence-list-panel", - content: TourContent.ADD_SEQUENCES, + content: t(TourContent.ADD_SEQUENCES), title: t("Create sequences"), }, { target: ".regimen-list-panel", - content: TourContent.ADD_REGIMENS, + content: t(TourContent.ADD_REGIMENS), title: t("Create regimens"), }, { target: ".farm-event-panel", - content: TourContent.ADD_FARM_EVENTS, + content: t(TourContent.ADD_FARM_EVENTS), title: t("Create events"), }, ], [Tours.monitoring]: [ { target: ".move-widget", - content: TourContent.LOCATION_GRID, + content: t(TourContent.LOCATION_GRID), title: t("View current location"), }, { target: ".farm-designer", - content: TourContent.VIRTUAL_FARMBOT, + content: t(TourContent.VIRTUAL_FARMBOT), title: t("View current location"), }, { target: ".logs-table", - content: TourContent.LOGS_TABLE, + content: t(TourContent.LOGS_TABLE), title: t("View log messages"), }, { target: ".photos", - content: TourContent.PHOTOS, + content: t(TourContent.PHOTOS), title: t("Take and view photos"), }, ], [Tours.funStuff]: [ { target: ".app-settings-widget", - content: TourContent.APP_SETTINGS, + content: t(TourContent.APP_SETTINGS), title: t("Customize your web app experience"), }, ], @@ -112,6 +154,10 @@ export const tourPageNavigation = (nextStepTarget: string | HTMLElement) => { case ".toolbay-list": history.push("/app/tools"); break; + case ".tools": + case ".tool-slots": + history.push("/app/designer/tools"); + break; case ".photos": history.push("/app/farmware"); break; diff --git a/frontend/messages/__tests__/alerts_test.tsx b/frontend/messages/__tests__/alerts_test.tsx index 08a61add7..8f85c4f3b 100644 --- a/frontend/messages/__tests__/alerts_test.tsx +++ b/frontend/messages/__tests__/alerts_test.tsx @@ -52,8 +52,7 @@ describe("", () => { const p = fakeProps(); p.alerts = [FIRMWARE_MISSING_ALERT, SEED_DATA_MISSING_ALERT]; const wrapper = mount(); - expect(wrapper.text()).toContain("2"); - expect(wrapper.text()).toContain("Your device has no firmware"); + expect(wrapper.text()).not.toContain("Your device has no firmware"); expect(wrapper.text()).toContain("Choose your FarmBot"); }); @@ -61,7 +60,6 @@ describe("", () => { const p = fakeProps(); p.alerts = [FIRMWARE_MISSING_ALERT, UNKNOWN_ALERT]; const wrapper = mount(); - expect(wrapper.text()).toContain("1"); expect(wrapper.text()).toContain("firmware: alert"); }); }); diff --git a/frontend/messages/alerts.tsx b/frontend/messages/alerts.tsx index 30ed30b09..5a1141819 100644 --- a/frontend/messages/alerts.tsx +++ b/frontend/messages/alerts.tsx @@ -34,6 +34,7 @@ export const Alerts = (props: AlertsProps) =>
{sortAlerts(props.alerts) .filter(filterIncompleteAlerts) + .filter(x => x.problem_tag != "farmbot_os.firmware.missing") .map(x => { step: resourceUpdate({ label: "mounted_tool_id", value: 0 }), resourceIndex: fakeResourceIndex() }); - expect(result).toEqual(DISMOUNTED); + expect(result).toEqual(DISMOUNTED()); }); it("unpacks valid tool_ids", () => { @@ -37,7 +37,7 @@ describe("unpackStep()", () => { resourceIndex }); const actionLabel = "Mounted to: Generic Tool"; - const { label, value } = TOOL_MOUNT; + const { label, value } = TOOL_MOUNT(); assertGoodness(result, actionLabel, "mounted", label, value); }); @@ -47,7 +47,7 @@ describe("unpackStep()", () => { resourceIndex: fakeResourceIndex() }); const actionLabel = "Mounted to: an unknown tool"; - const { label, value } = TOOL_MOUNT; + const { label, value } = TOOL_MOUNT(); assertGoodness(result, actionLabel, "mounted", label, value); }); diff --git a/frontend/sequences/step_tiles/mark_as/unpack_step.ts b/frontend/sequences/step_tiles/mark_as/unpack_step.ts index ccf7a59f0..b4d5f7da8 100644 --- a/frontend/sequences/step_tiles/mark_as/unpack_step.ts +++ b/frontend/sequences/step_tiles/mark_as/unpack_step.ts @@ -9,15 +9,16 @@ import { GenericPointer } from "farmbot/dist/resources/api_resources"; import { MOUNTED_TO } from "./constants"; import { DropDownPair, StepWithResourceIndex } from "./interfaces"; import { TaggedPoint, TaggedPlantPointer } from "farmbot"; +import { t } from "../../../i18next_wrapper"; -export const TOOL_MOUNT: DropDownItem = { - label: "Tool Mount", value: "tool_mount" -}; -const NOT_IN_USE: DropDownItem = { label: "Not Mounted", value: 0 }; -export const DISMOUNTED: DropDownPair = { - leftSide: TOOL_MOUNT, - rightSide: NOT_IN_USE -}; +export const TOOL_MOUNT = (): DropDownItem => ({ + label: t("Tool Mount"), value: "tool_mount" +}); +const NOT_IN_USE = (): DropDownItem => ({ label: t("Not Mounted"), value: 0 }); +export const DISMOUNTED = (): DropDownPair => ({ + leftSide: TOOL_MOUNT(), + rightSide: NOT_IN_USE() +}); const DEFAULT_TOOL_NAME = "Untitled Tool"; const REMOVED_ACTION = { label: "Removed", value: "removed" }; @@ -30,13 +31,13 @@ function mountTool(i: StepWithResourceIndex): DropDownPair { if (typeof value === "number" && value > 0) { try { // Good tool id const tool = findToolById(i.resourceIndex, value as number); - return { leftSide: TOOL_MOUNT, rightSide: mountedTo(tool.body.name) }; + return { leftSide: TOOL_MOUNT(), rightSide: mountedTo(tool.body.name) }; } catch { // Bad tool ID or app still loading. - return { leftSide: TOOL_MOUNT, rightSide: mountedTo("an unknown tool") }; + return { leftSide: TOOL_MOUNT(), rightSide: mountedTo("an unknown tool") }; } } else { // No tool id - return DISMOUNTED; + return DISMOUNTED(); } } @@ -55,10 +56,10 @@ function unknownOption(i: StepWithResourceIndex): DropDownPair { /** The user wants to mark a the `discarded_at` attribute of a Point. */ function discardPoint(i: StepWithResourceIndex): DropDownPair { const { resource_id } = i.step.args; - const t = + const genericPointerBody = findPointerByTypeAndId(i.resourceIndex, "GenericPointer", resource_id).body; return { - leftSide: pointer2ddi(t as GenericPointer), + leftSide: pointer2ddi(genericPointerBody as GenericPointer), rightSide: REMOVED_ACTION }; } diff --git a/public/app-resources/languages/_helper.js b/public/app-resources/languages/_helper.js index c01b7d3b9..ae133fc46 100644 --- a/public/app-resources/languages/_helper.js +++ b/public/app-resources/languages/_helper.js @@ -48,7 +48,7 @@ var HelperNamespace = (function () { var T_REGEX = /[.{[(\s]t\(["`]([\w\s{}().,:'\-=\\?\/%!]*)["`],*\s*.*\)/g; // '``' - var C_REGEX = /[`]([\w\s{}().,:'\-=\\?"+!]*)[`].*/g; + var C_REGEX = /[`]([\w\s{}().,:'\-=\/\\?"+!]*)[`].*/g; /** Some additional phrases the regex can't find. */ var EXTRA_TAGS = [ @@ -60,6 +60,7 @@ var HelperNamespace = (function () { "Else Execute", "Connecting FarmBot to the Internet", "move to home", "emergency stop", "SYNC ERROR", "inactive", "error", "No messages.", "back to regimens", "back to sequences", "back to farmware list", + "Verify Password", ]; /** diff --git a/public/app-resources/languages/translation_metrics.md b/public/app-resources/languages/translation_metrics.md index e55daf997..08b1e8de3 100644 --- a/public/app-resources/languages/translation_metrics.md +++ b/public/app-resources/languages/translation_metrics.md @@ -21,20 +21,20 @@ For example, `sudo docker-compose run web npm run translation-check`._ See the [README](https://github.com/FarmBot/Farmbot-Web-App#translating-the-web-app-into-your-language) for contribution instructions. -Total number of phrases identified by the language helper for translation: __1139__ +Total number of phrases identified by the language helper for translation: __1238__ |Language|Percent translated|Translated|Untranslated|Other Translations| |:---:|---:|---:|---:|---:| -|da|10%|109|1030|44| -|de|36%|413|726|141| -|es|88%|1002|137|173| -|fr|90%|1022|117|198| -|it|8%|91|1048|189| -|nl|7%|79|1060|161| -|pt|6%|71|1068|180| -|ru|52%|596|543|221| -|th|0%|0|1139|0| -|zh|8%|86|1053|161| +|da|8%|105|1133|77| +|de|32%|397|841|168| +|es|78%|965|273|210| +|fr|80%|985|253|242| +|it|7%|87|1151|215| +|nl|6%|75|1163|187| +|pt|5%|66|1172|207| +|ru|46%|575|663|246| +|th|0%|0|1238|0| +|zh|7%|82|1156|187| **Percent translated** refers to the percent of phrases identified by the language helper that have been translated. Additional phrases not identified