diff --git a/Gemfile.lock b/Gemfile.lock index a9bd18e878..422fbcd4d9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,13 +68,13 @@ GEM addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) amq-protocol (2.3.3) - ast (2.4.2) + ast (2.4.3) base64 (0.2.0) bcrypt (3.1.20) bigdecimal (3.1.9) builder (3.3.0) - bunny (2.23.0) - amq-protocol (~> 2.3, >= 2.3.1) + bunny (2.24.0) + amq-protocol (~> 2.3) sorted_set (~> 1, >= 1.0.2) case_transform (0.2) activesupport @@ -104,7 +104,7 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - diff-lcs (1.6.0) + diff-lcs (1.6.1) digest-crc (0.7.0) rake (>= 12.0.0, < 14.0.0) discard (1.4.0) @@ -144,7 +144,8 @@ GEM google-cloud-core (1.8.0) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) - google-cloud-env (2.2.1) + google-cloud-env (2.2.2) + base64 (~> 0.2) faraday (>= 1.0, < 3.a) google-cloud-errors (1.5.0) google-cloud-storage (1.55.0) @@ -157,7 +158,7 @@ GEM googleauth (~> 1.9) mini_mime (~> 1.0) google-logging-utils (0.1.0) - googleauth (1.13.1) + googleauth (1.14.0) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.2) google-logging-utils (~> 0.1) @@ -171,7 +172,7 @@ GEM mutex_m i18n (1.14.7) concurrent-ruby (~> 1.0) - json (2.10.1) + json (2.10.2) jsonapi-renderer (0.2.2) jwt (2.10.1) base64 @@ -204,7 +205,7 @@ GEM marcel (1.0.4) method_source (1.1.0) mini_mime (1.1.5) - minitest (5.25.4) + minitest (5.25.5) multi_json (1.15.0) mutations (0.9.1) activesupport @@ -221,13 +222,13 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.4) - nokogiri (1.18.3-aarch64-linux-gnu) + nokogiri (1.18.6-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.3-x86_64-linux-gnu) + nokogiri (1.18.6-x86_64-linux-gnu) racc (~> 1.4) orm_adapter (0.5.0) os (1.1.4) - parser (3.3.7.1) + parser (3.3.7.3) ast (~> 2.4.1) racc passenger (6.0.23) @@ -332,7 +333,7 @@ GEM scenic (1.8.0) activerecord (>= 4.0.0) railties (>= 4.0.0) - scout_apm (5.6.1) + scout_apm (5.6.2) parser secure_headers (7.1.0) set (1.1.1) @@ -367,7 +368,7 @@ GEM trailblazer-option (0.1.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - tzinfo-data (1.2025.1) + tzinfo-data (1.2025.2) tzinfo (>= 1.0.0) uber (0.1.0) uri (1.0.3) diff --git a/frontend/__test_support__/fake_designer_state.ts b/frontend/__test_support__/fake_designer_state.ts index 1ffae18e72..56a89232fa 100644 --- a/frontend/__test_support__/fake_designer_state.ts +++ b/frontend/__test_support__/fake_designer_state.ts @@ -51,6 +51,7 @@ export const fakeDesignerState = (): DesignerState => ({ cropRadius: undefined, distanceIndicator: "", panelOpen: true, + threeDTopDownView: false, }); export const fakeHelpState = (): HelpState => ({ diff --git a/frontend/__test_support__/three_d_mocks.tsx b/frontend/__test_support__/three_d_mocks.tsx index 664ce68788..d8832210fe 100644 --- a/frontend/__test_support__/three_d_mocks.tsx +++ b/frontend/__test_support__/three_d_mocks.tsx @@ -583,6 +583,8 @@ jest.mock("@react-three/drei", () => {
{name}
, Cylinder: ({ name }: { name: string }) =>
{name}
, + Torus: ({ name }: { name: string }) => +
{name}
, // eslint-disable-next-line @typescript-eslint/no-explicit-any Sphere: (props: any) =>
{props.children}
, diff --git a/frontend/constants.ts b/frontend/constants.ts index 10a7738df0..8550508d87 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -1064,6 +1064,17 @@ export namespace Content { trim(`Are you sure you want to delete all logs? A page refresh will be required.`); + export const SHOW_3D_VIEW_DESCRIPTION_DESKTOP = + (`**3D Controls** + - Scroll to zoom + - Click and drag to rotate + - Right-click and drag to pan`); + + export const SHOW_3D_VIEW_DESCRIPTION_MOBILE = + (`**3D Controls** + - Pinch to zoom and pan + - Touch and drag to rotate`); + // Front Page export const TOS_UPDATE = trim(`The terms of service have recently changed. You must accept the @@ -2215,7 +2226,7 @@ export enum DeviceSetting { showReadingsMapLayer = `Show Readings Map Layer`, showMoisture = `Moisture`, showMoistureInterpolationMapLayer = `Show Moisture Interpolation Map Layer`, - show3DMap = `3D Map`, + show3DMap = `3D Map beta`, // Controls invertJogButtonXAxis = `X Axis`, @@ -2475,6 +2486,7 @@ export enum Actions { // 3D SET_DISTANCE_INDICATOR = "SET_DISTANCE_INDICATOR", + TOGGLE_3D_TOP_DOWN_VIEW = "TOGGLE_3D_TOP_DOWN_VIEW", // Regimens PUSH_WEEK = "PUSH_WEEK", diff --git a/frontend/css/farm_designer/farm_designer.scss b/frontend/css/farm_designer/farm_designer.scss index a9f5649faf..1f959ae609 100644 --- a/frontend/css/farm_designer/farm_designer.scss +++ b/frontend/css/farm_designer/farm_designer.scss @@ -805,3 +805,26 @@ transform: translateX(-22.5rem); } } + +.three-d-map-toggle-menu { + position: fixed; + bottom: 0; + right: 0; + padding: 1rem; + button { + height: 3.5rem; + width: 3.5rem; + i { + font-size: 1.5rem; + } + &.active { + background-color: $blue !important; + } + } + .three-d-map-toggle { + padding: 0 1.5rem; + background-color: var(--main-bg); + border-radius: 5px; + height: 3.5rem; + } +} diff --git a/frontend/farm_designer/__tests__/reducer_test.ts b/frontend/farm_designer/__tests__/reducer_test.ts index cdbf437c45..950985f050 100644 --- a/frontend/farm_designer/__tests__/reducer_test.ts +++ b/frontend/farm_designer/__tests__/reducer_test.ts @@ -126,6 +126,15 @@ describe("designer reducer", () => { expect(newState.distanceIndicator).toEqual("setting"); }); + it("sets top down view", () => { + const action: ReduxAction = { + type: Actions.TOGGLE_3D_TOP_DOWN_VIEW, + payload: true, + }; + const newState = designer(oldState(), action); + expect(newState.threeDTopDownView).toEqual(true); + }); + it("sets panel open state", () => { const action: ReduxAction = { type: Actions.SET_PANEL_OPEN, diff --git a/frontend/farm_designer/index.tsx b/frontend/farm_designer/index.tsx index 5e2d3862ef..74042a2768 100755 --- a/frontend/farm_designer/index.tsx +++ b/frontend/farm_designer/index.tsx @@ -28,6 +28,8 @@ import { Outlet } from "react-router"; import { ErrorBoundary } from "../error_boundary"; import { get3DConfigValueFunction } from "../settings/three_d_settings"; import { isDesktop, isMobile } from "../screen_size"; +import { NavigationContext } from "../routes_helpers"; +import { ThreeDGardenToggle } from "../three_d_garden"; export const getDefaultAxisLength = (getConfigValue: GetWebAppConfigValue): Record => { @@ -139,6 +141,10 @@ export class RawFarmDesigner get mapPanelClassName() { return mapPanelClassName(this.props.designer); } + static contextType = NavigationContext; + context!: React.ContextType; + navigate = this.context; + render() { const { legend_menu_open, @@ -163,6 +169,8 @@ export class RawFarmDesigner const mapPadding = getMapPadding(getPanelStatus(this.props.designer)); const padHeightOffset = mapPadding.top - mapPadding.top / zoom_level; + const threeDGarden = !!this.props.getConfigValue(BooleanSetting.three_d_garden); + return
- {this.props.getConfigValue(BooleanSetting.three_d_garden) + {threeDGarden ? } - {!this.props.getConfigValue(BooleanSetting.three_d_garden) && + {!threeDGarden && } + + ; } } diff --git a/frontend/farm_designer/interfaces.ts b/frontend/farm_designer/interfaces.ts index dac57756b1..5982c63e13 100644 --- a/frontend/farm_designer/interfaces.ts +++ b/frontend/farm_designer/interfaces.ts @@ -177,6 +177,7 @@ export interface DesignerState { cropRadius: number | undefined; distanceIndicator: string; panelOpen: boolean; + threeDTopDownView: boolean; } export type TaggedExecutable = TaggedSequence | TaggedRegimen; 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 11cd0e8d75..b0a439ed8e 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,16 +10,12 @@ 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 { GardenMapLegend, ZoomControls, PointsSubMenu, FarmbotSubMenu, PlantsSubMenu, MapSettingsContent, SettingsSubMenuProps, + ZoomControlsProps, } from "../garden_map_legend"; import { GardenMapLegendProps } from "../../interfaces"; import { BooleanSetting } from "../../../../session_keys"; @@ -83,23 +79,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("", () => { + const fakeProps = (): ZoomControlsProps => ({ + zoom: jest.fn(), + getConfigValue: jest.fn(), + }); + const expectDisabledBtnCountToEqual = (expected: number) => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(".disabled").length).toEqual(expected); }; diff --git a/frontend/farm_designer/map/legend/garden_map_legend.tsx b/frontend/farm_designer/map/legend/garden_map_legend.tsx index 2122e3b944..c37cd21488 100644 --- a/frontend/farm_designer/map/legend/garden_map_legend.tsx +++ b/frontend/farm_designer/map/legend/garden_map_legend.tsx @@ -23,23 +23,29 @@ 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, - getConfigValue: GetWebAppConfigValue -}) => { +export interface ZoomControlsProps { + zoom(value: number): () => void; + getConfigValue: GetWebAppConfigValue; +} + +export const ZoomControls = (props: ZoomControlsProps) => { + const { zoom, getConfigValue } = props; const plusBtnClass = atMaxZoom(getConfigValue) ? "disabled" : ""; const minusBtnClass = atMinZoom(getConfigValue) ? "disabled" : ""; return
; }; diff --git a/frontend/farm_designer/map/legend/layer_toggle.tsx b/frontend/farm_designer/map/legend/layer_toggle.tsx index caac1d6b7c..5fa31cae02 100644 --- a/frontend/farm_designer/map/legend/layer_toggle.tsx +++ b/frontend/farm_designer/map/legend/layer_toggle.tsx @@ -13,6 +13,7 @@ export interface LayerToggleProps { onClick(): void; popover?: React.ReactElement; submenuTitle?: string; + className?: string; } /** A flipper type switch for showing/hiding the layers of the garden map. */ @@ -25,6 +26,7 @@ export function LayerToggle(props: LayerToggleProps) { "fb-layer-toggle", value ? "green" : "red", getModifiedClassName(props.settingName), + props.className, ].join(" "); return