designer settings menu
parent
cba5646e23
commit
9e8ae68da9
|
@ -37,10 +37,8 @@ const LengthInput = (props: LengthInputProps) =>
|
|||
</Row>;
|
||||
|
||||
export const MapSizeSetting =
|
||||
({ dispatch, getConfigValue }: MapSizeSettingProps) => {
|
||||
const mapSizeX = parseInt("" + getConfigValue(NumericSetting.map_size_x));
|
||||
const mapSizeY = parseInt("" + getConfigValue(NumericSetting.map_size_y));
|
||||
return <div className={"map-size-inputs"}>
|
||||
({ dispatch, getConfigValue }: MapSizeSettingProps) =>
|
||||
<div className={"map-size-setting"}>
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<label>{t("garden map size")}</label>
|
||||
|
@ -49,17 +47,28 @@ export const MapSizeSetting =
|
|||
<p>{t(Content.MAP_SIZE)}</p>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<LengthInput
|
||||
value={mapSizeX}
|
||||
label={t("x (mm)")}
|
||||
setting={NumericSetting.map_size_x}
|
||||
dispatch={dispatch} />
|
||||
<LengthInput
|
||||
value={mapSizeY}
|
||||
label={t("y (mm)")}
|
||||
setting={NumericSetting.map_size_y}
|
||||
<MapSizeInputs
|
||||
getConfigValue={getConfigValue}
|
||||
dispatch={dispatch} />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>;
|
||||
};
|
||||
|
||||
interface MapSizeInputsProps {
|
||||
dispatch: Function;
|
||||
getConfigValue: GetWebAppConfigValue;
|
||||
}
|
||||
|
||||
export const MapSizeInputs = (props: MapSizeInputsProps) =>
|
||||
<div className="map-size-inputs">
|
||||
<LengthInput
|
||||
value={parseInt("" + props.getConfigValue(NumericSetting.map_size_x))}
|
||||
label={t("x (mm)")}
|
||||
setting={NumericSetting.map_size_x}
|
||||
dispatch={props.dispatch} />
|
||||
<LengthInput
|
||||
value={parseInt("" + props.getConfigValue(NumericSetting.map_size_y))}
|
||||
label={t("y (mm)")}
|
||||
setting={NumericSetting.map_size_y}
|
||||
dispatch={props.dispatch} />
|
||||
</div>;
|
||||
|
|
|
@ -4,7 +4,7 @@ describe("fetchLabFeatures", () => {
|
|||
Object.defineProperty(window.location, "reload", { value: jest.fn() });
|
||||
it("basically just initializes stuff", () => {
|
||||
const val = fetchLabFeatures(jest.fn());
|
||||
expect(val.length).toBe(10);
|
||||
expect(val.length).toBe(7);
|
||||
expect(val[0].value).toBeFalsy();
|
||||
const { callback } = val[0];
|
||||
if (callback) {
|
||||
|
|
|
@ -5,7 +5,6 @@ import { maybeToggleFeature } from "./labs_features_list_data";
|
|||
import { ToolTips } from "../../constants";
|
||||
import { GetWebAppConfigValue } from "../../config_storage/actions";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { MapSizeSetting } from "../components/map_size_setting";
|
||||
|
||||
interface LabsFeaturesProps {
|
||||
getConfigValue: GetWebAppConfigValue;
|
||||
|
@ -28,9 +27,6 @@ export class LabsFeatures extends React.Component<LabsFeaturesProps, {}> {
|
|||
maybeToggleFeature(getConfigValue, dispatch)(x);
|
||||
this.forceUpdate();
|
||||
}} />
|
||||
<MapSizeSetting
|
||||
dispatch={this.props.dispatch}
|
||||
getConfigValue={this.props.getConfigValue} />
|
||||
</WidgetBody>
|
||||
</Widget>;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { BooleanSetting } from "../../session_keys";
|
||||
import { Content } from "../../constants";
|
||||
import { VirtualTrail } from "../../farm_designer/map/layers/farmbot/bot_trail";
|
||||
import { GetWebAppConfigValue, setWebAppConfigValue } from "../../config_storage/actions";
|
||||
import {
|
||||
GetWebAppConfigValue, setWebAppConfigValue
|
||||
} from "../../config_storage/actions";
|
||||
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
|
@ -44,13 +45,6 @@ export const fetchLabFeatures =
|
|||
storageKey: BooleanSetting.hide_sensors,
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: t("Display plant animations"),
|
||||
description: t(Content.PLANT_ANIMATIONS),
|
||||
storageKey: BooleanSetting.disable_animations,
|
||||
value: false,
|
||||
displayInvert: true
|
||||
},
|
||||
{
|
||||
name: t("Read speak logs in browser"),
|
||||
description: t(Content.BROWSER_SPEAK_LOGS),
|
||||
|
@ -64,13 +58,6 @@ export const fetchLabFeatures =
|
|||
value: false,
|
||||
confirmationMessage: t(Content.DISCARD_UNSAVED_CHANGES_CONFIRM)
|
||||
},
|
||||
{
|
||||
name: t("Display virtual FarmBot trail"),
|
||||
description: t(Content.VIRTUAL_TRAIL),
|
||||
storageKey: BooleanSetting.display_trail,
|
||||
value: false,
|
||||
callback: () => sessionStorage.setItem(VirtualTrail.records, "[]")
|
||||
},
|
||||
{
|
||||
name: t("Use 24-hour time format"),
|
||||
description: t(Content.TIME_FORMAT_24_HOUR),
|
||||
|
@ -85,12 +72,6 @@ export const fetchLabFeatures =
|
|||
value: false,
|
||||
displayInvert: true,
|
||||
},
|
||||
{
|
||||
name: t("Dynamic map size"),
|
||||
description: t(Content.DYNAMIC_MAP_SIZE),
|
||||
storageKey: BooleanSetting.dynamic_map,
|
||||
value: false
|
||||
},
|
||||
].map(fetchSettingValue(getConfigValue)));
|
||||
|
||||
/** Always allow toggling from true => false (deactivate).
|
||||
|
|
|
@ -381,6 +381,25 @@
|
|||
}
|
||||
}
|
||||
|
||||
.settings-panel-content {
|
||||
margin-top: 5rem;
|
||||
button {
|
||||
margin-top: 1.75rem;
|
||||
}
|
||||
p {
|
||||
padding: 1rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.map-size-inputs {
|
||||
.row {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
label {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.saved-garden-panel-content {
|
||||
&.with-nav {
|
||||
margin-top: 6rem;
|
||||
|
|
|
@ -989,7 +989,7 @@ ul {
|
|||
}
|
||||
}
|
||||
|
||||
.map-size-inputs {
|
||||
.map-size-setting {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,4 +43,12 @@ describe("<DesignerNavTabs />", () => {
|
|||
expect(wrapper.hasClass("green-panel")).toBeTruthy();
|
||||
expect(wrapper.html()).toContain("active");
|
||||
});
|
||||
|
||||
it("renders for settings", () => {
|
||||
mockPath = "/app/designer/settings";
|
||||
mockDev = true;
|
||||
const wrapper = shallow(<DesignerNavTabs />);
|
||||
expect(wrapper.hasClass("gray-panel")).toBeTruthy();
|
||||
expect(wrapper.html()).toContain("active");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
jest.mock("react-redux", () => ({ connect: jest.fn() }));
|
||||
|
||||
jest.mock("../../config_storage/actions", () => ({
|
||||
getWebAppConfigValue: jest.fn(() => jest.fn(() => true)),
|
||||
setWebAppConfigValue: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import {
|
||||
DesignerSettings, DesignerSettingsProps, mapStateToProps
|
||||
} from "../settings";
|
||||
import { fakeState } from "../../__test_support__/fake_state";
|
||||
import { BooleanSetting } from "../../session_keys";
|
||||
import { setWebAppConfigValue } from "../../config_storage/actions";
|
||||
|
||||
describe("<DesignerSettings />", () => {
|
||||
const fakeProps = (): DesignerSettingsProps => ({
|
||||
dispatch: jest.fn(),
|
||||
getConfigValue: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders settings", () => {
|
||||
const wrapper = mount(<DesignerSettings {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("size");
|
||||
});
|
||||
|
||||
it("toggles setting", () => {
|
||||
const wrapper = mount(<DesignerSettings {...fakeProps()} />);
|
||||
wrapper.find("button").at(1).simulate("click");
|
||||
expect(setWebAppConfigValue)
|
||||
.toHaveBeenCalledWith(BooleanSetting.display_trail, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const props = mapStateToProps(fakeState());
|
||||
const value = props.getConfigValue(BooleanSetting.show_plants);
|
||||
expect(value).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -1,6 +1,8 @@
|
|||
import * as React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { BotTrail, BotTrailProps, VirtualTrail } from "../bot_trail";
|
||||
import {
|
||||
BotTrail, BotTrailProps, VirtualTrail, resetVirtualTrail
|
||||
} from "../bot_trail";
|
||||
import {
|
||||
fakeMapTransformProps
|
||||
} from "../../../../../__test_support__/map_transform_props";
|
||||
|
@ -75,3 +77,11 @@ describe("<BotTrail/>", () => {
|
|||
});
|
||||
|
||||
});
|
||||
|
||||
describe("resetVirtualTrail()", () => {
|
||||
it("clears data", () => {
|
||||
sessionStorage.setItem(VirtualTrail.records, "[1]");
|
||||
resetVirtualTrail();
|
||||
expect(sessionStorage.getItem(VirtualTrail.records)).toEqual("[]");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -70,3 +70,6 @@ export function BotTrail(props: BotTrailProps) {
|
|||
})}
|
||||
</g>;
|
||||
}
|
||||
|
||||
export const resetVirtualTrail = () =>
|
||||
sessionStorage.setItem(VirtualTrail.records, "[]");
|
||||
|
|
|
@ -9,6 +9,7 @@ export enum Panel {
|
|||
Plants = "Plants",
|
||||
FarmEvents = "FarmEvents",
|
||||
SavedGardens = "SavedGardens",
|
||||
Settings = "Settings",
|
||||
}
|
||||
|
||||
type Tabs = keyof typeof Panel;
|
||||
|
@ -18,6 +19,7 @@ export const TAB_COLOR: { [key in Panel]: string } = {
|
|||
[Panel.Plants]: "green",
|
||||
[Panel.FarmEvents]: "yellow",
|
||||
[Panel.SavedGardens]: "green",
|
||||
[Panel.Settings]: "gray",
|
||||
};
|
||||
|
||||
const iconFile = (icon: string) => `/app-resources/img/icons/${icon}.svg`;
|
||||
|
@ -27,6 +29,7 @@ export const TAB_ICON: { [key in Panel]: string } = {
|
|||
[Panel.Plants]: iconFile("plant"),
|
||||
[Panel.FarmEvents]: iconFile("calendar"),
|
||||
[Panel.SavedGardens]: iconFile("gardens"),
|
||||
[Panel.Settings]: iconFile("gardens"),
|
||||
};
|
||||
|
||||
const getCurrentTab = (): Tabs => {
|
||||
|
@ -37,6 +40,8 @@ const getCurrentTab = (): Tabs => {
|
|||
return Panel.FarmEvents;
|
||||
} else if (pathArray.includes("saved_gardens")) {
|
||||
return Panel.SavedGardens;
|
||||
} else if (pathArray.includes("settings")) {
|
||||
return Panel.Settings;
|
||||
} else {
|
||||
return Panel.Plants;
|
||||
}
|
||||
|
@ -48,15 +53,16 @@ interface NavTabProps {
|
|||
panel: Panel;
|
||||
linkTo: string;
|
||||
title: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
const NavTab = (props: NavTabProps) =>
|
||||
<Link to={props.linkTo}
|
||||
<Link to={props.linkTo} style={props.icon ? { flex: 0.3 } : {}}
|
||||
className={getCurrentTab() === props.panel ? "active" : ""}>
|
||||
{DevSettings.futureFeaturesEnabled()
|
||||
? <img {...common}
|
||||
src={TAB_ICON[props.panel]} title={props.title} />
|
||||
: props.title}
|
||||
: props.icon ? <i className={props.icon} /> : props.title}
|
||||
</Link>;
|
||||
|
||||
export function DesignerNavTabs(props: { hidden?: boolean }) {
|
||||
|
@ -73,6 +79,8 @@ export function DesignerNavTabs(props: { hidden?: boolean }) {
|
|||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<NavTab panel={Panel.SavedGardens}
|
||||
linkTo={"/app/designer/saved_gardens"} title={t("Gardens")} />}
|
||||
<NavTab panel={Panel.Settings} icon={"fa fa-gear"}
|
||||
linkTo={"/app/designer/settings"} title={t("Settings")} />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
import * as React from "react";
|
||||
import { Everything } from "../interfaces";
|
||||
import { connect } from "react-redux";
|
||||
import { Content } from "../constants";
|
||||
import { DesignerPanel, DesignerPanelContent } from "./plants/designer_panel";
|
||||
import { t } from "../i18next_wrapper";
|
||||
import {
|
||||
GetWebAppConfigValue, getWebAppConfigValue, setWebAppConfigValue
|
||||
} from "../config_storage/actions";
|
||||
import { Row, Col } from "../ui";
|
||||
import { ToggleButton } from "../controls/toggle_button";
|
||||
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||
import { BooleanSetting } from "../session_keys";
|
||||
import { resetVirtualTrail } from "./map/layers/farmbot/bot_trail";
|
||||
import { MapSizeInputs } from "../account/components/map_size_setting";
|
||||
import { DesignerNavTabs } from "./panel_header";
|
||||
|
||||
export const mapStateToProps = (props: Everything): DesignerSettingsProps => ({
|
||||
dispatch: props.dispatch,
|
||||
getConfigValue: getWebAppConfigValue(() => props),
|
||||
});
|
||||
|
||||
export interface DesignerSettingsProps {
|
||||
dispatch: Function;
|
||||
getConfigValue: GetWebAppConfigValue;
|
||||
}
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class DesignerSettings
|
||||
extends React.Component<DesignerSettingsProps, {}> {
|
||||
|
||||
render() {
|
||||
return <DesignerPanel panelName={"settings"} panelColor={"gray"}>
|
||||
<DesignerNavTabs />
|
||||
<DesignerPanelContent panelName={"settings"}>
|
||||
{DESIGNER_SETTINGS.map(setting =>
|
||||
<Setting key={setting.title}
|
||||
dispatch={this.props.dispatch}
|
||||
getConfigValue={this.props.getConfigValue}
|
||||
setting={setting.setting}
|
||||
title={setting.title}
|
||||
description={setting.description}
|
||||
invert={setting.invert}
|
||||
callback={setting.callback} />)}
|
||||
<MapSizeInputs
|
||||
getConfigValue={this.props.getConfigValue}
|
||||
dispatch={this.props.dispatch} />
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
}
|
||||
|
||||
interface SettingDescriptionProps {
|
||||
setting?: BooleanConfigKey;
|
||||
title: string;
|
||||
description: string;
|
||||
invert?: boolean;
|
||||
callback?: () => void;
|
||||
}
|
||||
|
||||
interface SettingProps
|
||||
extends DesignerSettingsProps, SettingDescriptionProps { }
|
||||
|
||||
const Setting = (props: SettingProps) => {
|
||||
const { title, setting, callback } = props;
|
||||
const value = setting ? !!props.getConfigValue(setting) : undefined;
|
||||
return <div className="designer-setting">
|
||||
<Row>
|
||||
<Col xs={9}>
|
||||
<label>{t(title)}</label>
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
{setting && <ToggleButton
|
||||
toggleValue={props.invert ? !value : value}
|
||||
toggleAction={() => {
|
||||
props.dispatch(setWebAppConfigValue(setting, !value));
|
||||
callback && callback();
|
||||
}}
|
||||
title={`${t("toggle")} ${title}`}
|
||||
customText={{ textFalse: t("off"), textTrue: t("on") }} />}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<p>{t(props.description)}</p>
|
||||
</Row>
|
||||
</div>;
|
||||
};
|
||||
|
||||
const DESIGNER_SETTINGS: SettingDescriptionProps[] = [
|
||||
{
|
||||
title: t("Display plant animations"),
|
||||
description: t(Content.PLANT_ANIMATIONS),
|
||||
setting: BooleanSetting.disable_animations,
|
||||
invert: true
|
||||
},
|
||||
{
|
||||
title: t("Display virtual FarmBot trail"),
|
||||
description: t(Content.VIRTUAL_TRAIL),
|
||||
setting: BooleanSetting.display_trail,
|
||||
callback: resetVirtualTrail,
|
||||
},
|
||||
{
|
||||
title: t("Dynamic map size"),
|
||||
description: t(Content.DYNAMIC_MAP_SIZE),
|
||||
setting: BooleanSetting.dynamic_map,
|
||||
},
|
||||
{
|
||||
title: t("Map size"),
|
||||
description: t(Content.MAP_SIZE),
|
||||
},
|
||||
];
|
|
@ -304,4 +304,12 @@ export const UNBOUND_ROUTES = [
|
|||
getChild: () => import("./farm_designer/saved_gardens/saved_gardens"),
|
||||
childKey: "SavedGardens"
|
||||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/settings",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/settings"),
|
||||
childKey: "DesignerSettings"
|
||||
}),
|
||||
].concat([NOT_FOUND_ROUTE]);
|
||||
|
|
Loading…
Reference in New Issue