From 748cbf00a76c6bd121d9dd22ad9dafbd5436c38c Mon Sep 17 00:00:00 2001 From: gabrielburnworth Date: Fri, 28 Jun 2024 09:54:57 -0700 Subject: [PATCH] add 3D garden environment --- app/controllers/dashboard_controller.rb | 2 + app/views/dashboard/promo.html.erb | 6 + config/routes.rb | 1 + ...38_add_three_d_garden_to_web_app_config.rb | 9 ++ db/structure.sql | 6 +- frontend/constants.ts | 1 + frontend/css/farm_designer/farm_designer.scss | 7 + .../farm_designer/__tests__/index_test.tsx | 8 ++ .../__tests__/three_d_garden_map_test.tsx | 13 ++ frontend/farm_designer/index.tsx | 126 +++++++++--------- .../__tests__/garden_map_legend_test.tsx | 16 +++ .../map/legend/garden_map_legend.tsx | 9 ++ .../farm_designer/map/legend/layer_toggle.tsx | 1 + frontend/farm_designer/three_d_garden_map.tsx | 10 ++ frontend/promo/__tests__/index_test.tsx | 11 ++ frontend/promo/__tests__/promo_test.tsx | 11 ++ frontend/promo/index.tsx | 4 + frontend/promo/promo.tsx | 5 + frontend/session_keys.ts | 3 +- frontend/settings/default_values.ts | 1 + .../three_d_garden/__tests__/index_test.tsx | 41 ++++++ frontend/three_d_garden/index.tsx | 47 +++++++ 22 files changed, 274 insertions(+), 64 deletions(-) create mode 100644 app/views/dashboard/promo.html.erb create mode 100644 db/migrate/20240625195838_add_three_d_garden_to_web_app_config.rb create mode 100644 frontend/farm_designer/__tests__/three_d_garden_map_test.tsx create mode 100644 frontend/farm_designer/three_d_garden_map.tsx create mode 100644 frontend/promo/__tests__/index_test.tsx create mode 100644 frontend/promo/__tests__/promo_test.tsx create mode 100644 frontend/promo/index.tsx create mode 100644 frontend/promo/promo.tsx create mode 100644 frontend/three_d_garden/__tests__/index_test.tsx create mode 100644 frontend/three_d_garden/index.tsx diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 6d72bedc23..4140ba75fd 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -13,6 +13,7 @@ class DashboardController < ApplicationController :terminal, :tos_update, :try_farmbot, + :promo, ] OUTPUT_URL = "/" + File.join("assets", "parcel") # <= served from public/ dir @@ -32,6 +33,7 @@ class DashboardController < ApplicationController tos_update: "/tos_update/index.tsx", demo: "/demo/index.tsx", try_farmbot: "/try_farmbot/index.tsx", + promo: "/promo/index.tsx", os_download: "/os_download/index.tsx", featured: "/featured/index.tsx", terminal: "/terminal/index.tsx", diff --git a/app/views/dashboard/promo.html.erb b/app/views/dashboard/promo.html.erb new file mode 100644 index 0000000000..f23398816f --- /dev/null +++ b/app/views/dashboard/promo.html.erb @@ -0,0 +1,6 @@ +<% # Intentionally blank %> + diff --git a/config/routes.rb b/config/routes.rb index 0c750f4347..2fccffaf7c 100755 --- a/config/routes.rb +++ b/config/routes.rb @@ -130,6 +130,7 @@ get "/demo" => "dashboard#demo", as: :demo_main get "/try_farmbot" => "dashboard#try_farmbot", as: :try_farmbot_main + get "/promo" => "dashboard#promo", as: :promo_main get "/os" => "dashboard#os_download", as: :os_download get "/featured" => "dashboard#featured", as: :featured get "/password_reset/*token" => "dashboard#password_reset", as: :password_reset diff --git a/db/migrate/20240625195838_add_three_d_garden_to_web_app_config.rb b/db/migrate/20240625195838_add_three_d_garden_to_web_app_config.rb new file mode 100644 index 0000000000..66ee11d2df --- /dev/null +++ b/db/migrate/20240625195838_add_three_d_garden_to_web_app_config.rb @@ -0,0 +1,9 @@ +class AddThreeDGardenToWebAppConfig < ActiveRecord::Migration[6.1] + def up + add_column :web_app_configs, :three_d_garden, :boolean, default: false + end + + def down + remove_column :web_app_configs, :three_d_garden + end +end diff --git a/db/structure.sql b/db/structure.sql index b704281f4e..7c33c62e9d 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -2036,7 +2036,8 @@ CREATE TABLE public.web_app_configs ( show_uncropped_camera_view_area boolean DEFAULT false, default_plant_depth integer DEFAULT 5, show_missed_step_plot boolean DEFAULT false, - enable_3d_electronics_box_top boolean DEFAULT true + enable_3d_electronics_box_top boolean DEFAULT true, + three_d_garden boolean DEFAULT false ); @@ -3987,6 +3988,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240118204046'), ('20240202171922'), ('20240207234421'), -('20240405171128'); +('20240405171128'), +('20240625195838'); diff --git a/frontend/constants.ts b/frontend/constants.ts index 5f1cb92797..9c9a2ef8a5 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -2166,6 +2166,7 @@ export enum DeviceSetting { showReadingsMapLayer = `Show Readings Map Layer`, showMoisture = `Moisture`, showMoistureInterpolationMapLayer = `Show Moisture Interpolation Map Layer`, + show3DMap = `3D Map`, // Controls invertJogButtonXAxis = `X Axis`, diff --git a/frontend/css/farm_designer/farm_designer.scss b/frontend/css/farm_designer/farm_designer.scss index d5d2060d8a..c1d421f73a 100644 --- a/frontend/css/farm_designer/farm_designer.scss +++ b/frontend/css/farm_designer/farm_designer.scss @@ -1201,3 +1201,10 @@ } } } + +.three-d-garden { + position: relative; + height: 100vh; + width: 100vw; + cursor: grab; +} diff --git a/frontend/farm_designer/__tests__/index_test.tsx b/frontend/farm_designer/__tests__/index_test.tsx index 26acd1746b..fdd940d4c5 100644 --- a/frontend/farm_designer/__tests__/index_test.tsx +++ b/frontend/farm_designer/__tests__/index_test.tsx @@ -129,6 +129,7 @@ describe("", () => { p.designer.openedSavedGarden = 1; const wrapper = mount(); expect(wrapper.text().toLowerCase()).toContain("viewing saved garden"); + expect(wrapper.html()).not.toContain("three-d-garden"); }); it("toggles setting", () => { @@ -157,6 +158,13 @@ describe("", () => { const i = new FarmDesigner(p); expect(i.getBotOriginQuadrant()).toEqual(1); }); + + it("renders 3D garden", () => { + const p = fakeProps(); + p.getConfigValue = () => true; + const wrapper = mount(); + expect(wrapper.html()).toContain("three-d-garden"); + }); }); describe("getDefaultAxisLength()", () => { diff --git a/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx b/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx new file mode 100644 index 0000000000..5a5b0dc7f9 --- /dev/null +++ b/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { mount } from "enzyme"; +import { ThreeDGardenMapProps, ThreeDGardenMap } from "../three_d_garden_map"; + +describe("", () => { + const fakeProps = (): ThreeDGardenMapProps => ({ + }); + + it("renders", () => { + const wrapper = mount(); + expect(wrapper.html()).toContain("three-d-garden"); + }); +}); diff --git a/frontend/farm_designer/index.tsx b/frontend/farm_designer/index.tsx index 015872b2ce..d43c375747 100755 --- a/frontend/farm_designer/index.tsx +++ b/frontend/farm_designer/index.tsx @@ -24,6 +24,7 @@ import { SavedGardenHUD } from "../saved_gardens/saved_gardens"; import { calculateImageAgeInfo } from "../photos/photo_filter_settings/util"; import { Xyz } from "farmbot"; import { ProfileViewer } from "./map/profile"; +import { ThreeDGardenMap } from "./three_d_garden_map"; export const getDefaultAxisLength = (getConfigValue: GetWebAppConfigValue): Record => { @@ -192,72 +193,75 @@ export class RawFarmDesigner {this.props.children || React.createElement(Plants)} -
- + :
+ +
} + + {this.props.designer.openedSavedGarden && + } + + {!this.props.getConfigValue(BooleanSetting.three_d_garden) && + -
- - {this.props.designer.openedSavedGarden && - } - - + mapTransformProps={this.mapTransformProps} + allPoints={this.props.allPoints} />} ; } } diff --git a/frontend/farm_designer/map/legend/__tests__/garden_map_legend_test.tsx b/frontend/farm_designer/map/legend/__tests__/garden_map_legend_test.tsx index 29c1147ead..11cd0e8d75 100644 --- a/frontend/farm_designer/map/legend/__tests__/garden_map_legend_test.tsx +++ b/frontend/farm_designer/map/legend/__tests__/garden_map_legend_test.tsx @@ -10,6 +10,11 @@ jest.mock("../../../../config_storage/actions", () => ({ setWebAppConfigValue: jest.fn(), })); +let mockDev = false; +jest.mock("../../../../settings/dev/dev_support", () => ({ + DevSettings: { futureFeaturesEnabled: () => mockDev } +})); + import React from "react"; import { shallow, mount } from "enzyme"; import { @@ -63,6 +68,7 @@ describe("", () => { expect(wrapper.html()).toContain("filter"); expect(wrapper.html()).toContain("extras"); expect(wrapper.html()).not.toContain("-100"); + expect(wrapper.text().toLowerCase()).not.toContain("3d map"); }); it("renders with readings", () => { @@ -77,6 +83,16 @@ describe("", () => { wrapper.find(".fb-toggle-button").last().simulate("click"); expect(wrapper.html()).toContain("-100"); }); + + it("renders 3D map toggle", () => { + mockDev = true; + const p = fakeProps(); + const wrapper = mount(); + expect(wrapper.text().toLowerCase()).toContain("3d map"); + wrapper.find(".fb-layer-toggle").last().simulate("click"); + expect(setWebAppConfigValue).toHaveBeenCalledWith( + BooleanSetting.three_d_garden, true); + }); }); describe("", () => { diff --git a/frontend/farm_designer/map/legend/garden_map_legend.tsx b/frontend/farm_designer/map/legend/garden_map_legend.tsx index cd60de29fe..212fe96085 100644 --- a/frontend/farm_designer/map/legend/garden_map_legend.tsx +++ b/frontend/farm_designer/map/legend/garden_map_legend.tsx @@ -23,6 +23,7 @@ import { getModifiedClassName } from "../../../settings/default_values"; import { Position } from "@blueprintjs/core"; import { MapSizeInputs } from "../../map_size_setting"; import { OriginSelector } from "../../../settings/farm_designer_settings"; +import { DevSettings } from "../../../settings/dev/dev_support"; export const ZoomControls = ({ zoom, getConfigValue }: { zoom: (value: number) => () => void, @@ -115,6 +116,7 @@ export const FarmbotSubMenu = (props: SettingsSubMenuProps) => const LayerToggles = (props: GardenMapLegendProps) => { const { toggle, getConfigValue, dispatch } = props; const subMenuProps = { dispatch, getConfigValue }; + const threeDGarden = !!props.getConfigValue(BooleanSetting.three_d_garden); return
{ value={props.showMoistureInterpolationMap} label={DeviceSetting.showMoisture} onClick={toggle(BooleanSetting.show_moisture_interpolation_map)} />} + {DevSettings.futureFeaturesEnabled() && + dispatch(setWebAppConfigValue( + BooleanSetting.three_d_garden, !threeDGarden))} />}
; }; diff --git a/frontend/farm_designer/map/legend/layer_toggle.tsx b/frontend/farm_designer/map/legend/layer_toggle.tsx index d69d685bef..174e360daa 100644 --- a/frontend/farm_designer/map/legend/layer_toggle.tsx +++ b/frontend/farm_designer/map/legend/layer_toggle.tsx @@ -22,6 +22,7 @@ export function LayerToggle(props: LayerToggleProps) { const classNames = [ "fb-button", "fb-toggle-button", + "fb-layer-toggle", value ? "green" : "red", getModifiedClassName(props.settingName), ].join(" "); diff --git a/frontend/farm_designer/three_d_garden_map.tsx b/frontend/farm_designer/three_d_garden_map.tsx new file mode 100644 index 0000000000..63a288d2a6 --- /dev/null +++ b/frontend/farm_designer/three_d_garden_map.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import { ThreeDGarden } from "../three_d_garden"; + +export interface ThreeDGardenMapProps { +} + +export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => { + props; + return ; +}; diff --git a/frontend/promo/__tests__/index_test.tsx b/frontend/promo/__tests__/index_test.tsx new file mode 100644 index 0000000000..2ea54fe356 --- /dev/null +++ b/frontend/promo/__tests__/index_test.tsx @@ -0,0 +1,11 @@ +jest.mock("../../util/page", () => ({ entryPoint: jest.fn() })); + +import { entryPoint } from "../../util"; +import { Promo } from "../promo"; + +describe("Promo loader", () => { + it("calls entryPoint", async () => { + await import("../index"); + expect(entryPoint).toHaveBeenCalledWith(Promo); + }); +}); diff --git a/frontend/promo/__tests__/promo_test.tsx b/frontend/promo/__tests__/promo_test.tsx new file mode 100644 index 0000000000..39728c29b2 --- /dev/null +++ b/frontend/promo/__tests__/promo_test.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import { shallow } from "enzyme"; +import { Promo } from "../promo"; + +describe("", () => { + it("renders", () => { + console.error = jest.fn(); + const wrapper = shallow(); + expect(wrapper.html()).toContain("three-d-garden"); + }); +}); diff --git a/frontend/promo/index.tsx b/frontend/promo/index.tsx new file mode 100644 index 0000000000..2f8aeed030 --- /dev/null +++ b/frontend/promo/index.tsx @@ -0,0 +1,4 @@ +import { entryPoint } from "../util"; +import { Promo } from "./promo"; + +entryPoint(Promo); diff --git a/frontend/promo/promo.tsx b/frontend/promo/promo.tsx new file mode 100644 index 0000000000..71c89815d1 --- /dev/null +++ b/frontend/promo/promo.tsx @@ -0,0 +1,5 @@ +import React from "react"; +import { ThreeDGarden } from "../three_d_garden"; + +export const Promo = () => + ; diff --git a/frontend/session_keys.ts b/frontend/session_keys.ts index a112a97444..ad503516bc 100644 --- a/frontend/session_keys.ts +++ b/frontend/session_keys.ts @@ -4,7 +4,7 @@ import { StringConfigKey as WebAppStringConfigKey, } from "farmbot/dist/resources/configs/web_app"; -type WebAppBooleanConfigKeyAll = WebAppBooleanConfigKey; +type WebAppBooleanConfigKeyAll = WebAppBooleanConfigKey | "three_d_garden"; type WebAppNumberConfigKeyAll = WebAppNumberConfigKey; type WebAppStringConfigKeyAll = WebAppStringConfigKey; @@ -49,6 +49,7 @@ export const BooleanSetting: BooleanSettings = { crop_images: "crop_images", clip_image_layer: "clip_image_layer", highlight_modified_settings: "highlight_modified_settings", + three_d_garden: "three_d_garden" as WebAppBooleanConfigKey, /** Sequence settings */ confirm_step_deletion: "confirm_step_deletion", diff --git a/frontend/settings/default_values.ts b/frontend/settings/default_values.ts index e12074f823..e92f4a2c96 100644 --- a/frontend/settings/default_values.ts +++ b/frontend/settings/default_values.ts @@ -87,6 +87,7 @@ const DEFAULT_WEB_APP_CONFIG_VALUES: Record = { view_celery_script: false, highlight_modified_settings: true, show_advanced_settings: false, + ["three_d_garden" as Key]: false, }; const DEFAULT_EXPRESS_WEB_APP_CONFIG_VALUES = diff --git a/frontend/three_d_garden/__tests__/index_test.tsx b/frontend/three_d_garden/__tests__/index_test.tsx new file mode 100644 index 0000000000..5304f2c663 --- /dev/null +++ b/frontend/three_d_garden/__tests__/index_test.tsx @@ -0,0 +1,41 @@ +jest.mock("@react-three/drei", () => { + return { + Box: () =>
, + Circle: () =>
, + PerspectiveCamera: () =>
, + OrbitControls: () =>
, + }; +}); + +jest.mock("@react-three/fiber", () => ({ + Canvas: () =>
, + addEffect: jest.fn(), +})); + +import { mount } from "enzyme"; +import { + ThreeDGardenProps, ThreeDGarden, + ThreeDGardenModel, ThreeDGardenModelProps, + +} from ".."; +import React from "react"; + +describe("", () => { + const fakeProps = (): ThreeDGardenProps => ({ + }); + + it("renders", () => { + const wrapper = mount(); + expect(wrapper.html()).toContain("three-d-garden"); + }); +}); + +describe("", () => { + const fakeProps = (): ThreeDGardenModelProps => ({ + }); + + it("renders model", () => { + const wrapper = mount(); + expect(wrapper.html()).toContain("three-d-garden-model"); + }); +}); diff --git a/frontend/three_d_garden/index.tsx b/frontend/three_d_garden/index.tsx new file mode 100644 index 0000000000..a1f26de56b --- /dev/null +++ b/frontend/three_d_garden/index.tsx @@ -0,0 +1,47 @@ +/* eslint-disable react/no-unknown-property */ +import { Circle, PerspectiveCamera, OrbitControls, Box } from "@react-three/drei"; +import { Canvas } from "@react-three/fiber"; +import React from "react"; + +export interface ThreeDGardenProps { +} + +export const ThreeDGarden = (props: ThreeDGardenProps) => { + props; + return
+ + + +
; +}; + +export interface ThreeDGardenModelProps { +} + +export const ThreeDGardenModel = (props: ThreeDGardenModelProps) => { + props; + return + + + + + + + + + + + ; +};