From 40150a307cdd48680b09974a7c37df05bb80c4e1 Mon Sep 17 00:00:00 2001 From: gabrielburnworth Date: Thu, 20 Feb 2020 18:38:50 -0800 Subject: [PATCH 1/9] tool updates --- app/models/in_use_point.rb | 2 +- app/models/tool_slot.rb | 2 +- app/mutations/tools/destroy.rb | 8 +- frontend/constants.ts | 19 +- frontend/css/farm_designer/farm_designer.scss | 3 + .../farm_designer/farm_designer_panels.scss | 82 +++++++- frontend/css/global.scss | 17 +- frontend/css/inputs.scss | 8 + frontend/devices/__tests__/actions_test.ts | 3 +- frontend/devices/actions.ts | 2 +- .../__tests__/fbos_details_test.tsx | 2 + .../components/fbos_settings/fbos_details.tsx | 6 +- .../fbos_settings/ota_time_selector.tsx | 33 ++-- .../devices/components/maybe_highlight.tsx | 14 +- .../__tests__/farm_designer_test.tsx | 1 + .../__tests__/state_to_props_test.tsx | 19 +- .../__tests__/add_farm_event_test.tsx | 2 +- .../farm_events/add_farm_event.tsx | 4 +- frontend/farm_designer/index.tsx | 1 + frontend/farm_designer/interfaces.ts | 2 + .../map/__tests__/garden_map_test.tsx | 24 ++- frontend/farm_designer/map/garden_map.tsx | 5 + frontend/farm_designer/map/interfaces.ts | 1 + .../farmbot/__tests__/bot_figure_test.tsx | 12 +- .../farmbot/__tests__/farmbot_layer_test.tsx | 1 + .../layers/farmbot/__tests__/index_test.tsx | 1 + .../map/layers/farmbot/bot_figure.tsx | 43 +++- .../map/layers/farmbot/farmbot_layer.tsx | 3 +- .../map/layers/farmbot/index.tsx | 1 + .../__tests__/tool_graphics_test.tsx | 172 ++++++++++++++-- .../tool_slots/__tests__/tool_label_test.ts | 77 ++++---- .../__tests__/tool_slot_point_test.tsx | 30 ++- .../map/layers/tool_slots/tool_graphics.tsx | 184 +++++++++++++++++- .../map/layers/tool_slots/tool_label.tsx | 21 +- .../map/layers/tool_slots/tool_slot_point.tsx | 16 +- .../__tests__/group_detail_active_test.tsx | 15 -- .../point_groups/__tests__/paths_test.tsx | 2 +- ...ctor_test.tsx => point_group_sort_test.ts} | 39 +--- .../point_groups/criteria/add.tsx | 2 +- .../point_groups/group_detail_active.tsx | 19 +- .../point_groups/group_list_panel.tsx | 2 +- .../point_groups/group_order_visual.tsx | 2 +- frontend/farm_designer/point_groups/paths.tsx | 2 +- ..._sort_selector.tsx => point_group_sort.ts} | 32 --- .../points/__tests__/create_points_test.tsx | 4 +- .../farm_designer/points/create_points.tsx | 2 +- .../__tests__/garden_add_test.tsx | 2 +- .../saved_gardens/garden_add.tsx | 2 +- .../saved_gardens/garden_snapshot.tsx | 2 +- frontend/farm_designer/state_to_props.ts | 10 +- .../tools/__tests__/add_tool_slot_test.tsx | 27 +-- .../tools/__tests__/add_tool_test.tsx | 29 ++- .../tools/__tests__/edit_tool_slot_test.tsx | 59 +++++- .../tools/__tests__/edit_tool_test.tsx | 51 ++++- .../tools/__tests__/index_test.tsx | 62 +++++- .../tool_slot_edit_components_test.tsx | 78 ++++---- frontend/farm_designer/tools/add_tool.tsx | 54 +++-- .../farm_designer/tools/add_tool_slot.tsx | 44 +---- frontend/farm_designer/tools/edit_tool.tsx | 31 ++- .../farm_designer/tools/edit_tool_slot.tsx | 45 ++--- frontend/farm_designer/tools/index.tsx | 74 +++++-- .../tools/map_to_props_add_edit.ts | 67 +++++++ .../tools/tool_slot_edit_components.tsx | 95 +++++---- frontend/help/__tests__/tours_test.ts | 2 +- frontend/help/tours.ts | 4 +- frontend/resources/sequence_meta.ts | 6 +- .../__tests__/location_form_list_test.ts | 2 +- .../locals_list/location_form_list.ts | 17 +- .../__tests__/tile_move_absolute_test.tsx | 22 ++- .../step_tiles/tile_move_absolute.tsx | 10 +- package.json | 2 +- 71 files changed, 1283 insertions(+), 456 deletions(-) rename frontend/farm_designer/point_groups/__tests__/{point_group_sort_selector_test.tsx => point_group_sort_test.ts} (59%) rename frontend/farm_designer/point_groups/{point_group_sort_selector.tsx => point_group_sort.ts} (58%) create mode 100644 frontend/farm_designer/tools/map_to_props_add_edit.ts diff --git a/app/models/in_use_point.rb b/app/models/in_use_point.rb index 820684516..4c29f3894 100644 --- a/app/models/in_use_point.rb +++ b/app/models/in_use_point.rb @@ -6,7 +6,7 @@ class InUsePoint < ApplicationRecord DEFAULT_NAME = "point" FANCY_NAMES = { GenericPointer.name => DEFAULT_NAME, - ToolSlot.name => "tool slot", + ToolSlot.name => "slot", Plant.name => "plant", } diff --git a/app/models/tool_slot.rb b/app/models/tool_slot.rb index 22a0abd90..e416dadbd 100644 --- a/app/models/tool_slot.rb +++ b/app/models/tool_slot.rb @@ -11,7 +11,7 @@ class ToolSlot < Point MIN_PULLOUT = PULLOUT_DIRECTIONS.min PULLOUT_ERR = "must be a value between #{MIN_PULLOUT} and #{MAX_PULLOUT}. "\ "%{value} is not valid." - IN_USE = "already in use by another tool slot. "\ + IN_USE = "already in use by another slot. "\ "Please un-assign the tool from its current slot"\ " before reassigning." diff --git a/app/mutations/tools/destroy.rb b/app/mutations/tools/destroy.rb index f60fb3c29..3618a5d98 100644 --- a/app/mutations/tools/destroy.rb +++ b/app/mutations/tools/destroy.rb @@ -1,9 +1,9 @@ module Tools class Destroy < Mutations::Command - STILL_IN_USE = "Can't delete tool because the following sequences are " \ - "still using it: %s" - STILL_IN_SLOT = "Can't delete tool because it is still in a tool slot. " \ - "Please remove it from the tool slot first." + STILL_IN_USE = "Can't delete tool or seed container because the " \ + "following sequences are still using it: %s" + STILL_IN_SLOT = "Can't delete tool or seed container because it is " \ + "still in a slot. Please remove it from the slot first." required do model :tool, class: Tool diff --git a/frontend/constants.ts b/frontend/constants.ts index 7adb0a80f..2c9f6449d 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -648,8 +648,8 @@ export namespace Content { trim(`Restart the Farmduino or Arduino firmware.`); export const OS_AUTO_UPDATE = - trim(`When enabled, FarmBot OS will periodically check for, download, - and install updates automatically.`); + trim(`When enabled, FarmBot OS will automatically download and install + software updates at the chosen time.`); export const AUTO_SYNC = trim(`When enabled, device resources such as sequences and regimens @@ -663,7 +663,7 @@ export namespace Content { back on, unplug FarmBot and plug it back in.`); export const OS_BETA_RELEASES = - trim(`Warning! Opting in to FarmBot OS beta releases may reduce + trim(`Warning! Leaving the stable FarmBot OS release channel may reduce FarmBot system stability. Are you sure?`); export const DIAGNOSTIC_CHECK = @@ -897,16 +897,16 @@ export namespace TourContent { export const ADD_TOOLS_AND_SLOTS = trim(`Press the + button to add tools and seed containers. Then create - tool slots for them to by pressing the tool slot + button.`); + slots for them to by pressing the slot + button.`); export const ADD_SEED_CONTAINERS_AND_SLOTS = trim(`Press the + button to add seed containers. Then create - slots for them to by pressing the seed container slot + button.`); + slots for them to by pressing the slot + button.`); export const ADD_TOOLS_SLOTS = trim(`Add the newly created tools and seed containers to the - corresponding tool slots on FarmBot: - press the + button to create a tool slot.`); + corresponding slots on FarmBot: + press the + button to create a slot.`); export const ADD_PERIPHERALS = trim(`Press edit and then the + button to add peripherals.`); @@ -998,7 +998,7 @@ export enum DeviceSetting { pinGuard = `Pin Guard`, // Danger Zone - dangerZone = `dangerZone`, + dangerZone = `Danger Zone`, resetHardwareParams = `Reset hardware parameter defaults`, // Pin Bindings @@ -1009,7 +1009,8 @@ export enum DeviceSetting { timezone = `timezone`, camera = `camera`, firmware = `firmware`, - farmbotOSAutoUpdate = `Farmbot OS Auto Update`, + applySoftwareUpdates = `update time`, + farmbotOSAutoUpdate = `auto update`, farmbotOS = `Farmbot OS`, autoSync = `Auto Sync`, bootSequence = `Boot Sequence`, diff --git a/frontend/css/farm_designer/farm_designer.scss b/frontend/css/farm_designer/farm_designer.scss index 74d11b8af..bb34b097a 100644 --- a/frontend/css/farm_designer/farm_designer.scss +++ b/frontend/css/farm_designer/farm_designer.scss @@ -30,6 +30,9 @@ padding: 35rem 2rem 2rem 2rem; // at zoom = 1.0: 350px 20px 20px 20px } transition: 0.2s ease; + &::-webkit-scrollbar { + display: none; + } } .drop-area { diff --git a/frontend/css/farm_designer/farm_designer_panels.scss b/frontend/css/farm_designer/farm_designer_panels.scss index fea4106db..a915f1f5e 100644 --- a/frontend/css/farm_designer/farm_designer_panels.scss +++ b/frontend/css/farm_designer/farm_designer_panels.scss @@ -552,8 +552,12 @@ } .tool-slots-panel-content, .tools-panel-content { + max-height: calc(100vh - 15rem); + overflow-y: auto; + overflow-x: hidden; .tool-search-item, .tool-slot-search-item { + line-height: 4rem; cursor: pointer; margin-left: -15px; margin-right: -15px; @@ -562,11 +566,32 @@ margin-right: 0; } p { - line-height: 3rem; + font-size: 1.2rem; + line-height: 4rem; + &.tool-status, &.tool-slot-position { float: right; } } + .filter-search { + .bp3-button { + min-height: 2.5rem; + max-height: 2.5rem; + span { + line-height: 1.5rem; + } + } + i { + line-height: 2.5rem; + } + } + svg { + vertical-align: middle; + } + .tool-slot-position-info { + padding: 0; + padding-right: 1rem; + } } .mounted-tool-header { display: flex; @@ -624,6 +649,13 @@ float: left; } } + svg { + display: block; + margin: auto; + width: 10rem; + height: 10rem; + margin-top: 2rem; + } .add-stock-tools { .filter-search { margin-bottom: 1rem; @@ -634,6 +666,25 @@ ul { font-size: 1.2rem; padding-left: 1rem; + li { + margin-top: 0.5rem; + line-height: 2rem; + cursor: pointer; + width: 50%; + &:hover { + font-weight: bold; + } + .fb-checkbox { + display: inline; + } + p { + display: inline; + line-height: 2.25rem; + font-size: 1.2rem; + vertical-align: top; + margin-left: 1rem; + } + } } button { .fa-plus { @@ -645,6 +696,13 @@ .add-tool-slot-panel-content, .edit-tool-slot-panel-content { + svg { + display: block; + margin: auto; + width: 10rem; + height: 10rem; + margin-top: 2rem; + } label { margin-top: 0 !important; } @@ -657,12 +715,24 @@ .direction-icon { margin-left: 1rem; } - .use-current-location-input { + .help-icon { + color: $dark_gray; + } + .tool-slot-location-input { + .axis-inputs { + padding-left: 0; + } + .use-current-location { + padding: 0; + margin-left: -1rem; + } button { - margin: 0; - float: none; - margin-left: 1rem; - vertical-align: middle; + margin-top: 0.5rem; + margin-right: 0.5rem; + height: 2.5rem; + .fa { + font-size: 1.5rem; + } } } .gantry-mounted-input { diff --git a/frontend/css/global.scss b/frontend/css/global.scss index 02ff86b62..e8530d2c6 100644 --- a/frontend/css/global.scss +++ b/frontend/css/global.scss @@ -226,7 +226,7 @@ fieldset { .percent-bar { position: absolute; top: 2px; - left: 12rem; + right: 0; height: 1rem; width: 25%; clip-path: polygon(0 85%, 100% 0, 100% 100%, 0% 100%); @@ -1543,16 +1543,21 @@ textarea:focus { cursor: pointer; margin-top: 0.25rem; margin-bottom: 0.25rem; - border: 2px solid $panel_light_blue; + border: 2px solid darken($panel_light_blue, 30%); + border-radius: 5px; &:hover, &.selected { - border: 2px solid $medium_gray; - border-radius: 2px; .sort-path-info-bar { - background: darken($light_gray, 10%); + background: darken($panel_light_blue, 40%); } } + &:hover { + border: 2px solid darken($panel_light_blue, 40%); + } + &.selected { + border: 2px solid $medium_gray; + } .sort-path-info-bar { - background: $light_gray; + background: darken($panel_light_blue, 30%); font-size: 1.2rem; padding-left: 0.5rem; white-space: nowrap; diff --git a/frontend/css/inputs.scss b/frontend/css/inputs.scss index e31b27443..9326c1a44 100644 --- a/frontend/css/inputs.scss +++ b/frontend/css/inputs.scss @@ -154,4 +154,12 @@ select { } } } + &.disabled { + input[type="checkbox"] { + cursor: not-allowed; + &:checked:after { + border-color: $gray; + } + } + } } diff --git a/frontend/devices/__tests__/actions_test.ts b/frontend/devices/__tests__/actions_test.ts index 2d0e5a1cd..2f69f5bc3 100644 --- a/frontend/devices/__tests__/actions_test.ts +++ b/frontend/devices/__tests__/actions_test.ts @@ -353,9 +353,10 @@ describe("fetchReleases()", () => { it("fails to fetches latest OS release version", async () => { mockGetRelease = Promise.reject("error"); const dispatch = jest.fn(); + console.error = jest.fn(); await actions.fetchReleases("url")(dispatch); await expect(axios.get).toHaveBeenCalledWith("url"); - expect(error).toHaveBeenCalledWith( + expect(console.error).toHaveBeenCalledWith( "Could not download FarmBot OS update information."); expect(dispatch).toHaveBeenCalledWith({ payload: "error", diff --git a/frontend/devices/actions.ts b/frontend/devices/actions.ts index edea6ce0f..c1a9565ea 100644 --- a/frontend/devices/actions.ts +++ b/frontend/devices/actions.ts @@ -212,7 +212,7 @@ export const fetchReleases = }) .catch((ferror) => { !options.beta && - error(t("Could not download FarmBot OS update information.")); + console.error(t("Could not download FarmBot OS update information.")); dispatch({ type: options.beta ? "FETCH_BETA_OS_UPDATE_INFO_ERROR" diff --git a/frontend/devices/components/fbos_settings/__tests__/fbos_details_test.tsx b/frontend/devices/components/fbos_settings/__tests__/fbos_details_test.tsx index 0c4cdb602..55042b2b4 100644 --- a/frontend/devices/components/fbos_settings/__tests__/fbos_details_test.tsx +++ b/frontend/devices/components/fbos_settings/__tests__/fbos_details_test.tsx @@ -89,6 +89,7 @@ describe("", () => { const p = fakeProps(); const commit = "abcdefgh"; p.botInfoSettings.firmware_commit = commit; + p.botInfoSettings.firmware_version = "1.0.0"; const wrapper = mount(); expect(wrapper.find("a").last().text()).toEqual(commit); expect(wrapper.find("a").last().props().href?.split("/").slice(-1)[0]) @@ -115,6 +116,7 @@ describe("", () => { it("doesn't display link without commit", () => { const p = fakeProps(); + p.botInfoSettings.firmware_version = undefined; p.botInfoSettings.commit = "---"; p.botInfoSettings.firmware_commit = "---"; const wrapper = mount(); diff --git a/frontend/devices/components/fbos_settings/fbos_details.tsx b/frontend/devices/components/fbos_settings/fbos_details.tsx index c06a4b627..c4b138cbd 100644 --- a/frontend/devices/components/fbos_settings/fbos_details.tsx +++ b/frontend/devices/components/fbos_settings/fbos_details.tsx @@ -69,7 +69,7 @@ export function WiFiStrengthDisplay( return

{t("WiFi strength")}: - {wifiStrength ? dbString : "N/A"} + {wifiStrength ? `${dbString} (${percentString})` : "N/A"}

{wifiStrength &&
@@ -261,8 +261,8 @@ export function FbosDetails(props: FbosDetailsProps) { wifi_level_percent, cpu_usage, private_ip, } = props.botInfoSettings; const { last_ota, last_ota_checkup } = props.deviceAccount.body; - const firmwareCommit = [firmware_commit, firmware_version].includes("---") - ? firmware_commit : firmware_version?.split("-")[1] || firmware_commit; + const infoFwCommit = firmware_version?.includes(".") ? firmware_commit : "---"; + const firmwareCommit = firmware_version?.split("-")[1] || infoFwCommit; return
; type EveryTimeTable = Record; +const ASAP = () => t("As soon as possible"); const TIME_TABLE_12H = (): TimeTable => ({ 0: { label: t("Midnight"), value: 0 }, 1: { label: "1:00 AM", value: 1 }, @@ -62,7 +65,7 @@ const TIME_TABLE_12H = (): TimeTable => ({ 21: { label: "9:00 PM", value: 21 }, 22: { label: "10:00 PM", value: 22 }, 23: { label: "11:00 PM", value: 23 }, - [IMMEDIATELY]: { label: t("as soon as possible"), value: IMMEDIATELY }, + [IMMEDIATELY]: { label: ASAP(), value: IMMEDIATELY }, }); const TIME_TABLE_24H = (): TimeTable => ({ 0: { label: "00:00", value: 0 }, @@ -89,7 +92,7 @@ const TIME_TABLE_24H = (): TimeTable => ({ 21: { label: "21:00", value: 21 }, 22: { label: "22:00", value: 22 }, 23: { label: "23:00", value: 23 }, - [IMMEDIATELY]: { label: t("as soon as possible"), value: IMMEDIATELY }, + [IMMEDIATELY]: { label: ASAP(), value: IMMEDIATELY }, }); const DEFAULT_HOUR: keyof TimeTable = IMMEDIATELY; @@ -144,17 +147,19 @@ export const OtaTimeSelector = (props: OtaTimeSelectorProps): JSX.Element => { const selectedItem = (typeof value == "number") ? theTimeTable[value as HOUR] : theTimeTable[DEFAULT_HOUR]; return - - - - - - + + + + + + + + ; }; diff --git a/frontend/devices/components/maybe_highlight.tsx b/frontend/devices/components/maybe_highlight.tsx index eab806b0f..7381534fa 100644 --- a/frontend/devices/components/maybe_highlight.tsx +++ b/frontend/devices/components/maybe_highlight.tsx @@ -3,6 +3,7 @@ import { ControlPanelState } from "../interfaces"; import { toggleControlPanel } from "../actions"; import { urlFriendly } from "../../util"; import { DeviceSetting } from "../../constants"; +import { trim } from "lodash"; const HOMING_PANEL = [ DeviceSetting.homingAndCalibration, @@ -86,10 +87,15 @@ DANGER_ZONE_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "danger_zone"); PIN_BINDINGS_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "pin_bindings"); POWER_AND_RESET_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "power_and_reset"); +/** Keep string up until first `(` character (trailing whitespace removed). */ +const stripUnits = (settingName: string) => trim(settingName.split("(")[0]); + /** Look up parent panels for settings using URL-friendly names. */ const URL_FRIENDLY_LOOKUP: Record = {}; -Object.entries(SETTING_PANEL_LOOKUP).map(([setting, panel]) => - URL_FRIENDLY_LOOKUP[urlFriendly(setting)] = panel); +Object.entries(SETTING_PANEL_LOOKUP).map(([setting, panel]) => { + URL_FRIENDLY_LOOKUP[urlFriendly(setting)] = panel; + URL_FRIENDLY_LOOKUP[urlFriendly(stripUnits(setting))] = panel; +}); /** Look up all relevant names for the same setting. */ const ALTERNATE_NAMES = @@ -100,7 +106,9 @@ ALTERNATE_NAMES[DeviceSetting.stallDetection].push(DeviceSetting.encoders); /** Generate array of names for the same setting. Most only have one. */ const compareValues = (settingName: DeviceSetting) => - (ALTERNATE_NAMES[settingName]).map(s => urlFriendly(s)); + (ALTERNATE_NAMES[settingName] as string[]) + .concat(stripUnits(settingName)) + .map(s => urlFriendly(s)); /** Retrieve a highlight search term. */ const getHighlightName = () => location.search.split("?highlight=").pop(); diff --git a/frontend/farm_designer/__tests__/farm_designer_test.tsx b/frontend/farm_designer/__tests__/farm_designer_test.tsx index 82249de16..4b7251dcc 100644 --- a/frontend/farm_designer/__tests__/farm_designer_test.tsx +++ b/frontend/farm_designer/__tests__/farm_designer_test.tsx @@ -62,6 +62,7 @@ describe("", () => { sensors: [], groups: [], shouldDisplay: () => false, + mountedToolName: undefined, }); it("loads default map settings", () => { diff --git a/frontend/farm_designer/__tests__/state_to_props_test.tsx b/frontend/farm_designer/__tests__/state_to_props_test.tsx index 68dc494d8..5e48b7ef3 100644 --- a/frontend/farm_designer/__tests__/state_to_props_test.tsx +++ b/frontend/farm_designer/__tests__/state_to_props_test.tsx @@ -1,7 +1,7 @@ import { mapStateToProps, getPlants } from "../state_to_props"; import { fakeState } from "../../__test_support__/fake_state"; import { - buildResourceIndex + buildResourceIndex, fakeDevice } from "../../__test_support__/resource_index_builder"; import { fakePlant, @@ -49,7 +49,7 @@ describe("mapStateToProps()", () => { it("returns selected plant", () => { const state = fakeState(); - state.resources = buildResourceIndex([fakePlant()]); + state.resources = buildResourceIndex([fakePlant(), fakeDevice()]); const plantUuid = Object.keys(state.resources.index.byKind["Point"])[0]; state.resources.consumers.farm_designer.selectedPlants = [plantUuid]; expect(mapStateToProps(state).selectedPlant).toEqual( @@ -66,7 +66,9 @@ describe("mapStateToProps()", () => { point2.body.discarded_at = DISCARDED_AT; const point3 = fakePoint(); point3.body.discarded_at = DISCARDED_AT; - state.resources = buildResourceIndex([webAppConfig, point1, point2, point3]); + state.resources = buildResourceIndex([ + webAppConfig, point1, point2, point3, fakeDevice() + ]); expect(mapStateToProps(state).genericPoints.length).toEqual(3); }); @@ -80,7 +82,9 @@ describe("mapStateToProps()", () => { point2.body.discarded_at = DISCARDED_AT; const point3 = fakePoint(); point3.body.discarded_at = DISCARDED_AT; - state.resources = buildResourceIndex([webAppConfig, point1, point2, point3]); + state.resources = buildResourceIndex([ + webAppConfig, point1, point2, point3, fakeDevice() + ]); expect(mapStateToProps(state).genericPoints.length).toEqual(1); }); @@ -90,7 +94,7 @@ describe("mapStateToProps()", () => { sr1.body.created_at = "2018-01-14T20:20:38.362Z"; const sr2 = fakeSensorReading(); sr2.body.created_at = "2018-01-11T20:20:38.362Z"; - state.resources = buildResourceIndex([sr1, sr2]); + state.resources = buildResourceIndex([sr1, sr2, fakeDevice()]); const uuid1 = Object.keys(state.resources.index.byKind["SensorReading"])[0]; const uuid2 = Object.keys(state.resources.index.byKind["SensorReading"])[1]; expect(mapStateToProps(state).sensorReadings).toEqual([ @@ -112,7 +116,8 @@ describe("getPlants()", () => { const template2 = fakePlantTemplate(); template2.body.saved_garden_id = 2; return buildResourceIndex([ - savedGarden, plant1, plant2, template1, template2]); + savedGarden, plant1, plant2, template1, template2, fakeDevice() + ]); }; it("returns plants", () => { expect(getPlants(fakeResources()).length).toEqual(2); @@ -133,7 +138,7 @@ describe("getPlants()", () => { const fwEnv = fakeFarmwareEnv(); fwEnv.body.key = "CAMERA_CALIBRATION_total_rotation_angle"; fwEnv.body.value = 15; - state.resources = buildResourceIndex([fwEnv]); + state.resources = buildResourceIndex([fwEnv, fakeDevice()]); const props = mapStateToProps(state); expect(props.cameraCalibrationData).toEqual( expect.objectContaining({ rotation: "15" })); diff --git a/frontend/farm_designer/farm_events/__tests__/add_farm_event_test.tsx b/frontend/farm_designer/farm_events/__tests__/add_farm_event_test.tsx index 04dfb8a2d..85d74e786 100644 --- a/frontend/farm_designer/farm_events/__tests__/add_farm_event_test.tsx +++ b/frontend/farm_designer/farm_events/__tests__/add_farm_event_test.tsx @@ -55,7 +55,7 @@ describe("", () => { const wrapper = mount(); wrapper.setState({ uuid: "FarmEvent" }); ["Add Event", "Sequence or Regimen", "fake", "Save"].map(string => - expect(wrapper.text()).toContain(string)); + expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase())); const deleteBtn = wrapper.find("button").last(); expect(deleteBtn.text()).toEqual("Delete"); expect(deleteBtn.props().hidden).toBeTruthy(); diff --git a/frontend/farm_designer/farm_events/add_farm_event.tsx b/frontend/farm_designer/farm_events/add_farm_event.tsx index 95e11f9af..878f6dabb 100644 --- a/frontend/farm_designer/farm_events/add_farm_event.tsx +++ b/frontend/farm_designer/farm_events/add_farm_event.tsx @@ -102,7 +102,7 @@ export class RawAddFarmEvent this.props.dispatch(destroyOK(farmEvent)) : undefined} /> @@ -115,7 +115,7 @@ export class RawAddFarmEvent executableOptions={this.props.executableOptions} dispatch={this.props.dispatch} findExecutable={this.props.findExecutable} - title={t("Add Event")} + title={t("Add event")} timeSettings={this.props.timeSettings} autoSyncEnabled={this.props.autoSyncEnabled} resources={this.props.resources} diff --git a/frontend/farm_designer/index.tsx b/frontend/farm_designer/index.tsx index 746bb48cb..ab6721152 100755 --- a/frontend/farm_designer/index.tsx +++ b/frontend/farm_designer/index.tsx @@ -211,6 +211,7 @@ export class RawFarmDesigner extends React.Component> { timeSettings={this.props.timeSettings} sensors={this.props.sensors} groups={this.props.groups} + mountedToolName={this.props.mountedToolName} shouldDisplay={this.props.shouldDisplay} />
diff --git a/frontend/farm_designer/interfaces.ts b/frontend/farm_designer/interfaces.ts index d265e5fd9..2f243ae78 100644 --- a/frontend/farm_designer/interfaces.ts +++ b/frontend/farm_designer/interfaces.ts @@ -80,6 +80,7 @@ export interface Props { sensors: TaggedSensor[]; groups: TaggedPointGroup[]; shouldDisplay: ShouldDisplay; + mountedToolName: string | undefined; } export interface MovePlantProps { @@ -210,6 +211,7 @@ export interface GardenMapProps { timeSettings: TimeSettings; groups: TaggedPointGroup[]; shouldDisplay: ShouldDisplay; + mountedToolName: string | undefined; } export interface GardenMapState { diff --git a/frontend/farm_designer/map/__tests__/garden_map_test.tsx b/frontend/farm_designer/map/__tests__/garden_map_test.tsx index f2d6b6ac7..61b48f64f 100644 --- a/frontend/farm_designer/map/__tests__/garden_map_test.tsx +++ b/frontend/farm_designer/map/__tests__/garden_map_test.tsx @@ -124,6 +124,7 @@ const fakeProps = (): GardenMapProps => ({ timeSettings: fakeTimeSettings(), groups: [], shouldDisplay: () => false, + mountedToolName: undefined, }); describe("", () => { @@ -200,6 +201,16 @@ describe("", () => { expect(getGardenCoordinates).not.toHaveBeenCalled(); }); + it("starts drag on background: does nothing when in move mode", () => { + const wrapper = mount(); + mockMode = Mode.moveTo; + const e = { pageX: 1000, pageY: 2000 }; + wrapper.find(".drop-area-background").simulate("mouseDown", e); + expect(startNewSelectionBox).not.toHaveBeenCalled(); + expect(history.push).not.toHaveBeenCalled(); + expect(getGardenCoordinates).not.toHaveBeenCalled(); + }); + it("starts drag on background: creating points", () => { const wrapper = mount(); mockMode = Mode.createPoint; @@ -348,7 +359,7 @@ describe("", () => { expect(closePlantInfo).toHaveBeenCalled(); }); - it("doesn't close panel", () => { + it("doesn't close panel: box select", () => { mockMode = Mode.boxSelect; const p = fakeProps(); p.designer.selectedPlants = [fakePlant().uuid]; @@ -357,6 +368,15 @@ describe("", () => { expect(closePlantInfo).not.toHaveBeenCalled(); }); + it("doesn't close panel: move mode", () => { + mockMode = Mode.moveTo; + const p = fakeProps(); + p.designer.selectedPlants = [fakePlant().uuid]; + const wrapper = mount(); + wrapper.instance().closePanel()(); + expect(closePlantInfo).not.toHaveBeenCalled(); + }); + it("calls unselectPlant on unmount", () => { const wrapper = shallow(); wrapper.unmount(); @@ -405,7 +425,7 @@ describe("", () => { const point = fakePoint(); point.body.id = 1; p.allPoints = [point]; - const wrapper = shallow(); + const wrapper = mount(); expect(wrapper.instance().pointsSelectedByGroup).toEqual([point]); }); }); diff --git a/frontend/farm_designer/map/garden_map.tsx b/frontend/farm_designer/map/garden_map.tsx index 063c5ccbf..fa2694827 100644 --- a/frontend/farm_designer/map/garden_map.tsx +++ b/frontend/farm_designer/map/garden_map.tsx @@ -160,6 +160,8 @@ export class GardenMap extends /** Map background drag start actions. */ startDragOnBackground = (e: React.MouseEvent): void => { switch (getMode()) { + case Mode.moveTo: + break; case Mode.createPoint: case Mode.clickToAdd: case Mode.editPlant: @@ -301,6 +303,8 @@ export class GardenMap extends /** Return to garden (unless selecting more plants). */ closePanel = () => { switch (getMode()) { + case Mode.moveTo: + return () => { }; case Mode.boxSelect: return this.props.designer.selectedPlants ? () => { } @@ -410,6 +414,7 @@ export class GardenMap extends plantAreaOffset={this.props.gridOffset} peripherals={this.props.peripherals} eStopStatus={this.props.eStopStatus} + mountedToolName={this.props.mountedToolName} getConfigValue={this.props.getConfigValue} /> HoveredPlant = () => ", () => { p.position.x = 100; const wrapper = shallow(); expect(wrapper.instance().state.hovered).toBeFalsy(); - const utm = wrapper.find("#UTM"); + const utm = wrapper.find("#UTM-wrapper"); utm.simulate("mouseOver"); expect(wrapper.instance().state.hovered).toBeTruthy(); expect(wrapper.find("text").props()).toEqual(expect.objectContaining({ @@ -105,7 +105,7 @@ describe("", () => { p.position.x = 100; p.mapTransformProps.xySwap = true; const wrapper = shallow(); - const utm = wrapper.find("#UTM"); + const utm = wrapper.find("#UTM-wrapper"); utm.simulate("mouseOver"); expect(wrapper.instance().state.hovered).toBeTruthy(); expect(wrapper.find("text").props()).toEqual(expect.objectContaining({ @@ -114,4 +114,12 @@ describe("", () => { })); expect(wrapper.text()).toEqual("(100, 0, 0)"); }); + + it("shows mounted tool", () => { + const p = fakeProps(); + p.mountedToolName = "Seeder"; + const wrapper = shallow(); + expect(wrapper.find("#UTM-wrapper").find("#mounted-tool").length) + .toEqual(1); + }); }); diff --git a/frontend/farm_designer/map/layers/farmbot/__tests__/farmbot_layer_test.tsx b/frontend/farm_designer/map/layers/farmbot/__tests__/farmbot_layer_test.tsx index 8649870e5..c61de5f08 100644 --- a/frontend/farm_designer/map/layers/farmbot/__tests__/farmbot_layer_test.tsx +++ b/frontend/farm_designer/map/layers/farmbot/__tests__/farmbot_layer_test.tsx @@ -25,6 +25,7 @@ describe("", () => { peripherals: [], eStopStatus: false, getConfigValue: jest.fn(), + mountedToolName: undefined, }; } diff --git a/frontend/farm_designer/map/layers/farmbot/__tests__/index_test.tsx b/frontend/farm_designer/map/layers/farmbot/__tests__/index_test.tsx index 305d29d8c..3ea77f5b4 100644 --- a/frontend/farm_designer/map/layers/farmbot/__tests__/index_test.tsx +++ b/frontend/farm_designer/map/layers/farmbot/__tests__/index_test.tsx @@ -19,6 +19,7 @@ describe("", () => { peripherals: [], eStopStatus: false, getConfigValue: () => true, + mountedToolName: undefined, }; } diff --git a/frontend/farm_designer/map/layers/farmbot/bot_figure.tsx b/frontend/farm_designer/map/layers/farmbot/bot_figure.tsx index 59d699041..9ceef1efa 100644 --- a/frontend/farm_designer/map/layers/farmbot/bot_figure.tsx +++ b/frontend/farm_designer/map/layers/farmbot/bot_figure.tsx @@ -4,6 +4,9 @@ import { getMapSize, transformXY } from "../../util"; import { BotPosition } from "../../../../devices/interfaces"; import { Color } from "../../../../ui/index"; import { botPositionLabel } from "./bot_position_label"; +import { Tool } from "../tool_slots/tool_graphics"; +import { reduceToolName } from "../tool_slots/tool_slot_point"; +import { noop } from "lodash"; export interface BotFigureProps { name: string; @@ -11,6 +14,7 @@ export interface BotFigureProps { mapTransformProps: MapTransformProps; plantAreaOffset: AxisNumberProperty; eStopStatus?: boolean; + mountedToolName?: string | undefined; } interface BotFigureState { @@ -24,7 +28,8 @@ export class BotFigure extends setHover = (state: boolean) => { this.setState({ hovered: state }); }; render() { - const { name, position, plantAreaOffset, eStopStatus, mapTransformProps + const { + name, position, plantAreaOffset, eStopStatus, mapTransformProps, } = this.props; const { xySwap } = mapTransformProps; const mapSize = getMapSize(mapTransformProps, plantAreaOffset); @@ -32,6 +37,14 @@ export class BotFigure extends (position.x || 0), (position.y || 0), mapTransformProps); const color = eStopStatus ? Color.virtualRed : Color.darkGray; const opacity = name.includes("encoder") ? 0.25 : 0.5; + const toolProps = { + x: positionQ.qx, + y: positionQ.qy, + hovered: this.state.hovered, + dispatch: noop, + uuid: "utm", + xySwap, + }; return - this.setHover(true)} onMouseLeave={() => this.setHover(false)} - cx={positionQ.qx} - cy={positionQ.qy} - r={35} fillOpacity={opacity} - fill={color} /> + fill={color}> + {this.props.mountedToolName + ? + + + + : } + {encoderFigure && ", () => { const fakeProps = (): ToolSlotGraphicProps => ({ @@ -15,6 +19,7 @@ describe("", () => { pulloutDirection: 0, quadrant: 2, xySwap: false, + occupied: true, }); it.each<[number, BotOriginQuadrant, boolean, string]>([ @@ -89,6 +94,29 @@ describe("", () => { }); }; + it("renders empty tool slot styling", () => { + const p = fakeProps(); + p.tool = ToolNames.emptyToolSlot; + const wrapper = svgMount(); + const props = wrapper.find("circle").last().props(); + expect(props.r).toEqual(34); + expect(props.fill).toEqual("none"); + expect(props.strokeDasharray).toEqual("10 5"); + }); + + it("renders empty tool slot hover styling", () => { + const p = fakeProps(); + p.tool = ToolNames.emptyToolSlot; + p.toolProps.hovered = true; + const wrapper = svgMount(); + const props = wrapper.find("circle").first().props(); + expect(props.fill).toEqual(Color.darkGray); + }); + + it("sets hover state for empty tool slot", () => { + testHoverActions(ToolNames.emptyToolSlot); + }); + it("renders standard tool styling", () => { const wrapper = svgMount(); const props = wrapper.find("circle").last().props(); @@ -107,12 +135,96 @@ describe("", () => { }); it("sets hover state for tool", () => { - testHoverActions("tool"); + testHoverActions(ToolNames.tool); + }); + + it("renders special tool styling: weeder", () => { + const p = fakeProps(); + p.tool = ToolNames.weeder; + const wrapper = svgMount(); + const elements = wrapper.find("#weeder").find("line"); + expect(elements.length).toEqual(2); + }); + + it("renders weeder hover styling", () => { + const p = fakeProps(); + p.tool = ToolNames.weeder; + p.toolProps.hovered = true; + const wrapper = svgMount(); + expect(wrapper.find("#weeder").find("circle").props().fill) + .toEqual(Color.darkGray); + }); + + it("sets hover state for weeder", () => { + testHoverActions(ToolNames.weeder); + }); + + it("renders special tool styling: watering nozzle", () => { + const p = fakeProps(); + p.tool = ToolNames.wateringNozzle; + const wrapper = svgMount(); + const elements = wrapper.find("#watering-nozzle").find("circle"); + expect(elements.length).toEqual(3); + }); + + it("renders watering nozzle hover styling", () => { + const p = fakeProps(); + p.tool = ToolNames.wateringNozzle; + p.toolProps.hovered = true; + const wrapper = svgMount(); + expect(wrapper.find("#watering-nozzle").find("circle").at(1).props().fill) + .toEqual(Color.darkGray); + }); + + it("sets hover state for watering nozzle", () => { + testHoverActions(ToolNames.wateringNozzle); + }); + + it("renders special tool styling: seeder", () => { + const p = fakeProps(); + p.tool = ToolNames.seeder; + const wrapper = svgMount(); + const elements = wrapper.find("#seeder").find("circle"); + expect(elements.length).toEqual(2); + }); + + it("renders seeder hover styling", () => { + const p = fakeProps(); + p.tool = ToolNames.seeder; + p.toolProps.hovered = true; + const wrapper = svgMount(); + expect(wrapper.find("#seeder").find("circle").first().props().fill) + .toEqual(Color.darkGray); + }); + + it("sets hover state for seeder", () => { + testHoverActions(ToolNames.seeder); + }); + + it("renders special tool styling: soil sensor", () => { + const p = fakeProps(); + p.tool = ToolNames.soilSensor; + const wrapper = svgMount(); + const elements = wrapper.find("#soil-sensor").find("line"); + expect(elements.length).toEqual(2); + }); + + it("renders soil sensor hover styling", () => { + const p = fakeProps(); + p.tool = ToolNames.soilSensor; + p.toolProps.hovered = true; + const wrapper = svgMount(); + expect(wrapper.find("#soil-sensor").find("circle").props().fill) + .toEqual(Color.darkGray); + }); + + it("sets hover state for soil sensor", () => { + testHoverActions(ToolNames.soilSensor); }); it("renders special tool styling: bin", () => { const p = fakeProps(); - p.tool = "seedBin"; + p.tool = ToolNames.seedBin; const wrapper = svgMount(); const elements = wrapper.find("#seed-bin").find("circle"); expect(elements.length).toEqual(2); @@ -121,20 +233,19 @@ describe("", () => { it("renders bin hover styling", () => { const p = fakeProps(); - p.tool = "seedBin"; + p.tool = ToolNames.seedBin; p.toolProps.hovered = true; const wrapper = svgMount(); - p.toolProps.hovered = true; expect(wrapper.find("#seed-bin").find("circle").length).toEqual(3); }); it("sets hover state for bin", () => { - testHoverActions("seedBin"); + testHoverActions(ToolNames.seedBin); }); it("renders special tool styling: tray", () => { const p = fakeProps(); - p.tool = "seedTray"; + p.tool = ToolNames.seedTray; const wrapper = svgMount(); const elements = wrapper.find("#seed-tray"); expect(elements.find("circle").length).toEqual(2); @@ -144,20 +255,19 @@ describe("", () => { it("renders tray hover styling", () => { const p = fakeProps(); - p.tool = "seedTray"; + p.tool = ToolNames.seedTray; p.toolProps.hovered = true; const wrapper = svgMount(); - p.toolProps.hovered = true; expect(wrapper.find("#seed-tray").find("circle").length).toEqual(3); }); it("sets hover state for tray", () => { - testHoverActions("seedTray"); + testHoverActions(ToolNames.seedTray); }); it("renders special tool styling: trough", () => { const p = fakeProps(); - p.tool = "seedTrough"; + p.tool = ToolNames.seedTrough; const wrapper = svgMount(); const elements = wrapper.find("#seed-trough"); expect(elements.find("circle").length).toEqual(0); @@ -166,15 +276,49 @@ describe("", () => { it("renders trough hover styling", () => { const p = fakeProps(); - p.tool = "seedTrough"; + p.tool = ToolNames.seedTrough; p.toolProps.hovered = true; const wrapper = svgMount(); - p.toolProps.hovered = true; expect(wrapper.find("#seed-trough").find("circle").length).toEqual(0); expect(wrapper.find("#seed-trough").find("rect").length).toEqual(1); }); it("sets hover state for trough", () => { - testHoverActions("seedTrough"); + testHoverActions(ToolNames.seedTrough); + }); +}); + +describe("", () => { + const fakeProps = (): ToolSVGProps => ({ + toolName: "seed trough", + }); + + it("renders trough", () => { + const wrapper = shallow(); + expect(wrapper.find("svg").props().viewBox).toEqual("-25 0 50 1"); + }); +}); + +describe("", () => { + const fakeProps = (): ToolSlotSVGProps => ({ + toolSlot: fakeToolSlot(), + toolName: "seeder", + renderRotation: false, + xySwap: false, + quadrant: 2, + }); + + it("renders slot", () => { + const p = fakeProps(); + p.toolSlot.body.pullout_direction = ToolPulloutDirection.POSITIVE_X; + const wrapper = shallow(); + expect(wrapper.find(ToolbaySlot).length).toEqual(1); + }); + + it("doesn't render slot", () => { + const p = fakeProps(); + p.toolSlot.body.pullout_direction = ToolPulloutDirection.NONE; + const wrapper = shallow(); + expect(wrapper.find(ToolbaySlot).length).toEqual(0); }); }); diff --git a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_label_test.ts b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_label_test.ts index 41323925d..7b72f45ae 100644 --- a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_label_test.ts +++ b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_label_test.ts @@ -7,50 +7,61 @@ describe("textAnchorPosition()", () => { const MIDDLE_BOTTOM = { anchor: "middle", x: 0, y: -40 }; it("returns correct label position: positive x", () => { - expect(textAnchorPosition(1, 1, false)).toEqual(END); - expect(textAnchorPosition(1, 2, false)).toEqual(START); - expect(textAnchorPosition(1, 3, false)).toEqual(START); - expect(textAnchorPosition(1, 4, false)).toEqual(END); - expect(textAnchorPosition(1, 1, true)).toEqual(MIDDLE_TOP); - expect(textAnchorPosition(1, 2, true)).toEqual(MIDDLE_TOP); - expect(textAnchorPosition(1, 3, true)).toEqual(MIDDLE_BOTTOM); - expect(textAnchorPosition(1, 4, true)).toEqual(MIDDLE_BOTTOM); + expect(textAnchorPosition(1, 1, false, false)).toEqual(END); + expect(textAnchorPosition(1, 2, false, false)).toEqual(START); + expect(textAnchorPosition(1, 3, false, false)).toEqual(START); + expect(textAnchorPosition(1, 4, false, false)).toEqual(END); + expect(textAnchorPosition(1, 1, true, false)).toEqual(MIDDLE_TOP); + expect(textAnchorPosition(1, 2, true, false)).toEqual(MIDDLE_TOP); + expect(textAnchorPosition(1, 3, true, false)).toEqual(MIDDLE_BOTTOM); + expect(textAnchorPosition(1, 4, true, false)).toEqual(MIDDLE_BOTTOM); }); it("returns correct label position: negative x", () => { - expect(textAnchorPosition(2, 1, false)).toEqual(START); - expect(textAnchorPosition(2, 2, false)).toEqual(END); - expect(textAnchorPosition(2, 3, false)).toEqual(END); - expect(textAnchorPosition(2, 4, false)).toEqual(START); - expect(textAnchorPosition(2, 1, true)).toEqual(MIDDLE_BOTTOM); - expect(textAnchorPosition(2, 2, true)).toEqual(MIDDLE_BOTTOM); - expect(textAnchorPosition(2, 3, true)).toEqual(MIDDLE_TOP); - expect(textAnchorPosition(2, 4, true)).toEqual(MIDDLE_TOP); + expect(textAnchorPosition(2, 1, false, false)).toEqual(START); + expect(textAnchorPosition(2, 2, false, false)).toEqual(END); + expect(textAnchorPosition(2, 3, false, false)).toEqual(END); + expect(textAnchorPosition(2, 4, false, false)).toEqual(START); + expect(textAnchorPosition(2, 1, true, false)).toEqual(MIDDLE_BOTTOM); + expect(textAnchorPosition(2, 2, true, false)).toEqual(MIDDLE_BOTTOM); + expect(textAnchorPosition(2, 3, true, false)).toEqual(MIDDLE_TOP); + expect(textAnchorPosition(2, 4, true, false)).toEqual(MIDDLE_TOP); }); it("returns correct label position: positive y", () => { - expect(textAnchorPosition(3, 1, false)).toEqual(MIDDLE_TOP); - expect(textAnchorPosition(3, 2, false)).toEqual(MIDDLE_TOP); - expect(textAnchorPosition(3, 3, false)).toEqual(MIDDLE_BOTTOM); - expect(textAnchorPosition(3, 4, false)).toEqual(MIDDLE_BOTTOM); - expect(textAnchorPosition(3, 1, true)).toEqual(END); - expect(textAnchorPosition(3, 2, true)).toEqual(START); - expect(textAnchorPosition(3, 3, true)).toEqual(START); - expect(textAnchorPosition(3, 4, true)).toEqual(END); + expect(textAnchorPosition(3, 1, false, false)).toEqual(MIDDLE_TOP); + expect(textAnchorPosition(3, 2, false, false)).toEqual(MIDDLE_TOP); + expect(textAnchorPosition(3, 3, false, false)).toEqual(MIDDLE_BOTTOM); + expect(textAnchorPosition(3, 4, false, false)).toEqual(MIDDLE_BOTTOM); + expect(textAnchorPosition(3, 1, true, false)).toEqual(END); + expect(textAnchorPosition(3, 2, true, false)).toEqual(START); + expect(textAnchorPosition(3, 3, true, false)).toEqual(START); + expect(textAnchorPosition(3, 4, true, false)).toEqual(END); }); it("returns correct label position: negative y", () => { - expect(textAnchorPosition(4, 1, false)).toEqual(MIDDLE_BOTTOM); - expect(textAnchorPosition(4, 2, false)).toEqual(MIDDLE_BOTTOM); - expect(textAnchorPosition(4, 3, false)).toEqual(MIDDLE_TOP); - expect(textAnchorPosition(4, 4, false)).toEqual(MIDDLE_TOP); - expect(textAnchorPosition(4, 1, true)).toEqual(START); - expect(textAnchorPosition(4, 2, true)).toEqual(END); - expect(textAnchorPosition(4, 3, true)).toEqual(END); - expect(textAnchorPosition(4, 4, true)).toEqual(START); + expect(textAnchorPosition(4, 1, false, false)).toEqual(MIDDLE_BOTTOM); + expect(textAnchorPosition(4, 2, false, false)).toEqual(MIDDLE_BOTTOM); + expect(textAnchorPosition(4, 3, false, false)).toEqual(MIDDLE_TOP); + expect(textAnchorPosition(4, 4, false, false)).toEqual(MIDDLE_TOP); + expect(textAnchorPosition(4, 1, true, false)).toEqual(START); + expect(textAnchorPosition(4, 2, true, false)).toEqual(END); + expect(textAnchorPosition(4, 3, true, false)).toEqual(END); + expect(textAnchorPosition(4, 4, true, false)).toEqual(START); + }); + + it("returns correct label position: no pullout direction", () => { + expect(textAnchorPosition(0, 1, false, false)).toEqual(END); + expect(textAnchorPosition(1, 1, false, true)).toEqual(END); + expect(textAnchorPosition(0, 1, true, false)).toEqual(MIDDLE_TOP); + expect(textAnchorPosition(1, 1, true, true)).toEqual(MIDDLE_TOP); + expect(textAnchorPosition(0, 2, false, false)).toEqual(START); + expect(textAnchorPosition(1, 2, false, true)).toEqual(START); + expect(textAnchorPosition(0, 2, true, false)).toEqual(MIDDLE_TOP); + expect(textAnchorPosition(1, 2, true, true)).toEqual(MIDDLE_TOP); }); it("handles bad data", () => { - expect(textAnchorPosition(1.1, 1.1, false)).toEqual(START); + expect(textAnchorPosition(1.1, 1.1, false, false)).toEqual(START); }); }); diff --git a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx index f47a485eb..a53c1508f 100644 --- a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx +++ b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx @@ -71,7 +71,7 @@ describe("", () => { p.slot.tool = undefined; p.hoveredToolSlot = p.slot.toolSlot.uuid; const wrapper = svgMount(); - expect(wrapper.find("text").text()).toEqual("empty"); + expect(wrapper.find("text").text()).toEqual("Empty"); expect(wrapper.find("text").props().dx).toEqual(40); }); @@ -80,6 +80,34 @@ describe("", () => { expect(wrapper.find("text").props().visibility).toEqual("hidden"); }); + it("renders weeder", () => { + const p = fakeProps(); + if (p.slot.tool) { p.slot.tool.body.name = "weeder"; } + const wrapper = svgMount(); + expect(wrapper.find("#weeder").length).toEqual(1); + }); + + it("renders watering nozzle", () => { + const p = fakeProps(); + if (p.slot.tool) { p.slot.tool.body.name = "watering nozzle"; } + const wrapper = svgMount(); + expect(wrapper.find("#watering-nozzle").length).toEqual(1); + }); + + it("renders seeder", () => { + const p = fakeProps(); + if (p.slot.tool) { p.slot.tool.body.name = "seeder"; } + const wrapper = svgMount(); + expect(wrapper.find("#seeder").length).toEqual(1); + }); + + it("renders soil sensor", () => { + const p = fakeProps(); + if (p.slot.tool) { p.slot.tool.body.name = "soil sensor"; } + const wrapper = svgMount(); + expect(wrapper.find("#soil-sensor").length).toEqual(1); + }); + it("renders bin", () => { const p = fakeProps(); if (p.slot.tool) { p.slot.tool.body.name = "seed bin"; } diff --git a/frontend/farm_designer/map/layers/tool_slots/tool_graphics.tsx b/frontend/farm_designer/map/layers/tool_slots/tool_graphics.tsx index b87288589..51dc35f5d 100644 --- a/frontend/farm_designer/map/layers/tool_slots/tool_graphics.tsx +++ b/frontend/farm_designer/map/layers/tool_slots/tool_graphics.tsx @@ -5,6 +5,9 @@ import { BotOriginQuadrant } from "../../../interfaces"; import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources"; import { Actions } from "../../../../constants"; import { UUID } from "../../../../resources/interfaces"; +import { TaggedToolSlotPointer } from "farmbot"; +import { reduceToolName } from "./tool_slot_point"; +import { noop } from "lodash"; export interface ToolGraphicProps { x: number; @@ -27,6 +30,7 @@ export interface ToolSlotGraphicProps { pulloutDirection: ToolPulloutDirection; quadrant: BotOriginQuadrant; xySwap: boolean; + occupied: boolean; } const toolbaySlotAngle = ( @@ -57,10 +61,15 @@ const toolbaySlotAngle = ( }; export enum ToolNames { + weeder = "weeder", + wateringNozzle = "wateringNozzle", + seeder = "seeder", + soilSensor = "soilSensor", seedBin = "seedBin", seedTray = "seedTray", seedTrough = "seedTrough", tool = "tool", + emptyToolSlot = "emptyToolSlot", } export const ToolbaySlot = (props: ToolSlotGraphicProps) => { @@ -82,7 +91,7 @@ export const ToolbaySlot = (props: ToolSlotGraphicProps) => { - @@ -91,9 +100,14 @@ export const ToolbaySlot = (props: ToolSlotGraphicProps) => { export const Tool = (props: ToolProps) => { switch (props.tool) { + case ToolNames.weeder: return ; + case ToolNames.wateringNozzle: return ; + case ToolNames.seeder: return ; + case ToolNames.soilSensor: return ; case ToolNames.seedBin: return ; case ToolNames.seedTray: return ; case ToolNames.seedTrough: return ; + case ToolNames.emptyToolSlot: return ; default: return ; } }; @@ -115,6 +129,115 @@ const StandardTool = (props: ToolGraphicProps) => { ; }; +const EmptySlot = (props: ToolGraphicProps) => { + const { x, y, hovered, dispatch, uuid } = props; + return dispatch(setToolHover(uuid))} + onMouseLeave={() => dispatch(setToolHover(undefined))}> + + + ; +}; + +const Weeder = (props: ToolGraphicProps) => { + const { x, y, hovered, dispatch, uuid } = props; + const size = 10; + return dispatch(setToolHover(uuid))} + onMouseLeave={() => dispatch(setToolHover(undefined))}> + + + + ; +}; + +const WateringNozzle = (props: ToolGraphicProps) => { + const { x, y, hovered, dispatch, uuid } = props; + return dispatch(setToolHover(uuid))} + onMouseLeave={() => dispatch(setToolHover(undefined))}> + + + + + + + + + + ; +}; + +const Seeder = (props: ToolGraphicProps) => { + const { x, y, hovered, dispatch, uuid } = props; + const size = 10; + return dispatch(setToolHover(uuid))} + onMouseLeave={() => dispatch(setToolHover(undefined))}> + + + ; +}; + +const SoilSensor = (props: ToolGraphicProps) => { + const { x, y, hovered, dispatch, uuid } = props; + const size = 20; + return dispatch(setToolHover(uuid))} + onMouseLeave={() => dispatch(setToolHover(undefined))}> + + + + ; +}; + const seedBinGradient = @@ -214,3 +337,62 @@ const SeedTrough = (props: ToolGraphicProps) => { fill={hovered ? Color.darkGray : Color.mediumGray} /> ; }; + +export interface ToolSlotSVGProps { + toolSlot: TaggedToolSlotPointer; + toolName: string | undefined; + renderRotation: boolean; + xySwap?: boolean; + quadrant?: BotOriginQuadrant; +} + +export const ToolSlotSVG = (props: ToolSlotSVGProps) => { + const xySwap = props.renderRotation ? !!props.xySwap : false; + const toolProps = { + x: 0, y: 0, + hovered: false, + dispatch: noop, + uuid: props.toolSlot.uuid, + xySwap, + }; + const pulloutDirection = props.renderRotation + ? props.toolSlot.body.pullout_direction + : ToolPulloutDirection.POSITIVE_X; + const quadrant = props.renderRotation && props.quadrant ? props.quadrant : 2; + const viewBox = props.renderRotation ? "-25 0 50 1" : "-25 0 50 1"; + return props.toolSlot.body.gantry_mounted + ? + + {props.toolSlot.body.tool_id && + } + + : + {props.toolSlot.body.pullout_direction && + } + {(props.toolSlot.body.tool_id || + !props.toolSlot.body.pullout_direction) && + } + ; +}; + +export interface ToolSVGProps { + toolName: string | undefined; +} + +export const ToolSVG = (props: ToolSVGProps) => { + const toolProps = { + x: 0, y: 0, hovered: false, dispatch: noop, uuid: "", xySwap: false, + }; + const viewBox = reduceToolName(props.toolName) === ToolNames.seedTrough + ? "-25 0 50 1" : "-40 0 80 1"; + return + } +; +}; diff --git a/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx b/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx index 18d9ff713..93bfcf04f 100644 --- a/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx +++ b/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx @@ -13,9 +13,17 @@ enum Anchor { export const textAnchorPosition = ( pulloutDirection: ToolPulloutDirection, quadrant: BotOriginQuadrant, - xySwap: boolean): { x: number, y: number, anchor: string } => { + xySwap: boolean, + gantryMounted: boolean, +): { x: number, y: number, anchor: string } => { const rawAnchor = () => { - const direction = pulloutDirection + (xySwap ? 2 : 0); + const noDirection = !pulloutDirection || gantryMounted; + const noDirectionXY = xySwap + ? ToolPulloutDirection.POSITIVE_Y + : ToolPulloutDirection.POSITIVE_X; + const direction = noDirection + ? noDirectionXY + : pulloutDirection + (xySwap ? 2 : 0); switch (direction > 4 ? direction % 4 : direction) { case ToolPulloutDirection.POSITIVE_X: return Anchor.start; case ToolPulloutDirection.NEGATIVE_X: return Anchor.end; @@ -51,12 +59,15 @@ interface ToolLabelProps { pulloutDirection: ToolPulloutDirection; quadrant: BotOriginQuadrant; xySwap: boolean; + gantryMounted: boolean; } export const ToolLabel = (props: ToolLabelProps) => { - const { toolName, hovered, x, y, pulloutDirection, quadrant, xySwap } = props; - const labelAnchor = textAnchorPosition(pulloutDirection, quadrant, xySwap); - + const { + toolName, hovered, x, y, pulloutDirection, quadrant, xySwap, gantryMounted, + } = props; + const labelAnchor = textAnchorPosition + (pulloutDirection, quadrant, xySwap, gantryMounted); return { +export const reduceToolName = (raw: string | undefined) => { const lower = (raw || "").toLowerCase(); + if (raw == "Empty") { return ToolNames.emptyToolSlot; } + if (includes(lower, "weeder")) { return ToolNames.weeder; } + if (includes(lower, "watering nozzle")) { return ToolNames.wateringNozzle; } + if (includes(lower, "seeder")) { return ToolNames.seeder; } + if (includes(lower, "soil sensor")) { return ToolNames.soilSensor; } if (includes(lower, "seed bin")) { return ToolNames.seedBin; } if (includes(lower, "seed tray")) { return ToolNames.seedTray; } if (includes(lower, "seed trough")) { return ToolNames.seedTrough; } @@ -32,7 +38,7 @@ export const ToolSlotPoint = (props: TSPProps) => { const { quadrant, xySwap } = mapTransformProps; const xPosition = gantry_mounted ? (botPositionX || 0) : x; const { qx, qy } = transformXY(xPosition, y, props.mapTransformProps); - const toolName = props.slot.tool ? props.slot.tool.body.name : "empty"; + const toolName = props.slot.tool ? props.slot.tool.body.name : t("Empty"); const hovered = props.slot.toolSlot.uuid === props.hoveredToolSlot; const toolProps = { x: qx, @@ -45,13 +51,14 @@ export const ToolSlotPoint = (props: TSPProps) => { return !DevSettings.futureFeaturesEnabled() && history.push(`/app/designer/tool-slots/${id}`)}> - {pullout_direction && + {pullout_direction && !gantry_mounted && } {gantry_mounted && } @@ -67,6 +74,7 @@ export const ToolSlotPoint = (props: TSPProps) => { x={qx} y={qy} pulloutDirection={pullout_direction} + gantryMounted={gantry_mounted} quadrant={quadrant} xySwap={xySwap} /> ; diff --git a/frontend/farm_designer/point_groups/__tests__/group_detail_active_test.tsx b/frontend/farm_designer/point_groups/__tests__/group_detail_active_test.tsx index 1054d9806..d987e4db7 100644 --- a/frontend/farm_designer/point_groups/__tests__/group_detail_active_test.tsx +++ b/frontend/farm_designer/point_groups/__tests__/group_detail_active_test.tsx @@ -6,13 +6,6 @@ jest.mock("../../../api/crud", () => ({ jest.mock("../../map/actions", () => ({ setHoveredPlant: jest.fn() })); -let mockDev = false; -jest.mock("../../../account/dev/dev_support", () => ({ - DevSettings: { - futureFeaturesEnabled: () => mockDev, - } -})); - import React from "react"; import { GroupDetailActive, GroupDetailActiveProps @@ -107,19 +100,11 @@ describe("", () => { }); it("shows paths", () => { - mockDev = false; const p = fakeProps(); const wrapper = mount(); expect(wrapper.text().toLowerCase()).toContain("0m"); }); - it("doesn't show paths", () => { - mockDev = true; - const p = fakeProps(); - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).not.toContain("0m"); - }); - it("shows random warning text", () => { const p = fakeProps(); p.group.body.sort_type = "random"; diff --git a/frontend/farm_designer/point_groups/__tests__/paths_test.tsx b/frontend/farm_designer/point_groups/__tests__/paths_test.tsx index 31cecd0ba..4ad44cabf 100644 --- a/frontend/farm_designer/point_groups/__tests__/paths_test.tsx +++ b/frontend/farm_designer/point_groups/__tests__/paths_test.tsx @@ -22,7 +22,7 @@ import { Actions } from "../../../constants"; import { edit } from "../../../api/crud"; import { error } from "../../../toast/toast"; import { svgMount } from "../../../__test_support__/svg_mount"; -import { SORT_OPTIONS } from "../point_group_sort_selector"; +import { SORT_OPTIONS } from "../point_group_sort"; import { PointGroupSortType } from "farmbot/dist/resources/api_resources"; /** diff --git a/frontend/farm_designer/point_groups/__tests__/point_group_sort_selector_test.tsx b/frontend/farm_designer/point_groups/__tests__/point_group_sort_test.ts similarity index 59% rename from frontend/farm_designer/point_groups/__tests__/point_group_sort_selector_test.tsx rename to frontend/farm_designer/point_groups/__tests__/point_group_sort_test.ts index bfd1f1d60..096e36b7d 100644 --- a/frontend/farm_designer/point_groups/__tests__/point_group_sort_selector_test.tsx +++ b/frontend/farm_designer/point_groups/__tests__/point_group_sort_test.ts @@ -1,45 +1,8 @@ -import { - isSortType, sortTypeChange, SORT_OPTIONS -} from "../point_group_sort_selector"; -import { DropDownItem } from "../../../ui"; +import { SORT_OPTIONS } from "../point_group_sort"; import { PointGroupSortType } from "farmbot/dist/resources/api_resources"; import { TaggedPoint } from "farmbot"; import { fakePlant } from "../../../__test_support__/fake_state/resources"; -const tests: [string, boolean][] = [ - ["", false], - ["nope", false], - ["random", true], - ["xy_ascending", true], - ["xy_descending", true], - ["yx_ascending", true], - ["yx_descending", true] -]; - -describe("isSortType", () => { - it("identifies malformed sort types", () => { - tests.map(([sortType, valid]) => { - expect(isSortType(sortType)).toBe(valid); - }); - }); -}); - -describe("sortTypeChange", () => { - it("selectively triggers the callback", () => { - tests.map(([value, valid]) => { - const cb = jest.fn(); - const ddi: DropDownItem = { value, label: "TEST" }; - if (valid) { - sortTypeChange(cb)(ddi); - expect(cb).toHaveBeenCalledWith(value); - } else { - sortTypeChange(cb)(ddi); - expect(cb).not.toHaveBeenCalled(); - } - }); - }); -}); - describe("sort()", () => { const phony = (name: string, x: number, y: number): TaggedPoint => { const plant = fakePlant(); diff --git a/frontend/farm_designer/point_groups/criteria/add.tsx b/frontend/farm_designer/point_groups/criteria/add.tsx index d4f9275b2..a24826756 100644 --- a/frontend/farm_designer/point_groups/criteria/add.tsx +++ b/frontend/farm_designer/point_groups/criteria/add.tsx @@ -70,7 +70,7 @@ export const CRITERIA_TYPE_LIST = () => [ export const POINTER_TYPE_DDI_LOOKUP = (): { [x: string]: DropDownItem } => ({ Plant: { label: t("Plants"), value: "Plant" }, GenericPointer: { label: t("Points"), value: "GenericPointer" }, - ToolSlot: { label: t("Tool Slots"), value: "ToolSlot" }, + ToolSlot: { label: t("Slots"), value: "ToolSlot" }, }); export const POINTER_TYPE_LIST = () => [ POINTER_TYPE_DDI_LOOKUP().Plant, diff --git a/frontend/farm_designer/point_groups/group_detail_active.tsx b/frontend/farm_designer/point_groups/group_detail_active.tsx index 5c50e92a3..66ae340e7 100644 --- a/frontend/farm_designer/point_groups/group_detail_active.tsx +++ b/frontend/farm_designer/point_groups/group_detail_active.tsx @@ -7,11 +7,10 @@ import { import { TaggedPointGroup, TaggedPoint } from "farmbot"; import { DeleteButton } from "../../ui/delete_button"; import { save, edit } from "../../api/crud"; -import { PointGroupSortSelector, sortGroupBy } from "./point_group_sort_selector"; +import { sortGroupBy } from "./point_group_sort"; import { PointGroupSortType } from "farmbot/dist/resources/api_resources"; import { PointGroupItem } from "./point_group_item"; import { Paths } from "./paths"; -import { DevSettings } from "../../account/dev/dev_support"; import { Feature, ShouldDisplay } from "../../devices/interfaces"; import { ErrorBoundary } from "../../error_boundary"; import { @@ -103,16 +102,12 @@ export class GroupDetailActive - {!DevSettings.futureFeaturesEnabled() - ? p.body.id))} - pathPoints={this.pointsSelectedByGroup} - dispatch={dispatch} - group={group} /> - : } + p.body.id))} + pathPoints={this.pointsSelectedByGroup} + dispatch={dispatch} + group={group} />

{group.body.sort_type == "random" && t(Content.SORT_DESCRIPTION)}

diff --git a/frontend/farm_designer/point_groups/group_list_panel.tsx b/frontend/farm_designer/point_groups/group_list_panel.tsx index 6f7949970..93f7884d8 100644 --- a/frontend/farm_designer/point_groups/group_list_panel.tsx +++ b/frontend/farm_designer/point_groups/group_list_panel.tsx @@ -48,7 +48,7 @@ export class RawGroupListPanel extends React.Component + title={t("Add group")}> diff --git a/frontend/farm_designer/point_groups/group_order_visual.tsx b/frontend/farm_designer/point_groups/group_order_visual.tsx index cdf9f21cc..800df2c13 100644 --- a/frontend/farm_designer/point_groups/group_order_visual.tsx +++ b/frontend/farm_designer/point_groups/group_order_visual.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { store } from "../../redux/store"; import { MapTransformProps } from "../map/interfaces"; import { isUndefined } from "lodash"; -import { sortGroupBy } from "./point_group_sort_selector"; +import { sortGroupBy } from "./point_group_sort"; import { Color } from "../../ui"; import { transformXY } from "../map/util"; import { nn } from "./paths"; diff --git a/frontend/farm_designer/point_groups/paths.tsx b/frontend/farm_designer/point_groups/paths.tsx index e2368e967..9c9b5b134 100644 --- a/frontend/farm_designer/point_groups/paths.tsx +++ b/frontend/farm_designer/point_groups/paths.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { MapTransformProps } from "../map/interfaces"; -import { sortGroupBy, sortOptionsTable } from "./point_group_sort_selector"; +import { sortGroupBy, sortOptionsTable } from "./point_group_sort"; import { sortBy, isNumber } from "lodash"; import { PointsPathLine } from "./group_order_visual"; import { Color } from "../../ui"; diff --git a/frontend/farm_designer/point_groups/point_group_sort_selector.tsx b/frontend/farm_designer/point_groups/point_group_sort.ts similarity index 58% rename from frontend/farm_designer/point_groups/point_group_sort_selector.tsx rename to frontend/farm_designer/point_groups/point_group_sort.ts index e1c335dd9..36803a2d2 100644 --- a/frontend/farm_designer/point_groups/point_group_sort_selector.tsx +++ b/frontend/farm_designer/point_groups/point_group_sort.ts @@ -1,6 +1,4 @@ -import * as React from "react"; import { PointGroupSortType } from "farmbot/dist/resources/api_resources"; -import { FBSelect, DropDownItem } from "../../ui"; import { t } from "../../i18next_wrapper"; import { shuffle, sortBy } from "lodash"; import { TaggedPoint } from "farmbot"; @@ -18,36 +16,6 @@ export const sortOptionsTable = (): Record => ({ "yx_descending": t("Y/X, Descending"), }); // Typechecker will remind us when this needs an update. Don't simplify - RC -const optionPlusDescriptions = () => - (Object - .entries(sortOptionsTable()) as [PointGroupSortType, string][]) - .map(x => ({ label: x[1], value: x[0] })); - -const optionList = - optionPlusDescriptions().map(x => x.value); - -export const isSortType = (x: unknown): x is PointGroupSortType => { - return optionList.includes(x as PointGroupSortType); -}; - -const selected = (value: PointGroupSortType) => ({ - label: t(sortOptionsTable()[value] || value), - value: value -}); - -export const sortTypeChange = (cb: Function) => (ddi: DropDownItem) => { - const { value } = ddi; - isSortType(value) && cb(value); -}; - -export function PointGroupSortSelector(p: PointGroupSortSelectorProps) { - return ; -} - type Sorter = (p: TaggedPoint[]) => TaggedPoint[]; type SortDictionary = Record; diff --git a/frontend/farm_designer/points/__tests__/create_points_test.tsx b/frontend/farm_designer/points/__tests__/create_points_test.tsx index 669958c6e..1d66b188c 100644 --- a/frontend/farm_designer/points/__tests__/create_points_test.tsx +++ b/frontend/farm_designer/points/__tests__/create_points_test.tsx @@ -70,14 +70,14 @@ describe("", () => { it("renders for points", () => { mockPath = "/app/designer"; const wrapper = mount(); - ["create point", "delete", "x", "y", "radius", "color"] + ["add point", "delete", "x", "y", "radius", "color"] .map(string => expect(wrapper.text().toLowerCase()).toContain(string)); }); it("renders for weeds", () => { mockPath = "/app/designer/weeds/add"; const wrapper = mount(); - ["create weed", "delete", "x", "y", "radius", "color"] + ["add weed", "delete", "x", "y", "radius", "color"] .map(string => expect(wrapper.text().toLowerCase()).toContain(string)); }); diff --git a/frontend/farm_designer/points/create_points.tsx b/frontend/farm_designer/points/create_points.tsx index 227ab3051..a0ebd2143 100644 --- a/frontend/farm_designer/points/create_points.tsx +++ b/frontend/farm_designer/points/create_points.tsx @@ -274,7 +274,7 @@ export class RawCreatePoints diff --git a/frontend/farm_designer/saved_gardens/__tests__/garden_add_test.tsx b/frontend/farm_designer/saved_gardens/__tests__/garden_add_test.tsx index 883600f9c..aeaa2a0b1 100644 --- a/frontend/farm_designer/saved_gardens/__tests__/garden_add_test.tsx +++ b/frontend/farm_designer/saved_gardens/__tests__/garden_add_test.tsx @@ -15,7 +15,7 @@ describe("", () => { it("renders add garden panel", () => { const wrapper = mount(); - expect(wrapper.text()).toContain("create new garden"); + expect(wrapper.text().toLowerCase()).toContain("add garden"); }); }); diff --git a/frontend/farm_designer/saved_gardens/garden_add.tsx b/frontend/farm_designer/saved_gardens/garden_add.tsx index b0a1b3daa..255f43dde 100644 --- a/frontend/farm_designer/saved_gardens/garden_add.tsx +++ b/frontend/farm_designer/saved_gardens/garden_add.tsx @@ -29,7 +29,7 @@ export class RawAddGarden extends React.Component { diff --git a/frontend/farm_designer/saved_gardens/garden_snapshot.tsx b/frontend/farm_designer/saved_gardens/garden_snapshot.tsx index 6687f10e0..2c906ae38 100644 --- a/frontend/farm_designer/saved_gardens/garden_snapshot.tsx +++ b/frontend/farm_designer/saved_gardens/garden_snapshot.tsx @@ -49,7 +49,7 @@ export class GardenSnapshot
; } diff --git a/frontend/farm_designer/state_to_props.ts b/frontend/farm_designer/state_to_props.ts index b5f76e15b..c1e32879b 100644 --- a/frontend/farm_designer/state_to_props.ts +++ b/frontend/farm_designer/state_to_props.ts @@ -11,7 +11,9 @@ import { selectAllSensors, maybeGetTimeSettings, selectAllPoints, - selectAllPointGroups + selectAllPointGroups, + getDeviceAccountSettings, + maybeFindToolById } from "../resources/selectors"; import { validBotLocationData, validFwConfig, unpackUUID } from "../util"; import { getWebAppConfigValue } from "../config_storage/actions"; @@ -64,6 +66,11 @@ export function mapStateToProps(props: Everything): Props { y: calcMicrostepsPerMm(fw.movement_step_per_mm_y, fw.movement_microsteps_y), }; + const mountedToolId = + getDeviceAccountSettings(props.resources.index).body.mounted_tool_id; + const mountedToolName = + maybeFindToolById(props.resources.index, mountedToolId)?.body.name; + const peripherals = uniq(selectAllPeripherals(props.resources.index)) .map(x => { const label = x.body.label; @@ -123,5 +130,6 @@ export function mapStateToProps(props: Everything): Props { sensors: selectAllSensors(props.resources.index), groups: selectAllPointGroups(props.resources.index), shouldDisplay, + mountedToolName, }; } diff --git a/frontend/farm_designer/tools/__tests__/add_tool_slot_test.tsx b/frontend/farm_designer/tools/__tests__/add_tool_slot_test.tsx index 76a5c991b..3c631136f 100644 --- a/frontend/farm_designer/tools/__tests__/add_tool_slot_test.tsx +++ b/frontend/farm_designer/tools/__tests__/add_tool_slot_test.tsx @@ -9,12 +9,10 @@ jest.mock("../../../history", () => ({ history: { push: jest.fn() } })); import * as React from "react"; import { mount, shallow } from "enzyme"; -import { - RawAddToolSlot as AddToolSlot, AddToolSlotProps, mapStateToProps -} from "../add_tool_slot"; +import { RawAddToolSlot as AddToolSlot } from "../add_tool_slot"; import { fakeState } from "../../../__test_support__/fake_state"; import { - fakeTool, fakeToolSlot + fakeTool, fakeToolSlot, fakeWebAppConfig } from "../../../__test_support__/fake_state/resources"; import { buildResourceIndex @@ -23,6 +21,7 @@ import { init, save, edit, destroy } from "../../../api/crud"; import { history } from "../../../history"; import { SpecialStatus } from "farmbot"; import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources"; +import { AddToolSlotProps, mapStateToPropsAdd } from "../map_to_props_add_edit"; describe("", () => { const fakeProps = (): AddToolSlotProps => ({ @@ -32,15 +31,18 @@ describe("", () => { dispatch: jest.fn(), findToolSlot: fakeToolSlot, firmwareHardware: undefined, + xySwap: false, + quadrant: 2, + isActive: jest.fn(), }); it("renders", () => { const wrapper = mount(); - ["add new tool slot", "x (mm)", "y (mm)", "z (mm)", "tool or seed container", - "change slot direction", "use current location", "gantry-mounted" + ["add new slot", "x (mm)", "y (mm)", "z (mm)", "tool or seed container", + "change direction", "gantry-mounted" ].map(string => expect(wrapper.text().toLowerCase()).toContain(string)); expect(init).toHaveBeenCalledWith("Point", { - pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {}, + pointer_type: "ToolSlot", name: "Slot", radius: 0, meta: {}, x: 0, y: 0, z: 0, tool_id: undefined, pullout_direction: ToolPulloutDirection.NONE, gantry_mounted: false, @@ -116,7 +118,7 @@ describe("", () => { const wrapper = mount(); expect(wrapper.text().toLowerCase()).not.toContain("tool"); expect(init).toHaveBeenCalledWith("Point", { - pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {}, + pointer_type: "ToolSlot", name: "Slot", radius: 0, meta: {}, x: 0, y: 0, z: 0, tool_id: undefined, pullout_direction: ToolPulloutDirection.NONE, gantry_mounted: true, @@ -124,14 +126,17 @@ describe("", () => { }); }); -describe("mapStateToProps()", () => { +describe("mapStateToPropsAdd()", () => { it("returns props", () => { + const webAppConfig = fakeWebAppConfig(); + webAppConfig.body.bot_origin_quadrant = 1; const tool = fakeTool(); tool.body.id = 1; const toolSlot = fakeToolSlot(); const state = fakeState(); - state.resources = buildResourceIndex([tool, toolSlot]); - const props = mapStateToProps(state); + state.resources = buildResourceIndex([tool, toolSlot, webAppConfig]); + const props = mapStateToPropsAdd(state); + expect(props.quadrant).toEqual(1); expect(props.findTool(1)).toEqual(tool); expect(props.findToolSlot(toolSlot.uuid)).toEqual(toolSlot); }); diff --git a/frontend/farm_designer/tools/__tests__/add_tool_test.tsx b/frontend/farm_designer/tools/__tests__/add_tool_test.tsx index c8d06b478..91b6f2d1f 100644 --- a/frontend/farm_designer/tools/__tests__/add_tool_test.tsx +++ b/frontend/farm_designer/tools/__tests__/add_tool_test.tsx @@ -22,7 +22,7 @@ describe("", () => { it("renders", () => { const wrapper = mount(); - expect(wrapper.text()).toContain("Add new tool"); + expect(wrapper.text()).toContain("Add new"); }); it("edits tool name", () => { @@ -60,11 +60,36 @@ describe("", () => { p.firmwareHardware = "express_k10"; p.existingToolNames = ["Seed Trough 1"]; const wrapper = mount(); - wrapper.setState({ model: "express" }); wrapper.find("button").last().simulate("click"); expect(initSave).toHaveBeenCalledTimes(1); expect(history.push).toHaveBeenCalledWith("/app/designer/tools"); }); + + it("copies a tool name", () => { + const p = fakeProps(); + p.firmwareHardware = "express_k10"; + const wrapper = mount(); + wrapper.find("p").last().simulate("click"); + expect(wrapper.state().toolName).toEqual("Seed Trough 2"); + }); + + it("deselects a tool", () => { + const p = fakeProps(); + p.firmwareHardware = "express_k10"; + const wrapper = mount(); + expect(wrapper.state().toAdd).toEqual(["Seed Trough 1", "Seed Trough 2"]); + wrapper.find("input").last().simulate("change"); + expect(wrapper.state().toAdd).toEqual(["Seed Trough 1"]); + }); + + it("selects a tool", () => { + const p = fakeProps(); + p.firmwareHardware = "express_k10"; + const wrapper = mount(); + wrapper.setState({ toAdd: [] }); + wrapper.find("input").last().simulate("change"); + expect(wrapper.state().toAdd).toEqual(["Seed Trough 2"]); + }); }); describe("mapStateToProps()", () => { diff --git a/frontend/farm_designer/tools/__tests__/edit_tool_slot_test.tsx b/frontend/farm_designer/tools/__tests__/edit_tool_slot_test.tsx index 7057f20dd..d1938eed9 100644 --- a/frontend/farm_designer/tools/__tests__/edit_tool_slot_test.tsx +++ b/frontend/farm_designer/tools/__tests__/edit_tool_slot_test.tsx @@ -9,9 +9,7 @@ jest.mock("../../../device", () => ({ getDevice: () => mockDevice })); import * as React from "react"; import { mount, shallow } from "enzyme"; -import { - RawEditToolSlot as EditToolSlot, EditToolSlotProps, mapStateToProps -} from "../edit_tool_slot"; +import { RawEditToolSlot as EditToolSlot } from "../edit_tool_slot"; import { fakeState } from "../../../__test_support__/fake_state"; import { fakeToolSlot, fakeTool @@ -20,6 +18,10 @@ import { buildResourceIndex } from "../../../__test_support__/resource_index_builder"; import { destroy, edit, save } from "../../../api/crud"; +import { + EditToolSlotProps, mapStateToPropsEdit +} from "../map_to_props_add_edit"; +import { SlotEditRows } from "../tool_slot_edit_components"; describe("", () => { const fakeProps = (): EditToolSlotProps => ({ @@ -29,6 +31,9 @@ describe("", () => { botPosition: { x: undefined, y: undefined, z: undefined }, dispatch: jest.fn(), firmwareHardware: undefined, + xySwap: false, + quadrant: 2, + isActive: jest.fn(), }); it("redirects", () => { @@ -40,8 +45,8 @@ describe("", () => { const p = fakeProps(); p.findToolSlot = () => fakeToolSlot(); const wrapper = mount(); - ["edit tool slot", "x (mm)", "y (mm)", "z (mm)", "tool or seed container", - "change slot direction", "use current location", "gantry-mounted" + ["edit slot", "x (mm)", "y (mm)", "z (mm)", "tool or seed container", + "change direction", "gantry-mounted" ].map(string => expect(wrapper.text().toLowerCase()).toContain(string)); }); @@ -65,6 +70,34 @@ describe("", () => { expect(mockDevice.moveAbsolute).toHaveBeenCalledWith({ x: 1, y: 2, z: 3 }); }); + it("moves to gantry-mounted tool slot", () => { + const p = fakeProps(); + p.botPosition = { x: 10, y: 20, z: 30 }; + const toolSlot = fakeToolSlot(); + toolSlot.body.gantry_mounted = true; + toolSlot.body.x = 1; + toolSlot.body.y = 2; + toolSlot.body.z = 3; + p.findToolSlot = () => toolSlot; + const wrapper = shallow(); + wrapper.find(".gray").last().simulate("click"); + expect(mockDevice.moveAbsolute).toHaveBeenCalledWith({ x: 10, y: 2, z: 3 }); + }); + + it("falls back to tool slot when moving to gantry-mounted tool slot", () => { + const p = fakeProps(); + p.botPosition = { x: undefined, y: undefined, z: undefined }; + const toolSlot = fakeToolSlot(); + toolSlot.body.gantry_mounted = true; + toolSlot.body.x = 1; + toolSlot.body.y = 2; + toolSlot.body.z = 3; + p.findToolSlot = () => toolSlot; + const wrapper = shallow(); + wrapper.find(".gray").last().simulate("click"); + expect(mockDevice.moveAbsolute).toHaveBeenCalledWith({ x: 1, y: 2, z: 3 }); + }); + it("removes tool slot", () => { const p = fakeProps(); const toolSlot = fakeToolSlot(); @@ -73,9 +106,19 @@ describe("", () => { wrapper.find("button").last().simulate("click"); expect(destroy).toHaveBeenCalledWith(toolSlot.uuid); }); + + it("finds tool", () => { + const p = fakeProps(); + const toolSlot = fakeToolSlot(); + p.findToolSlot = () => toolSlot; + const tool = fakeTool(); + p.findTool = () => tool; + const wrapper = mount(); + expect(wrapper.find(SlotEditRows).props().tool).toEqual(tool); + }); }); -describe("mapStateToProps()", () => { +describe("mapStateToPropsEdit()", () => { it("returns props", () => { const tool = fakeTool(); tool.body.id = 1; @@ -83,7 +126,7 @@ describe("mapStateToProps()", () => { toolSlot.body.id = 1; const state = fakeState(); state.resources = buildResourceIndex([tool, toolSlot]); - const props = mapStateToProps(state); + const props = mapStateToPropsEdit(state); expect(props.findTool(1)).toEqual(tool); expect(props.findToolSlot("1")).toEqual(toolSlot); }); @@ -91,7 +134,7 @@ describe("mapStateToProps()", () => { it("doesn't find tool slot", () => { const state = fakeState(); state.resources = buildResourceIndex([]); - const props = mapStateToProps(state); + const props = mapStateToPropsEdit(state); expect(props.findToolSlot("1")).toEqual(undefined); }); }); diff --git a/frontend/farm_designer/tools/__tests__/edit_tool_test.tsx b/frontend/farm_designer/tools/__tests__/edit_tool_test.tsx index 88193b14c..2bef0eae0 100644 --- a/frontend/farm_designer/tools/__tests__/edit_tool_test.tsx +++ b/frontend/farm_designer/tools/__tests__/edit_tool_test.tsx @@ -13,16 +13,17 @@ jest.mock("../../../history", () => ({ import * as React from "react"; import { mount, shallow } from "enzyme"; import { - RawEditTool as EditTool, EditToolProps, mapStateToProps + RawEditTool as EditTool, EditToolProps, mapStateToProps, isActive } from "../edit_tool"; -import { fakeTool } from "../../../__test_support__/fake_state/resources"; +import { fakeTool, fakeToolSlot } from "../../../__test_support__/fake_state/resources"; import { fakeState } from "../../../__test_support__/fake_state"; import { - buildResourceIndex + buildResourceIndex, fakeDevice } from "../../../__test_support__/resource_index_builder"; import { SaveBtn } from "../../../ui"; import { history } from "../../../history"; import { edit, destroy } from "../../../api/crud"; +import { clickButton } from "../../../__test_support__/helpers"; describe("", () => { beforeEach(() => { @@ -32,6 +33,8 @@ describe("", () => { const fakeProps = (): EditToolProps => ({ findTool: jest.fn(() => fakeTool()), dispatch: jest.fn(), + mountedToolId: undefined, + isActive: jest.fn(), }); it("renders", () => { @@ -75,11 +78,38 @@ describe("", () => { it("removes tool", () => { const p = fakeProps(); const tool = fakeTool(); + tool.body.id = 1; p.findTool = () => tool; + p.isActive = () => false; + p.mountedToolId = undefined; const wrapper = shallow(); - wrapper.find("button").last().simulate("click"); + clickButton(wrapper, 0, "delete"); expect(destroy).toHaveBeenCalledWith(tool.uuid); }); + + it("doesn't remove tool: active", () => { + const p = fakeProps(); + const tool = fakeTool(); + tool.body.id = 1; + p.findTool = () => tool; + p.isActive = () => true; + p.mountedToolId = undefined; + const wrapper = shallow(); + clickButton(wrapper, 0, "delete"); + expect(destroy).not.toHaveBeenCalledWith(tool.uuid); + }); + + it("doesn't remove tool: mounted", () => { + const p = fakeProps(); + const tool = fakeTool(); + tool.body.id = 1; + p.findTool = () => tool; + p.isActive = () => false; + p.mountedToolId = tool.body.id; + const wrapper = shallow(); + clickButton(wrapper, 0, "delete"); + expect(destroy).not.toHaveBeenCalledWith(tool.uuid); + }); }); describe("mapStateToProps()", () => { @@ -87,8 +117,19 @@ describe("mapStateToProps()", () => { const state = fakeState(); const tool = fakeTool(); tool.body.id = 123; - state.resources = buildResourceIndex([tool]); + state.resources = buildResourceIndex([tool, fakeDevice()]); const props = mapStateToProps(state); expect(props.findTool("" + tool.body.id)).toEqual(tool); }); }); + +describe("isActive()", () => { + it("returns tool state", () => { + const toolSlot = fakeToolSlot(); + toolSlot.body.tool_id = 1; + const active = isActive([toolSlot]); + expect(active(1)).toEqual(true); + expect(active(2)).toEqual(false); + expect(active(undefined)).toEqual(false); + }); +}); diff --git a/frontend/farm_designer/tools/__tests__/index_test.tsx b/frontend/farm_designer/tools/__tests__/index_test.tsx index c4584f781..22f074c0e 100644 --- a/frontend/farm_designer/tools/__tests__/index_test.tsx +++ b/frontend/farm_designer/tools/__tests__/index_test.tsx @@ -13,7 +13,10 @@ jest.mock("../../../device", () => ({ getDevice: () => mockDevice })); import * as React from "react"; import { mount, shallow } from "enzyme"; -import { RawTools as Tools, ToolsProps, mapStateToProps } from "../index"; +import { + RawTools as Tools, ToolsProps, mapStateToProps, + ToolSlotInventoryItem, ToolSlotInventoryItemProps, +} from "../index"; import { fakeTool, fakeToolSlot, fakeSensor } from "../../../__test_support__/fake_state/resources"; @@ -40,6 +43,7 @@ describe("", () => { botToMqttStatus: "down", hoveredToolSlot: undefined, firmwareHardware: undefined, + isActive: jest.fn(), }); it("renders with no tools", () => { @@ -182,6 +186,62 @@ describe("", () => { const wrapper = mount(); expect(wrapper.text().toLowerCase()).not.toContain("mounted tool"); }); + + it("displays tool as active", () => { + const p = fakeProps(); + p.tools = [fakeTool()]; + p.isActive = () => true; + p.device.body.mounted_tool_id = undefined; + const wrapper = mount(); + expect(wrapper.text().toLowerCase()).toContain("active"); + }); + + it("displays tool as mounted", () => { + const p = fakeProps(); + const tool = fakeTool(); + tool.body.id = 1; + p.findTool = () => tool; + p.tools = [tool]; + p.device.body.mounted_tool_id = 1; + const wrapper = mount(); + expect(wrapper.find("p").last().text().toLowerCase()).toContain("mounted"); + }); + + it("handles missing tools", () => { + const p = fakeProps(); + const tool = fakeTool(); + tool.body.id = 1; + p.findTool = () => undefined; + p.tools = [tool]; + p.device.body.mounted_tool_id = 1; + const wrapper = mount(); + expect(wrapper.find("p").last().text().toLowerCase()).not.toContain("mounted"); + }); +}); + +describe("", () => { + const fakeProps = (): ToolSlotInventoryItemProps => ({ + toolSlot: fakeToolSlot(), + tools: [], + hovered: false, + dispatch: jest.fn(), + isActive: jest.fn(), + }); + + it("changes tool", () => { + const p = fakeProps(); + const wrapper = shallow(); + wrapper.find(ToolSelection).simulate("change", { tool_id: 1 }); + expect(edit).toHaveBeenCalledWith(p.toolSlot, { tool_id: 1 }); + expect(save).toHaveBeenCalledWith(p.toolSlot.uuid); + }); + + it("doesn't open tool slot", () => { + const wrapper = shallow(); + const e = { stopPropagation: jest.fn() }; + wrapper.find(".tool-selection-wrapper").first().simulate("click", e); + expect(e.stopPropagation).toHaveBeenCalled(); + }); }); describe("mapStateToProps()", () => { diff --git a/frontend/farm_designer/tools/__tests__/tool_slot_edit_components_test.tsx b/frontend/farm_designer/tools/__tests__/tool_slot_edit_components_test.tsx index 861c26b3f..e75dbf479 100644 --- a/frontend/farm_designer/tools/__tests__/tool_slot_edit_components_test.tsx +++ b/frontend/farm_designer/tools/__tests__/tool_slot_edit_components_test.tsx @@ -2,14 +2,13 @@ import * as React from "react"; import { shallow, mount } from "enzyme"; import { GantryMountedInput, GantryMountedInputProps, - UseCurrentLocationInputRow, UseCurrentLocationInputRowProps, SlotDirectionInputRow, SlotDirectionInputRowProps, ToolInputRow, ToolInputRowProps, SlotLocationInputRow, SlotLocationInputRowProps, - ToolSelection, ToolSelectionProps, + ToolSelection, ToolSelectionProps, SlotEditRows, SlotEditRowsProps, } from "../tool_slot_edit_components"; -import { fakeTool } from "../../../__test_support__/fake_state/resources"; -import { FBSelect } from "../../../ui"; +import { fakeTool, fakeToolSlot } from "../../../__test_support__/fake_state/resources"; +import { FBSelect, NULL_CHOICE } from "../../../ui"; describe("", () => { const fakeProps = (): GantryMountedInputProps => ({ @@ -30,33 +29,6 @@ describe("", () => { }); }); -describe("", () => { - const fakeProps = (): UseCurrentLocationInputRowProps => ({ - botPosition: { x: undefined, y: undefined, z: undefined }, - onChange: jest.fn(), - }); - - it("renders", () => { - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("use current location"); - }); - - it("doesn't change value", () => { - const p = fakeProps(); - const wrapper = shallow(); - wrapper.find("button").simulate("click"); - expect(p.onChange).not.toHaveBeenCalled(); - }); - - it("changes value", () => { - const p = fakeProps(); - p.botPosition = { x: 0, y: 1, z: 2 }; - const wrapper = shallow(); - wrapper.find("button").simulate("click"); - expect(p.onChange).toHaveBeenCalledWith(p.botPosition); - }); -}); - describe("", () => { const fakeProps = (): SlotDirectionInputRowProps => ({ toolPulloutDirection: 0, @@ -65,7 +37,7 @@ describe("", () => { it("renders", () => { const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("change slot direction"); + expect(wrapper.text().toLowerCase()).toContain("change direction"); }); it("changes value by click", () => { @@ -89,6 +61,7 @@ describe("", () => { selectedTool: undefined, onChange: jest.fn(), filterSelectedTool: false, + isActive: jest.fn(), }); it("renders", () => { @@ -98,12 +71,13 @@ describe("", () => { it("handles missing tool data", () => { const p = fakeProps(); + p.filterSelectedTool = true; const tool = fakeTool(); tool.body.name = undefined; tool.body.id = undefined; p.tools = [tool]; const wrapper = shallow(); - expect(wrapper.find("FBSelect").props().list).toEqual([]); + expect(wrapper.find("FBSelect").props().list).toEqual([NULL_CHOICE]); }); it("handles missing selected tool data", () => { @@ -137,6 +111,7 @@ describe("", () => { selectedTool: undefined, onChange: jest.fn(), isExpress: false, + isActive: jest.fn(), }); it("renders", () => { @@ -164,6 +139,7 @@ describe("", () => { slotLocation: { x: 0, y: 0, z: 0 }, gantryMounted: false, onChange: jest.fn(), + botPosition: { x: undefined, y: undefined, z: undefined }, }); it("renders", () => { @@ -195,4 +171,40 @@ describe("", () => { expect(p.onChange).toHaveBeenCalledWith({ y: 2 }); expect(p.onChange).toHaveBeenCalledWith({ z: 3 }); }); + + it("doesn't use current coordinates", () => { + const p = fakeProps(); + const wrapper = shallow(); + wrapper.find("button").simulate("click"); + expect(p.onChange).not.toHaveBeenCalled(); + }); + + it("uses current coordinates", () => { + const p = fakeProps(); + p.botPosition = { x: 0, y: 1, z: 2 }; + const wrapper = shallow(); + wrapper.find("button").simulate("click"); + expect(p.onChange).toHaveBeenCalledWith(p.botPosition); + }); +}); + +describe("", () => { + const fakeProps = (): SlotEditRowsProps => ({ + toolSlot: fakeToolSlot(), + tools: [], + tool: undefined, + botPosition: { x: undefined, y: undefined, z: undefined }, + updateToolSlot: jest.fn(), + isExpress: false, + xySwap: false, + quadrant: 2, + isActive: () => false, + }); + + it("handles missing tool", () => { + const p = fakeProps(); + p.tool = undefined; + const wrapper = mount(); + expect(wrapper.text()).toContain("None"); + }); }); diff --git a/frontend/farm_designer/tools/add_tool.tsx b/frontend/farm_designer/tools/add_tool.tsx index 0eb08d86d..4078e7957 100644 --- a/frontend/farm_designer/tools/add_tool.tsx +++ b/frontend/farm_designer/tools/add_tool.tsx @@ -13,9 +13,10 @@ import { history } from "../../history"; import { selectAllTools } from "../../resources/selectors"; import { betterCompact } from "../../util"; import { - isExpressBoard, getFwHardwareValue + getFwHardwareValue } from "../../devices/components/firmware_hardware_support"; import { getFbosConfig } from "../../resources/getters"; +import { ToolSVG } from "../map/layers/tool_slots/tool_graphics"; export interface AddToolProps { dispatch: Function; @@ -25,6 +26,7 @@ export interface AddToolProps { export interface AddToolState { toolName: string; + toAdd: string[]; } export const mapStateToProps = (props: Everything): AddToolProps => ({ @@ -35,7 +37,19 @@ export const mapStateToProps = (props: Everything): AddToolProps => ({ }); export class RawAddTool extends React.Component { - state: AddToolState = { toolName: "" }; + state: AddToolState = { toolName: "", toAdd: [] }; + + filterExisting = (n: string) => !this.props.existingToolNames.includes(n); + + add = (n: string) => this.filterExisting(n) && !this.state.toAdd.includes(n) && + this.setState({ toAdd: this.state.toAdd.concat([n]) }); + + remove = (n: string) => + this.setState({ toAdd: this.state.toAdd.filter(name => name != n) }); + + componentDidMount = () => this.setState({ + toAdd: this.stockToolNames().filter(this.filterExisting) + }); newTool = (name: string) => { this.props.dispatch(initSave("Tool", { name })); @@ -79,22 +93,38 @@ export class RawAddTool extends React.Component { } } + StockToolCheckbox = ({ toolName }: { toolName: string }) => { + const alreadyAdded = !this.filterExisting(toolName); + const checked = this.state.toAdd.includes(toolName) || alreadyAdded; + return
+ checked + ? this.remove(toolName) + : this.add(toolName)} /> +
; + } + AddStockTools = () =>
- +
    - {this.stockToolNames().map(n =>
  • {n}
  • )} + {this.stockToolNames().map(n => +
  • + +

    this.setState({ toolName: n })}>{n}

    +
  • )}
@@ -103,16 +133,16 @@ export class RawAddTool extends React.Component { return
+ - - this.setState({ toolName: e.currentTarget.value })} /> + + this.setState({ toolName: e.currentTarget.value })} />
diff --git a/frontend/farm_designer/tools/add_tool_slot.tsx b/frontend/farm_designer/tools/add_tool_slot.tsx index ffd4062d8..381abcfdc 100644 --- a/frontend/farm_designer/tools/add_tool_slot.tsx +++ b/frontend/farm_designer/tools/add_tool_slot.tsx @@ -3,58 +3,31 @@ import { connect } from "react-redux"; import { DesignerPanel, DesignerPanelContent, DesignerPanelHeader } from "../designer_panel"; -import { Everything } from "../../interfaces"; import { t } from "../../i18next_wrapper"; import { SaveBtn } from "../../ui"; -import { - SpecialStatus, TaggedTool, TaggedToolSlotPointer, FirmwareHardware -} from "farmbot"; +import { SpecialStatus, TaggedToolSlotPointer } from "farmbot"; import { init, save, edit, destroy } from "../../api/crud"; import { Panel } from "../panel_header"; import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources"; -import { - selectAllTools, maybeFindToolById, maybeGetToolSlot -} from "../../resources/selectors"; -import { BotPosition } from "../../devices/interfaces"; -import { validBotLocationData } from "../../util"; import { history } from "../../history"; import { SlotEditRows } from "./tool_slot_edit_components"; import { UUID } from "../../resources/interfaces"; import { - isExpressBoard, getFwHardwareValue + isExpressBoard } from "../../devices/components/firmware_hardware_support"; -import { getFbosConfig } from "../../resources/getters"; - -export interface AddToolSlotProps { - tools: TaggedTool[]; - dispatch: Function; - botPosition: BotPosition; - findTool(id: number): TaggedTool | undefined; - findToolSlot(uuid: UUID | undefined): TaggedToolSlotPointer | undefined; - firmwareHardware: FirmwareHardware | undefined; -} +import { AddToolSlotProps, mapStateToPropsAdd } from "./map_to_props_add_edit"; export interface AddToolSlotState { uuid: UUID | undefined; } -export const mapStateToProps = (props: Everything): AddToolSlotProps => ({ - tools: selectAllTools(props.resources.index), - dispatch: props.dispatch, - botPosition: validBotLocationData(props.bot.hardware.location_data).position, - findTool: (id: number) => maybeFindToolById(props.resources.index, id), - findToolSlot: (uuid: UUID | undefined) => - maybeGetToolSlot(props.resources.index, uuid), - firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)), -}); - export class RawAddToolSlot extends React.Component { state: AddToolSlotState = { uuid: undefined }; componentDidMount() { const action = init("Point", { - pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {}, + pointer_type: "ToolSlot", name: t("Slot"), radius: 0, meta: {}, x: 0, y: 0, z: 0, tool_id: undefined, pullout_direction: ToolPulloutDirection.NONE, gantry_mounted: isExpressBoard(this.props.firmwareHardware) ? true : false, @@ -95,9 +68,7 @@ export class RawAddToolSlot return @@ -108,6 +79,9 @@ export class RawAddToolSlot tools={this.props.tools} tool={this.tool} botPosition={this.props.botPosition} + xySwap={this.props.xySwap} + quadrant={this.props.quadrant} + isActive={this.props.isActive} updateToolSlot={this.updateSlot(this.toolSlot)} /> : "initializing"} @@ -116,4 +90,4 @@ export class RawAddToolSlot } } -export const AddToolSlot = connect(mapStateToProps)(RawAddToolSlot); +export const AddToolSlot = connect(mapStateToPropsAdd)(RawAddToolSlot); diff --git a/frontend/farm_designer/tools/edit_tool.tsx b/frontend/farm_designer/tools/edit_tool.tsx index 41b0fe387..27cfbbca7 100644 --- a/frontend/farm_designer/tools/edit_tool.tsx +++ b/frontend/farm_designer/tools/edit_tool.tsx @@ -6,16 +6,26 @@ import { import { Everything } from "../../interfaces"; import { t } from "../../i18next_wrapper"; import { getPathArray } from "../../history"; -import { TaggedTool, SpecialStatus } from "farmbot"; -import { maybeFindToolById } from "../../resources/selectors"; +import { TaggedTool, SpecialStatus, TaggedToolSlotPointer } from "farmbot"; +import { + maybeFindToolById, getDeviceAccountSettings, selectAllToolSlotPointers +} from "../../resources/selectors"; import { SaveBtn } from "../../ui"; import { edit, destroy } from "../../api/crud"; import { history } from "../../history"; import { Panel } from "../panel_header"; +import { ToolSVG } from "../map/layers/tool_slots/tool_graphics"; +import { error } from "../../toast/toast"; + +export const isActive = (toolSlots: TaggedToolSlotPointer[]) => + (toolId: number | undefined) => + !!(toolId && toolSlots.map(x => x.body.tool_id).includes(toolId)); export interface EditToolProps { findTool(id: string): TaggedTool | undefined; dispatch: Function; + mountedToolId: number | undefined; + isActive(id: number | undefined): boolean; } export interface EditToolState { @@ -26,6 +36,9 @@ export const mapStateToProps = (props: Everything): EditToolProps => ({ findTool: (id: string) => maybeFindToolById(props.resources.index, parseInt(id)), dispatch: props.dispatch, + mountedToolId: getDeviceAccountSettings(props.resources.index) + .body.mounted_tool_id, + isActive: isActive(selectAllToolSlotPointers(props.resources.index)), }); export class RawEditTool extends React.Component { @@ -44,6 +57,11 @@ export class RawEditTool extends React.Component { const { dispatch } = this.props; const { toolName } = this.state; const panelName = "edit-tool"; + const isMounted = this.props.mountedToolId == tool.body.id; + const message = isMounted + ? t("Cannot delete while mounted.") + : t("Cannot delete while in a slot."); + const activeOrMounted = this.props.isActive(tool.body.id) || isMounted; return { backTo={"/app/designer/tools"} panel={Panel.Tools} /> + { }} status={SpecialStatus.DIRTY} /> diff --git a/frontend/farm_designer/tools/edit_tool_slot.tsx b/frontend/farm_designer/tools/edit_tool_slot.tsx index 210b283d3..7bf2bc05e 100644 --- a/frontend/farm_designer/tools/edit_tool_slot.tsx +++ b/frontend/farm_designer/tools/edit_tool_slot.tsx @@ -3,43 +3,18 @@ import { connect } from "react-redux"; import { DesignerPanel, DesignerPanelContent, DesignerPanelHeader } from "../designer_panel"; -import { Everything } from "../../interfaces"; import { t } from "../../i18next_wrapper"; import { getPathArray } from "../../history"; -import { TaggedToolSlotPointer, TaggedTool, FirmwareHardware } from "farmbot"; +import { TaggedToolSlotPointer } from "farmbot"; import { edit, save, destroy } from "../../api/crud"; import { history } from "../../history"; import { Panel } from "../panel_header"; -import { - maybeFindToolSlotById, selectAllTools, maybeFindToolById -} from "../../resources/selectors"; -import { BotPosition } from "../../devices/interfaces"; -import { validBotLocationData } from "../../util"; import { SlotEditRows } from "./tool_slot_edit_components"; import { moveAbs } from "../../devices/actions"; import { - getFwHardwareValue, isExpressBoard + isExpressBoard } from "../../devices/components/firmware_hardware_support"; -import { getFbosConfig } from "../../resources/getters"; - -export interface EditToolSlotProps { - findToolSlot(id: string): TaggedToolSlotPointer | undefined; - tools: TaggedTool[]; - findTool(id: number): TaggedTool | undefined; - dispatch: Function; - botPosition: BotPosition; - firmwareHardware: FirmwareHardware | undefined; -} - -export const mapStateToProps = (props: Everything): EditToolSlotProps => ({ - findToolSlot: (id: string) => - maybeFindToolSlotById(props.resources.index, parseInt(id)), - tools: selectAllTools(props.resources.index), - findTool: (id: number) => maybeFindToolById(props.resources.index, id), - dispatch: props.dispatch, - botPosition: validBotLocationData(props.bot.hardware.location_data).position, - firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)), -}); +import { EditToolSlotProps, mapStateToPropsEdit } from "./map_to_props_add_edit"; export class RawEditToolSlot extends React.Component { @@ -65,7 +40,7 @@ export class RawEditToolSlot extends React.Component { return @@ -75,14 +50,20 @@ export class RawEditToolSlot extends React.Component { tools={this.props.tools} tool={this.tool} botPosition={this.props.botPosition} + xySwap={this.props.xySwap} + quadrant={this.props.quadrant} + isActive={this.props.isActive} updateToolSlot={this.updateSlot(toolSlot)} /> -

{positionButtonTitle(props.botPosition)}

- ; - export interface SlotDirectionInputRowProps { toolPulloutDirection: ToolPulloutDirection; onChange(update: { pullout_direction: ToolPulloutDirection }): void; @@ -51,7 +35,7 @@ export interface SlotDirectionInputRowProps { export const SlotDirectionInputRow = (props: SlotDirectionInputRowProps) =>
(!props.filterSelectedTool || !props.selectedTool) - || tool.body.id != props.selectedTool.body.id) + list={([NULL_CHOICE] as DropDownItem[]).concat(props.tools + .filter(tool => !props.filterSelectedTool + || tool.body.id != props.selectedTool?.body.id) + .filter(tool => !props.isActive(tool.body.id)) .map(tool => ({ label: tool.body.name || "untitled", value: tool.body.id || 0, })) - .filter(ddi => ddi.value > 0)} + .filter(ddi => ddi.value > 0))} selectedItem={props.selectedTool ? { label: props.selectedTool.body.name || "untitled", value: "" + props.selectedTool.body.id } : NULL_CHOICE} - allowEmpty={true} onChange={ddi => props.onChange({ tool_id: parseInt("" + ddi.value) })} />; @@ -98,6 +83,7 @@ export interface ToolInputRowProps { selectedTool: TaggedTool | undefined; onChange(update: { tool_id: number }): void; isExpress: boolean; + isActive(id: number | undefined): boolean; } export const ToolInputRow = (props: ToolInputRowProps) => @@ -113,6 +99,7 @@ export const ToolInputRow = (props: ToolInputRowProps) => tools={props.tools} selectedTool={props.selectedTool} onChange={props.onChange} + isActive={props.isActive} filterSelectedTool={false} /> @@ -122,24 +109,43 @@ export interface SlotLocationInputRowProps { slotLocation: Record; gantryMounted: boolean; onChange(update: Partial>): void; + botPosition: BotPosition; } export const SlotLocationInputRow = (props: SlotLocationInputRowProps) =>
- {["x", "y", "z"].map((axis: Xyz) => - - - {axis == "x" && props.gantryMounted - ? - : props.onChange({ - [axis]: parseFloat(e.currentTarget.value) - })} />} - )} + + {["x", "y", "z"].map((axis: Xyz) => + + + {axis == "x" && props.gantryMounted + ? + : props.onChange({ + [axis]: parseFloat(e.currentTarget.value) + })} />} + )} + + + + +
+ +

{positionButtonTitle(props.botPosition)}

+
+
+ +
; @@ -150,26 +156,31 @@ export interface SlotEditRowsProps { botPosition: BotPosition; updateToolSlot(update: Partial): void; isExpress: boolean; + xySwap: boolean; + quadrant: BotOriginQuadrant; + isActive(id: number | undefined): boolean; } export const SlotEditRows = (props: SlotEditRowsProps) =>
+ {!props.toolSlot.body.gantry_mounted && } - {!props.isExpress && { 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()).toContain("Add tools and slots"); expect(getTitles()).not.toContain("Add seed containers"); const fbosConfig = fakeFbosConfig(); fbosConfig.body.firmware_hardware = "express_k10"; diff --git a/frontend/help/tours.ts b/frontend/help/tours.ts index fc4339da5..26f78eda6 100644 --- a/frontend/help/tours.ts +++ b/frontend/help/tours.ts @@ -46,14 +46,14 @@ const toolsStep = () => hasTools() : t(TourContent.ADD_TOOLS_AND_SLOTS), title: isExpress() ? t("Add seed containers and slots") - : t("Add tools and tool slots"), + : t("Add tools and slots"), }]; const toolSlotsStep = () => hasTools() ? [{ target: ".tool-slots", content: t(TourContent.ADD_TOOLS_AND_SLOTS), - title: t("Add tool slots"), + title: t("Add slots"), }] : []; diff --git a/frontend/resources/sequence_meta.ts b/frontend/resources/sequence_meta.ts index be03fff20..b137f2032 100644 --- a/frontend/resources/sequence_meta.ts +++ b/frontend/resources/sequence_meta.ts @@ -20,10 +20,14 @@ import { import { VariableNode } from "../sequences/locals_list/locals_list_support"; import { t } from "../i18next_wrapper"; +export interface Vector3Plus extends Vector3 { + gantry_mounted: boolean; +} + export interface SequenceMeta { celeryNode: VariableNode; dropdown: DropDownItem; - vector: Vector3 | undefined; + vector: Vector3 | Vector3Plus | undefined; default?: boolean; } diff --git a/frontend/sequences/locals_list/__tests__/location_form_list_test.ts b/frontend/sequences/locals_list/__tests__/location_form_list_test.ts index c857e97c6..2d431305e 100644 --- a/frontend/sequences/locals_list/__tests__/location_form_list_test.ts +++ b/frontend/sequences/locals_list/__tests__/location_form_list_test.ts @@ -82,7 +82,7 @@ describe("formatTool()", () => { const toolSlot = fakeToolSlot(); toolSlot.body.gantry_mounted = true; const ddi = formatTool(fakeTool(), toolSlot); - expect(ddi.label).toEqual("Foo (---, 0, 0)"); + expect(ddi.label).toEqual("Foo (gantry, 0, 0)"); }); }); diff --git a/frontend/sequences/locals_list/location_form_list.ts b/frontend/sequences/locals_list/location_form_list.ts index 2faa683ab..4d93c95e8 100644 --- a/frontend/sequences/locals_list/location_form_list.ts +++ b/frontend/sequences/locals_list/location_form_list.ts @@ -38,7 +38,7 @@ type DropdownHeadingId = export const NAME_MAP: Record = { "GenericPointer": "Map Points", "Plant": "Plants", - "ToolSlot": "Tool Slots", + "ToolSlot": "Slots", "Tool": "Tools and Seed Containers", "PointGroup": "Groups", "Other": "Other", @@ -100,24 +100,27 @@ export const formatTool = const { id, name } = tool.body; const coordinate = slot ? { - x: slot.body.gantry_mounted ? undefined : slot.body.x, + x: slot.body.x, y: slot.body.y, z: slot.body.z } : undefined; + const gantryMounted = !!slot?.body.gantry_mounted; return { - label: dropDownName((name || "Untitled tool"), coordinate), + label: dropDownName((name || "Untitled tool"), coordinate, gantryMounted), value: "" + id, headingId: TOOL }; }; /** Uniformly generate a label for things that have an X/Y/Z value. */ -export function dropDownName(name: string, v?: Record) { +export function dropDownName(name: string, v?: Record, + gantryMounted = false) { let label = name || "untitled"; if (v) { const labelFor = (axis: number | undefined) => isNumber(axis) ? axis : "---"; - label += ` (${labelFor(v.x)}, ${labelFor(v.y)}, ${labelFor(v.z)})`; + const xLabel = gantryMounted ? t("Gantry") : labelFor(v.x); + label += ` (${xLabel}, ${labelFor(v.y)}, ${labelFor(v.z)})`; } return capitalize(label); } @@ -125,8 +128,8 @@ export function dropDownName(name: string, v?: Record) export const ALL_POINT_LABELS = { "Plant": "All plants", "GenericPointer": "All map points", - "Tool": "All tools", - "ToolSlot": "All tool slots", + "Tool": "All tools and seed containers", + "ToolSlot": "All slots", }; export type EveryPointType = keyof typeof ALL_POINT_LABELS; diff --git a/frontend/sequences/step_tiles/__tests__/tile_move_absolute_test.tsx b/frontend/sequences/step_tiles/__tests__/tile_move_absolute_test.tsx index b34d3d098..d398b1b0f 100644 --- a/frontend/sequences/step_tiles/__tests__/tile_move_absolute_test.tsx +++ b/frontend/sequences/step_tiles/__tests__/tile_move_absolute_test.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { TileMoveAbsolute } from "../tile_move_absolute"; import { mount, ReactWrapper } from "enzyme"; import { - fakeSequence, fakePoint, fakeTool + fakeSequence, fakePoint, fakeTool, fakeToolSlot } from "../../../__test_support__/fake_state/resources"; import { Coordinate, @@ -17,6 +17,7 @@ import { import { emptyState } from "../../../resources/reducer"; import { inputEvent } from "../../../__test_support__/fake_html_events"; import { StepParams } from "../../interfaces"; +import { buildResourceIndex } from "../../../__test_support__/resource_index_builder"; describe("", () => { const fakeProps = (): StepParams => { @@ -75,6 +76,25 @@ describe("", () => { checkField(block, 5, "z-offset", "6"); }); + it("disables x-offset", () => { + const p = fakeProps(); + const toolSlot = fakeToolSlot(); + toolSlot.body.gantry_mounted = true; + toolSlot.body.tool_id = 1; + const tool = fakeTool(); + tool.body.id = 1; + p.resources = buildResourceIndex([toolSlot, tool]).index; + const toolKind: Tool = { kind: "tool", args: { tool_id: 1 } }; + (p.currentStep as MoveAbsolute).args.location = toolKind; + const block = mount(); + const xOffsetInput = block.find("input").at(1); + expect(xOffsetInput.props().name).toEqual("offset-x"); + expect(xOffsetInput.props().disabled).toBeTruthy(); + const yOffsetInput = block.find("input").at(2); + expect(yOffsetInput.props().name).toEqual("offset-y"); + expect(yOffsetInput.props().disabled).toBeFalsy(); + }); + it("updates input value", () => { const tma = ordinaryMoveAbs(); const mock = jest.fn(); diff --git a/frontend/sequences/step_tiles/tile_move_absolute.tsx b/frontend/sequences/step_tiles/tile_move_absolute.tsx index 5a87fe7b9..a909cfe47 100644 --- a/frontend/sequences/step_tiles/tile_move_absolute.tsx +++ b/frontend/sequences/step_tiles/tile_move_absolute.tsx @@ -11,7 +11,7 @@ import { ToolTips } from "../../constants"; import { StepWrapper, StepHeader, StepContent } from "../step_ui"; import { StepInputBox } from "../inputs/step_input_box"; import { - determineDropdown, determineVector + determineDropdown, determineVector, Vector3Plus } from "../../resources/sequence_meta"; import { LocationForm } from "../locals_list/location_form"; import { @@ -75,11 +75,16 @@ export class TileMoveAbsolute extends React.Component }; } - get vector(): Vector3 | undefined { + get vector(): Vector3 | Vector3Plus | undefined { const sequenceUuid = this.props.currentSequence.uuid; return determineVector(this.celeryNode, this.props.resources, sequenceUuid); } + get gantryMounted() { + return this.vector && ("gantry_mounted" in this.vector) + && this.vector.gantry_mounted; + } + LocationForm = () => {t("{{axis}}-Offset", { axis })} diff --git a/package.json b/package.json index d6fa95495..512d04ad1 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "sass-lint": "./node_modules/sass-lint/bin/sass-lint.js -c .sass-lint.yml -v -q", "sass-check": "./node_modules/sass/sass.js --no-source-map frontend/css/_index.scss sass.log", "translation-check": " ./node_modules/jshint/bin/jshint --config public/app-resources/languages/.config public/app-resources/languages/*.js*", - "linters": "npm run typecheck && npm run tslint && npm run sass-lint && npm run sass-check && npm run translation-check" + "linters": "npm run typecheck; npm run tslint; npm run sass-lint; npm run sass-check; npm run translation-check" }, "keywords": [ "farmbot" From 1a4a106179833c01b7cb4da7bf01b00df5d7c352 Mon Sep 17 00:00:00 2001 From: Rick Carlino Date: Fri, 21 Feb 2020 20:13:29 -0600 Subject: [PATCH 2/9] days => days_ago --- app/models/point_group.rb | 2 +- app/mutations/point_groups/helpers.rb | 2 +- frontend/__test_support__/fake_state/resources.ts | 2 +- .../point_groups/criteria/__tests__/apply_test.ts | 6 +++--- .../point_groups/criteria/__tests__/edit_test.ts | 2 +- .../point_groups/criteria/__tests__/show_test.tsx | 2 +- frontend/farm_designer/point_groups/criteria/apply.ts | 4 ++-- .../farm_designer/point_groups/criteria/interfaces.ts | 2 +- frontend/farm_designer/point_groups/criteria/show.tsx | 8 ++++---- package.json | 2 +- spec/controllers/api/point_groups/create_spec.rb | 4 ++-- spec/controllers/api/point_groups/update_spec.rb | 6 +++--- 12 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/models/point_group.rb b/app/models/point_group.rb index eef492243..36c56ffd4 100644 --- a/app/models/point_group.rb +++ b/app/models/point_group.rb @@ -4,7 +4,7 @@ class PointGroup < ApplicationRecord BAD_SORT = "%{value} is not valid. Valid options are: " + SORT_TYPES.map(&:inspect).join(", ") DEFAULT_CRITERIA = { - day: { op: "<", days: 0 }, + day: { op: "<", days_ago: 0 }, string_eq: {}, number_eq: {}, number_lt: {}, diff --git a/app/mutations/point_groups/helpers.rb b/app/mutations/point_groups/helpers.rb index 0f5a3d7f5..7d6094e45 100644 --- a/app/mutations/point_groups/helpers.rb +++ b/app/mutations/point_groups/helpers.rb @@ -5,7 +5,7 @@ module PointGroups hash :criteria do hash(:day) do string :op, in: [">", "<"] - integer :days + integer :days_ago end hash(:string_eq) { array :*, class: String } hash(:number_eq) { array :*, class: Integer } diff --git a/frontend/__test_support__/fake_state/resources.ts b/frontend/__test_support__/fake_state/resources.ts index 641cba1bb..cbddcceae 100644 --- a/frontend/__test_support__/fake_state/resources.ts +++ b/frontend/__test_support__/fake_state/resources.ts @@ -460,7 +460,7 @@ export function fakePointGroup(): TaggedPointGroup { sort_type: "xy_ascending", point_ids: [], criteria: { - day: { op: "<", days: 0 }, + day: { op: "<", days_ago: 0 }, number_eq: {}, number_gt: {}, number_lt: {}, diff --git a/frontend/farm_designer/point_groups/criteria/__tests__/apply_test.ts b/frontend/farm_designer/point_groups/criteria/__tests__/apply_test.ts index 48b10dca6..a7ac5efdb 100644 --- a/frontend/farm_designer/point_groups/criteria/__tests__/apply_test.ts +++ b/frontend/farm_designer/point_groups/criteria/__tests__/apply_test.ts @@ -57,7 +57,7 @@ describe("selectPointsByCriteria()", () => { it("matches age greater than 1 day old", () => { const criteria = fakeCriteria(); - criteria.day = { days: 1, op: ">" }; + criteria.day = { days_ago: 1, op: ">" }; const matchingPoint = fakePoint(); matchingPoint.body.created_at = "2020-01-20T20:00:00.000Z"; const otherPoint = fakePoint(); @@ -70,7 +70,7 @@ describe("selectPointsByCriteria()", () => { it("matches age less than 1 day old", () => { const criteria = fakeCriteria(); - criteria.day = { days: 1, op: "<" }; + criteria.day = { days_ago: 1, op: "<" }; const matchingPoint = fakePoint(); matchingPoint.body.created_at = "2020-02-20T20:00:00.000Z"; const otherPoint = fakePoint(); @@ -83,7 +83,7 @@ describe("selectPointsByCriteria()", () => { it("matches planted date less than 1 day old", () => { const criteria = fakeCriteria(); - criteria.day = { days: 1, op: "<" }; + criteria.day = { days_ago: 1, op: "<" }; const matchingPoint = fakePlant(); matchingPoint.body.planted_at = "2020-02-20T20:00:00.000Z"; matchingPoint.body.created_at = "2020-01-20T20:00:00.000Z"; diff --git a/frontend/farm_designer/point_groups/criteria/__tests__/edit_test.ts b/frontend/farm_designer/point_groups/criteria/__tests__/edit_test.ts index 04fc25cf3..572e4ca91 100644 --- a/frontend/farm_designer/point_groups/criteria/__tests__/edit_test.ts +++ b/frontend/farm_designer/point_groups/criteria/__tests__/edit_test.ts @@ -36,7 +36,7 @@ describe("editCriteria()", () => { it("edits criteria: full update", () => { const group = fakePointGroup(); const criteria: PointGroup["criteria"] = { - day: { days: 1, op: "<" }, + day: { days_ago: 1, op: "<" }, string_eq: { openfarm_slug: ["slug"] }, number_eq: { x: [0] }, number_gt: { x: 0 }, diff --git a/frontend/farm_designer/point_groups/criteria/__tests__/show_test.tsx b/frontend/farm_designer/point_groups/criteria/__tests__/show_test.tsx index 4e5550fe9..626fabd24 100644 --- a/frontend/farm_designer/point_groups/criteria/__tests__/show_test.tsx +++ b/frontend/farm_designer/point_groups/criteria/__tests__/show_test.tsx @@ -100,7 +100,7 @@ describe("", () => { currentTarget: { value: "1" } }); const expectedBody = cloneDeep(p.group.body); - expectedBody.criteria.day.days = 1; + expectedBody.criteria.day.days_ago = 1; expect(overwrite).toHaveBeenCalledWith(p.group, expectedBody); }); diff --git a/frontend/farm_designer/point_groups/criteria/apply.ts b/frontend/farm_designer/point_groups/criteria/apply.ts index 87ea058c3..10aa45403 100644 --- a/frontend/farm_designer/point_groups/criteria/apply.ts +++ b/frontend/farm_designer/point_groups/criteria/apply.ts @@ -30,11 +30,11 @@ const checkCriteria = ? point.body.planted_at : point.body.created_at); const compareDate = moment(now) - .subtract(criteria[criteriaKey].days, "days"); + .subtract(criteria[criteriaKey].days_ago, "days"); const matchesDays = criteria[criteriaKey].op == "<" ? pointDate.isAfter(compareDate) : pointDate.isBefore(compareDate); - return matchesDays || !criteria[criteriaKey].days; + return matchesDays || !criteria[criteriaKey].days_ago; } }; diff --git a/frontend/farm_designer/point_groups/criteria/interfaces.ts b/frontend/farm_designer/point_groups/criteria/interfaces.ts index 70c1d90ae..4ca2bd4fc 100644 --- a/frontend/farm_designer/point_groups/criteria/interfaces.ts +++ b/frontend/farm_designer/point_groups/criteria/interfaces.ts @@ -2,7 +2,7 @@ import { TaggedPointGroup } from "farmbot"; import { PointGroup } from "farmbot/dist/resources/api_resources"; export const DEFAULT_CRITERIA: Readonly = { - day: { op: "<", days: 0 }, + day: { op: "<", days_ago: 0 }, number_eq: {}, number_gt: {}, number_lt: {}, diff --git a/frontend/farm_designer/point_groups/criteria/show.tsx b/frontend/farm_designer/point_groups/criteria/show.tsx index bc90265f6..ad79f2326 100644 --- a/frontend/farm_designer/point_groups/criteria/show.tsx +++ b/frontend/farm_designer/point_groups/criteria/show.tsx @@ -105,16 +105,16 @@ export const DaySelection = (props: CriteriaSelectionProps) => { selectedItem={DAY_OPERATOR_DDI_LOOKUP()[dayCriteria.op]} onChange={ddi => dispatch(editCriteria(group, { day: { - days: dayCriteria.days, + days_ago: dayCriteria.days_ago, op: ddi.value as PointGroup["criteria"]["day"]["op"] } }))} /> - { + { const { op } = dayCriteria; - const days = parseInt(e.currentTarget.value); - dispatch(editCriteria(group, { day: { days, op } })); + const days_ago = parseInt(e.currentTarget.value); + dispatch(editCriteria(group, { day: { days_ago, op } })); }} /> diff --git a/package.json b/package.json index d6fa95495..304669d29 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "coveralls": "3.0.9", "enzyme": "3.11.0", "enzyme-adapter-react-16": "1.15.2", - "farmbot": "9.1.0", + "farmbot": "9.1.2", "i18next": "19.0.3", "install": "0.13.0", "lodash": "4.17.15", diff --git a/spec/controllers/api/point_groups/create_spec.rb b/spec/controllers/api/point_groups/create_spec.rb index 2a534624f..6ba7d173d 100644 --- a/spec/controllers/api/point_groups/create_spec.rb +++ b/spec/controllers/api/point_groups/create_spec.rb @@ -70,7 +70,7 @@ describe Api::PointGroupsController do }, day: { op: "<", - days: 0, + days_ago: 0, }, }, } @@ -85,7 +85,7 @@ describe Api::PointGroupsController do expect(hash.dig(:number_gt, :x)).to eq(1) expect(hash.dig(:number_gt, :y)).to eq(1) expect(hash.dig(:day, :op)).to eq("<") - expect(hash.dig(:day, :days)).to eq(0) + expect(hash.dig(:day, :days_ago)).to eq(0) expect(hash.dig(:string_eq, :openfarm_slug)).to eq(["carrot"]) end end diff --git a/spec/controllers/api/point_groups/update_spec.rb b/spec/controllers/api/point_groups/update_spec.rb index 0ea2c04ad..87db51144 100644 --- a/spec/controllers/api/point_groups/update_spec.rb +++ b/spec/controllers/api/point_groups/update_spec.rb @@ -57,7 +57,7 @@ describe Api::PointGroupsController do number_eq: { z: [24, 25, 26] }, number_lt: { x: 4, y: 4 }, number_gt: { x: 1, y: 1 }, - day: { op: "<", days: 0 }, + day: { op: "<", days_ago: 0 }, }, } pg = PointGroups::Create.run!(initial_params) @@ -68,12 +68,12 @@ describe Api::PointGroupsController do number_eq: { x: [42, 52, 62] }, number_lt: { y: 8 }, number_gt: { z: 2 }, - day: { op: ">", days: 10 }, + day: { op: ">", days_ago: 10 }, }, } put :update, body: payload.to_json, format: :json, params: { id: pg.id } expect(response.status).to eq(200) - expect(json.dig(:criteria, :day, :days)).to eq(10) + expect(json.dig(:criteria, :day, :days_ago)).to eq(10) expect(json.dig(:criteria, :day, :op)).to eq(">") expect(json.dig(:criteria, :number_eq, :x)).to eq([42, 52, 62]) expect(json.dig(:criteria, :number_eq, :z)).to eq(nil) From 19eebde8e2116aa7ac17ccf341d5f27c15efdd16 Mon Sep 17 00:00:00 2001 From: gabrielburnworth Date: Mon, 24 Feb 2020 08:55:37 -0800 Subject: [PATCH 3/9] dep updates (fe) --- package.json | 22 +++++++++++----------- tsconfig.json | 1 - 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 58e964b81..58a807c65 100644 --- a/package.json +++ b/package.json @@ -24,18 +24,18 @@ "author": "farmbot.io", "license": "MIT", "dependencies": { - "@babel/core": "7.8.3", + "@babel/core": "7.8.4", "@blueprintjs/core": "3.23.1", "@blueprintjs/datetime": "3.15.2", "@blueprintjs/select": "3.11.2", - "@types/enzyme": "3.10.4", - "@types/jest": "24.9.1", + "@types/enzyme": "3.10.5", + "@types/jest": "25.1.3", "@types/lodash": "4.14.149", "@types/markdown-it": "0.0.9", "@types/moxios": "0.4.9", - "@types/node": "13.5.0", + "@types/node": "13.7.4", "@types/promise-timeout": "1.3.0", - "@types/react": "16.9.19", + "@types/react": "16.9.22", "@types/react-color": "3.0.1", "@types/react-dom": "16.9.5", "@types/react-redux": "7.1.7", @@ -46,7 +46,7 @@ "enzyme": "3.11.0", "enzyme-adapter-react-16": "1.15.2", "farmbot": "9.1.2", - "i18next": "19.0.3", + "i18next": "19.3.1", "install": "0.13.0", "lodash": "4.17.15", "markdown-it": "10.0.0", @@ -54,7 +54,7 @@ "moment": "2.24.0", "moxios": "0.4.0", "mqtt": "3.0.0", - "npm": "6.13.6", + "npm": "6.13.7", "parcel-bundler": "1.12.4", "promise-timeout": "1.3.0", "raf": "3.4.1", @@ -63,7 +63,7 @@ "react-color": "2.18.0", "react-dom": "16.12.0", "react-joyride": "2.2.1", - "react-redux": "7.1.3", + "react-redux": "7.2.0", "react-test-renderer": "16.12.0", "react-transition-group": "4.3.0", "redux": "4.0.5", @@ -71,10 +71,10 @@ "redux-thunk": "2.3.0", "sass-lint": "1.13.1", "takeme": "0.11.3", - "ts-jest": "25.0.0", + "ts-jest": "25.2.1", "ts-lint": "4.5.1", "tslint": "6.0.0", - "typescript": "3.7.5", + "typescript": "3.8.2", "which": "2.0.2" }, "devDependencies": { @@ -83,7 +83,7 @@ "jest-junit": "10.0.0", "jest-skipped-reporter": "0.0.5", "jshint": "2.11.0", - "madge": "3.6.0", + "madge": "3.7.0", "sass": "1.25.0" } } diff --git a/tsconfig.json b/tsconfig.json index 569f7f997..2c63ffef7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "incremental": true, "lib": [ "es7", "dom", From 4a0035b9ebc5872dc5facc2d7f28b043702406a5 Mon Sep 17 00:00:00 2001 From: gabrielburnworth Date: Mon, 24 Feb 2020 08:55:57 -0800 Subject: [PATCH 4/9] minor bug fixes --- frontend/css/farm_designer/farm_designer_panels.scss | 4 ++-- .../devices/components/fbos_settings/fbos_details.tsx | 9 ++++++--- frontend/devices/connectivity/connectivity.tsx | 7 +++++-- .../__tests__/list_and_label_support_test.tsx | 4 +++- frontend/devices/pin_bindings/list_and_label_support.tsx | 2 +- .../tools/__tests__/tool_slot_edit_components_test.tsx | 1 + frontend/farm_designer/tools/index.tsx | 6 ++++-- .../farm_designer/tools/tool_slot_edit_components.tsx | 7 +++++-- 8 files changed, 27 insertions(+), 13 deletions(-) diff --git a/frontend/css/farm_designer/farm_designer_panels.scss b/frontend/css/farm_designer/farm_designer_panels.scss index a915f1f5e..6c5a5614d 100644 --- a/frontend/css/farm_designer/farm_designer_panels.scss +++ b/frontend/css/farm_designer/farm_designer_panels.scss @@ -552,7 +552,7 @@ } .tool-slots-panel-content, .tools-panel-content { - max-height: calc(100vh - 15rem); + max-height: calc(100vh - 19rem); overflow-y: auto; overflow-x: hidden; .tool-search-item, @@ -582,7 +582,7 @@ } } i { - line-height: 2.5rem; + line-height: 2rem; } } svg { diff --git a/frontend/devices/components/fbos_settings/fbos_details.tsx b/frontend/devices/components/fbos_settings/fbos_details.tsx index c4b138cbd..10cee6822 100644 --- a/frontend/devices/components/fbos_settings/fbos_details.tsx +++ b/frontend/devices/components/fbos_settings/fbos_details.tsx @@ -55,21 +55,24 @@ export function ChipTemperatureDisplay( interface WiFiStrengthDisplayProps { wifiStrength: number | undefined; wifiStrengthPercent?: number | undefined; + extraInfo?: boolean; } /** WiFi signal strength display row: label, strength, indicator. */ export function WiFiStrengthDisplay( - { wifiStrength, wifiStrengthPercent }: WiFiStrengthDisplayProps + { wifiStrength, wifiStrengthPercent, extraInfo }: WiFiStrengthDisplayProps ): JSX.Element { const percent = wifiStrength ? Math.round(-0.0154 * wifiStrength ** 2 - 0.4 * wifiStrength + 98) : 0; const dbString = `${wifiStrength || 0}dBm`; const percentString = `${wifiStrengthPercent || percent}%`; + const numberDisplay = + extraInfo ? `${percentString} (${dbString})` : percentString; return

{t("WiFi strength")}: - {wifiStrength ? `${dbString} (${percentString})` : "N/A"} + {wifiStrength ? numberDisplay : "N/A"}

{wifiStrength &&
@@ -287,7 +290,7 @@ export function FbosDetails(props: FbosDetailsProps) { {isNumber(disk_usage) &&

{t("Disk usage")}: {disk_usage}%

} {isNumber(cpu_usage) &&

{t("CPU usage")}: {cpu_usage}%

} - @@ -42,7 +44,8 @@ export class Connectivity
- +
diff --git a/frontend/devices/pin_bindings/__tests__/list_and_label_support_test.tsx b/frontend/devices/pin_bindings/__tests__/list_and_label_support_test.tsx index 6742019cf..24a8443b6 100644 --- a/frontend/devices/pin_bindings/__tests__/list_and_label_support_test.tsx +++ b/frontend/devices/pin_bindings/__tests__/list_and_label_support_test.tsx @@ -1,4 +1,6 @@ -import { sortByNameAndPin, ButtonPin, getSpecialActionLabel } from "../list_and_label_support"; +import { + sortByNameAndPin, ButtonPin, getSpecialActionLabel +} from "../list_and_label_support"; import { PinBindingSpecialAction } from "farmbot/dist/resources/api_resources"; describe("sortByNameAndPin()", () => { diff --git a/frontend/devices/pin_bindings/list_and_label_support.tsx b/frontend/devices/pin_bindings/list_and_label_support.tsx index ddc7be7c6..5d803b118 100644 --- a/frontend/devices/pin_bindings/list_and_label_support.tsx +++ b/frontend/devices/pin_bindings/list_and_label_support.tsx @@ -92,7 +92,7 @@ export const reservedPiGPIO = piI2c0Pins; 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.btn3]: t("Button {{ num }}", { num: 3 }), [ButtonPin.btn4]: t("Button {{ num }}", { num: 4 }), [ButtonPin.btn5]: t("Button {{ num }}", { num: 5 }), }); 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 e75dbf479..1ee855342 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 @@ -62,6 +62,7 @@ describe("", () => { onChange: jest.fn(), filterSelectedTool: false, isActive: jest.fn(), + filterActiveTools: true, }); it("renders", () => { diff --git a/frontend/farm_designer/tools/index.tsx b/frontend/farm_designer/tools/index.tsx index 3e4adbb4b..222c4face 100644 --- a/frontend/farm_designer/tools/index.tsx +++ b/frontend/farm_designer/tools/index.tsx @@ -137,7 +137,8 @@ export class RawTools extends React.Component { this.props.dispatch(save(this.props.device.uuid)); }} isActive={this.props.isActive} - filterSelectedTool={true} /> + filterSelectedTool={true} + filterActiveTools={false} />

{t("status")}: {toolStatus(this.toolVerificationValue)}

diff --git a/frontend/farm_designer/tools/tool_slot_edit_components.tsx b/frontend/farm_designer/tools/tool_slot_edit_components.tsx index cf63a55e1..903088e82 100644 --- a/frontend/farm_designer/tools/tool_slot_edit_components.tsx +++ b/frontend/farm_designer/tools/tool_slot_edit_components.tsx @@ -57,6 +57,7 @@ export interface ToolSelectionProps { onChange(update: { tool_id: number }): void; filterSelectedTool: boolean; isActive(id: number | undefined): boolean; + filterActiveTools: boolean; } export const ToolSelection = (props: ToolSelectionProps) => @@ -64,7 +65,8 @@ export const ToolSelection = (props: ToolSelectionProps) => list={([NULL_CHOICE] as DropDownItem[]).concat(props.tools .filter(tool => !props.filterSelectedTool || tool.body.id != props.selectedTool?.body.id) - .filter(tool => !props.isActive(tool.body.id)) + .filter(tool => !props.filterActiveTools + || !props.isActive(tool.body.id)) .map(tool => ({ label: tool.body.name || "untitled", value: tool.body.id || 0, @@ -100,7 +102,8 @@ export const ToolInputRow = (props: ToolInputRowProps) => selectedTool={props.selectedTool} onChange={props.onChange} isActive={props.isActive} - filterSelectedTool={false} /> + filterSelectedTool={false} + filterActiveTools={true} />
; From 6bc0034d676cae8da92666c5a7ad8f052ca9be57 Mon Sep 17 00:00:00 2001 From: Rick Carlino Date: Wed, 26 Feb 2020 08:18:25 -0600 Subject: [PATCH 5/9] Dep updates --- Gemfile.lock | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f0f340215..6b1c2886e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -72,7 +72,7 @@ GEM amq-protocol (2.3.0) bcrypt (3.1.13) builder (3.2.4) - bunny (2.14.3) + bunny (2.14.4) amq-protocol (~> 2.3, >= 2.3.0) case_transform (0.2) activesupport @@ -82,9 +82,9 @@ GEM simplecov url coderay (1.1.2) - concurrent-ruby (1.1.5) + concurrent-ruby (1.1.6) crass (1.0.6) - database_cleaner (1.7.0) + database_cleaner (1.8.3) declarative (0.0.10) declarative-option (0.1.0) delayed_job (4.1.8) @@ -100,7 +100,7 @@ GEM warden (~> 1.2.3) diff-lcs (1.3) digest-crc (0.4.1) - discard (1.1.0) + discard (1.2.0) activerecord (>= 4.2, < 7) docile (1.3.2) erubi (1.9.0) @@ -109,7 +109,7 @@ GEM factory_bot_rails (5.1.1) factory_bot (~> 5.1.0) railties (>= 4.2.0) - faker (2.10.1) + faker (2.10.2) i18n (>= 1.6, < 2) faraday (0.15.4) multipart-post (>= 1.2, < 3) @@ -119,7 +119,7 @@ GEM railties (>= 3.2, < 6.1) globalid (0.4.2) activesupport (>= 4.2.0) - google-api-client (0.36.4) + google-api-client (0.37.1) addressable (~> 2.5, >= 2.5.1) googleauth (~> 0.9) httpclient (>= 2.8.1, < 3.0) @@ -127,10 +127,12 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) signet (~> 0.12) - google-cloud-core (1.4.1) + google-cloud-core (1.5.0) google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) google-cloud-env (1.3.0) faraday (~> 0.11) + google-cloud-errors (1.0.0) google-cloud-storage (1.25.1) addressable (~> 2.5) digest-crc (~> 0.4) @@ -174,7 +176,7 @@ GEM mimemagic (~> 0.3.2) memoist (0.16.2) method_source (0.9.2) - mimemagic (0.3.3) + mimemagic (0.3.4) mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.14.0) @@ -183,7 +185,7 @@ GEM mutations (0.9.0) activesupport nio4r (2.5.2) - nokogiri (1.10.7) + nokogiri (1.10.8) mini_portile2 (~> 2.4.0) orm_adapter (0.5.0) os (1.0.1) @@ -202,7 +204,7 @@ GEM faraday_middleware (~> 0.13.0) hashie (~> 3.6) multi_json (~> 1.13.1) - rack (2.1.1) + rack (2.2.2) rack-attack (6.2.2) rack (>= 1.0, < 3) rack-cors (1.1.1) @@ -252,7 +254,7 @@ GEM actionpack (>= 5.0) railties (>= 5.0) retriable (3.1.2) - rollbar (2.23.2) + rollbar (2.24.0) rspec (3.9.0) rspec-core (~> 3.9.0) rspec-expectations (~> 3.9.0) @@ -276,7 +278,7 @@ GEM rspec-support (3.9.2) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) - scenic (1.5.1) + scenic (1.5.2) activerecord (>= 4.0.0) railties (>= 4.0.0) secure_headers (6.3.0) @@ -285,11 +287,10 @@ GEM faraday (~> 0.9) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simplecov (0.17.1) + simplecov (0.18.5) docile (~> 1.1) - json (>= 1.8, < 3) - simplecov-html (~> 0.10.0) - simplecov-html (0.10.2) + simplecov-html (~> 0.11) + simplecov-html (0.12.1) sprockets (4.0.0) concurrent-ruby (~> 1.0) rack (> 1, < 3) From 9bd98aca1e573e8cc069eafd0116fbaf50c2eb5b Mon Sep 17 00:00:00 2001 From: gabrielburnworth Date: Wed, 26 Feb 2020 10:10:59 -0800 Subject: [PATCH 6/9] misc updates --- frontend/controls/controls.tsx | 4 +- frontend/controls/move/bot_position_rows.tsx | 6 +-- frontend/controls/move/settings_menu.tsx | 4 +- .../peripherals/__tests__/index_test.tsx | 10 +++++ frontend/controls/peripherals/index.tsx | 2 +- .../controls/sensors/__tests__/index_test.tsx | 15 ++++++- frontend/controls/sensors/index.tsx | 18 ++++----- .../farm_designer/farm_designer_panels.scss | 7 ++++ frontend/css/global.scss | 18 +++++++++ frontend/css/sequences.scss | 17 ++++++-- .../__tests__/hardware_settings_test.tsx | 39 +++++++++++++++++++ .../components/firmware_hardware_support.ts | 20 +++++++++- .../devices/components/hardware_settings.tsx | 38 ++++++++++++++++-- .../__tests__/pin_bindings_test.tsx | 1 + .../components/hardware_settings/encoders.tsx | 20 +++++----- .../homing_and_calibration.tsx | 8 ++-- .../hardware_settings/pin_bindings.tsx | 5 ++- frontend/devices/components/interfaces.ts | 1 + .../connectivity/__tests__/diagram_test.tsx | 7 ++-- .../__tests__/pin_bindings_test.tsx | 1 + .../tagged_pin_binding_init_test.tsx | 30 +++++++++----- frontend/devices/pin_bindings/interfaces.ts | 2 + .../devices/pin_bindings/pin_bindings.tsx | 5 ++- .../pin_bindings/tagged_pin_binding_init.tsx | 14 +++++-- .../farm_designer/__tests__/plant_test.ts | 19 +++++++++ .../farm_designer/__tests__/reducer_test.ts | 13 +++++++ frontend/farm_designer/designer_panel.tsx | 2 +- .../farm_events/edit_farm_event.tsx | 4 +- .../farm_events/edit_fe_form.tsx | 32 ++++++++------- .../farm_designer/farm_events/farm_events.tsx | 2 +- .../images/__tests__/map_image_test.tsx | 36 +++++++++-------- frontend/farm_designer/panel_header.tsx | 8 +++- .../point_groups/group_detail_active.tsx | 2 +- .../points/__tests__/create_points_test.tsx | 1 + .../farm_designer/points/create_points.tsx | 9 ++++- .../points/point_inventory_item.tsx | 2 +- .../saved_gardens/garden_edit.tsx | 2 +- .../saved_gardens/garden_list.tsx | 2 +- .../tools/__tests__/index_test.tsx | 2 +- frontend/farm_designer/tools/index.tsx | 6 +-- frontend/folders/__tests__/component_test.tsx | 4 +- frontend/folders/component.tsx | 4 +- frontend/folders/data_transfer.ts | 9 +++-- frontend/help/tours.ts | 14 +++---- frontend/logs/__tests__/index_test.tsx | 10 ++++- .../components/__tests__/filter_menu_test.tsx | 7 ++-- .../components/__tests__/logs_table_test.tsx | 20 ++++++++++ frontend/logs/components/filter_menu.tsx | 2 +- frontend/logs/components/logs_table.tsx | 18 ++++++++- frontend/logs/index.tsx | 18 ++++++++- frontend/logs/interfaces.ts | 1 + frontend/nav/additional_menu.tsx | 8 ++-- frontend/read_only_mode/index.tsx | 16 +++----- frontend/regimens/index.tsx | 3 +- frontend/regimens/list/index.tsx | 4 +- frontend/resources/selectors_by_id.ts | 16 -------- .../sequences/step_tiles/tile_if/index.tsx | 2 +- frontend/ui/back_arrow.tsx | 2 +- 58 files changed, 424 insertions(+), 168 deletions(-) create mode 100644 frontend/farm_designer/__tests__/plant_test.ts create mode 100644 frontend/logs/components/__tests__/logs_table_test.tsx diff --git a/frontend/controls/controls.tsx b/frontend/controls/controls.tsx index 5195c58ba..5149af504 100644 --- a/frontend/controls/controls.tsx +++ b/frontend/controls/controls.tsx @@ -10,6 +10,7 @@ import { Move } from "./move/move"; import { BooleanSetting } from "../session_keys"; import { SensorReadings } from "./sensor_readings/sensor_readings"; import { isBotOnline } from "../devices/must_be_online"; +import { hasSensors } from "../devices/components/firmware_hardware_support"; /** Controls page. */ export class RawControls extends React.Component { @@ -24,7 +25,8 @@ export class RawControls extends React.Component { } get hideSensors() { - return this.props.getWebAppConfigVal(BooleanSetting.hide_sensors); + return this.props.getWebAppConfigVal(BooleanSetting.hide_sensors) + || !hasSensors(this.props.firmwareHardware); } move = () => { - {!isExpressBoard(props.firmwareHardware) && + {hasEncoders(props.firmwareHardware) && getValue(BooleanSetting.scaled_encoders) && } - {!isExpressBoard(props.firmwareHardware) && + {hasEncoders(props.firmwareHardware) && getValue(BooleanSetting.raw_encoders) && @@ -36,7 +36,7 @@ export const MoveWidgetSettingsMenu = ( - {!isExpressBoard(firmwareHardware) && + {hasEncoders(firmwareHardware) &&

{t("Display Encoder Data")}

", () => { clickButton(wrapper, 3, "stock"); expect(p.dispatch).toHaveBeenCalledTimes(expectedAdds); }); + + it("hides stock button", () => { + const p = fakeProps(); + p.firmwareHardware = "none"; + const wrapper = mount(); + wrapper.setState({ isEditing: true }); + const btn = wrapper.find("button").at(3); + expect(btn.text().toLowerCase()).toContain("stock"); + expect(btn.props().hidden).toBeTruthy(); + }); }); diff --git a/frontend/controls/peripherals/index.tsx b/frontend/controls/peripherals/index.tsx index 69decd905..de5e6eba3 100644 --- a/frontend/controls/peripherals/index.tsx +++ b/frontend/controls/peripherals/index.tsx @@ -108,7 +108,7 @@ export class Peripherals - {!isExpressBoard(this.props.firmwareHardware) && - } + {this.showPins()} diff --git a/frontend/css/farm_designer/farm_designer_panels.scss b/frontend/css/farm_designer/farm_designer_panels.scss index 6c5a5614d..e67e5ac60 100644 --- a/frontend/css/farm_designer/farm_designer_panels.scss +++ b/frontend/css/farm_designer/farm_designer_panels.scss @@ -958,3 +958,10 @@ margin-right: 1.5rem; &:hover { color: $white; } } + +.desktop-hide { + display: none !important; + @media screen and (max-width: 1075px) { + display: block !important; + } +} diff --git a/frontend/css/global.scss b/frontend/css/global.scss index e8530d2c6..9b63e725d 100644 --- a/frontend/css/global.scss +++ b/frontend/css/global.scss @@ -407,6 +407,18 @@ a { } } +.load-progress-bar-wrapper { + position: absolute; + top: 3.2rem; + bottom: 0; + right: 0; + width: 100%; + height: 1px; + .load-progress-bar { + height: 100%; + } +} + .firmware-setting-export-menu { button { margin-bottom: 1rem; @@ -1654,3 +1666,9 @@ textarea:focus { background-color: transparent; box-shadow: none; } + +.read-only-icon { + margin: 9px 0px 0px 9px; + float: right; + box-sizing: inherit; +} diff --git a/frontend/css/sequences.scss b/frontend/css/sequences.scss index be3cdff30..83472c0e4 100644 --- a/frontend/css/sequences.scss +++ b/frontend/css/sequences.scss @@ -322,6 +322,9 @@ border-left: 4px solid transparent; &.active { border-left: 4px solid $dark_gray; + p { + font-weight: bold; + } } .fa-chevron-down, .fa-chevron-right { position: absolute; @@ -330,11 +333,11 @@ font-size: 1.1rem; } .folder-settings-icon, - .fa-bars { + .fa-arrows-v { position: absolute; right: 0; } - .fa-bars, .fa-ellipsis-v { + .fa-arrows-v, .fa-ellipsis-v { display: none; } .fa-ellipsis-v { @@ -342,8 +345,14 @@ display: block; } } + @media screen and (max-width: 450px) { + .fa-arrows-v, .fa-ellipsis-v { + display: block; + margin-right: 0.5rem; + } + } &:hover { - .fa-bars, .fa-ellipsis-v { + .fa-arrows-v, .fa-ellipsis-v { display: block; } } @@ -367,7 +376,7 @@ white-space: nowrap; text-overflow: ellipsis; font-size: 1.2rem; - font-weight: bold; + font-weight: normal; width: 75%; padding: 0.5rem; padding-left: 0; diff --git a/frontend/devices/components/__tests__/hardware_settings_test.tsx b/frontend/devices/components/__tests__/hardware_settings_test.tsx index 427070cce..840ada969 100644 --- a/frontend/devices/components/__tests__/hardware_settings_test.tsx +++ b/frontend/devices/components/__tests__/hardware_settings_test.tsx @@ -12,6 +12,8 @@ import { clickButton } from "../../../__test_support__/helpers"; import { buildResourceIndex } from "../../../__test_support__/resource_index_builder"; +import type { FirmwareConfig } from "farmbot/dist/resources/configs/firmware"; +import { Color } from "../../../ui"; describe("", () => { const fakeProps = (): HardwareSettingsProps => ({ @@ -68,4 +70,41 @@ describe("", () => { const wrapper = shallow(); expect(wrapper.html()).toContain("fa-download"); }); + + it("shows setting load progress", () => { + type ConsistencyLookup = Record; + const consistent: Partial = + ({ id: false, encoder_invert_x: true, encoder_enabled_y: false }); + const consistencyLookup = consistent as ConsistencyLookup; + const p = fakeProps(); + const fakeConfig: Partial = + ({ id: 0, encoder_invert_x: 1, encoder_enabled_y: 0 }); + p.firmwareConfig = fakeConfig as FirmwareConfig; + p.sourceFwConfig = x => + ({ value: p.firmwareConfig?.[x], consistent: consistencyLookup[x] }); + const wrapper = mount(); + const barStyle = wrapper.find(".load-progress-bar").props().style; + expect(barStyle?.background).toEqual(Color.white); + expect(barStyle?.width).toEqual("50%"); + }); + + it("shows setting load progress: 0%", () => { + const p = fakeProps(); + p.firmwareConfig = fakeFirmwareConfig().body; + p.sourceFwConfig = () => ({ value: 0, consistent: false }); + const wrapper = mount(); + const barStyle = wrapper.find(".load-progress-bar").props().style; + expect(barStyle?.width).toEqual("0%"); + expect(barStyle?.background).toEqual(Color.darkGray); + }); + + it("shows setting load progress: 100%", () => { + const p = fakeProps(); + p.firmwareConfig = fakeFirmwareConfig().body; + p.sourceFwConfig = () => ({ value: 0, consistent: true }); + const wrapper = mount(); + const barStyle = wrapper.find(".load-progress-bar").props().style; + expect(barStyle?.width).toEqual("100%"); + expect(barStyle?.background).toEqual(Color.darkGray); + }); }); diff --git a/frontend/devices/components/firmware_hardware_support.ts b/frontend/devices/components/firmware_hardware_support.ts index 71756e221..d3c4d95e3 100644 --- a/frontend/devices/components/firmware_hardware_support.ts +++ b/frontend/devices/components/firmware_hardware_support.ts @@ -16,15 +16,31 @@ export const getFwHardwareValue = return isFwHardwareValue(value) ? value : undefined; }; -const TMC_BOARDS = ["express_k10", "farmduino_k15"]; +const NO_BUTTONS = ["arduino", "farmduino", "none"]; const EXPRESS_BOARDS = ["express_k10"]; +const NO_SENSORS = [...EXPRESS_BOARDS]; +const NO_ENCODERS = [...EXPRESS_BOARDS]; +const NO_TOOLS = [...EXPRESS_BOARDS]; +const NO_TMC = ["arduino", "farmduino", "farmduino_k14"]; export const isTMCBoard = (firmwareHardware: FirmwareHardware | undefined) => - !!(firmwareHardware && TMC_BOARDS.includes(firmwareHardware)); + !firmwareHardware || !NO_TMC.includes(firmwareHardware); export const isExpressBoard = (firmwareHardware: FirmwareHardware | undefined) => !!(firmwareHardware && EXPRESS_BOARDS.includes(firmwareHardware)); +export const hasButtons = (firmwareHardware: FirmwareHardware | undefined) => + !firmwareHardware || !NO_BUTTONS.includes(firmwareHardware); + +export const hasEncoders = (firmwareHardware: FirmwareHardware | undefined) => + !firmwareHardware || !NO_ENCODERS.includes(firmwareHardware); + +export const hasSensors = (firmwareHardware: FirmwareHardware | undefined) => + !firmwareHardware || !NO_SENSORS.includes(firmwareHardware); + +export const hasUTM = (firmwareHardware: FirmwareHardware | undefined) => + !firmwareHardware || !NO_TOOLS.includes(firmwareHardware); + export const getBoardIdentifier = (firmwareVersion: string | undefined): string => firmwareVersion ? firmwareVersion.split(".")[3] : "undefined"; diff --git a/frontend/devices/components/hardware_settings.tsx b/frontend/devices/components/hardware_settings.tsx index 1f12f68fe..f4a2c6612 100644 --- a/frontend/devices/components/hardware_settings.tsx +++ b/frontend/devices/components/hardware_settings.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { MCUFactoryReset, bulkToggleControlPanel } from "../actions"; -import { Widget, WidgetHeader, WidgetBody } from "../../ui/index"; -import { HardwareSettingsProps } from "../interfaces"; +import { Widget, WidgetHeader, WidgetBody, Color } from "../../ui/index"; +import { HardwareSettingsProps, SourceFwConfig } from "../interfaces"; import { isBotOnline } from "../must_be_online"; import { ToolTips } from "../../constants"; import { DangerZone } from "./hardware_settings/danger_zone"; @@ -19,6 +19,8 @@ import { t } from "../../i18next_wrapper"; import { PinBindings } from "./hardware_settings/pin_bindings"; import { ErrorHandling } from "./hardware_settings/error_handling"; import { maybeOpenPanel } from "./maybe_highlight"; +import type { FirmwareConfig } from "farmbot/dist/resources/configs/firmware"; +import type { McuParamName } from "farmbot"; export class HardwareSettings extends React.Component { @@ -36,7 +38,10 @@ export class HardwareSettings extends const botDisconnected = !isBotOnline(sync_status, botToMqttStatus); const commonProps = { dispatch, controlPanelState }; return - + + +
; diff --git a/frontend/farm_designer/__tests__/plant_test.ts b/frontend/farm_designer/__tests__/plant_test.ts new file mode 100644 index 000000000..12bb2eac5 --- /dev/null +++ b/frontend/farm_designer/__tests__/plant_test.ts @@ -0,0 +1,19 @@ +import { Plant } from "../plant"; + +describe("Plant()", () => { + it("returns defaults", () => { + expect(Plant({})).toEqual({ + created_at: "", + id: undefined, + meta: {}, + name: "Untitled Plant", + openfarm_slug: "not-set", + plant_stage: "planned", + pointer_type: "Plant", + radius: 25, + x: 0, + y: 0, + z: 0, + }); + }); +}); diff --git a/frontend/farm_designer/__tests__/reducer_test.ts b/frontend/farm_designer/__tests__/reducer_test.ts index 375b6106c..432e9aa14 100644 --- a/frontend/farm_designer/__tests__/reducer_test.ts +++ b/frontend/farm_designer/__tests__/reducer_test.ts @@ -94,6 +94,19 @@ describe("designer reducer", () => { }); }); + it("uses current point color", () => { + const action: ReduxAction = { + type: Actions.SET_CURRENT_POINT_DATA, + payload: { cx: 10, cy: 20, r: 30 } + }; + const state = oldState(); + state.currentPoint = { cx: 0, cy: 0, r: 0, color: "red" }; + const newState = designer(state, action); + expect(newState.currentPoint).toEqual({ + cx: 10, cy: 20, r: 30, color: "red" + }); + }); + it("sets opened saved garden", () => { const payload = "savedGardenUuid"; const action: ReduxAction = { diff --git a/frontend/farm_designer/designer_panel.tsx b/frontend/farm_designer/designer_panel.tsx index 3fc41850d..dd369abf5 100644 --- a/frontend/farm_designer/designer_panel.tsx +++ b/frontend/farm_designer/designer_panel.tsx @@ -90,7 +90,7 @@ export const DesignerPanelTop = (props: DesignerPanelTopProps) => {
{!props.noIcon && - } + } {props.children} diff --git a/frontend/farm_designer/farm_events/edit_farm_event.tsx b/frontend/farm_designer/farm_events/edit_farm_event.tsx index 5a86238d3..a89af9150 100644 --- a/frontend/farm_designer/farm_events/edit_farm_event.tsx +++ b/frontend/farm_designer/farm_events/edit_farm_event.tsx @@ -23,7 +23,7 @@ export class RawEditFarmEvent extends React.Component + title={t("Edit event")} /> executableOptions={this.props.executableOptions} dispatch={this.props.dispatch} findExecutable={this.props.findExecutable} - title={t("Edit Event")} + title={t("Edit event")} deleteBtn={true} timeSettings={this.props.timeSettings} autoSyncEnabled={this.props.autoSyncEnabled} diff --git a/frontend/farm_designer/farm_events/edit_fe_form.tsx b/frontend/farm_designer/farm_events/edit_fe_form.tsx index d2bb3d66b..681ef3a91 100644 --- a/frontend/farm_designer/farm_events/edit_fe_form.tsx +++ b/frontend/farm_designer/farm_events/edit_fe_form.tsx @@ -41,6 +41,7 @@ import { } from "../../sequences/locals_list/locals_list_support"; import { t } from "../../i18next_wrapper"; import { TimeSettings } from "../../interfaces"; +import { ErrorBoundary } from "../../error_boundary"; export const NEVER: TimeUnit = "never"; /** Separate each of the form fields into their own interface. Recombined later @@ -360,19 +361,24 @@ export class EditFEForm extends React.Component { render() { const { farmEvent } = this.props; return
- this.commitViewModel()}> - - + + this.commitViewModel()}> + + + + +
{ noFolder: (localMetaAttributes[PARENTLESS] || {}).sequences || [] }; const index = folders.map(setDefaultParentId).reduce(addToIndex, emptyIndex); - const childrenOf = (i: number) => sortBy(index[i] || [], (x) => x.name.toLowerCase()); + const childrenOf = (i: number) => + sortBy(index[i] || [], (x) => x.name.toLowerCase()); const terminal = (x: FolderNode): FolderNodeTerminal => ({ ...x, kind: "terminal", content: (localMetaAttributes[x.id] || {}).sequences || [], - open: true, + open: false, editing: false, // children: [], ...(localMetaAttributes[x.id] || {}) @@ -55,7 +56,7 @@ export const ingest: IngestFn = ({ folders, localMetaAttributes }) => { const medial = (x: FolderNode): FolderNodeMedial => ({ ...x, kind: "medial", - open: true, + open: false, editing: false, children: childrenOf(x.id).map(terminal), content: (localMetaAttributes[x.id] || {}).sequences || [], @@ -67,7 +68,7 @@ export const ingest: IngestFn = ({ folders, localMetaAttributes }) => { return output.folders.push({ ...root, kind: "initial", - open: true, + open: false, editing: false, children, content: (localMetaAttributes[root.id] || {}).sequences || [], diff --git a/frontend/help/tours.ts b/frontend/help/tours.ts index 26f78eda6..505f9a626 100644 --- a/frontend/help/tours.ts +++ b/frontend/help/tours.ts @@ -7,7 +7,7 @@ import { selectAllTools } from "../resources/selectors"; import { store } from "../redux/store"; import { getFbosConfig } from "../resources/getters"; import { - isExpressBoard, getFwHardwareValue + getFwHardwareValue, hasUTM } from "../devices/components/firmware_hardware_support"; export enum Tours { @@ -25,26 +25,26 @@ export const tourNames = () => [ const hasTools = () => selectAllTools(store.getState().resources.index).length > 0; -const isExpress = () => - isExpressBoard(getFwHardwareValue( +const noUTM = () => + !hasUTM(getFwHardwareValue( getFbosConfig(store.getState().resources.index))); const toolsStep = () => hasTools() ? [{ target: ".tools", - content: isExpress() + content: noUTM() ? t(TourContent.ADD_SEED_CONTAINERS) : t(TourContent.ADD_TOOLS), - title: isExpress() + title: noUTM() ? t("Add seed containers") : t("Add tools and seed containers"), }] : [{ target: ".tools", - content: isExpress() + content: noUTM() ? t(TourContent.ADD_SEED_CONTAINERS_AND_SLOTS) : t(TourContent.ADD_TOOLS_AND_SLOTS), - title: isExpress() + title: noUTM() ? t("Add seed containers and slots") : t("Add tools and slots"), }]; diff --git a/frontend/logs/__tests__/index_test.tsx b/frontend/logs/__tests__/index_test.tsx index 9d68e66af..66c4d5498 100644 --- a/frontend/logs/__tests__/index_test.tsx +++ b/frontend/logs/__tests__/index_test.tsx @@ -1,7 +1,7 @@ const mockStorj: Dictionary = {}; import * as React from "react"; -import { mount } from "enzyme"; +import { mount, shallow } from "enzyme"; import { RawLogs as Logs } from "../index"; import { ToolTips } from "../../constants"; import { TaggedLog, Dictionary } from "farmbot"; @@ -172,4 +172,12 @@ describe("", () => { wrapper.setState({ markdown: false }); expect(wrapper.html()).not.toContain("message"); }); + + it("changes search term", () => { + const p = fakeProps(); + const wrapper = shallow(); + wrapper.find("input").first().simulate("change", + { currentTarget: { value: "one" } }); + expect(wrapper.state().searchTerm).toEqual("one"); + }); }); diff --git a/frontend/logs/components/__tests__/filter_menu_test.tsx b/frontend/logs/components/__tests__/filter_menu_test.tsx index 968fcd1b4..e885bdccb 100644 --- a/frontend/logs/components/__tests__/filter_menu_test.tsx +++ b/frontend/logs/components/__tests__/filter_menu_test.tsx @@ -9,7 +9,8 @@ const logTypes = MESSAGE_TYPES; describe("", () => { const fakeState: LogsState = { - autoscroll: true, markdown: false, success: 1, busy: 1, warn: 1, + autoscroll: true, markdown: false, searchTerm: "", + success: 1, busy: 1, warn: 1, error: 1, info: 1, fun: 1, debug: 1, assertion: 1, }; @@ -24,7 +25,7 @@ describe("", () => { const wrapper = mount(); logTypes.filter(x => x !== "assertion").map(string => expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase())); - ["autoscroll", "markdown"].map(string => + ["autoscroll", "markdown", "searchTerm"].map(string => expect(wrapper.text().toLowerCase()).not.toContain(string)); }); @@ -34,7 +35,7 @@ describe("", () => { const wrapper = mount(); logTypes.map(string => expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase())); - ["autoscroll", "markdown"].map(string => + ["autoscroll", "markdown", "searchTerm"].map(string => expect(wrapper.text().toLowerCase()).not.toContain(string)); }); diff --git a/frontend/logs/components/__tests__/logs_table_test.tsx b/frontend/logs/components/__tests__/logs_table_test.tsx new file mode 100644 index 000000000..fa5eff31a --- /dev/null +++ b/frontend/logs/components/__tests__/logs_table_test.tsx @@ -0,0 +1,20 @@ +import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings"; +import { bySearchTerm } from "../logs_table"; +import { fakeLog } from "../../../__test_support__/fake_state/resources"; + +describe("bySearchTerm()", () => { + it("includes log", () => { + const log = fakeLog(); + log.body.message = "include this log"; + const result = bySearchTerm("include", fakeTimeSettings())(log); + expect(result).toBeTruthy(); + }); + + it("excludes log", () => { + const log = fakeLog(); + log.body.created_at = undefined; + log.body.message = "exclude this log"; + const result = bySearchTerm("include", fakeTimeSettings())(log); + expect(result).toBeFalsy(); + }); +}); diff --git a/frontend/logs/components/filter_menu.tsx b/frontend/logs/components/filter_menu.tsx index 12e8c6cd9..74cafce74 100644 --- a/frontend/logs/components/filter_menu.tsx +++ b/frontend/logs/components/filter_menu.tsx @@ -28,7 +28,7 @@ const menuSort = (a: string, b: string) => export const filterStateKeys = (state: LogsState, shouldDisplay: ShouldDisplay) => Object.keys(state) - .filter(key => !["autoscroll", "markdown"].includes(key)) + .filter(key => !["autoscroll", "markdown", "searchTerm"].includes(key)) .filter(key => shouldDisplay(Feature.assertion_block) || key !== "assertion"); diff --git a/frontend/logs/components/logs_table.tsx b/frontend/logs/components/logs_table.tsx index 71f9076fe..14277cd72 100644 --- a/frontend/logs/components/logs_table.tsx +++ b/frontend/logs/components/logs_table.tsx @@ -3,7 +3,7 @@ import { TaggedLog, ALLOWED_MESSAGE_TYPES } from "farmbot"; import { LogsState, LogsTableProps, Filters } from "../interfaces"; import { formatLogTime } from "../index"; import { Classes } from "@blueprintjs/core"; -import { isNumber, startCase } from "lodash"; +import { isNumber, startCase, some } from "lodash"; import { t } from "../../i18next_wrapper"; import { TimeSettings } from "../../interfaces"; import { UUID } from "../../resources/interfaces"; @@ -81,6 +81,7 @@ export const LogsTable = (props: LogsTableProps) => { {filterByVerbosity(getFilterLevel(props.state), props.logs) + .filter(bySearchTerm(props.state.searchTerm, props.timeSettings)) .map((log: TaggedLog) => + (log: TaggedLog) => { + const { x, y, z, created_at, message, type } = log.body; + const displayedTime = formatLogTime(created_at || NaN, timeSettings); + const displayedPosition = xyzTableEntry(x, y, z); + const lowerSearchTerm = searchTerm.toLowerCase(); + return some([message, type] + .map(string => string.toLowerCase().includes(lowerSearchTerm)) + .concat([ + displayedTime.toLowerCase().includes(lowerSearchTerm), + displayedPosition.includes(lowerSearchTerm), + ])); + }; diff --git a/frontend/logs/index.tsx b/frontend/logs/index.tsx index cb7b8ffd2..a6a5e13ed 100644 --- a/frontend/logs/index.tsx +++ b/frontend/logs/index.tsx @@ -49,6 +49,7 @@ export class RawLogs extends React.Component> { fun: this.initialize(NumericSetting.fun_log, 1), debug: this.initialize(NumericSetting.debug_log, 1), assertion: this.initialize(NumericSetting.assertion_log, 1), + searchTerm: "", markdown: true, }; @@ -85,13 +86,13 @@ export class RawLogs extends React.Component> { const filterBtnColor = this.filterActive ? "green" : "gray"; return - +

{t("Logs")}

- +
@@ -121,6 +122,19 @@ export class RawLogs extends React.Component> {
+ + +
+
+ + + this.setState({ searchTerm: e.currentTarget.value })} + placeholder={t("Search logs...")} /> +
+
+ +
; export interface LogsState extends Filters { autoscroll: boolean; + searchTerm: string; markdown: boolean; } diff --git a/frontend/nav/additional_menu.tsx b/frontend/nav/additional_menu.tsx index 4e0b1290f..f9e627495 100644 --- a/frontend/nav/additional_menu.tsx +++ b/frontend/nav/additional_menu.tsx @@ -9,23 +9,23 @@ export const AdditionalMenu = (props: AccountMenuProps) => { return
- + {t("Account Settings")}
- + {t("Logs")}
- + {t("Help")} diff --git a/frontend/read_only_mode/index.tsx b/frontend/read_only_mode/index.tsx index 67ca1d555..1166ded2b 100644 --- a/frontend/read_only_mode/index.tsx +++ b/frontend/read_only_mode/index.tsx @@ -3,6 +3,7 @@ import { store } from "../redux/store"; import { warning } from "../toast/toast"; import React from "react"; import { appIsReadonly } from "./app_is_read_only"; +import { t } from "../i18next_wrapper"; export const readOnlyInterceptor = (config: AxiosRequestConfig) => { const method = (config.method || "get").toLowerCase(); @@ -10,7 +11,7 @@ export const readOnlyInterceptor = (config: AxiosRequestConfig) => { if (relevant && appIsReadonly(store.getState().resources.index)) { if (!(config.url || "").includes("web_app_config")) { - warning("Refusing to modify data in read-only mode"); + warning(t("Refusing to modify data in read-only mode")); return Promise.reject(config); } } @@ -18,19 +19,12 @@ export const readOnlyInterceptor = (config: AxiosRequestConfig) => { return Promise.resolve(config); }; -const MOVE_ME_ELSEWHERE: React.CSSProperties = { - float: "right", - boxSizing: "inherit", - margin: "9px 0px 0px 9px" -}; - export const ReadOnlyIcon = (p: { locked: boolean }) => { if (p.locked) { - return
- - + return
+ +
; - } else { return
; } diff --git a/frontend/regimens/index.tsx b/frontend/regimens/index.tsx index 502a8c086..14b2f2e72 100644 --- a/frontend/regimens/index.tsx +++ b/frontend/regimens/index.tsx @@ -42,8 +42,7 @@ export class RawRegimens extends React.Component { + title={t("Regimens")}>
- + + placeholder={t("Search regimens...")} />
diff --git a/frontend/resources/selectors_by_id.ts b/frontend/resources/selectors_by_id.ts index 95fe95624..dc00eb0fe 100644 --- a/frontend/resources/selectors_by_id.ts +++ b/frontend/resources/selectors_by_id.ts @@ -11,8 +11,6 @@ import { isTaggedGenericPointer, isTaggedSavedGarden, isTaggedFolder, - isTaggedPoint, - isTaggedPointGroup, } from "./tagged_resources"; import { ResourceName, @@ -127,20 +125,6 @@ export function maybeFindGenericPointerById(index: ResourceIndex, id: number) { if (resource && isTaggedGenericPointer(resource)) { return resource; } } -/** Unlike other findById methods, this one allows undefined (missed) values */ -export function maybeFindPointById(index: ResourceIndex, id: number) { - const uuid = index.byKindAndId[joinKindAndId("Point", id)]; - const resource = index.references[uuid || "nope"]; - if (resource && isTaggedPoint(resource)) { return resource; } -} - -/** Unlike other findById methods, this one allows undefined (missed) values */ -export function maybeFindGroupById(index: ResourceIndex, id: number) { - const uuid = index.byKindAndId[joinKindAndId("PointGroup", id)]; - const resource = index.references[uuid || "nope"]; - if (resource && isTaggedPointGroup(resource)) { return resource; } -} - /** Unlike other findById methods, this one allows undefined (missed) values */ export function maybeFindSavedGardenById(index: ResourceIndex, id: number) { const uuid = index.byKindAndId[joinKindAndId("SavedGarden", id)]; diff --git a/frontend/sequences/step_tiles/tile_if/index.tsx b/frontend/sequences/step_tiles/tile_if/index.tsx index 393abb6d0..ddac3eadb 100644 --- a/frontend/sequences/step_tiles/tile_if/index.tsx +++ b/frontend/sequences/step_tiles/tile_if/index.tsx @@ -100,7 +100,7 @@ export function InnerIf(props: IfParams) { confirmStepDeletion={confirmStepDeletion}> {recursive && - +  {t("Recursive condition.")} } diff --git a/frontend/ui/back_arrow.tsx b/frontend/ui/back_arrow.tsx index f317111bf..b386d1934 100644 --- a/frontend/ui/back_arrow.tsx +++ b/frontend/ui/back_arrow.tsx @@ -10,6 +10,6 @@ export function BackArrow(props: BackArrowProps) { }; return - + ; } From 90ddd78bb8af678ff538fad571dc69e8f0b39a8d Mon Sep 17 00:00:00 2001 From: gabrielburnworth Date: Wed, 26 Feb 2020 10:11:31 -0800 Subject: [PATCH 7/9] remove old tools page --- frontend/__tests__/interface_test.ts | 1 - .../tools/tool_slot_edit_components.tsx | 52 ++++++++-- frontend/route_config.tsx | 6 -- frontend/tools/__tests__/index_test.tsx | 48 --------- .../tools/__tests__/state_to_props_test.ts | 30 ------ .../components/__tests__/tool_form_test.tsx | 91 ----------------- .../components/__tests__/tool_list_test.tsx | 24 ----- .../__tests__/tool_slot_row_test.tsx | 26 ----- .../__tests__/toolbay_form_test.tsx | 50 ---------- .../__tests__/toolbay_header_test.tsx | 10 -- .../__tests__/toolbay_list_test.tsx | 31 ------ .../__tests__/toolbay_number_column_test.tsx | 25 ----- .../toolbay_slot_direction_selection_test.tsx | 31 ------ .../__tests__/toolbay_slot_menu_test.tsx | 95 ------------------ frontend/tools/components/empty_tool_slot.ts | 14 --- frontend/tools/components/index.ts | 4 - frontend/tools/components/tool_form.tsx | 98 ------------------- frontend/tools/components/tool_list.tsx | 50 ---------- frontend/tools/components/tool_slot_row.tsx | 65 ------------ frontend/tools/components/toolbay_form.tsx | 70 ------------- frontend/tools/components/toolbay_header.tsx | 23 ----- frontend/tools/components/toolbay_list.tsx | 41 -------- .../components/toolbay_number_column.tsx | 23 ----- .../toolbay_slot_direction_selection.tsx | 50 ---------- .../tools/components/toolbay_slot_menu.tsx | 84 ---------------- frontend/tools/index.tsx | 51 ---------- frontend/tools/interfaces.ts | 56 ----------- frontend/tools/state_to_props.ts | 75 -------------- 28 files changed, 46 insertions(+), 1178 deletions(-) delete mode 100644 frontend/tools/__tests__/index_test.tsx delete mode 100644 frontend/tools/__tests__/state_to_props_test.ts delete mode 100644 frontend/tools/components/__tests__/tool_form_test.tsx delete mode 100644 frontend/tools/components/__tests__/tool_list_test.tsx delete mode 100644 frontend/tools/components/__tests__/tool_slot_row_test.tsx delete mode 100644 frontend/tools/components/__tests__/toolbay_form_test.tsx delete mode 100644 frontend/tools/components/__tests__/toolbay_header_test.tsx delete mode 100644 frontend/tools/components/__tests__/toolbay_list_test.tsx delete mode 100644 frontend/tools/components/__tests__/toolbay_number_column_test.tsx delete mode 100644 frontend/tools/components/__tests__/toolbay_slot_direction_selection_test.tsx delete mode 100644 frontend/tools/components/__tests__/toolbay_slot_menu_test.tsx delete mode 100644 frontend/tools/components/empty_tool_slot.ts delete mode 100644 frontend/tools/components/index.ts delete mode 100644 frontend/tools/components/tool_form.tsx delete mode 100644 frontend/tools/components/tool_list.tsx delete mode 100644 frontend/tools/components/tool_slot_row.tsx delete mode 100644 frontend/tools/components/toolbay_form.tsx delete mode 100644 frontend/tools/components/toolbay_header.tsx delete mode 100644 frontend/tools/components/toolbay_list.tsx delete mode 100644 frontend/tools/components/toolbay_number_column.tsx delete mode 100644 frontend/tools/components/toolbay_slot_direction_selection.tsx delete mode 100644 frontend/tools/components/toolbay_slot_menu.tsx delete mode 100644 frontend/tools/index.tsx delete mode 100644 frontend/tools/interfaces.ts delete mode 100644 frontend/tools/state_to_props.ts diff --git a/frontend/__tests__/interface_test.ts b/frontend/__tests__/interface_test.ts index bd461387f..92ee4a7b0 100644 --- a/frontend/__tests__/interface_test.ts +++ b/frontend/__tests__/interface_test.ts @@ -30,7 +30,6 @@ import "../regimens/editor/interfaces"; import "../regimens/interfaces"; import "../resources/interfaces"; import "../sequences/interfaces"; -import "../tools/interfaces"; describe("interfaces", () => { it("cant explain why coverage is 0 for interface files", () => { diff --git a/frontend/farm_designer/tools/tool_slot_edit_components.tsx b/frontend/farm_designer/tools/tool_slot_edit_components.tsx index 903088e82..6654bcde0 100644 --- a/frontend/farm_designer/tools/tool_slot_edit_components.tsx +++ b/frontend/farm_designer/tools/tool_slot_edit_components.tsx @@ -1,18 +1,15 @@ import React from "react"; import { t } from "../../i18next_wrapper"; import { Xyz, TaggedTool, TaggedToolSlotPointer } from "farmbot"; -import { Row, Col, BlurableInput, FBSelect, NULL_CHOICE, DropDownItem } from "../../ui"; import { - directionIconClass, positionButtonTitle, newSlotDirection, positionIsDefined -} from "../../tools/components/toolbay_slot_menu"; -import { - DIRECTION_CHOICES, DIRECTION_CHOICES_DDI -} from "../../tools/components/toolbay_slot_direction_selection"; + Row, Col, BlurableInput, FBSelect, NULL_CHOICE, DropDownItem +} from "../../ui"; import { BotPosition } from "../../devices/interfaces"; import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources"; import { Popover } from "@blueprintjs/core"; import { ToolSlotSVG } from "../map/layers/tool_slots/tool_graphics"; import { BotOriginQuadrant } from "../interfaces"; +import { isNumber } from "lodash"; export interface GantryMountedInputProps { gantryMounted: boolean; @@ -189,3 +186,46 @@ export const SlotEditRows = (props: SlotEditRowsProps) => gantryMounted={props.toolSlot.body.gantry_mounted} onChange={props.updateToolSlot} />}
; + +const directionIconClass = (slotDirection: ToolPulloutDirection) => { + switch (slotDirection) { + case ToolPulloutDirection.POSITIVE_X: return "fa fa-arrow-circle-right"; + case ToolPulloutDirection.NEGATIVE_X: return "fa fa-arrow-circle-left"; + case ToolPulloutDirection.POSITIVE_Y: return "fa fa-arrow-circle-up"; + case ToolPulloutDirection.NEGATIVE_Y: return "fa fa-arrow-circle-down"; + case ToolPulloutDirection.NONE: return "fa fa-dot-circle-o"; + } +}; + +export const positionButtonTitle = (position: BotPosition): string => + positionIsDefined(position) + ? `(${position.x}, ${position.y}, ${position.z})` + : t("(unknown)"); + +export const newSlotDirection = + (old: ToolPulloutDirection | undefined): ToolPulloutDirection => + isNumber(old) && old < 4 ? old + 1 : ToolPulloutDirection.NONE; + +export const positionIsDefined = (position: BotPosition): boolean => + isNumber(position.x) && isNumber(position.y) && isNumber(position.z); + +export const DIRECTION_CHOICES_DDI: { [index: number]: DropDownItem } = { + [ToolPulloutDirection.NONE]: + { label: t("None"), value: ToolPulloutDirection.NONE }, + [ToolPulloutDirection.POSITIVE_X]: + { label: t("Positive X"), value: ToolPulloutDirection.POSITIVE_X }, + [ToolPulloutDirection.NEGATIVE_X]: + { label: t("Negative X"), value: ToolPulloutDirection.NEGATIVE_X }, + [ToolPulloutDirection.POSITIVE_Y]: + { label: t("Positive Y"), value: ToolPulloutDirection.POSITIVE_Y }, + [ToolPulloutDirection.NEGATIVE_Y]: + { label: t("Negative Y"), value: ToolPulloutDirection.NEGATIVE_Y }, +}; + +export const DIRECTION_CHOICES: DropDownItem[] = [ + DIRECTION_CHOICES_DDI[ToolPulloutDirection.NONE], + DIRECTION_CHOICES_DDI[ToolPulloutDirection.POSITIVE_X], + DIRECTION_CHOICES_DDI[ToolPulloutDirection.NEGATIVE_X], + DIRECTION_CHOICES_DDI[ToolPulloutDirection.POSITIVE_Y], + DIRECTION_CHOICES_DDI[ToolPulloutDirection.NEGATIVE_Y], +]; diff --git a/frontend/route_config.tsx b/frontend/route_config.tsx index 71bae5fa3..77236b088 100644 --- a/frontend/route_config.tsx +++ b/frontend/route_config.tsx @@ -149,12 +149,6 @@ export const UNBOUND_ROUTES = [ getModule: () => import("./sequences/sequences"), key: "Sequences", }), - route({ - children: false, - $: "/tools", - getModule: () => import("./tools"), - key: "Tools", - }), route({ children: false, $: "/designer", diff --git a/frontend/tools/__tests__/index_test.tsx b/frontend/tools/__tests__/index_test.tsx deleted file mode 100644 index c7bd984f2..000000000 --- a/frontend/tools/__tests__/index_test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import * as React from "react"; -import { mount, shallow } from "enzyme"; -import { RawTools as Tools } from "../index"; -import { Props } from "../interfaces"; -import { - fakeToolSlot, fakeTool -} from "../../__test_support__/fake_state/resources"; - -describe("", () => { - const fakeProps = (): Props => ({ - toolSlots: [], - tools: [fakeTool()], - getToolSlots: () => [fakeToolSlot()], - getToolOptions: () => [], - getChosenToolOption: () => ({ label: "None", value: "" }), - getToolByToolSlotUUID: fakeTool, - changeToolSlot: jest.fn(), - isActive: () => true, - dispatch: jest.fn(), - botPosition: { x: undefined, y: undefined, z: undefined } - }); - - it("renders", () => { - const wrapper = mount(); - const txt = wrapper.text(); - const strings = [ - "Tool Slots", - "SlotXYZ", - "Tool or Seed Container", - "Tools", - "NameStatus", - "Fooactive"]; - strings.map(string => expect(txt).toContain(string)); - }); - - it("shows forms", () => { - const wrapper = shallow(); - expect(wrapper.find("ToolList").length).toEqual(1); - expect(wrapper.find("ToolBayList").length).toEqual(1); - expect(wrapper.find("ToolForm").length).toEqual(0); - expect(wrapper.find("ToolBayForm").length).toEqual(0); - wrapper.setState({ editingBays: true, editingTools: true }); - expect(wrapper.find("ToolList").length).toEqual(0); - expect(wrapper.find("ToolBayList").length).toEqual(0); - expect(wrapper.find("ToolForm").length).toEqual(1); - expect(wrapper.find("ToolBayForm").length).toEqual(1); - }); -}); diff --git a/frontend/tools/__tests__/state_to_props_test.ts b/frontend/tools/__tests__/state_to_props_test.ts deleted file mode 100644 index bf4c32c08..000000000 --- a/frontend/tools/__tests__/state_to_props_test.ts +++ /dev/null @@ -1,30 +0,0 @@ -jest.mock("../../api/crud", () => ({ edit: jest.fn() })); - -import { mapStateToProps } from "../state_to_props"; -import { fakeState } from "../../__test_support__/fake_state"; -import { NULL_CHOICE } from "../../ui"; -import { fakeToolSlot } from "../../__test_support__/fake_state/resources"; -import { edit } from "../../api/crud"; - -describe("mapStateToProps()", () => { - it("getChosenToolOption()", () => { - const props = mapStateToProps(fakeState()); - const result = props.getChosenToolOption(undefined); - expect(result).toEqual(NULL_CHOICE); - }); - - it("changeToolSlot(): no tool_id", () => { - const props = mapStateToProps(fakeState()); - const tool = fakeToolSlot(); - props.changeToolSlot(tool, jest.fn())({ label: "", value: "" }); - // tslint:disable-next-line:no-null-keyword - expect(edit).toHaveBeenCalledWith(tool, { tool_id: null }); - }); - - it("changeToolSlot(): tool_id", () => { - const props = mapStateToProps(fakeState()); - const tool = fakeToolSlot(); - props.changeToolSlot(tool, jest.fn())({ label: "", value: 1 }); - expect(edit).toHaveBeenCalledWith(tool, { tool_id: 1 }); - }); -}); diff --git a/frontend/tools/components/__tests__/tool_form_test.tsx b/frontend/tools/components/__tests__/tool_form_test.tsx deleted file mode 100644 index 134f6cb93..000000000 --- a/frontend/tools/components/__tests__/tool_form_test.tsx +++ /dev/null @@ -1,91 +0,0 @@ -jest.mock("../../../api/crud", () => ({ - init: jest.fn(), - saveAll: jest.fn(), - destroy: jest.fn(), - edit: jest.fn(), -})); - -import { SpecialStatus } from "farmbot"; -jest.mock("../../../resources/tagged_resources", () => ({ - getArrayStatus: () => SpecialStatus.SAVED, - isTaggedResource: () => true -})); - -import * as React from "react"; -import { ToolForm } from "../tool_form"; -import { mount, shallow } from "enzyme"; -import { fakeTool } from "../../../__test_support__/fake_state/resources"; -import { ToolListAndFormProps } from "../../interfaces"; -import { clickButton } from "../../../__test_support__/helpers"; -import { init, saveAll, destroy, edit } from "../../../api/crud"; - -describe("", () => { - function fakeProps(): ToolListAndFormProps { - return { - dispatch: jest.fn(), - toggle: jest.fn(), - tools: [fakeTool(), fakeTool()], - isActive: jest.fn(), - }; - } - - it("renders", () => { - const p = fakeProps(); - const wrapper = mount(); - expect(wrapper.find("input").length).toEqual(p.tools.length); - }); - - it("saves tools", () => { - const wrapper = mount(); - clickButton(wrapper, 1, "saved", { partial_match: true }); - expect(saveAll).toHaveBeenCalledTimes(1); - }); - - it("adds new tool", () => { - const wrapper = mount(); - expect(wrapper.props().tools.length).toEqual(2); - clickButton(wrapper, 2, ""); - expect(init).toHaveBeenCalledWith("Tool", { name: "Tool 3" }); - }); - - it("adds stock tools", () => { - const wrapper = mount(); - clickButton(wrapper, 3, "stock tools"); - expect(init).toHaveBeenCalledTimes(6); - }); - - it("changes tool name", () => { - const p = fakeProps(); - const wrapper = shallow(); - wrapper.find("BlurableInput").first().simulate("commit", { - currentTarget: { value: "New Tool Name" } - }); - expect(edit).toHaveBeenCalledWith(p.tools[0], { name: "New Tool Name" }); - }); - - it("has red delete button", () => { - const p = fakeProps(); - p.isActive = () => false; - const wrapper = mount(); - const delBtn = wrapper.find("button").last(); - expect(delBtn.hasClass("red")).toBeTruthy(); - }); - - it("deletes tool", () => { - const p = fakeProps(); - p.isActive = () => false; - const wrapper = mount(); - const delBtn = wrapper.find("button").last(); - delBtn.simulate("click"); - expect(destroy).toHaveBeenCalledWith(p.tools[1].uuid); - }); - - it("has gray delete button", () => { - const p = fakeProps(); - p.isActive = () => true; - const wrapper = mount(); - const delBtn = wrapper.find("button").last(); - expect(delBtn.hasClass("pseudo-disabled")).toBeTruthy(); - expect(delBtn.props().title).toContain("in slot"); - }); -}); diff --git a/frontend/tools/components/__tests__/tool_list_test.tsx b/frontend/tools/components/__tests__/tool_list_test.tsx deleted file mode 100644 index 9addb818b..000000000 --- a/frontend/tools/components/__tests__/tool_list_test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from "react"; -import { ToolList } from "../tool_list"; -import { mount } from "enzyme"; -import { mapStateToProps } from "../../state_to_props"; -import { fakeState } from "../../../__test_support__/fake_state"; -import { ToolListAndFormProps } from "../../interfaces"; - -describe("", () => { - const fakeProps = (): ToolListAndFormProps => { - const props = mapStateToProps(fakeState()); - return { - dispatch: jest.fn(), - tools: props.tools, - toggle: jest.fn(), - isActive: props.isActive, - }; - }; - - it("renders tool names and statuses", () => { - const wrapper = mount(); - expect(wrapper.text()).toContain("Trench Digging Toolactive"); - expect(wrapper.text()).toContain("Berry Picking Toolinactive"); - }); -}); diff --git a/frontend/tools/components/__tests__/tool_slot_row_test.tsx b/frontend/tools/components/__tests__/tool_slot_row_test.tsx deleted file mode 100644 index 5970ddb5d..000000000 --- a/frontend/tools/components/__tests__/tool_slot_row_test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -jest.mock("../../../api/crud", () => ({ destroy: jest.fn() })); - -import * as React from "react"; -import { ToolSlotRowProps, ToolSlotRow } from "../tool_slot_row"; -import { mount } from "enzyme"; -import { destroy } from "../../../api/crud"; -import { fakeToolSlot } from "../../../__test_support__/fake_state/resources"; - -describe("", () => { - const fakeProps = (): ToolSlotRowProps => ({ - dispatch: jest.fn(), - slot: fakeToolSlot(), - botPosition: { x: undefined, y: undefined, z: undefined }, - toolOptions: [], - chosenToolOption: { label: "", value: "" }, - onToolSlotChange: jest.fn(), - gantryMounted: false, - }); - - it("deletes slot", () => { - const p = fakeProps(); - const wrapper = mount(); - wrapper.find("button").last().simulate("click"); - expect(destroy).toHaveBeenCalledWith(p.slot.uuid); - }); -}); diff --git a/frontend/tools/components/__tests__/toolbay_form_test.tsx b/frontend/tools/components/__tests__/toolbay_form_test.tsx deleted file mode 100644 index 8d2a05348..000000000 --- a/frontend/tools/components/__tests__/toolbay_form_test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -jest.mock("../../../api/crud", () => ({ - init: jest.fn(), - saveAll: jest.fn(), -})); - -import * as React from "react"; -import { ToolBayForm } from "../toolbay_form"; -import { mount } from "enzyme"; -import { mapStateToProps } from "../../state_to_props"; -import { fakeState } from "../../../__test_support__/fake_state"; -import { ToolBayFormProps } from "../../interfaces"; -import { clickButton } from "../../../__test_support__/helpers"; -import { saveAll, init } from "../../../api/crud"; -import { emptyToolSlotBody } from "../empty_tool_slot"; - -describe("", () => { - const fakeProps = (): ToolBayFormProps => { - const props = mapStateToProps(fakeState()); - return { - toggle: jest.fn(), - dispatch: jest.fn(), - toolSlots: props.toolSlots, - getToolSlots: props.getToolSlots, - getChosenToolOption: props.getChosenToolOption, - getToolOptions: props.getToolOptions, - changeToolSlot: props.changeToolSlot, - botPosition: { x: 1, y: 2, z: 3 }, - }; - }; - - it("renders ToolSlot", () => { - const wrapper = mount(); - const inputs = wrapper.find("input"); - expect(inputs.length).toEqual(3); - expect(wrapper.text()).toContain("Trench Digging Tool"); - [0, 1, 2].map(i => expect(inputs.at(i).props().value).toEqual("10")); - }); - - it("saves tool slots", () => { - const wrapper = mount(); - clickButton(wrapper, 1, "saved", { partial_match: true }); - expect(saveAll).toHaveBeenCalledTimes(1); - }); - - it("adds new tool slot", () => { - const wrapper = mount(); - clickButton(wrapper, 2, ""); - expect(init).toHaveBeenCalledWith("Point", emptyToolSlotBody()); - }); -}); diff --git a/frontend/tools/components/__tests__/toolbay_header_test.tsx b/frontend/tools/components/__tests__/toolbay_header_test.tsx deleted file mode 100644 index 5e623a9d8..000000000 --- a/frontend/tools/components/__tests__/toolbay_header_test.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import * as React from "react"; -import { ToolBayHeader } from "../toolbay_header"; -import { mount } from "enzyme"; - -describe("", () => { - it("renders", () => { - const header = mount(); - expect(header.text()).toEqual("SlotXYZTool or Seed Container"); - }); -}); diff --git a/frontend/tools/components/__tests__/toolbay_list_test.tsx b/frontend/tools/components/__tests__/toolbay_list_test.tsx deleted file mode 100644 index 3981a4214..000000000 --- a/frontend/tools/components/__tests__/toolbay_list_test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as React from "react"; -import { ToolBayList } from "../toolbay_list"; -import { mount } from "enzyme"; -import { mapStateToProps } from "../../state_to_props"; -import { fakeState } from "../../../__test_support__/fake_state"; -import { ToolBayListProps } from "../../interfaces"; - -describe("", () => { - const fakeProps = (): ToolBayListProps => { - const props = mapStateToProps(fakeState()); - return { - getToolByToolSlotUUID: props.getToolByToolSlotUUID, - getToolSlots: props.getToolSlots, - toggle: jest.fn(), - }; - }; - - it("renders", () => { - const wrapper = mount(); - expect(wrapper.text()).toContain("1101010Trench Digging Tool"); - }); - - it("renders gantry mounted slot", () => { - const p = fakeProps(); - const slots = p.getToolSlots(); - slots[0].body.gantry_mounted = true; - p.getToolSlots = () => slots; - const wrapper = mount(); - expect(wrapper.text()).toContain("1Gantry1010Trench Digging Tool"); - }); -}); diff --git a/frontend/tools/components/__tests__/toolbay_number_column_test.tsx b/frontend/tools/components/__tests__/toolbay_number_column_test.tsx deleted file mode 100644 index 9f5a85915..000000000 --- a/frontend/tools/components/__tests__/toolbay_number_column_test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -jest.mock("../../../api/crud", () => ({ edit: jest.fn() })); - -import * as React from "react"; -import { shallow } from "enzyme"; -import { ToolBayNumberCol, TBNumColProps } from "../toolbay_number_column"; -import { edit } from "../../../api/crud"; -import { fakeToolSlot } from "../../../__test_support__/fake_state/resources"; - -describe("", () => { - const fakeProps = (): TBNumColProps => ({ - axis: "x", - value: 0, - dispatch: jest.fn(), - slot: fakeToolSlot(), - }); - - it("edits value", () => { - const p = fakeProps(); - const wrapper = shallow(); - wrapper.find("BlurableInput").simulate("commit", { - currentTarget: { value: "1.23" } - }); - expect(edit).toHaveBeenCalledWith(p.slot, { x: 1.23 }); - }); -}); diff --git a/frontend/tools/components/__tests__/toolbay_slot_direction_selection_test.tsx b/frontend/tools/components/__tests__/toolbay_slot_direction_selection_test.tsx deleted file mode 100644 index aeb9c2b23..000000000 --- a/frontend/tools/components/__tests__/toolbay_slot_direction_selection_test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as React from "react"; -import { shallow } from "enzyme"; -import { - SlotDirectionSelect, SlotDirectionSelectProps -} from "../toolbay_slot_direction_selection"; -import { fakeToolSlot } from "../../../__test_support__/fake_state/resources"; -import { Actions } from "../../../constants"; -import { SpecialStatus } from "farmbot"; - -describe("", () => { - const fakeProps = (): SlotDirectionSelectProps => { - return { - dispatch: jest.fn(), - slot: fakeToolSlot() - }; - }; - - it("changes slot direction", () => { - const p = fakeProps(); - const wrapper = shallow(); - wrapper.simulate("change", { value: 1 }); - expect(p.dispatch).toHaveBeenCalledWith({ - payload: { - specialStatus: SpecialStatus.DIRTY, - update: { pullout_direction: 1 }, - uuid: expect.any(String) - }, - type: Actions.EDIT_RESOURCE - }); - }); -}); diff --git a/frontend/tools/components/__tests__/toolbay_slot_menu_test.tsx b/frontend/tools/components/__tests__/toolbay_slot_menu_test.tsx deleted file mode 100644 index 31272cca1..000000000 --- a/frontend/tools/components/__tests__/toolbay_slot_menu_test.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import * as React from "react"; -import { mount } from "enzyme"; -import { SlotMenu, SlotMenuProps } from "../toolbay_slot_menu"; -import { fakeToolSlot } from "../../../__test_support__/fake_state/resources"; -import { Actions } from "../../../constants"; -import { SpecialStatus } from "farmbot"; - -describe("", () => { - const fakeProps = (): SlotMenuProps => { - return { - dispatch: jest.fn(), - slot: fakeToolSlot(), - botPosition: { x: 1, y: 2, z: 3 } - }; - }; - - it("changes slot direction", () => { - const p = fakeProps(); - const wrapper = mount(); - wrapper.find("i").first().simulate("click"); - expect(p.dispatch).toHaveBeenCalledWith({ - payload: { - specialStatus: SpecialStatus.DIRTY, - update: { pullout_direction: 1 }, - uuid: expect.any(String) - }, - type: Actions.EDIT_RESOURCE - }); - }); - - it("changes slot direction: reset", () => { - const p = fakeProps(); - p.slot.body.pullout_direction = 4; - const wrapper = mount(); - wrapper.find("i").first().simulate("click"); - expect(p.dispatch).toHaveBeenCalledWith({ - payload: { - specialStatus: SpecialStatus.DIRTY, - update: { pullout_direction: 0 }, - uuid: expect.any(String) - }, - type: Actions.EDIT_RESOURCE - }); - }); - - const checkDirection = (direction: number, expected: string) => { - it("icon shows direction", () => { - const p = fakeProps(); - p.slot.body.pullout_direction = direction; - const wrapper = mount(); - expect(wrapper.html()).toContain(expected); - }); - }; - checkDirection(1, "right"); - checkDirection(2, "left"); - checkDirection(3, "up"); - checkDirection(4, "down"); - - it("fills inputs with bot position", () => { - const p = fakeProps(); - const wrapper = mount(); - const buttons = wrapper.find("button"); - buttons.last().simulate("click"); - expect(p.dispatch).toHaveBeenCalledWith({ - type: Actions.EDIT_RESOURCE, - payload: expect.objectContaining({ - update: { x: 1, y: 2, z: 3 } - }) - }); - }); - - it("doesn't fills inputs with bot position unknown", () => { - const p = fakeProps(); - p.botPosition = { x: undefined, y: undefined, z: undefined }; - const wrapper = mount(); - const buttons = wrapper.find("button"); - buttons.last().simulate("click"); - expect(p.dispatch).not.toHaveBeenCalled(); - }); - - it("sets gantry_mounted", () => { - const p = fakeProps(); - p.slot.body.gantry_mounted = false; - const wrapper = mount(); - wrapper.find("input").last().simulate("change"); - expect(p.dispatch).toHaveBeenCalledWith({ - payload: { - specialStatus: SpecialStatus.DIRTY, - update: { gantry_mounted: true }, - uuid: expect.any(String) - }, - type: Actions.EDIT_RESOURCE - }); - }); -}); diff --git a/frontend/tools/components/empty_tool_slot.ts b/frontend/tools/components/empty_tool_slot.ts deleted file mode 100644 index 57125bdfc..000000000 --- a/frontend/tools/components/empty_tool_slot.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ToolSlotPointer } from "farmbot/dist/resources/api_resources"; - -export const emptyToolSlotBody = (): ToolSlotPointer => ({ - x: 0, - y: 0, - z: 0, - radius: 25, - pointer_type: "ToolSlot", - meta: {}, - tool_id: undefined, - name: "Tool Slot", - pullout_direction: 0, - gantry_mounted: false, -}); diff --git a/frontend/tools/components/index.ts b/frontend/tools/components/index.ts deleted file mode 100644 index 9dcea62c6..000000000 --- a/frontend/tools/components/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./toolbay_form"; -export * from "./toolbay_list"; -export * from "./tool_list"; -export * from "./tool_form"; diff --git a/frontend/tools/components/tool_form.tsx b/frontend/tools/components/tool_form.tsx deleted file mode 100644 index 42dbb8593..000000000 --- a/frontend/tools/components/tool_form.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import * as React from "react"; -import { ToolListAndFormProps } from "../interfaces"; -import { - Row, - Col, - Widget, - WidgetBody, - WidgetHeader, - BlurableInput, - SaveBtn -} from "../../ui"; -import { getArrayStatus } from "../../resources/tagged_resources"; -import { edit, destroy, init, saveAll } from "../../api/crud"; -import { ToolTips } from "../../constants"; -import { TaggedTool } from "farmbot"; -import { t } from "../../i18next_wrapper"; - -export class ToolForm extends React.Component { - get newToolName() { return t("Tool ") + (this.props.tools.length + 1); } - - newTool = (name = this.newToolName) => { - this.props.dispatch(init("Tool", { name })); - }; - - stockTools = () => { - this.newTool(t("Seeder")); - this.newTool(t("Watering Nozzle")); - this.newTool(t("Weeder")); - this.newTool(t("Soil Sensor")); - this.newTool(t("Seed Bin")); - this.newTool(t("Seed Tray")); - } - - HeaderButtons = () => { - const { dispatch, tools, toggle } = this.props; - const specialStatus = getArrayStatus(tools); - return
- - dispatch(saveAll(tools, toggle))} /> - - -
; - } - - ToolForm = (tool: TaggedTool, index: number) => { - const { dispatch, isActive } = this.props; - const inSlotClass = isActive(tool) ? "pseudo-disabled" : ""; - return - - - dispatch(edit(tool, { name: e.currentTarget.value }))} /> - - - - - ; - } - - render() { - return - - - - - - - - - - {this.props.tools.map(this.ToolForm)} - - ; - } -} diff --git a/frontend/tools/components/tool_list.tsx b/frontend/tools/components/tool_list.tsx deleted file mode 100644 index 2de0a9eac..000000000 --- a/frontend/tools/components/tool_list.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from "react"; -import { Row, Col, Widget, WidgetBody, WidgetHeader } from "../../ui"; -import { ToolListAndFormProps } from "../interfaces"; -import { TaggedTool } from "farmbot"; -import { ToolTips } from "../../constants"; -import { t } from "../../i18next_wrapper"; - -enum ColWidth { - toolName = 8, - status = 4, -} - -export class ToolList extends React.Component { - - ToolListItem = (tool: TaggedTool) => { - return - - {tool.body.name || "Name not found"} - - - {this.props.isActive(tool) ? t("active") : t("inactive")} - - ; - } - - render() { - const { tools, toggle } = this.props; - - return - - - - - - - - - - - - - {tools.map(this.ToolListItem)} - - ; - } -} diff --git a/frontend/tools/components/tool_slot_row.tsx b/frontend/tools/components/tool_slot_row.tsx deleted file mode 100644 index 53df2919f..000000000 --- a/frontend/tools/components/tool_slot_row.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import * as React from "react"; -import { Row, Col, FBSelect, DropDownItem } from "../../ui"; -import { Popover, Position } from "@blueprintjs/core"; -import { SlotMenu } from "./toolbay_slot_menu"; -import { TaggedToolSlotPointer } from "farmbot"; -import { destroy } from "../../api/crud"; -import { Xyz } from "../../devices/interfaces"; -import { ToolBayNumberCol } from "./toolbay_number_column"; -import { t } from "../../i18next_wrapper"; - -export interface ToolSlotRowProps { - dispatch: Function; - slot: TaggedToolSlotPointer; - botPosition: Record<"x" | "y" | "z", number | undefined>; - /** List of all legal tool options for the current tool slot. */ - toolOptions: DropDownItem[]; - /** The current tool (if any) in the slot. */ - chosenToolOption: DropDownItem; - /** Broadcast tool change back up to parent. */ - onToolSlotChange(item: DropDownItem): void; - /** Gantry-mounted tool slot. */ - gantryMounted: boolean; -} - -type Axis = Xyz & keyof (TaggedToolSlotPointer["body"]); -const axes: Axis[] = ["x", "y", "z"]; - -export function ToolSlotRow(props: ToolSlotRowProps) { - const { dispatch, slot, botPosition, toolOptions, onToolSlotChange, - chosenToolOption, gantryMounted } = props; - - return - - - - - - - {axes - .map(axis => ({ axis, dispatch, slot, value: (slot.body[axis] || 0) })) - .map(axisProps => (axisProps.axis === "x" && gantryMounted) - ? - - - : )} - - - - - - - ; -} diff --git a/frontend/tools/components/toolbay_form.tsx b/frontend/tools/components/toolbay_form.tsx deleted file mode 100644 index 1ccee1a77..000000000 --- a/frontend/tools/components/toolbay_form.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import * as React from "react"; -import { ToolBayFormProps } from "../interfaces"; -import { - Widget, - WidgetBody, - WidgetHeader, - SaveBtn, -} from "../../ui"; - -import { - getArrayStatus -} from "../../resources/tagged_resources"; -import { saveAll, init } from "../../api/crud"; -import { ToolBayHeader } from "./toolbay_header"; -import { ToolTips } from "../../constants"; -import { ToolSlotRow } from "./tool_slot_row"; -import { emptyToolSlotBody } from "./empty_tool_slot"; -import { TaggedToolSlotPointer } from "farmbot"; -import { t } from "../../i18next_wrapper"; - -export class ToolBayForm extends React.Component { - - HeaderButtons = () => { - const { toggle, dispatch, toolSlots } = this.props; - const toolSlotStatus = getArrayStatus(toolSlots); - return
- - dispatch(saveAll(toolSlots, toggle))} /> - -
; - } - - ToolbayForm = (slot: TaggedToolSlotPointer) => { - const { dispatch, botPosition } = this.props; - return ; - } - - render() { - return
- - - - - - - {this.props.getToolSlots().map(this.ToolbayForm)} - - -
; - } -} diff --git a/frontend/tools/components/toolbay_header.tsx b/frontend/tools/components/toolbay_header.tsx deleted file mode 100644 index f3c9eac33..000000000 --- a/frontend/tools/components/toolbay_header.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from "react"; -import { Col, Row } from "../../ui"; -import { t } from "../../i18next_wrapper"; - -export function ToolBayHeader() { - return - - - - - - - - - - - - - - - - ; -} diff --git a/frontend/tools/components/toolbay_list.tsx b/frontend/tools/components/toolbay_list.tsx deleted file mode 100644 index 76c722556..000000000 --- a/frontend/tools/components/toolbay_list.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as React from "react"; -import { Row, Col, Widget, WidgetBody, WidgetHeader } from "../../ui"; -import { ToolBayListProps } from "../interfaces"; -import { TaggedToolSlotPointer } from "farmbot"; -import { ToolBayHeader } from "./toolbay_header"; -import { ToolTips } from "../../constants"; -import { t } from "../../i18next_wrapper"; - -export class ToolBayList extends React.Component { - - ToolSlotListItem = (slot: TaggedToolSlotPointer, index: number) => { - const { getToolByToolSlotUUID } = this.props; - const tool = getToolByToolSlotUUID(slot.uuid); - const name = (tool?.body.name) || t("None"); - return - - {slot.body.gantry_mounted ? t("Gantry") : slot.body.x} - {slot.body.y} - {slot.body.z} - {name} - ; - } - - render() { - return - - - - - - {this.props.getToolSlots().map(this.ToolSlotListItem)} - - ; - } -} diff --git a/frontend/tools/components/toolbay_number_column.tsx b/frontend/tools/components/toolbay_number_column.tsx deleted file mode 100644 index ae9cdccd8..000000000 --- a/frontend/tools/components/toolbay_number_column.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from "react"; -import { TaggedToolSlotPointer } from "farmbot"; -import { Col, BlurableInput } from "../../ui"; -import { edit } from "../../api/crud"; - -export interface TBNumColProps { - axis: "x" | "y" | "z"; - value: number; - dispatch: Function; - slot: TaggedToolSlotPointer; -} - -/** Used to display and edit the X/Y/Z numeric values in the tool bay form. */ -export function ToolBayNumberCol(props: TBNumColProps) { - const { axis, value, dispatch, slot } = props; - return - - dispatch(edit(slot, { [axis]: parseFloat(e.currentTarget.value) }))} - type="number" /> - ; -} diff --git a/frontend/tools/components/toolbay_slot_direction_selection.tsx b/frontend/tools/components/toolbay_slot_direction_selection.tsx deleted file mode 100644 index c07383527..000000000 --- a/frontend/tools/components/toolbay_slot_direction_selection.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from "react"; -import { FBSelect, DropDownItem } from "../../ui"; -import { TaggedToolSlotPointer } from "farmbot"; -import { edit } from "../../api/crud"; -import { isNumber } from "lodash"; -import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources"; -import { t } from "../../i18next_wrapper"; - -export const DIRECTION_CHOICES_DDI: { [index: number]: DropDownItem } = { - [ToolPulloutDirection.NONE]: - { label: t("None"), value: ToolPulloutDirection.NONE }, - [ToolPulloutDirection.POSITIVE_X]: - { label: t("Positive X"), value: ToolPulloutDirection.POSITIVE_X }, - [ToolPulloutDirection.NEGATIVE_X]: - { label: t("Negative X"), value: ToolPulloutDirection.NEGATIVE_X }, - [ToolPulloutDirection.POSITIVE_Y]: - { label: t("Positive Y"), value: ToolPulloutDirection.POSITIVE_Y }, - [ToolPulloutDirection.NEGATIVE_Y]: - { label: t("Negative Y"), value: ToolPulloutDirection.NEGATIVE_Y }, -}; - -export const DIRECTION_CHOICES: DropDownItem[] = [ - DIRECTION_CHOICES_DDI[ToolPulloutDirection.NONE], - DIRECTION_CHOICES_DDI[ToolPulloutDirection.POSITIVE_X], - DIRECTION_CHOICES_DDI[ToolPulloutDirection.NEGATIVE_X], - DIRECTION_CHOICES_DDI[ToolPulloutDirection.POSITIVE_Y], - DIRECTION_CHOICES_DDI[ToolPulloutDirection.NEGATIVE_Y], -]; - -export interface SlotDirectionSelectProps { - dispatch: Function; - slot: TaggedToolSlotPointer; -} - -export function SlotDirectionSelect(props: SlotDirectionSelectProps) { - const { dispatch, slot } = props; - const direction = slot.body.pullout_direction; - - const changePulloutDirection = (selectedDirection: DropDownItem) => { - const { value } = selectedDirection; - dispatch(edit(slot, { - pullout_direction: isNumber(value) ? value : parseInt(value) - })); - }; - - return ; -} diff --git a/frontend/tools/components/toolbay_slot_menu.tsx b/frontend/tools/components/toolbay_slot_menu.tsx deleted file mode 100644 index b9688890b..000000000 --- a/frontend/tools/components/toolbay_slot_menu.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import * as React from "react"; -import { isNumber } from "lodash"; -import { BotPosition } from "../../devices/interfaces"; -import { TaggedToolSlotPointer } from "farmbot"; -import { edit } from "../../api/crud"; -import { SlotDirectionSelect } from "./toolbay_slot_direction_selection"; -import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources"; -import { t } from "../../i18next_wrapper"; - -export const positionIsDefined = (position: BotPosition): boolean => - isNumber(position.x) && isNumber(position.y) && isNumber(position.z); - -export const useCurrentPosition = ( - dispatch: Function, slot: TaggedToolSlotPointer, position: BotPosition) => { - if (positionIsDefined(position)) { - dispatch(edit(slot, { x: position.x, y: position.y, z: position.z })); - } -}; - -export const positionButtonTitle = (position: BotPosition): string => - positionIsDefined(position) - ? `(${position.x}, ${position.y}, ${position.z})` - : t("(unknown)"); - -export const newSlotDirection = - (old: ToolPulloutDirection | undefined): ToolPulloutDirection => - isNumber(old) && old < 4 ? old + 1 : ToolPulloutDirection.NONE; - -export const changePulloutDirection = - (dispatch: Function, slot: TaggedToolSlotPointer) => () => { - dispatch(edit(slot, - { pullout_direction: newSlotDirection(slot.body.pullout_direction) })); - }; - -export const directionIconClass = (slotDirection: ToolPulloutDirection) => { - switch (slotDirection) { - case ToolPulloutDirection.POSITIVE_X: return "fa fa-arrow-circle-right"; - case ToolPulloutDirection.NEGATIVE_X: return "fa fa-arrow-circle-left"; - case ToolPulloutDirection.POSITIVE_Y: return "fa fa-arrow-circle-up"; - case ToolPulloutDirection.NEGATIVE_Y: return "fa fa-arrow-circle-down"; - case ToolPulloutDirection.NONE: return "fa fa-dot-circle-o"; - } -}; - -export interface SlotMenuProps { - dispatch: Function, - slot: TaggedToolSlotPointer, - botPosition: BotPosition -} - -export const SlotMenu = (props: SlotMenuProps) => { - const { dispatch, slot, botPosition } = props; - const { pullout_direction, gantry_mounted } = slot.body; - return
-
- - - -
-
- - -

{positionButtonTitle(botPosition)}

-
-
- - - dispatch(edit(slot, { gantry_mounted: !gantry_mounted }))} - checked={gantry_mounted} /> -
-
; -}; diff --git a/frontend/tools/index.tsx b/frontend/tools/index.tsx deleted file mode 100644 index ebaa70ef4..000000000 --- a/frontend/tools/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { ToolsState, Props } from "./interfaces"; -import { Col, Row, Page } from "../ui"; -import { ToolBayList, ToolBayForm, ToolList, ToolForm } from "./components"; -import { mapStateToProps } from "./state_to_props"; - -export class RawTools extends React.Component> { - state: ToolsState = { editingBays: false, editingTools: false }; - - toggle = (name: keyof ToolsState) => - () => this.setState({ [name]: !this.state[name] }); - - render() { - return - - - {this.state.editingBays - ? - : } - - - {this.state.editingTools - ? - : } - - - ; - } -} - -export const Tools = connect(mapStateToProps)(RawTools); diff --git a/frontend/tools/interfaces.ts b/frontend/tools/interfaces.ts deleted file mode 100644 index eed35a9ef..000000000 --- a/frontend/tools/interfaces.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { DropDownItem } from "../ui/index"; -import { - TaggedTool, - TaggedToolSlotPointer, -} from "farmbot"; -import { BotPosition } from "../devices/interfaces"; - -export interface ToolsState { - editingTools: boolean; - editingBays: boolean; -} - -export interface Props { - toolSlots: TaggedToolSlotPointer[]; - tools: TaggedTool[]; - getToolOptions(): DropDownItem[]; - getChosenToolOption(toolSlotUuid: string | undefined): DropDownItem; - getToolByToolSlotUUID(uuid: string): TaggedTool | undefined; - getToolSlots(): TaggedToolSlotPointer[]; - dispatch: Function; - isActive: (tool: TaggedTool) => boolean; - changeToolSlot(t: TaggedToolSlotPointer, dispatch: Function): - (d: DropDownItem) => void; - botPosition: BotPosition; -} - -export interface Tool { - id?: number | undefined; - name?: string; - status?: string | undefined; -} - -export interface ToolBayListProps { - toggle(): void; - getToolByToolSlotUUID(uuid: string): TaggedTool | undefined; - getToolSlots(): TaggedToolSlotPointer[]; -} - -export interface ToolBayFormProps { - dispatch: Function; - toolSlots: TaggedToolSlotPointer[]; - botPosition: BotPosition; - toggle(): void; - getToolOptions(): DropDownItem[]; - getChosenToolOption(uuid: string | undefined): DropDownItem; - getToolSlots(): TaggedToolSlotPointer[]; - changeToolSlot(t: TaggedToolSlotPointer, dispatch: Function): - (d: DropDownItem) => void; -} - -export interface ToolListAndFormProps { - dispatch: Function; - tools: TaggedTool[]; - toggle(): void; - isActive(tool: TaggedTool): boolean; -} diff --git a/frontend/tools/state_to_props.ts b/frontend/tools/state_to_props.ts deleted file mode 100644 index c421b42e7..000000000 --- a/frontend/tools/state_to_props.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Everything } from "../interfaces"; -import { Props } from "./interfaces"; -import { - selectAllToolSlotPointers, - selectAllTools, - currentToolInSlot, -} from "../resources/selectors"; -import { isTaggedTool } from "../resources/tagged_resources"; -import { edit } from "../api/crud"; -import { DropDownItem, NULL_CHOICE } from "../ui"; -import { validBotLocationData } from "../util"; -import { TaggedTool, TaggedToolSlotPointer } from "farmbot"; -import { isNumber, noop, compact } from "lodash"; - -export function mapStateToProps(props: Everything): Props { - const toolSlots = selectAllToolSlotPointers(props.resources.index); - const tools = selectAllTools(props.resources.index); - - /** Returns sorted tool slots specific to the tool bay id passed. */ - const getToolSlots = () => toolSlots; - - /** Returns all tools in an compatible format. */ - const getToolOptions = () => { - return compact(tools - .map(tool => ({ - label: tool.body.name || "untitled", - value: tool.body.id || 0, - })) - .filter(ddi => isNumber(ddi.value) && ddi.value > 0)); - }; - - const activeTools = compact(toolSlots.map(x => x.body.tool_id)); - - const isActive = - (t: TaggedTool) => !!(t.body.id && activeTools.includes(t.body.id)); - - const getToolByToolSlotUUID = currentToolInSlot(props.resources.index); - - /** Returns the current tool chosen in a slot based off the slot's id - * and in an compatible format. */ - const getChosenToolOption = (toolSlotUUID: string | undefined) => { - const chosenTool = toolSlotUUID && getToolByToolSlotUUID(toolSlotUUID); - return (chosenTool && isTaggedTool(chosenTool) && chosenTool.body.id) - ? { label: chosenTool.body.name || "untitled", value: chosenTool.uuid } - : NULL_CHOICE; - }; - - const changeToolSlot = (t: TaggedToolSlotPointer, - dispatch: Function) => - (d: DropDownItem) => { - // THIS IS IMPORTANT: - // If you remove the `any`, the tool will be serialized wrong and - // cause errors. - // tslint:disable-next-line:no-null-keyword no-any - const tool_id = d.value ? d.value : (null as any); - dispatch(edit(t, { tool_id })); - }; - - const botPosition = - validBotLocationData(props.bot.hardware.location_data).position; - - return { - toolSlots, - tools, - getToolSlots, - getToolOptions, - getChosenToolOption, - getToolByToolSlotUUID, - changeToolSlot, - isActive, - dispatch: noop, - botPosition, - }; - -} From edb96d3ca8d93daf1f94ac85666d734688fe6da7 Mon Sep 17 00:00:00 2001 From: gabrielburnworth Date: Wed, 26 Feb 2020 12:08:49 -0800 Subject: [PATCH 8/9] cleanup and refactoring --- .../components/farmbot_os_settings.tsx | 10 ++-- .../__tests__/auto_update_row_test.tsx | 10 ++-- .../fbos_settings/auto_update_row.tsx | 50 ++++++++----------- .../components/fbos_settings/interfaces.ts | 8 ++- .../fbos_settings/ota_time_selector.tsx | 13 ++++- .../__tests__/panel_header_test.tsx | 8 +++ .../__tests__/tool_slot_layer_test.tsx | 15 ------ .../__tests__/tool_slot_point_test.tsx | 13 ----- .../map/layers/tool_slots/tool_slot_layer.tsx | 10 +--- .../map/layers/tool_slots/tool_slot_point.tsx | 4 +- frontend/farm_designer/panel_header.tsx | 9 ++-- .../plants/edit_plant_status.tsx | 41 ++++++--------- .../criteria/__tests__/add_test.tsx | 2 +- .../point_groups/criteria/add.tsx | 16 ++---- .../point_groups/criteria/show.tsx | 3 +- frontend/help/__tests__/tours_test.ts | 18 ------- frontend/help/tours.ts | 13 +---- frontend/nav/__tests__/nav_links_test.tsx | 6 --- frontend/nav/nav_links.tsx | 3 -- 19 files changed, 85 insertions(+), 167 deletions(-) diff --git a/frontend/devices/components/farmbot_os_settings.tsx b/frontend/devices/components/farmbot_os_settings.tsx index 1498f5868..6b6dbf5f7 100644 --- a/frontend/devices/components/farmbot_os_settings.tsx +++ b/frontend/devices/components/farmbot_os_settings.tsx @@ -17,6 +17,7 @@ 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"; +import { OtaTimeSelectorRow } from "./fbos_settings/ota_time_selector"; export enum ColWidth { label = 3, @@ -78,8 +79,6 @@ export class FarmbotOsSettings const { bot, sourceFbosConfig, botToMqttStatus } = this.props; const { sync_status } = bot.hardware.informational_settings; const botOnline = isBotOnline(sync_status, botToMqttStatus); - const timeFormat = this.props.webAppConfig.body.time_format_24_hour ? - "24h" : "12h"; return
e.preventDefault()}> @@ -133,11 +132,14 @@ export class FarmbotOsSettings shouldDisplay={this.props.shouldDisplay} timeSettings={this.props.timeSettings} sourceFbosConfig={sourceFbosConfig} /> - + ", () => { @@ -20,10 +20,8 @@ describe("", () => { state.resources = buildResourceIndex([fakeConfig]); const fakeProps = (): AutoUpdateRowProps => ({ - timeFormat: "12h", - device: fakeDevice(), dispatch: jest.fn(x => x(jest.fn(), () => state)), - sourceFbosConfig: () => ({ value: 1, consistent: true }) + sourceFbosConfig: () => ({ value: 1, consistent: true }), }); it("renders", () => { @@ -35,7 +33,7 @@ describe("", () => { const p = fakeProps(); p.sourceFbosConfig = () => ({ value: 0, consistent: true }); const wrapper = mount(); - wrapper.find("button").at(1).simulate("click"); + wrapper.find("button").first().simulate("click"); expect(edit).toHaveBeenCalledWith(fakeConfig, { os_auto_update: true }); expect(save).toHaveBeenCalledWith(fakeConfig.uuid); }); @@ -44,7 +42,7 @@ describe("", () => { const p = fakeProps(); p.sourceFbosConfig = () => ({ value: 1, consistent: true }); const wrapper = mount(); - wrapper.find("button").at(1).simulate("click"); + wrapper.find("button").first().simulate("click"); expect(edit).toHaveBeenCalledWith(fakeConfig, { os_auto_update: false }); expect(save).toHaveBeenCalledWith(fakeConfig.uuid); }); diff --git a/frontend/devices/components/fbos_settings/auto_update_row.tsx b/frontend/devices/components/fbos_settings/auto_update_row.tsx index 2540daecf..173ed5ddf 100644 --- a/frontend/devices/components/fbos_settings/auto_update_row.tsx +++ b/frontend/devices/components/fbos_settings/auto_update_row.tsx @@ -6,38 +6,30 @@ import { updateConfig } from "../../actions"; 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"); - return
- - - - - - - -

- {t(Content.OS_AUTO_UPDATE)} -

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

+ {t(Content.OS_AUTO_UPDATE)} +

+ + + props.dispatch(updateConfig({ + os_auto_update: !osAutoUpdate.value + }))} /> + +
+
; } diff --git a/frontend/devices/components/fbos_settings/interfaces.ts b/frontend/devices/components/fbos_settings/interfaces.ts index c9549e389..1056ba10f 100644 --- a/frontend/devices/components/fbos_settings/interfaces.ts +++ b/frontend/devices/components/fbos_settings/interfaces.ts @@ -12,7 +12,6 @@ import { TaggedDevice, } from "farmbot"; import { TimeSettings } from "../../../interfaces"; -import { PreferredHourFormat } from "./ota_time_selector"; export interface AutoSyncRowProps { dispatch: Function; @@ -21,9 +20,14 @@ export interface AutoSyncRowProps { export interface AutoUpdateRowProps { dispatch: Function; - timeFormat: PreferredHourFormat; + sourceFbosConfig: SourceFbosConfig; +} + +export interface OtaTimeSelectorRowProps { + dispatch: Function; sourceFbosConfig: SourceFbosConfig; device: TaggedDevice; + timeSettings: TimeSettings; } export interface CameraSelectionProps { diff --git a/frontend/devices/components/fbos_settings/ota_time_selector.tsx b/frontend/devices/components/fbos_settings/ota_time_selector.tsx index 182ca4eb6..425579545 100644 --- a/frontend/devices/components/fbos_settings/ota_time_selector.tsx +++ b/frontend/devices/components/fbos_settings/ota_time_selector.tsx @@ -6,11 +6,12 @@ import { edit, save } from "../../../api/crud"; import { ColWidth } from "../farmbot_os_settings"; import { DeviceSetting } from "../../../constants"; import { Highlight } from "../maybe_highlight"; +import { OtaTimeSelectorRowProps } from "./interfaces"; // tslint:disable-next-line:no-null-keyword const UNDEFINED = null as unknown as undefined; const IMMEDIATELY = -1; -export type PreferredHourFormat = "12h" | "24h"; +type PreferredHourFormat = "12h" | "24h"; type HOUR = | typeof IMMEDIATELY | 0 @@ -163,3 +164,13 @@ export const OtaTimeSelector = (props: OtaTimeSelectorProps): JSX.Element => { ; }; + +export function OtaTimeSelectorRow(props: OtaTimeSelectorRowProps) { + const osAutoUpdate = props.sourceFbosConfig("os_auto_update"); + const timeFormat = props.timeSettings.hour24 ? "24h" : "12h"; + return ; +} diff --git a/frontend/farm_designer/__tests__/panel_header_test.tsx b/frontend/farm_designer/__tests__/panel_header_test.tsx index 546976771..5df39adab 100644 --- a/frontend/farm_designer/__tests__/panel_header_test.tsx +++ b/frontend/farm_designer/__tests__/panel_header_test.tsx @@ -71,6 +71,14 @@ describe("", () => { expect(wrapper.html()).toContain("active"); }); + it("renders for tools", () => { + mockPath = "/app/designer/tools"; + mockDev = false; + const wrapper = shallow(); + expect(wrapper.hasClass("gray-panel")).toBeTruthy(); + expect(wrapper.html()).toContain("active"); + }); + it("renders for zones", () => { mockPath = "/app/designer/zones"; mockDev = true; diff --git a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_layer_test.tsx b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_layer_test.tsx index 5788588ad..f9aa07f4f 100644 --- a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_layer_test.tsx +++ b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_layer_test.tsx @@ -4,11 +4,6 @@ jest.mock("../../../../../history", () => ({ getPathArray: jest.fn(() => { return mockPath.split("/"); }) })); -let mockDev = false; -jest.mock("../../../../../account/dev/dev_support", () => ({ - DevSettings: { futureFeaturesEnabled: () => mockDev } -})); - import * as React from "react"; import { ToolSlotLayer, ToolSlotLayerProps } from "../tool_slot_layer"; import { @@ -57,16 +52,6 @@ describe("", () => { expect(result.find(ToolSlotPoint).length).toEqual(1); }); - it("navigates to tools page", async () => { - mockDev = true; - mockPath = "/app/designer/plants"; - const p = fakeProps(); - const wrapper = shallow(); - const tools = wrapper.find("g").first(); - await tools.simulate("click"); - expect(history.push).toHaveBeenCalledWith("/app/tools"); - }); - it("doesn't navigate to tools page", async () => { mockPath = "/app/designer/plants/1"; const p = fakeProps(); 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 a53c1508f..1aa96fc12 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 @@ -1,8 +1,3 @@ -let mockDev = false; -jest.mock("../../../../../account/dev/dev_support", () => ({ - DevSettings: { futureFeaturesEnabled: () => mockDev } -})); - jest.mock("../../../../../history", () => ({ history: { push: jest.fn() } })); import * as React from "react"; @@ -17,10 +12,6 @@ import { svgMount } from "../../../../../__test_support__/svg_mount"; import { history } from "../../../../../history"; describe("", () => { - beforeEach(() => { - mockDev = false; - }); - const fakeProps = (): TSPProps => ({ mapTransformProps: fakeMapTransformProps(), botPositionX: undefined, @@ -48,10 +39,6 @@ describe("", () => { const p = fakeProps(); p.slot.toolSlot.body.id = 1; const wrapper = svgMount(); - mockDev = true; - wrapper.find("g").first().simulate("click"); - expect(history.push).not.toHaveBeenCalled(); - mockDev = false; wrapper.find("g").first().simulate("click"); expect(history.push).toHaveBeenCalledWith("/app/designer/tool-slots/1"); }); diff --git a/frontend/farm_designer/map/layers/tool_slots/tool_slot_layer.tsx b/frontend/farm_designer/map/layers/tool_slots/tool_slot_layer.tsx index d03dcef25..cf13ff88a 100644 --- a/frontend/farm_designer/map/layers/tool_slots/tool_slot_layer.tsx +++ b/frontend/farm_designer/map/layers/tool_slots/tool_slot_layer.tsx @@ -2,9 +2,7 @@ import * as React from "react"; import { SlotWithTool, UUID } from "../../../../resources/interfaces"; import { ToolSlotPoint } from "./tool_slot_point"; import { MapTransformProps } from "../../interfaces"; -import { history, getPathArray } from "../../../../history"; import { maybeNoPointer } from "../../util"; -import { DevSettings } from "../../../../account/dev/dev_support"; export interface ToolSlotLayerProps { visible: boolean; @@ -16,17 +14,11 @@ export interface ToolSlotLayerProps { } export function ToolSlotLayer(props: ToolSlotLayerProps) { - const pathArray = getPathArray(); - const canClickTool = !(pathArray[3] === "plants" && pathArray.length > 4); - const goToToolsPage = () => canClickTool && - DevSettings.futureFeaturesEnabled() && history.push("/app/tools"); const { slots, visible, mapTransformProps } = props; - const cursor = canClickTool ? "pointer" : "default"; return + style={maybeNoPointer({ cursor: "pointer" })}> {visible && slots.map(slot => { xySwap, }; return !DevSettings.futureFeaturesEnabled() && - history.push(`/app/designer/tool-slots/${id}`)}> + onClick={() => history.push(`/app/designer/tool-slots/${id}`)}> {pullout_direction && !gantry_mounted && - {!DevSettings.futureFeaturesEnabled() && - } + ({ + planned: { label: t("Planned"), value: "planned" }, + planted: { label: t("Planted"), value: "planted" }, + sprouted: { label: t("Sprouted"), value: "sprouted" }, + harvested: { label: t("Harvested"), value: "harvested" }, +}); +export const PLANT_STAGE_LIST = () => [ + PLANT_STAGE_DDI_LOOKUP().planned, + PLANT_STAGE_DDI_LOOKUP().planted, + PLANT_STAGE_DDI_LOOKUP().sprouted, + PLANT_STAGE_DDI_LOOKUP().harvested, ]; -const PLANT_STAGES_DDI = { - [PLANT_STAGES[0].value]: { - label: PLANT_STAGES[0].label, - value: PLANT_STAGES[0].value - }, - [PLANT_STAGES[1].value]: { - label: PLANT_STAGES[1].label, - value: PLANT_STAGES[1].value - }, - [PLANT_STAGES[2].value]: { - label: PLANT_STAGES[2].label, - value: PLANT_STAGES[2].value - }, - [PLANT_STAGES[3].value]: { - label: PLANT_STAGES[3].label, - value: PLANT_STAGES[3].value - }, -}; - /** Change `planted_at` value based on `plant_stage` update. */ const getUpdateByPlantStage = (plant_stage: PlantStage): PlantOptions => { const update: PlantOptions = { plant_stage }; @@ -52,8 +39,8 @@ const getUpdateByPlantStage = (plant_stage: PlantStage): PlantOptions => { export function EditPlantStatus(props: EditPlantStatusProps) { const { plantStatus, updatePlant, uuid } = props; return updatePlant(uuid, getUpdateByPlantStage(ddi.value as PlantStage))} />; } @@ -70,7 +57,7 @@ export const PlantStatusBulkUpdate = (props: PlantStatusBulkUpdateProps) =>

{t("update plant status to")}

{ diff --git a/frontend/farm_designer/point_groups/criteria/__tests__/add_test.tsx b/frontend/farm_designer/point_groups/criteria/__tests__/add_test.tsx index f1e170901..b5cd40b6f 100644 --- a/frontend/farm_designer/point_groups/criteria/__tests__/add_test.tsx +++ b/frontend/farm_designer/point_groups/criteria/__tests__/add_test.tsx @@ -9,7 +9,6 @@ import { AddEqCriteria, AddNumberCriteria, editCriteria, AddStringCriteria, toggleStringCriteria, POINTER_TYPE_LIST, - PLANT_STAGE_LIST } from ".."; import { AddEqCriteriaProps, NumberCriteriaProps, DEFAULT_CRITERIA, @@ -19,6 +18,7 @@ import { fakePointGroup } from "../../../../__test_support__/fake_state/resources"; import { PointGroup } from "farmbot/dist/resources/api_resources"; +import { PLANT_STAGE_LIST } from "../../../plants/edit_plant_status"; describe(" />", () => { const fakeProps = (): AddEqCriteriaProps => ({ diff --git a/frontend/farm_designer/point_groups/criteria/add.tsx b/frontend/farm_designer/point_groups/criteria/add.tsx index a24826756..5081181a8 100644 --- a/frontend/farm_designer/point_groups/criteria/add.tsx +++ b/frontend/farm_designer/point_groups/criteria/add.tsx @@ -10,6 +10,9 @@ import { AddNumberCriteriaState, AddStringCriteriaProps, } from "./interfaces"; +import { + PLANT_STAGE_DDI_LOOKUP, PLANT_STAGE_LIST +} from "../../plants/edit_plant_status"; export class AddEqCriteria extends React.Component, AddEqCriteriaState> { @@ -78,19 +81,6 @@ export const POINTER_TYPE_LIST = () => [ POINTER_TYPE_DDI_LOOKUP().ToolSlot, ]; -export const PLANT_STAGE_DDI_LOOKUP = (): { [x: string]: DropDownItem } => ({ - planned: { label: t("Planned"), value: "planned" }, - planted: { label: t("Planted"), value: "planted" }, - sprouted: { label: t("Sprouted"), value: "sprouted" }, - harvested: { label: t("Harvested"), value: "harvested" }, -}); -export const PLANT_STAGE_LIST = () => [ - PLANT_STAGE_DDI_LOOKUP().planned, - PLANT_STAGE_DDI_LOOKUP().planted, - PLANT_STAGE_DDI_LOOKUP().sprouted, - PLANT_STAGE_DDI_LOOKUP().harvested, -]; - export class AddStringCriteria extends React.Component { state: AddEqCriteriaState = { key: "", value: "" }; diff --git a/frontend/farm_designer/point_groups/criteria/show.tsx b/frontend/farm_designer/point_groups/criteria/show.tsx index ad79f2326..22f301e8a 100644 --- a/frontend/farm_designer/point_groups/criteria/show.tsx +++ b/frontend/farm_designer/point_groups/criteria/show.tsx @@ -3,7 +3,7 @@ import { cloneDeep, capitalize } from "lodash"; import { Row, Col, FBSelect, DropDownItem } from "../../../ui"; import { AddEqCriteria, toggleEqCriteria, editCriteria, AddNumberCriteria, - POINTER_TYPE_DDI_LOOKUP, PLANT_STAGE_DDI_LOOKUP, AddStringCriteria, + POINTER_TYPE_DDI_LOOKUP, AddStringCriteria, CRITERIA_TYPE_DDI_LOOKUP, toggleStringCriteria } from "."; import { @@ -14,6 +14,7 @@ import { } from "./interfaces"; import { t } from "../../../i18next_wrapper"; import { PointGroup } from "farmbot/dist/resources/api_resources"; +import { PLANT_STAGE_DDI_LOOKUP } from "../../plants/edit_plant_status"; export class EqCriteriaSelection extends React.Component> { diff --git a/frontend/help/__tests__/tours_test.ts b/frontend/help/__tests__/tours_test.ts index ef6dd92db..de9ee790f 100644 --- a/frontend/help/__tests__/tours_test.ts +++ b/frontend/help/__tests__/tours_test.ts @@ -1,12 +1,5 @@ jest.mock("../../history", () => ({ history: { push: jest.fn() } })); -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", () => ({ @@ -46,7 +39,6 @@ describe("tourPageNavigation()", () => { 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()]); @@ -56,7 +48,6 @@ describe("tourPageNavigation()", () => { 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 slots"); expect(getTitles()).not.toContain("Add seed containers"); @@ -69,13 +60,4 @@ describe("tourPageNavigation()", () => { 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/tours.ts b/frontend/help/tours.ts index 505f9a626..6d27b3b7b 100644 --- a/frontend/help/tours.ts +++ b/frontend/help/tours.ts @@ -2,7 +2,6 @@ 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"; @@ -64,16 +63,8 @@ export const TOUR_STEPS = (): { [x: string]: TourStep[] } => ({ content: t(TourContent.ADD_PLANTS), title: t("Add plants"), }, - ...(DevSettings.futureFeaturesEnabled() ? [{ - target: ".tool-list", - content: t(TourContent.ADD_TOOLS), - title: t("Add tools and seed containers"), - }] : toolsStep()), - ...(DevSettings.futureFeaturesEnabled() ? [{ - target: ".toolbay-list", - content: t(TourContent.ADD_TOOLS_SLOTS), - title: t("Add tools to tool bay"), - }] : toolSlotsStep()), + ...toolsStep(), + ...toolSlotsStep(), { target: ".peripherals-widget", content: t(TourContent.ADD_PERIPHERALS), diff --git a/frontend/nav/__tests__/nav_links_test.tsx b/frontend/nav/__tests__/nav_links_test.tsx index 3550df51c..db3682ccb 100644 --- a/frontend/nav/__tests__/nav_links_test.tsx +++ b/frontend/nav/__tests__/nav_links_test.tsx @@ -3,11 +3,6 @@ jest.mock("../../history", () => ({ getPathArray: jest.fn(() => mockPath.split("/")), })); -let mockDev = false; -jest.mock("../../account/dev/dev_support", () => ({ - DevSettings: { futureFeaturesEnabled: () => mockDev } -})); - import * as React from "react"; import { shallow, mount } from "enzyme"; import { NavLinks } from "../nav_links"; @@ -28,7 +23,6 @@ describe("", () => { }); it("shows links", () => { - mockDev = false; const wrapper = mount(); expect(wrapper.text().toLowerCase()).not.toContain("tools"); }); diff --git a/frontend/nav/nav_links.tsx b/frontend/nav/nav_links.tsx index e4d7bc76c..a0f0a6fb4 100644 --- a/frontend/nav/nav_links.tsx +++ b/frontend/nav/nav_links.tsx @@ -7,7 +7,6 @@ import { import { Link } from "../link"; import { t } from "../i18next_wrapper"; import { betterCompact } from "../util"; -import { DevSettings } from "../account/dev/dev_support"; /** Uses a slug and a child path to compute the `href` of a navbar link. */ export type LinkComputeFn = (slug: string, childPath: string) => string; @@ -37,8 +36,6 @@ export const getLinks = (): NavLinkParams[] => betterCompact([ name: "Regimens", icon: "calendar-check-o", slug: "regimens", computeHref: computeEditorUrlFromState("Regimen") }, - !DevSettings.futureFeaturesEnabled() ? undefined : - { name: "Tools", icon: "wrench", slug: "tools" }, { name: "Farmware", icon: "crosshairs", slug: "farmware", computeHref: computeFarmwareUrlFromState From c2308cb987eabf21199cf7baa9c05443d3f064d8 Mon Sep 17 00:00:00 2001 From: MarcRoland <56369838+MarcRoland@users.noreply.github.com> Date: Thu, 27 Feb 2020 11:52:14 -0800 Subject: [PATCH 9/9] Update de.json --- public/app-resources/languages/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app-resources/languages/de.json b/public/app-resources/languages/de.json index 474663aa8..8eeb1d5dc 100644 --- a/public/app-resources/languages/de.json +++ b/public/app-resources/languages/de.json @@ -536,7 +536,7 @@ "Can't execute unsaved sequences": "Can't execute unsaved sequences", "Cannot change from a Regimen to a Sequence.": "Cannot change from a Regimen to a Sequence.", "Cannot delete built-in pin binding.": "Cannot delete built-in pin binding.", - "clear filters": "clear filters", + "clear filters": "Filter löschen", "Click a spot in the grid to choose a location. Once selected, press button to move FarmBot to this position. Press the back arrow to exit.": "Click a spot in the grid to choose a location. Once selected, press button to move FarmBot to this position. Press the back arrow to exit.", "Click and drag to draw a point or use the inputs and press update. Press CREATE POINT to save, or the back arrow to exit.": "Click and drag to draw a point or use the inputs and press update. Press CREATE POINT to save, or the back arrow to exit.", "Click one in the Regimens panel to edit, or click \"+\" to create a new one.": "Click one in the Regimens panel to edit, or click \"+\" to create a new one.",