diff --git a/db/migrate/20200204230135_add_show_zones_to_web_app_config.rb b/db/migrate/20200204230135_add_show_zones_to_web_app_config.rb new file mode 100644 index 000000000..f4a23279b --- /dev/null +++ b/db/migrate/20200204230135_add_show_zones_to_web_app_config.rb @@ -0,0 +1,8 @@ +class AddShowZonesToWebAppConfig < ActiveRecord::Migration[6.0] + def change + add_column :web_app_configs, + :show_zones, + :boolean, + default: false + end +end diff --git a/frontend/__test_support__/additional_mocks.ts b/frontend/__test_support__/additional_mocks.tsx similarity index 73% rename from frontend/__test_support__/additional_mocks.ts rename to frontend/__test_support__/additional_mocks.tsx index 6d80eed8a..6b2b3046d 100644 --- a/frontend/__test_support__/additional_mocks.ts +++ b/frontend/__test_support__/additional_mocks.tsx @@ -1,3 +1,5 @@ +import * as React from "react"; + jest.mock("browser-speech", () => ({ talk: jest.fn(), })); @@ -16,3 +18,8 @@ window.location = { pathname: "", href: "", hash: "", search: "", hostname: "", origin: "", port: "", protocol: "", host: "", }; + +jest.mock("../error_boundary", () => ({ + // tslint:disable-next-line:no-any + ErrorBoundary: (p: any) =>
{p.children}
, +})); diff --git a/frontend/__test_support__/fake_html_events.ts b/frontend/__test_support__/fake_html_events.ts new file mode 100644 index 000000000..e0aecfe1e --- /dev/null +++ b/frontend/__test_support__/fake_html_events.ts @@ -0,0 +1,36 @@ +import { DeepPartial } from "redux"; + +type DomEvent = React.SyntheticEvent; +export const inputEvent = (value: string, name?: string): DomEvent => { + const event: DeepPartial = { currentTarget: { value, name } }; + return event as DomEvent; +}; + +type ChangeEvent = React.ChangeEvent; +export const changeEvent = (value: string): ChangeEvent => { + const event: DeepPartial = { currentTarget: { value } }; + return event as ChangeEvent; +}; + +type IMGEvent = React.SyntheticEvent; +export const imgEvent = (): IMGEvent => { + const event: DeepPartial = { + currentTarget: { + getAttribute: jest.fn(), + setAttribute: jest.fn(), + } + }; + return event as IMGEvent; +}; + +type FormEvent = React.FormEvent; +export const formEvent = (): FormEvent => { + const event: Partial = { preventDefault: jest.fn() }; + return event as FormEvent; +}; + +type DragEvent = React.DragEvent; +export const dragEvent = (key: string): DragEvent => { + const event: DeepPartial = { dataTransfer: { getData: () => key } }; + return event as DragEvent; +}; diff --git a/frontend/__test_support__/fake_input_event.ts b/frontend/__test_support__/fake_input_event.ts deleted file mode 100644 index bd742a7ca..000000000 --- a/frontend/__test_support__/fake_input_event.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { DeepPartial } from "redux"; - -type DomEvent = React.SyntheticEvent; -export const inputEvent = (value: string): DomEvent => { - const event: DeepPartial = { currentTarget: { value } }; - return event as DomEvent; -}; diff --git a/frontend/__test_support__/fake_state/resources.ts b/frontend/__test_support__/fake_state/resources.ts index 9a545bee4..641cba1bb 100644 --- a/frontend/__test_support__/fake_state/resources.ts +++ b/frontend/__test_support__/fake_state/resources.ts @@ -316,6 +316,7 @@ export function fakeWebAppConfig(): TaggedWebAppConfig { show_historic_points: false, time_format_24_hour: false, show_pins: false, + show_zones: false, disable_emergency_unlock_confirmation: false, map_size_x: 2900, map_size_y: 1400, @@ -459,7 +460,7 @@ export function fakePointGroup(): TaggedPointGroup { sort_type: "xy_ascending", point_ids: [], criteria: { - day: { op: ">", days: 0 }, + day: { op: "<", days: 0 }, number_eq: {}, number_gt: {}, number_lt: {}, diff --git a/frontend/__test_support__/unmock_i18next.ts b/frontend/__test_support__/mock_i18next.ts similarity index 100% rename from frontend/__test_support__/unmock_i18next.ts rename to frontend/__test_support__/mock_i18next.ts diff --git a/frontend/__tests__/error_boundary_test.tsx b/frontend/__tests__/error_boundary_test.tsx index 224731b7e..6de0190cd 100644 --- a/frontend/__tests__/error_boundary_test.tsx +++ b/frontend/__tests__/error_boundary_test.tsx @@ -1,3 +1,5 @@ +jest.unmock("../error_boundary"); + jest.mock("../util/errors.ts", () => ({ catchErrors: jest.fn() })); import * as React from "react"; diff --git a/frontend/connectivity/__tests__/ping_mqtt_test.ts b/frontend/connectivity/__tests__/ping_mqtt_test.ts index 9e0de13a1..ea00a23cb 100644 --- a/frontend/connectivity/__tests__/ping_mqtt_test.ts +++ b/frontend/connectivity/__tests__/ping_mqtt_test.ts @@ -4,8 +4,6 @@ jest.mock("../index", () => ({ dispatchQosStart: jest.fn(), pingOK: jest.fn() })); -const mockTimestamp = 0; -jest.mock("../../util", () => ({ timestamp: () => mockTimestamp })); import { readPing, diff --git a/frontend/constants.ts b/frontend/constants.ts index 8617b8829..71388071f 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -572,12 +572,6 @@ export namespace Content { export const CONFIRM_PLANT_DELETION = trim(`Show a confirmation dialog when deleting a plant.`); - export const SORT_DESCRIPTION = - trim(`When executing a sequence over a Group of locations, FarmBot will - travel to each group member in the order of the chosen sort method. - If the random option is chosen, FarmBot will travel in a random order - every time, so the ordering shown below will only be representative.`); - // Device export const NOT_HTTPS = trim(`WARNING: Sending passwords via HTTP:// is not secure.`); @@ -824,6 +818,20 @@ export namespace Content { trim(`You haven't made any sequences or regimens yet. To add an event, first create a sequence or regimen.`); + // Groups + export const SORT_DESCRIPTION = + trim(`When executing a sequence over a Group of locations, FarmBot will + travel to each group member in the order of the chosen sort method. + If the random option is chosen, FarmBot will travel in a random order + every time, so the ordering shown below will only be representative.`); + + export const CRITERIA_SELECTION_COUNT = + trim(`Criteria additions can only be removed by changing criteria. + Click and drag in the map to modify zone selection criteria. + Criteria will be applied at the time of sequence execution. The final + selection at that time may differ from the selection currently + displayed.`); + // Farmware export const NO_IMAGES_YET = trim(`You haven't yet taken any photos with your FarmBot. @@ -912,12 +920,12 @@ export namespace DiagnosticMessages { network, a firewall may be blocking port 5672. Ensure that the blue LED communications light on the FarmBot electronics box is illuminated.`); - export const WIFI_OR_CONFIG = trim(`Your browser is connected correctly, but - we have no recent record of FarmBot connecting to the internet. This usually - happens because of poor WiFi connectivity in the garden, a bad password during - configuration, a very long power outage, or blocked ports on FarmBot's local - network. Please refer IT staff to - https://software.farm.bot/docs/for-it-security-professionals`); + export const WIFI_OR_CONFIG = trim(`Your browser is connected correctly, + but we have no recent record of FarmBot connecting to the internet. + This usually happens because of poor WiFi connectivity in the garden, + a bad password during configuration, a very long power outage, or + blocked ports on FarmBot's local network. Please refer IT staff to + https://software.farm.bot/docs/for-it-security-professionals`); export const NO_WS_AVAILABLE = trim(`You are either offline, using a web browser that does not support WebSockets, or are behind a firewall that diff --git a/frontend/css/farm_designer/farm_designer.scss b/frontend/css/farm_designer/farm_designer.scss index 4a6529760..74d11b8af 100644 --- a/frontend/css/farm_designer/farm_designer.scss +++ b/frontend/css/farm_designer/farm_designer.scss @@ -213,7 +213,7 @@ .groups-list-wrapper { padding: 0.5em 0em; } - .groups-delete-btn { + .group-delete-btn { float: left; margin-top: 1em; } @@ -332,6 +332,21 @@ } } +.zones-layer { + [id*="zones-1D-"] { + stroke: $black; + stroke-width: 5; + } + [id*="zones-"] { + opacity: 0.1; + &.current { + opacity: 0.25; + fill: $white; + stroke: $white; + } + } +} + .virtual-bot-trail, .virtual-peripherals { pointer-events: none; diff --git a/frontend/css/farm_designer/farm_designer_panels.scss b/frontend/css/farm_designer/farm_designer_panels.scss index f00163816..a1867cc96 100644 --- a/frontend/css/farm_designer/farm_designer_panels.scss +++ b/frontend/css/farm_designer/farm_designer_panels.scss @@ -738,6 +738,114 @@ } } +.group-detail-panel { + .panel-content { + .group-criteria { + margin-top: 1rem; + .criteria-heading { + margin-top: 0; + } + .fb-button { + margin-top: 0.5rem; + } + .group-criteria-presets { + input[type="radio"] { + width: auto; + margin-right: 1rem; + } + p { + display: inline; + text-transform: uppercase; + } + } + .criteria-string, + .criteria-pointer-type, + .criteria-plant-status, + .criteria-slug { + margin-top: 1rem; + } + .location-criteria { + .row { + margin-top: 1rem; + p { + font-size: 1.4rem; + font-weight: bold; + } + label { + margin-top: 0; + } + } + } + .day-criteria { + p { + display: inline; + vertical-align: bottom; + } + } + .string-eq-criteria { + margin-top: 1rem; + .row { + margin-top: 1rem; + } + } + .number-eq-criteria, + .number-gt-lt-criteria { + margin-top: 1rem; + .row { + margin-top: 1rem; + } + p { + text-align: center; + margin-top: 0.5rem; + } + } + .expandable-header { + margin-top: 3rem; + } + } + .criteria-point-count-breakdown { + margin-bottom: 1rem; + .manual-group-member-count, + .criteria-group-member-count { + margin-left: 2rem; + div { + display: inline; + padding: 0.25rem; + font-size: 1.2rem; + border: 1px solid $panel_light_blue; + } + p { + display: inline; + margin-left: 1rem; + } + } + .criteria-group-member-count { + div { + border: 1px solid gray; + border-radius: 5px; + } + } + } + } +} + +.zone-info-panel { + .panel-content { + .location-criteria { + .row { + margin-top: 1rem; + p { + font-size: 1.4rem; + font-weight: bold; + } + label { + margin-top: 0; + } + } + } + } +} + .weeds-inventory-panel, .zones-inventory-panel, .groups-panel { diff --git a/frontend/css/inputs.scss b/frontend/css/inputs.scss index 4e5d9d508..e31b27443 100644 --- a/frontend/css/inputs.scss +++ b/frontend/css/inputs.scss @@ -95,9 +95,9 @@ select { } } &.disabled { + pointer-events: none; button { background: darken($white, 10%) !important; - pointer-events: none; } } } diff --git a/frontend/devices/components/fbos_settings/__tests__/boot_sequence_selector_test.tsx b/frontend/devices/components/fbos_settings/__tests__/boot_sequence_selector_test.tsx index 71b2b1abe..410b12931 100644 --- a/frontend/devices/components/fbos_settings/__tests__/boot_sequence_selector_test.tsx +++ b/frontend/devices/components/fbos_settings/__tests__/boot_sequence_selector_test.tsx @@ -1,7 +1,13 @@ -import { sequence2ddi, mapStateToProps, RawBootSequenceSelector } from "../boot_sequence_selector"; -import { fakeSequence, fakeFbosConfig } from "../../../../__test_support__/fake_state/resources"; +import { + sequence2ddi, mapStateToProps, RawBootSequenceSelector +} from "../boot_sequence_selector"; +import { + fakeSequence, fakeFbosConfig +} from "../../../../__test_support__/fake_state/resources"; import { fakeState } from "../../../../__test_support__/fake_state"; -import { buildResourceIndex } from "../../../../__test_support__/resource_index_builder"; +import { + buildResourceIndex +} from "../../../../__test_support__/resource_index_builder"; import React from "react"; import { mount } from "enzyme"; import { FBSelect } from "../../../../ui"; diff --git a/frontend/devices/components/hardware_settings/pin_guard.tsx b/frontend/devices/components/hardware_settings/pin_guard.tsx index 21222a8f0..9bd49c83b 100644 --- a/frontend/devices/components/hardware_settings/pin_guard.tsx +++ b/frontend/devices/components/hardware_settings/pin_guard.tsx @@ -24,7 +24,8 @@ export function PinGuard(props: PinGuardProps) { - +