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