From 9fb8ef212a6ed9060b19a988c2e3ed76e865ab12 Mon Sep 17 00:00:00 2001 From: gabrielburnworth Date: Fri, 28 Oct 2022 09:31:20 -0700 Subject: [PATCH 01/14] move demo telemetry source from API to FE --- .../devices/seeders/demo_account_seeder.rb | 17 ----------- .../farm_designer/farm_designer_panels.scss | 8 ++++- frontend/css/inputs.scss | 4 +++ .../fbos_metric_history_table_test.tsx | 19 +++++++++++- .../connectivity/fbos_metric_demo_data.ts | 30 +++++++++++++++++++ .../fbos_metric_history_table.tsx | 12 ++++++-- frontend/plants/plant_inventory.tsx | 4 +++ 7 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 frontend/devices/connectivity/fbos_metric_demo_data.ts diff --git a/app/mutations/devices/seeders/demo_account_seeder.rb b/app/mutations/devices/seeders/demo_account_seeder.rb index 480a17f171..d28fbe828d 100644 --- a/app/mutations/devices/seeders/demo_account_seeder.rb +++ b/app/mutations/devices/seeders/demo_account_seeder.rb @@ -77,23 +77,6 @@ def misc DEMO_LOGS .map { |p| p.merge(device: device) } .map { |p| Logs::Create.run!(p) } - 25.times do |n| - up = n * 60 * 60 - p = { - device: device, - soc_temp: 40, - throttled: "0x0", - wifi_level_percent: 80 + rand(-8...8), - uptime: up, - memory_usage: 50 + rand(-5...5), - disk_usage: 1, - cpu_usage: 5 + rand(-4...4), - target: "demo", - fbos_version: "100.0.0", - } - record = Telemetries::Create.run!(p) - record.update!(created_at: Time.now - 1.days + up.seconds) - end device .update!(fbos_version: READ_COMMENT_ABOVE) device diff --git a/frontend/css/farm_designer/farm_designer_panels.scss b/frontend/css/farm_designer/farm_designer_panels.scss index b228aa7cbf..777dbf1d3d 100644 --- a/frontend/css/farm_designer/farm_designer_panels.scss +++ b/frontend/css/farm_designer/farm_designer_panels.scss @@ -986,6 +986,11 @@ li { .delete { height: 2rem; } + .garden-indicator { + display: inline; + margin-left: 2rem; + font-size: 1rem; + } } .section-header { @@ -1613,7 +1618,7 @@ li { .change-password { .old-password, .new-password { - margin-bottom: 1rem; + margin-top: 1.5rem; input { width: 50%; float: right; @@ -1629,6 +1634,7 @@ li { } } .release-notes-wrapper { + margin-top: 0.75rem; float: right !important; } .settings-warning-banner { diff --git a/frontend/css/inputs.scss b/frontend/css/inputs.scss index a8fe23e9ef..3fd7e5750a 100644 --- a/frontend/css/inputs.scss +++ b/frontend/css/inputs.scss @@ -44,6 +44,10 @@ input:not([role="combobox"]) { } } +input:not([role="combobox"]):not([type="checkbox"]):not([type="radio"]) { + height: 3rem; +} + .input { position: relative; .bp4-popover-wrapper { diff --git a/frontend/devices/connectivity/__tests__/fbos_metric_history_table_test.tsx b/frontend/devices/connectivity/__tests__/fbos_metric_history_table_test.tsx index ee3cf801af..df3f10c38d 100644 --- a/frontend/devices/connectivity/__tests__/fbos_metric_history_table_test.tsx +++ b/frontend/devices/connectivity/__tests__/fbos_metric_history_table_test.tsx @@ -2,6 +2,11 @@ jest.mock("../fbos_metric_history_plot", () => ({ FbosMetricHistoryPlot: () =>
, })); +let mockDemo = false; +jest.mock("../../must_be_online", () => ({ + forceOnline: () => mockDemo, +})); + import React from "react"; import { mount } from "enzyme"; import { fakeTelemetry } from "../../../__test_support__/fake_state/resources"; @@ -27,8 +32,20 @@ describe("", () => { it("renders", () => { const p = fakeProps(); - const wrapper = mount(); + const wrapper = mount( + ); + expect(wrapper.instance().telemetry.length).toEqual(3); + expect(wrapper.text().toLowerCase()).toContain("wifi"); + }); + + it("renders demo data", () => { + mockDemo = true; + const p = fakeProps(); + const wrapper = mount( + ); + expect(wrapper.instance().telemetry.length).toEqual(100); expect(wrapper.text().toLowerCase()).toContain("wifi"); + mockDemo = false; }); it("sets metric hover state", () => { diff --git a/frontend/devices/connectivity/fbos_metric_demo_data.ts b/frontend/devices/connectivity/fbos_metric_demo_data.ts new file mode 100644 index 0000000000..1c139b5dd8 --- /dev/null +++ b/frontend/devices/connectivity/fbos_metric_demo_data.ts @@ -0,0 +1,30 @@ +import { SpecialStatus, TaggedTelemetry } from "farmbot"; +import { random, range } from "lodash"; + +export const generateDemoTelemetry = (): TaggedTelemetry[] => { + const count = 100; + return range(count).map(n => { + const up = n / count * 24 * 60 * 60; + const at = (new Date()).valueOf() / 1000 - 24 * 60 * 60 + up; + return { + kind: "Telemetry", + uuid: `Telemetry.${n}.${n}`, + specialStatus: SpecialStatus.SAVED, + body: { + id: n + 1, + soc_temp: 40, + throttled: "0x0", + wifi_level_percent: 80 + random(-8, 8), + uptime: up, + memory_usage: 50 + random(-5, 5), + disk_usage: 1, + cpu_usage: 5 + random(-4, 4), + target: "demo", + fbos_version: "100.0.0", + firmware_hardware: "farmduino_k16", + created_at: at, + updated_at: (new Date(at)).toISOString(), + } + }; + }); +}; diff --git a/frontend/devices/connectivity/fbos_metric_history_table.tsx b/frontend/devices/connectivity/fbos_metric_history_table.tsx index deea614216..279e01dfa3 100644 --- a/frontend/devices/connectivity/fbos_metric_history_table.tsx +++ b/frontend/devices/connectivity/fbos_metric_history_table.tsx @@ -12,6 +12,8 @@ import { formatTime } from "../../util"; import moment from "moment"; import { Telemetry } from "farmbot/dist/resources/api_resources"; import { cloneDeep, sortBy } from "lodash"; +import { forceOnline } from "../must_be_online"; +import { generateDemoTelemetry } from "./fbos_metric_demo_data"; const METRIC_TITLES = (): Partial> => ({ soc_temp: t("Temp"), @@ -86,6 +88,7 @@ export interface FbosMetricHistoryTableProps { interface FbosMetricHistoryState { hoveredMetric: keyof Telemetry | undefined; hoveredTime: number | undefined; + demoData: TaggedTelemetry[], } export class FbosMetricHistoryTable @@ -93,6 +96,7 @@ export class FbosMetricHistoryTable state: FbosMetricHistoryState = { hoveredMetric: undefined, hoveredTime: undefined, + demoData: generateDemoTelemetry(), }; hoverMetric: OnMetricHover = metricName => () => @@ -101,6 +105,10 @@ export class FbosMetricHistoryTable hoverTime = (time: number | undefined) => () => this.setState({ hoveredTime: time }); + get telemetry() { + return forceOnline() ? this.state.demoData : this.props.telemetry; + } + render() { const commonProps = { hoveredMetric: this.state.hoveredMetric, @@ -109,7 +117,7 @@ export class FbosMetricHistoryTable }; const rightAlignProps = { ...commonProps, rightAlign: true }; return
- +
@@ -128,7 +136,7 @@ export class FbosMetricHistoryTable - {sortBy(cloneDeep(this.props.telemetry), "body.created_at").reverse() + {sortBy(cloneDeep(this.telemetry), "body.created_at").reverse() .map(m => { const recordSelected = this.state.hoveredTime == m.body.created_at; const recordProps = { diff --git a/frontend/plants/plant_inventory.tsx b/frontend/plants/plant_inventory.tsx index 61e2491d44..28f9eafe3b 100644 --- a/frontend/plants/plant_inventory.tsx +++ b/frontend/plants/plant_inventory.tsx @@ -173,6 +173,8 @@ export class RawPlants addTitle={t("add plant")} addClassName={"plus-plant"} title={t("Plants")} + extraHeaderTitle={!!this.props.openedSavedGarden && + {t("saved garden")}} extraHeaderContent={ !this.props.openedSavedGarden && plantsPanelState.plants && + + props.updatePlant(props.uuid, { + ["depth" as keyof PlantPointer]: parseIntInput(e.currentTarget.value) + })} /> + + ; + interface DeleteButtonsProps { destroy(): void; } @@ -206,6 +226,9 @@ export function PlantPanel(props: PlantPanelProps) { + + + {(!inSavedGarden) ? diff --git a/frontend/plants/select_plants.tsx b/frontend/plants/select_plants.tsx index 6332af4f86..d851426c7e 100644 --- a/frontend/plants/select_plants.tsx +++ b/frontend/plants/select_plants.tsx @@ -21,6 +21,7 @@ import { PlantDateBulkUpdate, PlantSlugBulkUpdate, PlantStatusBulkUpdate, PointColorBulkUpdate, PointSizeBulkUpdate, + PlantDepthBulkUpdate, } from "./edit_plant_status"; import { FBSelect, DropDownItem } from "../ui"; import { @@ -302,6 +303,12 @@ export class RawSelectPlants allPoints={this.props.allPoints} selected={this.selected} dispatch={this.props.dispatch} />} + {["Plant"] + .includes(this.selectionPointType) && + } {["Weed", "GenericPointer"] .includes(this.selectionPointType) && Date: Fri, 28 Oct 2022 13:54:38 -0700 Subject: [PATCH 03/14] add default plant depth --- ...d_default_plant_depth_to_web_app_config.rb | 9 ++++ db/structure.sql | 6 ++- .../__test_support__/fake_state/resources.ts | 1 + frontend/constants.ts | 8 +++- frontend/css/farm_designer/farm_designer.scss | 2 +- .../farm_designer/farm_designer_panels.scss | 36 ++++++++++++++- frontend/css/inputs.scss | 8 +++- frontend/css/steps.scss | 3 ++ frontend/farm_designer/interfaces.ts | 7 ++- frontend/farm_designer/map/garden_map.tsx | 1 + .../plants/__tests__/plant_actions_test.ts | 5 +- .../map/layers/plants/plant_actions.ts | 21 +++++++-- .../farm_designer/map/profile/content.tsx | 1 + frontend/farm_designer/plant.ts | 2 +- frontend/plants/__tests__/crop_info_test.tsx | 7 +++ .../plants/__tests__/plant_inventory_test.tsx | 34 +++++++++++++- frontend/plants/crop_info.tsx | 32 ++++++++----- frontend/plants/plant_inventory.tsx | 29 ++++++++++-- frontend/plants/select_plants.tsx | 46 +++++++++++-------- frontend/session_keys.ts | 5 +- .../__tests__/farm_designer_settings_test.tsx | 4 +- frontend/settings/__tests__/index_test.tsx | 18 +++++--- frontend/settings/default_values.ts | 1 + frontend/settings/farm_designer_settings.tsx | 23 ++++++++-- frontend/settings/interfaces.ts | 6 +++ frontend/settings/maybe_highlight.tsx | 1 + frontend/ui/filter_search.tsx | 2 +- 27 files changed, 255 insertions(+), 63 deletions(-) create mode 100644 db/migrate/20221028172528_add_default_plant_depth_to_web_app_config.rb diff --git a/db/migrate/20221028172528_add_default_plant_depth_to_web_app_config.rb b/db/migrate/20221028172528_add_default_plant_depth_to_web_app_config.rb new file mode 100644 index 0000000000..d17b23e53a --- /dev/null +++ b/db/migrate/20221028172528_add_default_plant_depth_to_web_app_config.rb @@ -0,0 +1,9 @@ +class AddDefaultPlantDepthToWebAppConfig < ActiveRecord::Migration[6.1] + def up + add_column :web_app_configs, :default_plant_depth, :integer, default: 0 + end + + def down + remove_column :web_app_configs, :default_plant_depth + end +end diff --git a/db/structure.sql b/db/structure.sql index 5605d6b0d9..e27b342f46 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1949,7 +1949,8 @@ CREATE TABLE public.web_app_configs ( beep_verbosity integer DEFAULT 0, landing_page character varying(100) DEFAULT 'controls'::character varying, go_button_axes character varying(3) DEFAULT 'XY'::character varying NOT NULL, - show_uncropped_camera_view_area boolean DEFAULT false + show_uncropped_camera_view_area boolean DEFAULT false, + default_plant_depth integer DEFAULT 0 ); @@ -3833,6 +3834,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20220620225957'), ('20220810212545'), ('20220819170955'), -('20221027211207'); +('20221027211207'), +('20221028172528'); diff --git a/frontend/__test_support__/fake_state/resources.ts b/frontend/__test_support__/fake_state/resources.ts index c2ec76cbc9..7acedac81e 100644 --- a/frontend/__test_support__/fake_state/resources.ts +++ b/frontend/__test_support__/fake_state/resources.ts @@ -382,6 +382,7 @@ export function fakeWebAppConfig(): TaggedWebAppConfig { show_zones: false, show_camera_view_area: false, ["show_uncropped_camera_view_area" as keyof WebAppConfig]: false, + ["default_plant_depth" as keyof WebAppConfig]: 0, disable_emergency_unlock_confirmation: false, map_size_x: 2900, map_size_y: 1400, diff --git a/frontend/constants.ts b/frontend/constants.ts index ff448169a6..93ae5333d2 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -954,6 +954,10 @@ export namespace Content { export const CONFIRM_PLANT_DELETION = trim(`Show a confirmation dialog when deleting a plant.`); + export const DEFAULT_PLANT_DEPTH = + trim(`When adding plants to the map from the web app, set each new + plant's depth to this value (in millimeters).`); + // App export const APP_LOAD_TIMEOUT_MESSAGE = trim(`App could not be fully loaded, we recommend you try @@ -1103,8 +1107,7 @@ export namespace Content { trim(`Click and drag or use the inputs to draw a weed.`); export const BOX_SELECT_DESCRIPTION = - trim(`Drag a box around the items you would like to select. - Press the back arrow to exit.`); + trim(`Drag a box around the items you would like to select.`); export const SAVED_GARDENS = trim(`Create a new garden from scratch or by copying plants from the @@ -1944,6 +1947,7 @@ export enum DeviceSetting { cameraView = `Camera view`, uncroppedCameraView = `Uncropped Camera view`, confirmPlantDeletion = `Confirm plant deletion`, + defaultPlantDepth = `Default plant depth`, // Account accountSettings = `Account`, diff --git a/frontend/css/farm_designer/farm_designer.scss b/frontend/css/farm_designer/farm_designer.scss index 461dcc0c68..d82d383f89 100644 --- a/frontend/css/farm_designer/farm_designer.scss +++ b/frontend/css/farm_designer/farm_designer.scss @@ -1087,7 +1087,7 @@ .profile-viewer { position: fixed; bottom: 0; - z-index: 99; + z-index: 2; width: 100%; background: #e5e5e5; transform: translateY(21rem); diff --git a/frontend/css/farm_designer/farm_designer_panels.scss b/frontend/css/farm_designer/farm_designer_panels.scss index 360ec9e507..e112ae4c06 100644 --- a/frontend/css/farm_designer/farm_designer_panels.scss +++ b/frontend/css/farm_designer/farm_designer_panels.scss @@ -425,11 +425,25 @@ width: 100%; background: $panel_medium_light_gray; padding: 0.5rem; + .selection-type { + display: flex; + label { + line-height: 4rem; + } + .filter-search { + position: absolute; + right: 1rem; + width: 70%; + } + } + .buttons { + float: right; + } &.more-select { height: 23rem; } &.more-action { - height: 35rem; + height: 38rem; } &.more-select.more-action { height: 43rem; @@ -550,6 +564,10 @@ label { width: 100%; } + .filter-search { + display: inline-block; + width: 100%; + } } } .panel-content { @@ -563,7 +581,7 @@ padding-top: 23rem; } &.more-action { - padding-top: 35rem; + padding-top: 38rem; } &.more-select.more-action { padding-top: 43rem; @@ -1570,6 +1588,7 @@ li { } } +.plant-inventory-panel, .settings-panel { .panel-top { .fa-chevron-right, @@ -1591,6 +1610,19 @@ li { } } +.plants-panel-settings-menu { + label { + line-height: 3rem; + vertical-align: bottom; + margin-bottom: 0; + } + .bp4-popover-wrapper { + display: inline; + margin-left: 0.5rem; + line-height: 3rem; + } +} + .settings-panel-content { max-height: calc(100vh - 22rem); overflow-y: auto; diff --git a/frontend/css/inputs.scss b/frontend/css/inputs.scss index 3fd7e5750a..0ebf304aa7 100644 --- a/frontend/css/inputs.scss +++ b/frontend/css/inputs.scss @@ -99,11 +99,12 @@ select { .filter-search { span { width: 100%; + color: $dark_gray; } i { position: absolute; - top: 20%; - right: 0.65rem; + right: 1rem; + line-height: 3rem; color: $dark_gray } &.dim { @@ -139,6 +140,9 @@ select { padding-left: 0; padding-right: 0; } + .bp4-input { + height: auto !important; + } } &.few-items { .bp4-input-group { diff --git a/frontend/css/steps.scss b/frontend/css/steps.scss index dda79bd725..4b274e9854 100644 --- a/frontend/css/steps.scss +++ b/frontend/css/steps.scss @@ -83,6 +83,9 @@ .input { width: 100%; } + .step-label { + height: auto !important; + } input { width: 100%; padding: 0rem; diff --git a/frontend/farm_designer/interfaces.ts b/frontend/farm_designer/interfaces.ts index 8f5220e2b0..9951bad9b9 100644 --- a/frontend/farm_designer/interfaces.ts +++ b/frontend/farm_designer/interfaces.ts @@ -289,7 +289,11 @@ export interface GardenMapState { toLocation: Vector3 | undefined; } -export type PlantOptions = Partial; +interface PlantOptionsContent extends PlantPointer { + depth: number; +} + +export type PlantOptions = Partial; export interface EditPlantInfoProps { dispatch: Function; @@ -339,6 +343,7 @@ export interface CropInfoProps { openfarmCropFetch: OpenfarmSearch; botPosition: BotPosition; xySwap: boolean; + getConfigValue: GetWebAppConfigValue; } export interface CameraCalibrationData { diff --git a/frontend/farm_designer/map/garden_map.tsx b/frontend/farm_designer/map/garden_map.tsx index 7c43fac3ad..cf388b3302 100644 --- a/frontend/farm_designer/map/garden_map.tsx +++ b/frontend/farm_designer/map/garden_map.tsx @@ -300,6 +300,7 @@ export class GardenMap extends openedSavedGarden: this.props.designer.openedSavedGarden, gridSize: this.mapTransformProps.gridSize, dispatch: this.props.dispatch, + getConfigValue: this.props.getConfigValue, }); }; diff --git a/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts b/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts index f57ca47031..535fd93104 100644 --- a/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts +++ b/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.ts @@ -48,7 +48,8 @@ describe("newPlantKindAndBody()", () => { y: 0, slug: "mint", cropName: "Mint", - openedSavedGarden: "SavedGarden.1.1" + openedSavedGarden: "SavedGarden.1.1", + depth: 0, }; const result = newPlantKindAndBody(p); expect(result).toEqual(expect.objectContaining({ @@ -65,6 +66,7 @@ describe("createPlant()", () => { gridSize: { x: 1000, y: 2000 }, dispatch: jest.fn(), openedSavedGarden: undefined, + depth: 0, }); it("creates plant", () => { @@ -98,6 +100,7 @@ describe("dropPlant()", () => { openedSavedGarden: undefined, gridSize: { x: 1000, y: 2000 }, dispatch: jest.fn(), + getConfigValue: jest.fn(), }); it("drops plant", () => { diff --git a/frontend/farm_designer/map/layers/plants/plant_actions.ts b/frontend/farm_designer/map/layers/plants/plant_actions.ts index e55473c177..ba0c9eef0f 100644 --- a/frontend/farm_designer/map/layers/plants/plant_actions.ts +++ b/frontend/farm_designer/map/layers/plants/plant_actions.ts @@ -20,6 +20,8 @@ import { t } from "../../../../i18next_wrapper"; import { error } from "../../../../toast/toast"; import { TaggedPlantTemplate, TaggedPoint } from "farmbot"; import { Path } from "../../../../internal_urls"; +import { GetWebAppConfigValue } from "../../../../config_storage/actions"; +import { NumericSetting } from "../../../../session_keys"; export interface NewPlantKindAndBodyProps { x: number; @@ -27,6 +29,7 @@ export interface NewPlantKindAndBodyProps { slug: string; cropName: string; openedSavedGarden: string | undefined; + depth: number; } /** Return a new plant or plantTemplate object. */ @@ -58,7 +61,8 @@ export const newPlantKindAndBody = (props: NewPlantKindAndBodyProps): { openfarm_slug: props.slug, name: props.cropName, created_at: moment().toISOString(), - radius: DEFAULT_PLANT_RADIUS + radius: DEFAULT_PLANT_RADIUS, + depth: props.depth, }) }; }; @@ -70,11 +74,14 @@ export interface CreatePlantProps { gridSize: AxisNumberProperty | undefined; dispatch: Function; openedSavedGarden: string | undefined; + depth: number; } /** Create a new plant in the garden map. */ export const createPlant = (props: CreatePlantProps): void => { - const { cropName, slug, gardenCoords, gridSize, openedSavedGarden } = props; + const { + cropName, slug, gardenCoords, gridSize, openedSavedGarden, depth, + } = props; const { x, y } = gardenCoords; const tooLow = x < 0 || y < 0; // negative (beyond grid start) const tooHigh = gridSize @@ -84,7 +91,9 @@ export const createPlant = (props: CreatePlantProps): void => { if (outsideGrid) { error(t(Content.OUTSIDE_PLANTING_AREA)); } else { - const p = newPlantKindAndBody({ x, y, slug, cropName, openedSavedGarden }); + const p = newPlantKindAndBody({ + x, y, slug, cropName, openedSavedGarden, depth, + }); // Stop non-plant objects from creating generic plants in the map if (p.body.name != "name" && p.body.openfarm_slug != "slug") { // Create and save a new plant in the garden map @@ -100,11 +109,14 @@ export interface DropPlantProps { openedSavedGarden: string | undefined; gridSize: AxisNumberProperty; dispatch: Function; + getConfigValue: GetWebAppConfigValue; } /** Create a plant upon drop. */ export const dropPlant = (props: DropPlantProps) => { - const { gardenCoords, openedSavedGarden, gridSize, dispatch } = props; + const { + gardenCoords, openedSavedGarden, gridSize, dispatch, getConfigValue, + } = props; if (gardenCoords) { const slug = Path.getSlug(Path.plants(1)); if (!slug) { console.log("Missing slug."); return; } @@ -119,6 +131,7 @@ export const dropPlant = (props: DropPlantProps) => { gridSize, dispatch, openedSavedGarden, + depth: parseInt("" + getConfigValue(NumericSetting.default_plant_depth)), }); dispatch({ type: Actions.SET_COMPANION_INDEX, payload: undefined }); } else { diff --git a/frontend/farm_designer/map/profile/content.tsx b/frontend/farm_designer/map/profile/content.tsx index 40572245e5..4a2d1e80d9 100644 --- a/frontend/farm_designer/map/profile/content.tsx +++ b/frontend/farm_designer/map/profile/content.tsx @@ -48,6 +48,7 @@ export const ProfileSvg = (props: ProfileSvgProps) => { const getX = getProfileX({ profileAxis, mapTransformProps, width }); const reversed = flipProfile({ profileAxis, mapTransformProps }); return diff --git a/frontend/farm_designer/plant.ts b/frontend/farm_designer/plant.ts index 05b9252062..9eb19970ff 100644 --- a/frontend/farm_designer/plant.ts +++ b/frontend/farm_designer/plant.ts @@ -16,7 +16,7 @@ export function Plant(options: PlantOptions): PlantPointer { y: (options.y || 0), z: 0, radius: (options.radius || DEFAULT_PLANT_RADIUS), - ["depth" as keyof PlantPointer]: 0, + ["depth" as keyof PlantPointer]: options.depth || 0, openfarm_slug, plant_stage: "planned" }; diff --git a/frontend/plants/__tests__/crop_info_test.tsx b/frontend/plants/__tests__/crop_info_test.tsx index 956f35d554..8b2cf68c8e 100644 --- a/frontend/plants/__tests__/crop_info_test.tsx +++ b/frontend/plants/__tests__/crop_info_test.tsx @@ -29,6 +29,8 @@ import { fakeState } from "../../__test_support__/fake_state"; import { Actions } from "../../constants"; import { clickButton } from "../../__test_support__/helpers"; import { Path } from "../../internal_urls"; +import { fakeWebAppConfig } from "../../__test_support__/fake_state/resources"; +import { buildResourceIndex } from "../../__test_support__/resource_index_builder"; describe("", () => { const fakeProps = (): CropInfoProps => { @@ -44,6 +46,7 @@ describe("", () => { openedSavedGarden: undefined, botPosition: { x: undefined, y: undefined, z: undefined }, xySwap: false, + getConfigValue: jest.fn(), }; }; @@ -194,10 +197,14 @@ describe("getCropHeaderProps()", () => { describe("mapStateToProps()", () => { it("returns props", () => { const state = fakeState(); + const webAppConfig = fakeWebAppConfig(); + webAppConfig.body.show_plants = false; + state.resources = buildResourceIndex([webAppConfig]); state.resources.consumers.farm_designer.cropSearchInProgress = true; state.bot.hardware.location_data.position = { x: 1, y: 2, z: 3 }; const props = mapStateToProps(state); expect(props.cropSearchInProgress).toEqual(true); expect(props.botPosition).toEqual({ x: 1, y: 2, z: 3 }); + expect(props.getConfigValue("show_plants")).toEqual(false); }); }); diff --git a/frontend/plants/__tests__/plant_inventory_test.tsx b/frontend/plants/__tests__/plant_inventory_test.tsx index ae7a6f6fa8..c58e2c4ce8 100644 --- a/frontend/plants/__tests__/plant_inventory_test.tsx +++ b/frontend/plants/__tests__/plant_inventory_test.tsx @@ -15,6 +15,17 @@ jest.mock("../../api/delete_points", () => ({ deletePoints: jest.fn(), })); +import { PopoverProps } from "../../ui/popover"; +jest.mock("../../ui/popover", () => ({ + Popover: ({ target, content }: PopoverProps) =>
{target}{content}
, +})); + +let mockValue: number | boolean = 0; +jest.mock("../../config_storage/actions", () => ({ + setWebAppConfigValue: jest.fn(), + getWebAppConfigValue: jest.fn(x => { x(); return () => mockValue; }), +})); + import React from "react"; import { RawPlants as Plants, PlantInventoryProps, mapStateToProps, PanelSection, @@ -22,7 +33,7 @@ import { } from "../plant_inventory"; import { mount, shallow } from "enzyme"; import { - fakePlant, fakePointGroup, fakeSavedGarden, + fakePlant, fakePointGroup, fakeSavedGarden, fakeWebAppConfig, } from "../../__test_support__/fake_state/resources"; import { fakeState } from "../../__test_support__/fake_state"; import { SearchField } from "../../ui/search_field"; @@ -34,6 +45,10 @@ import { deletePoints } from "../../api/delete_points"; import { Panel } from "../../farm_designer/panel_header"; import { plantsPanelState } from "../../__test_support__/panel_state"; import { Path } from "../../internal_urls"; +import { buildResourceIndex } from "../../__test_support__/resource_index_builder"; +import { changeBlurableInput } from "../../__test_support__/helpers"; +import { setWebAppConfigValue } from "../../config_storage/actions"; +import { NumericSetting } from "../../session_keys"; describe("", () => { const fakeProps = (): PlantInventoryProps => ({ @@ -47,16 +62,26 @@ describe("", () => { plantPointerCount: 0, openedSavedGarden: undefined, plantsPanelState: plantsPanelState(), + getConfigValue: () => 0, }); it("renders", () => { const wrapper = mount(); ["Strawberry Plant 1", "1 day old"].map(string => expect(wrapper.text()).toContain(string)); - expect(wrapper.find("input").props().placeholder) + expect(wrapper.find("input").first().props().placeholder) .toEqual("Search your plants..."); }); + it("changes number setting", () => { + mockValue = 0; + const p = fakeProps(); + const wrapper = mount(); + changeBlurableInput(wrapper, "100", 1); + expect(setWebAppConfigValue).toHaveBeenCalledWith( + NumericSetting.default_plant_depth, 100); + }); + it("renders groups", () => { const p = fakeProps(); const group1 = fakePointGroup(); @@ -176,10 +201,15 @@ describe("", () => { describe("mapStateToProps()", () => { it("returns props", () => { + mockValue = false; const state = fakeState(); + const webAppConfig = fakeWebAppConfig(); + webAppConfig.body.show_plants = false; + state.resources = buildResourceIndex([webAppConfig]); state.resources.consumers.farm_designer.hoveredPlantListItem = "uuid"; const result = mapStateToProps(state); expect(result.hoveredPlantListItem).toEqual("uuid"); + expect(result.getConfigValue("show_plants")).toEqual(false); }); }); diff --git a/frontend/plants/crop_info.tsx b/frontend/plants/crop_info.tsx index 43a9e1a48c..6ccda22f3e 100644 --- a/frontend/plants/crop_info.tsx +++ b/frontend/plants/crop_info.tsx @@ -24,8 +24,10 @@ import { t } from "../i18next_wrapper"; import { Panel } from "../farm_designer/panel_header"; import { ExternalUrl } from "../external_urls"; import { PlantGrid } from "./grid/plant_grid"; -import { getWebAppConfigValue } from "../config_storage/actions"; -import { BooleanSetting } from "../session_keys"; +import { + GetWebAppConfigValue, getWebAppConfigValue, +} from "../config_storage/actions"; +import { BooleanSetting, NumericSetting } from "../session_keys"; import { Path } from "../internal_urls"; import { Link } from "../link"; @@ -176,15 +178,20 @@ const Companions = (props: CropInfoListProps) => { ; }; +interface AddPlantHereButtonProps { + botPosition: BotPosition; + openedSavedGarden: string | undefined; + cropName: string; + slug: string; + dispatch: Function; + getConfigValue: GetWebAppConfigValue; +} + /** Button to add a plant to the garden at the current bot position. */ -const AddPlantHereButton = (props: { - botPosition: BotPosition, - openedSavedGarden: string | undefined, - cropName: string, - slug: string, - dispatch: Function -}) => { - const { botPosition, openedSavedGarden, cropName, slug, dispatch } = props; +const AddPlantHereButton = (props: AddPlantHereButtonProps) => { + const { + botPosition, openedSavedGarden, cropName, slug, dispatch, getConfigValue, + } = props; const { x, y } = botPosition; const botXY = isNumber(x) && isNumber(y) ? { x: round(x), y: round(y) } @@ -193,7 +200,8 @@ const AddPlantHereButton = (props: { const click = () => botXY && createPlant({ cropName, slug, gardenCoords: botXY, gridSize: undefined, - dispatch, openedSavedGarden + dispatch, openedSavedGarden, + depth: parseInt("" + getConfigValue(NumericSetting.default_plant_depth)), }); return
+ + + + + + + } /> - - { - this.props.dispatch(selectPoint(undefined)); - this.setState({ group_id: undefined }); - this.props.dispatch(setSelectionPointType( - ddi.value == "All" ? POINTER_TYPES : validPointTypes([ddi.value]))); - }} /> +
+ + { + this.props.dispatch(selectPoint(undefined)); + this.setState({ group_id: undefined }); + this.props.dispatch(setSelectionPointType( + ddi.value == "All" ? POINTER_TYPES : validPointTypes([ddi.value]))); + }} /> +
- -
+ +
} +
+
this.setState({ moreActions: !this.state.moreActions })}> @@ -426,21 +432,25 @@ interface MoreProps { className: string; isOpen: boolean; toggleOpen(): void; - children: (React.ReactChild | false)[]; + customText?: { more: string, less: string }; + children: JSX.Element | (JSX.Element | false)[]; } -const More = (props: MoreProps) => -
+const More = (props: MoreProps) => { + const more = props.customText?.more || t("more"); + const less = props.customText?.less || t("less"); + return
-

{props.isOpen ? t("Less") : t("More")}

+

{props.isOpen ? less : more}

+ title={props.isOpen ? less : more} />
; +}; export interface GetFilteredPointsProps { selectionPointType: PointType[] | undefined; diff --git a/frontend/session_keys.ts b/frontend/session_keys.ts index c0bee1eb16..7ef9f800da 100644 --- a/frontend/session_keys.ts +++ b/frontend/session_keys.ts @@ -6,7 +6,8 @@ import { type WebAppBooleanConfigKeyAll = WebAppBooleanConfigKey | "show_uncropped_camera_view_area"; -type WebAppNumberConfigKeyAll = WebAppNumberConfigKey; +type WebAppNumberConfigKeyAll = WebAppNumberConfigKey + | "default_plant_depth"; type WebAppStringConfigKeyAll = WebAppStringConfigKey; type BooleanSettings = Record; @@ -93,6 +94,8 @@ export const NumericSetting: NumericSettings = { map_size_x: "map_size_x", map_size_y: "map_size_y", bot_origin_quadrant: "bot_origin_quadrant", + default_plant_depth: "default_plant_depth" as + WebAppNumberConfigKey, /** App settings */ beep_verbosity: "beep_verbosity", diff --git a/frontend/settings/__tests__/farm_designer_settings_test.tsx b/frontend/settings/__tests__/farm_designer_settings_test.tsx index d888a236bf..d4dfac2d6e 100644 --- a/frontend/settings/__tests__/farm_designer_settings_test.tsx +++ b/frontend/settings/__tests__/farm_designer_settings_test.tsx @@ -21,7 +21,7 @@ import { setWebAppConfigValue } from "../../config_storage/actions"; describe("", () => { const fakeProps = (): DesignerSettingsPropsBase => ({ dispatch: jest.fn(), - getConfigValue: jest.fn(), + getConfigValue: () => 0, }); it("renders", () => { @@ -47,7 +47,7 @@ describe("", () => { describe("", () => { const fakeProps = (): SettingProps => ({ dispatch: jest.fn(), - getConfigValue: jest.fn(), + getConfigValue: () => 0, setting: BooleanSetting.show_farmbot, title: DeviceSetting.showFarmbot, description: "description", diff --git a/frontend/settings/__tests__/index_test.tsx b/frontend/settings/__tests__/index_test.tsx index f016d0f238..4396148495 100644 --- a/frontend/settings/__tests__/index_test.tsx +++ b/frontend/settings/__tests__/index_test.tsx @@ -32,7 +32,9 @@ import { SearchField } from "../../ui/search_field"; import { maybeOpenPanel } from "../maybe_highlight"; import { SettingsPanelState } from "../../interfaces"; import { settingsPanelState } from "../../__test_support__/panel_state"; -import { fakeUser } from "../../__test_support__/fake_state/resources"; +import { + fakeUser, fakeWebAppConfig, +} from "../../__test_support__/fake_state/resources"; import { API } from "../../api"; import { push } from "../../history"; @@ -52,11 +54,11 @@ describe("", () => { const fakeProps = (): DesignerSettingsProps => ({ dispatch: jest.fn(), - getConfigValue: jest.fn(), + getConfigValue: () => 0, firmwareConfig: undefined, sourceFwConfig: () => ({ value: 10, consistent: true }), sourceFbosConfig: () => ({ value: 10, consistent: true }), - resources: buildResourceIndex().index, + resources: buildResourceIndex([]).index, deviceAccount: fakeDevice(), alerts: [], saveFarmwareEnv: jest.fn(), @@ -162,9 +164,11 @@ describe("", () => { it("renders defaultOn setting", () => { const p = fakeProps(); p.settingsPanelState.farm_designer = true; - p.getConfigValue = () => undefined; + const config = fakeWebAppConfig(); + config.body.confirm_plant_deletion = undefined as never; + p.getConfigValue = key => config.body[key]; const wrapper = mount(); - const confirmDeletion = getSetting(wrapper, 11, "confirm plant"); + const confirmDeletion = getSetting(wrapper, 12, "confirm plant"); expect(confirmDeletion.find("button").text()).toEqual("on"); }); @@ -238,7 +242,9 @@ describe("", () => { it("renders change ownership form", () => { API.setBaseUrl(""); const p = fakeProps(); - p.getConfigValue = () => true; + const config = fakeWebAppConfig(); + config.body.show_advanced_settings = true; + p.getConfigValue = key => config.body[key]; p.bot.hardware.informational_settings.sync_status = "synced"; p.bot.connectivity.uptime["bot.mqtt"] = { state: "up", at: 1 }; const wrapper = mount(); diff --git a/frontend/settings/default_values.ts b/frontend/settings/default_values.ts index 9899a65cda..ce276bc5ea 100644 --- a/frontend/settings/default_values.ts +++ b/frontend/settings/default_values.ts @@ -78,6 +78,7 @@ const DEFAULT_WEB_APP_CONFIG_VALUES: Record = { clip_image_layer: true, show_camera_view_area: true, ["show_uncropped_camera_view_area" as keyof WebAppConfig]: false, + ["default_plant_depth" as keyof WebAppConfig]: 0, view_celery_script: false, highlight_modified_settings: true, show_advanced_settings: false, diff --git a/frontend/settings/farm_designer_settings.tsx b/frontend/settings/farm_designer_settings.tsx index 59be50dba9..0679c2722b 100644 --- a/frontend/settings/farm_designer_settings.tsx +++ b/frontend/settings/farm_designer_settings.tsx @@ -2,7 +2,7 @@ import React from "react"; import { Content, DeviceSetting } from "../constants"; import { t } from "../i18next_wrapper"; import { setWebAppConfigValue } from "../config_storage/actions"; -import { Row, Col, Help, ToggleButton } from "../ui"; +import { Row, Col, Help, ToggleButton, BlurableInput } from "../ui"; import { BooleanSetting, NumericSetting } from "../session_keys"; import { resetVirtualTrail } from "../farm_designer/map/layers/farmbot/bot_trail"; import { MapSizeInputs } from "../farm_designer/map_size_setting"; @@ -12,7 +12,7 @@ import { Header } from "./hardware_settings/header"; import { Highlight } from "./maybe_highlight"; import { DesignerSettingsSectionProps, SettingProps, - DesignerSettingsPropsBase, SettingDescriptionProps, + DesignerSettingsPropsBase, SettingDescriptionProps, WebAppNumberSettingProps, } from "./interfaces"; import { getModifiedClassName } from "./default_values"; @@ -39,7 +39,7 @@ export const PlainDesignerSettings = useToolTip={true} />); export const Setting = (props: SettingProps) => { - const { title, setting, callback, defaultOn } = props; + const { title, setting, numberSetting, callback, defaultOn } = props; const raw_value = setting ? props.getConfigValue(setting) : undefined; const value = (defaultOn && isUndefined(raw_value)) ? true : !!raw_value; return @@ -62,6 +62,8 @@ export const Setting = (props: SettingProps) => { title={`${t("toggle")} ${title}`} className={getModifiedClassName(setting)} customText={{ textFalse: t("off"), textTrue: t("on") }} />} + {numberSetting && } {!props.useToolTip && @@ -72,6 +74,16 @@ export const Setting = (props: SettingProps) => { ; }; +export const WebAppNumberSetting = (props: WebAppNumberSettingProps) => { + const { dispatch, getConfigValue, numberSetting } = props; + return dispatch(setWebAppConfigValue(numberSetting, + parseInt(e.currentTarget.value)))} + value={parseInt("" + getConfigValue(numberSetting))} />; +}; + const DESIGNER_SETTINGS = (settingsProps: DesignerSettingsPropsBase): SettingDescriptionProps[] => ([ { @@ -133,6 +145,11 @@ const DESIGNER_SETTINGS = description: Content.SHOW_UNCROPPED_CAMERA_VIEW_AREA, setting: BooleanSetting.show_uncropped_camera_view_area, }, + { + title: DeviceSetting.defaultPlantDepth, + description: Content.DEFAULT_PLANT_DEPTH, + numberSetting: NumericSetting.default_plant_depth, + }, { title: DeviceSetting.confirmPlantDeletion, description: Content.CONFIRM_PLANT_DELETION, diff --git a/frontend/settings/interfaces.ts b/frontend/settings/interfaces.ts index 5b091b2f63..f923d6be71 100644 --- a/frontend/settings/interfaces.ts +++ b/frontend/settings/interfaces.ts @@ -11,6 +11,7 @@ import { SettingsPanelState, TimeSettings } from "../interfaces"; import { DeviceSetting } from "../constants"; import { BooleanConfigKey as WebAppBooleanConfigKey, + NumberConfigKey as WebAppNumberConfigKey, } from "farmbot/dist/resources/configs/web_app"; import { SaveFarmwareEnv } from "../farmware/interfaces"; @@ -44,6 +45,7 @@ export interface DesignerSettingsSectionProps { export interface SettingDescriptionProps { setting?: WebAppBooleanConfigKey; + numberSetting?: WebAppNumberConfigKey; title: DeviceSetting; description: string; invert?: boolean; @@ -58,6 +60,10 @@ export interface SettingDescriptionProps { export interface SettingProps extends DesignerSettingsPropsBase, SettingDescriptionProps { } +export interface WebAppNumberSettingProps extends DesignerSettingsPropsBase { + numberSetting: WebAppNumberConfigKey; +} + export interface CustomSettingsProps { dispatch: Function; settingsPanelState: SettingsPanelState; diff --git a/frontend/settings/maybe_highlight.tsx b/frontend/settings/maybe_highlight.tsx index 7d8c0bbdab..c645a4fcea 100644 --- a/frontend/settings/maybe_highlight.tsx +++ b/frontend/settings/maybe_highlight.tsx @@ -142,6 +142,7 @@ const FARM_DESIGNER_PANEL = [ DeviceSetting.clipPhotosOutOfBounds, DeviceSetting.cameraView, DeviceSetting.confirmPlantDeletion, + DeviceSetting.defaultPlantDepth, ]; const ACCOUNT_PANEL = [ DeviceSetting.accountSettings, diff --git a/frontend/ui/filter_search.tsx b/frontend/ui/filter_search.tsx index 8cf6a75fca..eef02685a4 100644 --- a/frontend/ui/filter_search.tsx +++ b/frontend/ui/filter_search.tsx @@ -45,7 +45,7 @@ export class FilterSearch
- +
-
+
this.setState({ moreActions: !this.state.moreActions })}> @@ -437,8 +437,8 @@ interface MoreProps { } const More = (props: MoreProps) => { - const more = props.customText?.more || t("more"); - const less = props.customText?.less || t("less"); + const more = props.customText?.more || t("More"); + const less = props.customText?.less || t("Less"); return
From 4b4a8fc4bfc1eb609a5e750be389ed9472b37c0e Mon Sep 17 00:00:00 2001 From: gabrielburnworth Date: Wed, 9 Nov 2022 13:34:17 -0800 Subject: [PATCH 09/14] fix mystery css bug --- frontend/ui/filter_search.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/ui/filter_search.tsx b/frontend/ui/filter_search.tsx index eef02685a4..ee4213f476 100644 --- a/frontend/ui/filter_search.tsx +++ b/frontend/ui/filter_search.tsx @@ -42,10 +42,10 @@ export class FilterSearch this.props.items.length < 4 ? "few-items" : "", ].join(" ") }}> +