settings panel updates
parent
4375a935f0
commit
bee1e0e074
|
@ -19,4 +19,5 @@ export const fakeDesignerState = (): DesignerState => ({
|
|||
openedSavedGarden: undefined,
|
||||
tryGroupSortType: undefined,
|
||||
editGroupAreaInMap: false,
|
||||
settingsSearchTerm: "",
|
||||
});
|
||||
|
|
|
@ -923,6 +923,8 @@ export namespace TourContent {
|
|||
}
|
||||
|
||||
export enum DeviceSetting {
|
||||
axisHeadingLabels = ``,
|
||||
|
||||
// Homing and calibration
|
||||
homingAndCalibration = `Homing and Calibration`,
|
||||
homing = `Homing`,
|
||||
|
@ -974,6 +976,11 @@ export enum DeviceSetting {
|
|||
|
||||
// Pin Guard
|
||||
pinGuard = `Pin Guard`,
|
||||
pinGuard1 = `Pin Guard 1`,
|
||||
pinGuard2 = `Pin Guard 2`,
|
||||
pinGuard3 = `Pin Guard 3`,
|
||||
pinGuard4 = `Pin Guard 4`,
|
||||
pinGuard5 = `Pin Guard 5`,
|
||||
|
||||
// Danger Zone
|
||||
dangerZone = `Danger Zone`,
|
||||
|
@ -981,6 +988,8 @@ export enum DeviceSetting {
|
|||
|
||||
// Pin Bindings
|
||||
pinBindings = `Pin Bindings`,
|
||||
savedPinBindings = `Saved pin bindings`,
|
||||
addNewPinBinding = `Add new pin binding`,
|
||||
|
||||
// FarmBot OS
|
||||
farmbot = `FarmBot`,
|
||||
|
@ -1131,6 +1140,7 @@ export enum Actions {
|
|||
SET_DRAWN_WEED_DATA = "SET_DRAWN_WEED_DATA",
|
||||
CHOOSE_SAVED_GARDEN = "CHOOSE_SAVED_GARDEN",
|
||||
TRY_SORT_TYPE = "TRY_SORT_TYPE",
|
||||
SET_SETTINGS_SEARCH_TERM = "SET_SETTINGS_SEARCH_TERM",
|
||||
EDIT_GROUP_AREA_IN_MAP = "EDIT_GROUP_AREA_IN_MAP",
|
||||
|
||||
// Regimens
|
||||
|
|
|
@ -38,6 +38,7 @@ $pink: #ebb;
|
|||
$light_red: #e99;
|
||||
$red: #e66;
|
||||
$dark_red: #f00;
|
||||
$medium_dark_red: #c00;
|
||||
$darkest_red: #900;
|
||||
$panel_green: #35761b;
|
||||
$panel_light_green: #f3f9f1;
|
||||
|
|
|
@ -158,6 +158,17 @@
|
|||
left: 1rem;
|
||||
cursor: default !important;
|
||||
}
|
||||
.fa-times {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 0.5rem;
|
||||
color: $darkest_red;
|
||||
font-size: 1.3rem;
|
||||
&:hover {
|
||||
color: $medium_dark_red;
|
||||
}
|
||||
}
|
||||
}
|
||||
input {
|
||||
background: transparent;
|
||||
|
|
|
@ -773,19 +773,101 @@
|
|||
}
|
||||
}
|
||||
|
||||
.no-pad {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.settings-panel-content {
|
||||
max-height: calc(100vh - 15rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin-top: 5rem;
|
||||
padding: 0;
|
||||
margin-top: 6rem;
|
||||
padding-bottom: 5rem;
|
||||
button {
|
||||
margin-top: 1.75rem;
|
||||
.section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
p {
|
||||
padding: 0.5rem;
|
||||
.bulk-expand-controls {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.row:first-child {
|
||||
margin-right: 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.row:nth-child(2) {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 3rem;
|
||||
}
|
||||
.label-headings {
|
||||
margin-right: 2rem;
|
||||
label {
|
||||
line-height: 1rem;
|
||||
}
|
||||
}
|
||||
.release-notes-wrapper {
|
||||
float: right !important;
|
||||
}
|
||||
.network-not-found-timer {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.pin-guard-input-row {
|
||||
.row {
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
padding-left: 0;
|
||||
padding-right: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
.pin-bindings {
|
||||
margin-right: 1rem;
|
||||
.row {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin-left: 1rem;
|
||||
margin-right: 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
div[class*=col-] {
|
||||
padding: 0;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.bindings-list {
|
||||
margin-left: -5px;
|
||||
.binding-action {
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
.pin-binding-input-rows {
|
||||
margin-right: 1rem;
|
||||
margin-left: -15px;
|
||||
label {
|
||||
margin-left: 1rem !important;
|
||||
}
|
||||
.green {
|
||||
float: left;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.row:last-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
.stock-pin-bindings-button {
|
||||
display: inline;
|
||||
button {
|
||||
margin: 0;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.fb-button {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
label {
|
||||
margin: 0 !important;
|
||||
line-height: 3rem;
|
||||
}
|
||||
.bp3-popover-wrapper {
|
||||
display: inline;
|
||||
float: none;
|
||||
}
|
||||
.map-size-inputs {
|
||||
.row {
|
||||
|
@ -795,6 +877,31 @@
|
|||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
.help-icon {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.all-settings-content {
|
||||
max-height: calc(100vh - 22rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin-top: 1rem;
|
||||
padding-left: 1rem;
|
||||
.expandable-header {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.designer-settings {
|
||||
max-height: calc(100vh - 14rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin-right: -10px;
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.designer-setting {
|
||||
&.disabled {
|
||||
input {
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
jest.mock("../../actions", () => ({
|
||||
toggleControlPanel: jest.fn(),
|
||||
bulkToggleControlPanel: jest.fn(),
|
||||
}));
|
||||
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
const mockState = fakeState();
|
||||
jest.mock("../../../redux/store", () => ({
|
||||
store: { getState: () => mockState },
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
|
@ -9,7 +16,7 @@ import {
|
|||
} from "../maybe_highlight";
|
||||
import { DeviceSetting } from "../../../constants";
|
||||
import { panelState } from "../../../__test_support__/control_panel_state";
|
||||
import { toggleControlPanel } from "../../actions";
|
||||
import { toggleControlPanel, bulkToggleControlPanel } from "../../actions";
|
||||
|
||||
describe("<Highlight />", () => {
|
||||
const fakeProps = (): HighlightProps => ({
|
||||
|
@ -25,6 +32,24 @@ describe("<Highlight />", () => {
|
|||
wrapper.instance().componentDidMount();
|
||||
expect(wrapper.state().className).toEqual("unhighlight");
|
||||
});
|
||||
|
||||
it("doesn't hide: no search term", () => {
|
||||
mockState.resources.consumers.farm_designer.settingsSearchTerm = "";
|
||||
const wrapper = mount(<Highlight {...fakeProps()} />);
|
||||
expect(wrapper.find("div").first().props().hidden).toEqual(false);
|
||||
});
|
||||
|
||||
it("doesn't hide: matches search term", () => {
|
||||
mockState.resources.consumers.farm_designer.settingsSearchTerm = "motor";
|
||||
const wrapper = mount(<Highlight {...fakeProps()} />);
|
||||
expect(wrapper.find("div").first().props().hidden).toEqual(false);
|
||||
});
|
||||
|
||||
it("hides", () => {
|
||||
mockState.resources.consumers.farm_designer.settingsSearchTerm = "encoder";
|
||||
const wrapper = mount(<Highlight {...fakeProps()} />);
|
||||
expect(wrapper.find("div").first().props().hidden).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("maybeHighlight()", () => {
|
||||
|
@ -78,4 +103,11 @@ describe("maybeOpenPanel()", () => {
|
|||
maybeOpenPanel(panelState())(jest.fn());
|
||||
expect(toggleControlPanel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("closes other panels", () => {
|
||||
location.search = "?highlight=motors";
|
||||
maybeOpenPanel(panelState(), true)(jest.fn());
|
||||
expect(toggleControlPanel).toHaveBeenCalledWith("motors");
|
||||
expect(bulkToggleControlPanel).toHaveBeenCalledWith(false, true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,11 +9,12 @@ import { settingToggle } from "../../actions";
|
|||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { DeviceSetting } from "../../../constants";
|
||||
|
||||
describe("<PinGuardMCUInputGroup/>", () => {
|
||||
const fakeProps = (): PinGuardMCUInputGroupProps => {
|
||||
return {
|
||||
label: "Pin Guard 1",
|
||||
label: DeviceSetting.pinGuard1,
|
||||
pinNumKey: "pin_guard_1_pin_nr",
|
||||
timeoutKey: "pin_guard_1_time_out",
|
||||
activeStateKey: "pin_guard_1_active_state",
|
||||
|
|
|
@ -13,11 +13,12 @@ import {
|
|||
import { TaggedFirmwareConfig } from "farmbot";
|
||||
import { FBSelect } from "../../../ui";
|
||||
import { updateMCU } from "../../actions";
|
||||
import { DeviceSetting } from "../../../constants";
|
||||
|
||||
describe("<PinNumberDropdown />", () => {
|
||||
const fakeProps =
|
||||
(firmwareConfig?: TaggedFirmwareConfig): PinGuardMCUInputGroupProps => ({
|
||||
label: "Pin Guard 1",
|
||||
label: DeviceSetting.pinGuard1,
|
||||
pinNumKey: "pin_guard_1_pin_nr",
|
||||
timeoutKey: "pin_guard_1_time_out",
|
||||
activeStateKey: "pin_guard_1_active_state",
|
||||
|
|
|
@ -24,7 +24,7 @@ export function DangerZone(props: DangerZoneProps) {
|
|||
<Highlight settingName={DeviceSetting.resetHardwareParams}>
|
||||
<Row>
|
||||
<Col xs={newFormat ? 8 : 4}>
|
||||
<label>
|
||||
<label style={{ lineHeight: "1.5rem" }}>
|
||||
{t(DeviceSetting.resetHardwareParams)}
|
||||
</label>
|
||||
</Col>
|
||||
|
|
|
@ -44,7 +44,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
</Col>
|
||||
</Row>}
|
||||
<PinGuardMCUInputGroup
|
||||
label={t("Pin Guard {{ num }}", { num: 1 })}
|
||||
label={DeviceSetting.pinGuard1}
|
||||
pinNumKey={"pin_guard_1_pin_nr"}
|
||||
timeoutKey={"pin_guard_1_time_out"}
|
||||
activeStateKey={"pin_guard_1_active_state"}
|
||||
|
@ -52,7 +52,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuardMCUInputGroup
|
||||
label={t("Pin Guard {{ num }}", { num: 2 })}
|
||||
label={DeviceSetting.pinGuard2}
|
||||
pinNumKey={"pin_guard_2_pin_nr"}
|
||||
timeoutKey={"pin_guard_2_time_out"}
|
||||
activeStateKey={"pin_guard_2_active_state"}
|
||||
|
@ -60,7 +60,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuardMCUInputGroup
|
||||
label={t("Pin Guard {{ num }}", { num: 3 })}
|
||||
label={DeviceSetting.pinGuard3}
|
||||
pinNumKey={"pin_guard_3_pin_nr"}
|
||||
timeoutKey={"pin_guard_3_time_out"}
|
||||
activeStateKey={"pin_guard_3_active_state"}
|
||||
|
@ -68,7 +68,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuardMCUInputGroup
|
||||
label={t("Pin Guard {{ num }}", { num: 4 })}
|
||||
label={DeviceSetting.pinGuard4}
|
||||
pinNumKey={"pin_guard_4_pin_nr"}
|
||||
timeoutKey={"pin_guard_4_time_out"}
|
||||
activeStateKey={"pin_guard_4_active_state"}
|
||||
|
@ -76,7 +76,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuardMCUInputGroup
|
||||
label={t("Pin Guard {{ num }}", { num: 5 })}
|
||||
label={DeviceSetting.pinGuard5}
|
||||
pinNumKey={"pin_guard_5_pin_nr"}
|
||||
timeoutKey={"pin_guard_5_time_out"}
|
||||
activeStateKey={"pin_guard_5_active_state"}
|
||||
|
|
|
@ -2,12 +2,15 @@ import * as React from "react";
|
|||
import { Row, Col } from "../../../ui/index";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { DevSettings } from "../../../account/dev/dev_support";
|
||||
import { Highlight } from "../maybe_highlight";
|
||||
import { DeviceSetting } from "../../../constants";
|
||||
|
||||
export function SpacePanelHeader() {
|
||||
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||
const width = newFormat ? 4 : 2;
|
||||
const offset = newFormat ? 0 : 6;
|
||||
return <div className="label-headings">
|
||||
<Highlight settingName={DeviceSetting.axisHeadingLabels}>
|
||||
<Row>
|
||||
<Col xs={width} xsOffset={offset} className={"centered-button-div"}>
|
||||
<label>
|
||||
|
@ -25,5 +28,6 @@ export function SpacePanelHeader() {
|
|||
</label>
|
||||
</Col>
|
||||
</Row>
|
||||
</Highlight>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ export interface NumericMCUInputGroupProps {
|
|||
export interface PinGuardMCUInputGroupProps {
|
||||
sourceFwConfig: SourceFwConfig;
|
||||
dispatch: Function;
|
||||
label: string;
|
||||
label: DeviceSetting;
|
||||
pinNumKey: McuParamName;
|
||||
timeoutKey: McuParamName;
|
||||
activeStateKey: McuParamName;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { store } from "../../redux/store";
|
||||
import { ControlPanelState } from "../interfaces";
|
||||
import { toggleControlPanel, bulkToggleControlPanel } from "../actions";
|
||||
import { urlFriendly } from "../../util";
|
||||
|
@ -56,6 +57,11 @@ const ERROR_HANDLING_PANEL = [
|
|||
];
|
||||
const PIN_GUARD_PANEL = [
|
||||
DeviceSetting.pinGuard,
|
||||
DeviceSetting.pinGuard1,
|
||||
DeviceSetting.pinGuard2,
|
||||
DeviceSetting.pinGuard3,
|
||||
DeviceSetting.pinGuard4,
|
||||
DeviceSetting.pinGuard5,
|
||||
];
|
||||
const DANGER_ZONE_PANEL = [
|
||||
DeviceSetting.dangerZone,
|
||||
|
@ -63,6 +69,8 @@ const DANGER_ZONE_PANEL = [
|
|||
];
|
||||
const PIN_BINDINGS_PANEL = [
|
||||
DeviceSetting.pinBindings,
|
||||
DeviceSetting.savedPinBindings,
|
||||
DeviceSetting.addNewPinBinding,
|
||||
];
|
||||
const POWER_AND_RESET_PANEL = [
|
||||
DeviceSetting.powerAndReset,
|
||||
|
@ -183,6 +191,7 @@ export interface HighlightProps {
|
|||
| (React.ReactChild | false)[]
|
||||
| (React.ReactChild | React.ReactChild[])[];
|
||||
className?: string;
|
||||
searchTerm?: string;
|
||||
}
|
||||
|
||||
interface HighlightState {
|
||||
|
@ -200,11 +209,19 @@ export class Highlight extends React.Component<HighlightProps, HighlightState> {
|
|||
}
|
||||
}
|
||||
|
||||
get searchTerm() {
|
||||
const { resources } = store.getState();
|
||||
return resources.consumers.farm_designer.settingsSearchTerm;
|
||||
}
|
||||
|
||||
render() {
|
||||
const show = !this.searchTerm ||
|
||||
this.props.settingName.toLowerCase().includes(this.searchTerm);
|
||||
return <div className={[
|
||||
this.props.className,
|
||||
this.state.className,
|
||||
].join(" ")}>
|
||||
].join(" ")}
|
||||
hidden={!show}>
|
||||
{this.props.children}
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { PinNumberDropdown } from "./pin_number_dropdown";
|
|||
import { DevSettings } from "../../account/dev/dev_support";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { Position } from "@blueprintjs/core";
|
||||
import { Highlight } from "./maybe_highlight";
|
||||
|
||||
export class PinGuardMCUInputGroup
|
||||
extends React.Component<PinGuardMCUInputGroupProps> {
|
||||
|
@ -50,7 +51,7 @@ export class PinGuardMCUInputGroup
|
|||
? <Row>
|
||||
<Col xs={3}>
|
||||
<label>
|
||||
{label}
|
||||
{t(label)}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
|
@ -63,11 +64,12 @@ export class PinGuardMCUInputGroup
|
|||
<this.State />
|
||||
</Col>
|
||||
</Row>
|
||||
: <div className={"pin-guard-input-row"}>
|
||||
: <Highlight settingName={label}>
|
||||
<div className={"pin-guard-input-row"}>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<label>
|
||||
{label}
|
||||
{t(label)}
|
||||
</label>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -103,6 +105,7 @@ export class PinGuardMCUInputGroup
|
|||
<this.State />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>;
|
||||
</div>
|
||||
</Highlight>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
} from "farmbot/dist/resources/api_resources";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { DevSettings } from "../../account/dev/dev_support";
|
||||
import { DeviceSetting } from "../../constants";
|
||||
|
||||
export class PinBindingInputGroup
|
||||
extends React.Component<PinBindingInputGroupProps, PinBindingInputGroupState> {
|
||||
|
@ -129,7 +130,7 @@ export class PinBindingInputGroup
|
|||
render() {
|
||||
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||
return <div className="pin-binding-input-rows">
|
||||
{newFormat && <Row><label>{t("add new pin binding")}</label></Row>}
|
||||
{newFormat && <Row><label>{t(DeviceSetting.addNewPinBinding)}</label></Row>}
|
||||
{newFormat && <this.Number />}
|
||||
{newFormat && <Row>
|
||||
<Col xs={5}>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col, Help } from "../../ui";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { ToolTips, DeviceSetting } from "../../constants";
|
||||
import { selectAllPinBindings } from "../../resources/selectors";
|
||||
import { PinBindingsContentProps, PinBindingListItems } from "./interfaces";
|
||||
import { PinBindingsList } from "./pin_bindings_list";
|
||||
|
@ -17,6 +17,7 @@ import {
|
|||
} from "farmbot/dist/resources/api_resources";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { DevSettings } from "../../account/dev/dev_support";
|
||||
import { Highlight } from "../components/maybe_highlight";
|
||||
|
||||
/** Width of UI columns in Pin Bindings widget. */
|
||||
export enum PinBindingColWidth {
|
||||
|
@ -73,6 +74,7 @@ export const PinBindingsContent = (props: PinBindingsContentProps) => {
|
|||
const pinBindings = apiPinBindings(resources);
|
||||
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||
return <div className="pin-bindings">
|
||||
<Highlight settingName={DeviceSetting.pinBindings}>
|
||||
<Row>
|
||||
{newFormat && <Help text={ToolTips.PIN_BINDINGS}
|
||||
position={Position.TOP_RIGHT} />}
|
||||
|
@ -89,16 +91,21 @@ export const PinBindingsContent = (props: PinBindingsContentProps) => {
|
|||
</div>
|
||||
</Popover>
|
||||
</Row>
|
||||
</Highlight>
|
||||
<div className={"pin-bindings-list-and-input"}>
|
||||
{!newFormat && <PinBindingsListHeader />}
|
||||
<Highlight settingName={DeviceSetting.savedPinBindings}>
|
||||
<PinBindingsList
|
||||
pinBindings={pinBindings}
|
||||
dispatch={dispatch}
|
||||
resources={resources} />
|
||||
</Highlight>
|
||||
<Highlight settingName={DeviceSetting.addNewPinBinding}>
|
||||
<PinBindingInputGroup
|
||||
pinBindings={pinBindings}
|
||||
dispatch={dispatch}
|
||||
resources={resources} />
|
||||
</Highlight>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ import { DevSettings } from "../../account/dev/dev_support";
|
|||
import {
|
||||
PinBindingType, PinBindingSpecialAction,
|
||||
} from "farmbot/dist/resources/api_resources";
|
||||
import { DeviceSetting } from "../../constants";
|
||||
|
||||
export const PinBindingsList = (props: PinBindingsListProps) => {
|
||||
const { pinBindings, resources, dispatch } = props;
|
||||
|
@ -41,7 +42,7 @@ export const PinBindingsList = (props: PinBindingsListProps) => {
|
|||
|
||||
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||
return <div className={"bindings-list"}>
|
||||
{newFormat && <Row><label>{t("saved pin bindings")}</label></Row>}
|
||||
{newFormat && <Row><label>{t(DeviceSetting.savedPinBindings)}</label></Row>}
|
||||
{pinBindings
|
||||
.sort((a, b) => sortByNameAndPin(a.pin_number, b.pin_number))
|
||||
.map(x => {
|
||||
|
|
|
@ -191,6 +191,16 @@ describe("designer reducer", () => {
|
|||
expect(newState.tryGroupSortType).toEqual("random");
|
||||
});
|
||||
|
||||
it("sets settings search term", () => {
|
||||
const state = oldState();
|
||||
state.settingsSearchTerm = "";
|
||||
const action: ReduxAction<string> = {
|
||||
type: Actions.SET_SETTINGS_SEARCH_TERM, payload: "random"
|
||||
};
|
||||
const newState = designer(state, action);
|
||||
expect(newState.settingsSearchTerm).toEqual("random");
|
||||
});
|
||||
|
||||
it("enables edit group area in map mode", () => {
|
||||
const state = oldState();
|
||||
state.editGroupAreaInMap = false;
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
jest.mock("../../config_storage/actions", () => ({
|
||||
getWebAppConfigValue: jest.fn(x => { x(); return jest.fn(() => true); }),
|
||||
setWebAppConfigValue: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, ReactWrapper } from "enzyme";
|
||||
import {
|
||||
RawDesignerSettings as DesignerSettings, DesignerSettingsProps,
|
||||
mapStateToProps,
|
||||
} from "../settings";
|
||||
import { fakeState } from "../../__test_support__/fake_state";
|
||||
import { BooleanSetting, NumericSetting } from "../../session_keys";
|
||||
import { setWebAppConfigValue } from "../../config_storage/actions";
|
||||
|
||||
const getSetting =
|
||||
(wrapper: ReactWrapper, position: number, containsString: string) => {
|
||||
const setting = wrapper.find(".designer-setting").at(position);
|
||||
expect(setting.text().toLowerCase())
|
||||
.toContain(containsString.toLowerCase());
|
||||
return setting;
|
||||
};
|
||||
|
||||
describe("<DesignerSettings />", () => {
|
||||
const fakeProps = (): DesignerSettingsProps => ({
|
||||
dispatch: jest.fn(),
|
||||
getConfigValue: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders settings", () => {
|
||||
const wrapper = mount(<DesignerSettings {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("size");
|
||||
const settings = wrapper.find(".designer-setting");
|
||||
expect(settings.length).toEqual(7);
|
||||
});
|
||||
|
||||
it("renders defaultOn setting", () => {
|
||||
const p = fakeProps();
|
||||
p.getConfigValue = () => undefined;
|
||||
const wrapper = mount(<DesignerSettings {...p} />);
|
||||
const confirmDeletion = getSetting(wrapper, 6, "confirm plant");
|
||||
expect(confirmDeletion.find("button").text()).toEqual("on");
|
||||
});
|
||||
|
||||
it("toggles setting", () => {
|
||||
const wrapper = mount(<DesignerSettings {...fakeProps()} />);
|
||||
const trailSetting = getSetting(wrapper, 1, "trail");
|
||||
trailSetting.find("button").simulate("click");
|
||||
expect(setWebAppConfigValue)
|
||||
.toHaveBeenCalledWith(BooleanSetting.display_trail, true);
|
||||
});
|
||||
|
||||
it("changes origin", () => {
|
||||
const p = fakeProps();
|
||||
p.getConfigValue = () => 2;
|
||||
const wrapper = mount(<DesignerSettings {...p} />);
|
||||
const originSetting = getSetting(wrapper, 5, "origin");
|
||||
originSetting.find("div").last().simulate("click");
|
||||
expect(setWebAppConfigValue).toHaveBeenCalledWith(
|
||||
NumericSetting.bot_origin_quadrant, 4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const props = mapStateToProps(fakeState());
|
||||
const value = props.getConfigValue(BooleanSetting.show_plants);
|
||||
expect(value).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -125,6 +125,7 @@ export interface DesignerState {
|
|||
openedSavedGarden: string | undefined;
|
||||
tryGroupSortType: PointGroupSortType | "nn" | undefined;
|
||||
editGroupAreaInMap: boolean;
|
||||
settingsSearchTerm: string;
|
||||
}
|
||||
|
||||
export type TaggedExecutable = TaggedSequence | TaggedRegimen;
|
||||
|
|
|
@ -27,6 +27,7 @@ export const initialState: DesignerState = {
|
|||
openedSavedGarden: undefined,
|
||||
tryGroupSortType: undefined,
|
||||
editGroupAreaInMap: false,
|
||||
settingsSearchTerm: "",
|
||||
};
|
||||
|
||||
export const designer = generateReducer<DesignerState>(initialState)
|
||||
|
@ -107,6 +108,10 @@ export const designer = generateReducer<DesignerState>(initialState)
|
|||
s.tryGroupSortType = payload;
|
||||
return s;
|
||||
})
|
||||
.add<string>(Actions.SET_SETTINGS_SEARCH_TERM, (s, { payload }) => {
|
||||
s.settingsSearchTerm = payload;
|
||||
return s;
|
||||
})
|
||||
.add<boolean>(Actions.EDIT_GROUP_AREA_IN_MAP, (s, { payload }) => {
|
||||
s.editGroupAreaInMap = payload;
|
||||
return s;
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { Everything } from "../interfaces";
|
||||
import { connect } from "react-redux";
|
||||
import { Content } from "../constants";
|
||||
import { DesignerPanel, DesignerPanelContent } from "./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, NumericSetting } from "../session_keys";
|
||||
import { resetVirtualTrail } from "./map/layers/farmbot/bot_trail";
|
||||
import { MapSizeInputs } from "./map_size_setting";
|
||||
import { DesignerNavTabs, Panel } from "./panel_header";
|
||||
import { isUndefined } from "lodash";
|
||||
|
||||
export const mapStateToProps = (props: Everything): DesignerSettingsProps => ({
|
||||
dispatch: props.dispatch,
|
||||
getConfigValue: getWebAppConfigValue(() => props),
|
||||
});
|
||||
|
||||
export interface DesignerSettingsProps {
|
||||
dispatch: Function;
|
||||
getConfigValue: GetWebAppConfigValue;
|
||||
}
|
||||
|
||||
export class RawDesignerSettings
|
||||
extends React.Component<DesignerSettingsProps, {}> {
|
||||
|
||||
render() {
|
||||
const { getConfigValue, dispatch } = this.props;
|
||||
const settingsProps = { getConfigValue, dispatch };
|
||||
return <DesignerPanel panelName={"settings"} panel={Panel.Settings}>
|
||||
<DesignerNavTabs />
|
||||
<DesignerPanelContent panelName={"settings"}>
|
||||
{DESIGNER_SETTINGS(settingsProps).map(setting =>
|
||||
<Setting key={setting.title} {...setting} {...settingsProps} />)}
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
}
|
||||
|
||||
interface SettingDescriptionProps {
|
||||
setting?: BooleanConfigKey;
|
||||
title: string;
|
||||
description: string;
|
||||
invert?: boolean;
|
||||
callback?: () => void;
|
||||
children?: React.ReactChild;
|
||||
defaultOn?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface SettingProps
|
||||
extends DesignerSettingsProps, SettingDescriptionProps { }
|
||||
|
||||
const Setting = (props: SettingProps) => {
|
||||
const { title, setting, callback, defaultOn } = props;
|
||||
const raw_value = setting ? props.getConfigValue(setting) : undefined;
|
||||
const value = (defaultOn && isUndefined(raw_value)) ? true : !!raw_value;
|
||||
return <div
|
||||
className={`designer-setting ${props.disabled ? "disabled" : ""}`}>
|
||||
<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?.();
|
||||
}}
|
||||
title={`${t("toggle")} ${title}`}
|
||||
customText={{ textFalse: t("off"), textTrue: t("on") }} />}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<p>{t(props.description)}</p>
|
||||
</Row>
|
||||
{props.children}
|
||||
</div>;
|
||||
};
|
||||
|
||||
const DESIGNER_SETTINGS =
|
||||
(settingsProps: DesignerSettingsProps): 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),
|
||||
children: <MapSizeInputs {...settingsProps} />,
|
||||
disabled: !!settingsProps.getConfigValue(BooleanSetting.dynamic_map),
|
||||
},
|
||||
{
|
||||
title: t("Rotate map"),
|
||||
description: t(Content.MAP_SWAP_XY),
|
||||
setting: BooleanSetting.xy_swap,
|
||||
},
|
||||
{
|
||||
title: t("Map origin"),
|
||||
description: t(Content.MAP_ORIGIN),
|
||||
children: <OriginSelector {...settingsProps} />
|
||||
},
|
||||
{
|
||||
title: t("Confirm plant deletion"),
|
||||
description: t(Content.CONFIRM_PLANT_DELETION),
|
||||
setting: BooleanSetting.confirm_plant_deletion,
|
||||
defaultOn: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const OriginSelector = (props: DesignerSettingsProps) => {
|
||||
const quadrant = props.getConfigValue(NumericSetting.bot_origin_quadrant);
|
||||
const update = (value: number) => () => props.dispatch(setWebAppConfigValue(
|
||||
NumericSetting.bot_origin_quadrant, value));
|
||||
return <div className="farmbot-origin">
|
||||
<div className="quadrants">
|
||||
{[2, 1, 3, 4].map(q =>
|
||||
<div key={"quadrant_" + q}
|
||||
className={`quadrant ${quadrant === q ? "selected" : ""}`}
|
||||
onClick={update(q)} />)}
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
export const DesignerSettings = connect(mapStateToProps)(RawDesignerSettings);
|
|
@ -0,0 +1,35 @@
|
|||
jest.mock("../../map/layers/farmbot/bot_trail", () => ({
|
||||
resetVirtualTrail: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { PlainDesignerSettings } from "../farm_designer_settings";
|
||||
import { DesignerSettingsPropsBase } from "../interfaces";
|
||||
import { resetVirtualTrail } from "../../map/layers/farmbot/bot_trail";
|
||||
|
||||
describe("<PlainDesignerSettings />", () => {
|
||||
const fakeProps = (): DesignerSettingsPropsBase => ({
|
||||
dispatch: jest.fn(),
|
||||
getConfigValue: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<div>{PlainDesignerSettings(fakeProps())}</div>);
|
||||
expect(wrapper.text().toLowerCase()).toContain("plant animations");
|
||||
});
|
||||
|
||||
it("doesn't call callback", () => {
|
||||
const wrapper = mount(<div>{PlainDesignerSettings(fakeProps())}</div>);
|
||||
expect(wrapper.find("label").at(0).text()).toContain("animations");
|
||||
wrapper.find("button").at(0).simulate("click");
|
||||
expect(resetVirtualTrail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls callback", () => {
|
||||
const wrapper = mount(<div>{PlainDesignerSettings(fakeProps())}</div>);
|
||||
expect(wrapper.find("label").at(1).text()).toContain("trail");
|
||||
wrapper.find("button").at(1).simulate("click");
|
||||
expect(resetVirtualTrail).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,172 @@
|
|||
jest.mock("../../../config_storage/actions", () => ({
|
||||
getWebAppConfigValue: jest.fn(x => { x(); return jest.fn(() => true); }),
|
||||
setWebAppConfigValue: jest.fn(),
|
||||
}));
|
||||
|
||||
let mockDev = false;
|
||||
jest.mock("../../../account/dev/dev_support", () => ({
|
||||
DevSettings: {
|
||||
futureFeaturesEnabled: () => mockDev,
|
||||
overriddenFbosVersion: jest.fn(),
|
||||
}
|
||||
}));
|
||||
|
||||
jest.mock("../../../devices/components/maybe_highlight", () => ({
|
||||
maybeOpenPanel: jest.fn(),
|
||||
Highlight: (p: { children: React.ReactChild }) => <div>{p.children}</div>,
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, ReactWrapper, shallow } from "enzyme";
|
||||
import { RawDesignerSettings as DesignerSettings } from "..";
|
||||
import { DesignerSettingsProps } from "../interfaces";
|
||||
import { BooleanSetting, NumericSetting } from "../../../session_keys";
|
||||
import { setWebAppConfigValue } from "../../../config_storage/actions";
|
||||
import {
|
||||
buildResourceIndex, fakeDevice,
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
||||
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||
import { clickButton } from "../../../__test_support__/helpers";
|
||||
import { Actions } from "../../../constants";
|
||||
import { Motors } from "../hardware_settings";
|
||||
import { SearchField } from "../../../ui/search_field";
|
||||
import { maybeOpenPanel } from "../../../devices/components/maybe_highlight";
|
||||
|
||||
const getSetting =
|
||||
(wrapper: ReactWrapper, position: number, containsString: string) => {
|
||||
const setting = wrapper.find(".designer-setting").at(position);
|
||||
expect(setting.text().toLowerCase())
|
||||
.toContain(containsString.toLowerCase());
|
||||
return setting;
|
||||
};
|
||||
|
||||
describe("<DesignerSettings />", () => {
|
||||
beforeEach(() => {
|
||||
mockDev = false;
|
||||
});
|
||||
|
||||
const fakeProps = (): DesignerSettingsProps => ({
|
||||
dispatch: jest.fn(),
|
||||
getConfigValue: jest.fn(),
|
||||
firmwareConfig: undefined,
|
||||
sourceFwConfig: () => ({ value: 10, consistent: true }),
|
||||
sourceFbosConfig: () => ({ value: 10, consistent: true }),
|
||||
resources: buildResourceIndex().index,
|
||||
deviceAccount: fakeDevice(),
|
||||
env: {},
|
||||
alerts: [],
|
||||
shouldDisplay: jest.fn(),
|
||||
saveFarmwareEnv: jest.fn(),
|
||||
timeSettings: fakeTimeSettings(),
|
||||
bot: bot,
|
||||
searchTerm: "",
|
||||
});
|
||||
|
||||
it("renders settings", () => {
|
||||
const wrapper = mount(<DesignerSettings {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("size");
|
||||
expect(wrapper.text().toLowerCase()).not.toContain("pin");
|
||||
const settings = wrapper.find(".designer-setting");
|
||||
expect(settings.length).toEqual(7);
|
||||
});
|
||||
|
||||
it("renders all settings", () => {
|
||||
mockDev = true;
|
||||
const wrapper = mount(<DesignerSettings {...fakeProps()} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("pin");
|
||||
});
|
||||
|
||||
it("mounts", () => {
|
||||
mount(<DesignerSettings {...fakeProps()} />);
|
||||
expect(maybeOpenPanel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("unmounts", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<DesignerSettings {...p} />);
|
||||
wrapper.unmount();
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.BULK_TOGGLE_CONTROL_PANEL,
|
||||
payload: { open: false, all: true },
|
||||
});
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.TOGGLE_CONTROL_PANEL_OPTION,
|
||||
payload: "farmbot_os",
|
||||
});
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SET_SETTINGS_SEARCH_TERM,
|
||||
payload: "",
|
||||
});
|
||||
});
|
||||
|
||||
it("changes search term", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<DesignerSettings {...p} />);
|
||||
wrapper.find(SearchField).simulate("change", "setting");
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.BULK_TOGGLE_CONTROL_PANEL,
|
||||
payload: { open: true, all: true },
|
||||
});
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SET_SETTINGS_SEARCH_TERM,
|
||||
payload: "setting",
|
||||
});
|
||||
});
|
||||
|
||||
it("fetches firmware_hardware", () => {
|
||||
mockDev = true;
|
||||
const p = fakeProps();
|
||||
p.sourceFbosConfig = () => ({ value: "arduino", consistent: true });
|
||||
const wrapper = mount(<DesignerSettings {...p} />);
|
||||
expect(wrapper.find(Motors).props().firmwareHardware).toEqual("arduino");
|
||||
});
|
||||
|
||||
it("expands all", () => {
|
||||
mockDev = true;
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<DesignerSettings {...p} />);
|
||||
clickButton(wrapper, 0, "expand all");
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.BULK_TOGGLE_CONTROL_PANEL,
|
||||
payload: { open: true, all: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("collapses all", () => {
|
||||
mockDev = true;
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<DesignerSettings {...p} />);
|
||||
clickButton(wrapper, 1, "collapse all");
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.BULK_TOGGLE_CONTROL_PANEL,
|
||||
payload: { open: false, all: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("renders defaultOn setting", () => {
|
||||
const p = fakeProps();
|
||||
p.getConfigValue = () => undefined;
|
||||
const wrapper = mount(<DesignerSettings {...p} />);
|
||||
const confirmDeletion = getSetting(wrapper, 6, "confirm plant");
|
||||
expect(confirmDeletion.find("button").text()).toEqual("on");
|
||||
});
|
||||
|
||||
it("toggles setting", () => {
|
||||
const wrapper = mount(<DesignerSettings {...fakeProps()} />);
|
||||
const trailSetting = getSetting(wrapper, 1, "trail");
|
||||
trailSetting.find("button").simulate("click");
|
||||
expect(setWebAppConfigValue)
|
||||
.toHaveBeenCalledWith(BooleanSetting.display_trail, true);
|
||||
});
|
||||
|
||||
it("changes origin", () => {
|
||||
const p = fakeProps();
|
||||
p.getConfigValue = () => 2;
|
||||
const wrapper = mount(<DesignerSettings {...p} />);
|
||||
const originSetting = getSetting(wrapper, 5, "origin");
|
||||
originSetting.find("div").last().simulate("click");
|
||||
expect(setWebAppConfigValue).toHaveBeenCalledWith(
|
||||
NumericSetting.bot_origin_quadrant, 4);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
jest.mock("../../../config_storage/actions", () => ({
|
||||
getWebAppConfigValue: jest.fn(x => { x(); return jest.fn(() => true); }),
|
||||
setWebAppConfigValue: jest.fn(),
|
||||
}));
|
||||
|
||||
import { mapStateToProps } from "../state_to_props";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import { BooleanSetting } from "../../../session_keys";
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const props = mapStateToProps(fakeState());
|
||||
const value = props.getConfigValue(BooleanSetting.show_plants);
|
||||
expect(value).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,125 @@
|
|||
import * as React from "react";
|
||||
import { Content, DeviceSetting } from "../../constants";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { setWebAppConfigValue } from "../../config_storage/actions";
|
||||
import { Row, Col } from "../../ui";
|
||||
import { ToggleButton } from "../../controls/toggle_button";
|
||||
import { BooleanSetting, NumericSetting } from "../../session_keys";
|
||||
import { resetVirtualTrail } from "../map/layers/farmbot/bot_trail";
|
||||
import { MapSizeInputs } from "../map_size_setting";
|
||||
import { isUndefined } from "lodash";
|
||||
import { Collapse } from "@blueprintjs/core";
|
||||
import { Header } from "../../devices/components/hardware_settings/header";
|
||||
import { Highlight } from "../../devices/components/maybe_highlight";
|
||||
import {
|
||||
DesignerSettingsSectionProps, SettingProps,
|
||||
DesignerSettingsPropsBase, SettingDescriptionProps,
|
||||
} from "./interfaces";
|
||||
|
||||
export const Designer = (props: DesignerSettingsSectionProps) => {
|
||||
const { getConfigValue, dispatch, controlPanelState } = props;
|
||||
const settingsProps = { getConfigValue, dispatch };
|
||||
return <Highlight className={"section"}
|
||||
settingName={DeviceSetting.farmDesigner}>
|
||||
<Header
|
||||
title={DeviceSetting.farmDesigner}
|
||||
panel={"farm_designer"}
|
||||
dispatch={dispatch}
|
||||
expanded={controlPanelState.farm_designer} />
|
||||
<Collapse isOpen={!!controlPanelState.farm_designer}>
|
||||
{PlainDesignerSettings(settingsProps)}
|
||||
</Collapse>
|
||||
</Highlight>;
|
||||
};
|
||||
|
||||
export const PlainDesignerSettings =
|
||||
(settingsProps: DesignerSettingsPropsBase) =>
|
||||
DESIGNER_SETTINGS(settingsProps).map(setting =>
|
||||
<Setting key={setting.title} {...setting} {...settingsProps} />);
|
||||
|
||||
const Setting = (props: SettingProps) => {
|
||||
const { title, setting, callback, defaultOn } = props;
|
||||
const raw_value = setting ? props.getConfigValue(setting) : undefined;
|
||||
const value = (defaultOn && isUndefined(raw_value)) ? true : !!raw_value;
|
||||
return <Highlight settingName={title}>
|
||||
<div
|
||||
className={`designer-setting ${props.disabled ? "disabled" : ""}`}>
|
||||
<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?.();
|
||||
}}
|
||||
title={`${t("toggle")} ${title}`}
|
||||
customText={{ textFalse: t("off"), textTrue: t("on") }} />}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<p>{t(props.description)}</p>
|
||||
</Row>
|
||||
{props.children}
|
||||
</div>
|
||||
</Highlight>;
|
||||
};
|
||||
|
||||
const DESIGNER_SETTINGS =
|
||||
(settingsProps: DesignerSettingsPropsBase): SettingDescriptionProps[] => ([
|
||||
{
|
||||
title: DeviceSetting.animations,
|
||||
description: t(Content.PLANT_ANIMATIONS),
|
||||
setting: BooleanSetting.disable_animations,
|
||||
invert: true
|
||||
},
|
||||
{
|
||||
title: DeviceSetting.trail,
|
||||
description: t(Content.VIRTUAL_TRAIL),
|
||||
setting: BooleanSetting.display_trail,
|
||||
callback: resetVirtualTrail,
|
||||
},
|
||||
{
|
||||
title: DeviceSetting.dynamicMap,
|
||||
description: t(Content.DYNAMIC_MAP_SIZE),
|
||||
setting: BooleanSetting.dynamic_map,
|
||||
},
|
||||
{
|
||||
title: DeviceSetting.mapSize,
|
||||
description: t(Content.MAP_SIZE),
|
||||
children: <MapSizeInputs {...settingsProps} />,
|
||||
disabled: !!settingsProps.getConfigValue(BooleanSetting.dynamic_map),
|
||||
},
|
||||
{
|
||||
title: DeviceSetting.rotateMap,
|
||||
description: t(Content.MAP_SWAP_XY),
|
||||
setting: BooleanSetting.xy_swap,
|
||||
},
|
||||
{
|
||||
title: DeviceSetting.mapOrigin,
|
||||
description: t(Content.MAP_ORIGIN),
|
||||
children: <OriginSelector {...settingsProps} />
|
||||
},
|
||||
{
|
||||
title: DeviceSetting.confirmPlantDeletion,
|
||||
description: t(Content.CONFIRM_PLANT_DELETION),
|
||||
setting: BooleanSetting.confirm_plant_deletion,
|
||||
defaultOn: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const OriginSelector = (props: DesignerSettingsPropsBase) => {
|
||||
const quadrant = props.getConfigValue(NumericSetting.bot_origin_quadrant);
|
||||
const update = (value: number) => () => props.dispatch(setWebAppConfigValue(
|
||||
NumericSetting.bot_origin_quadrant, value));
|
||||
return <div className="farmbot-origin">
|
||||
<div className="quadrants">
|
||||
{[2, 1, 3, 4].map(q =>
|
||||
<div key={"quadrant_" + q}
|
||||
className={`quadrant ${quadrant === q ? "selected" : ""}`}
|
||||
onClick={update(q)} />)}
|
||||
</div>
|
||||
</div>;
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
export * from "../../devices/components/fbos_settings/power_and_reset";
|
||||
export * from "../../devices/components/fbos_settings/firmware";
|
||||
export * from "../../devices/components/farmbot_os_settings";
|
|
@ -0,0 +1,9 @@
|
|||
export * from "../../devices/components/hardware_settings/homing_and_calibration";
|
||||
export * from "../../devices/components/hardware_settings/motors";
|
||||
export * from "../../devices/components/hardware_settings/encoders";
|
||||
export * from "../../devices/components/hardware_settings/endstops";
|
||||
export * from "../../devices/components/hardware_settings/error_handling";
|
||||
export * from "../../devices/components/hardware_settings/pin_bindings";
|
||||
export * from "../../devices/components/hardware_settings/pin_guard";
|
||||
export * from "../../devices/components/hardware_settings/danger_zone";
|
||||
export * from "../../devices/components/firmware_hardware_support";
|
|
@ -0,0 +1,135 @@
|
|||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { DesignerPanel, DesignerPanelContent } from "../designer_panel";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { DesignerNavTabs, Panel } from "../panel_header";
|
||||
import {
|
||||
bulkToggleControlPanel, MCUFactoryReset, toggleControlPanel,
|
||||
} from "../../devices/actions";
|
||||
import { FarmBotSettings, Firmware, PowerAndReset } from "./fbos_settings";
|
||||
import {
|
||||
HomingAndCalibration, Motors, Encoders, EndStops, ErrorHandling,
|
||||
PinGuard, DangerZone, PinBindings, isFwHardwareValue,
|
||||
} from "./hardware_settings";
|
||||
import { DevSettings } from "../../account/dev/dev_support";
|
||||
import { maybeOpenPanel } from "../../devices/components/maybe_highlight";
|
||||
import { isBotOnlineFromState } from "../../devices/must_be_online";
|
||||
import { DesignerSettingsProps } from "./interfaces";
|
||||
import { Designer, PlainDesignerSettings } from "./farm_designer_settings";
|
||||
import { SearchField } from "../../ui/search_field";
|
||||
import { mapStateToProps } from "./state_to_props";
|
||||
import { Actions } from "../../constants";
|
||||
|
||||
export class RawDesignerSettings
|
||||
extends React.Component<DesignerSettingsProps, {}> {
|
||||
|
||||
componentDidMount = () =>
|
||||
this.props.dispatch(maybeOpenPanel(this.props.bot.controlPanelState, true));
|
||||
|
||||
componentWillUnmount = () => {
|
||||
this.props.dispatch(bulkToggleControlPanel(false, true));
|
||||
this.props.dispatch(toggleControlPanel("farmbot_os"));
|
||||
this.props.dispatch({
|
||||
type: Actions.SET_SETTINGS_SEARCH_TERM,
|
||||
payload: ""
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { getConfigValue, dispatch, firmwareConfig,
|
||||
sourceFwConfig, sourceFbosConfig, resources,
|
||||
} = this.props;
|
||||
const { controlPanelState } = this.props.bot;
|
||||
const settingsProps = { getConfigValue, dispatch };
|
||||
const commonProps = { dispatch, controlPanelState };
|
||||
const { value } = this.props.sourceFbosConfig("firmware_hardware");
|
||||
const firmwareHardware = isFwHardwareValue(value) ? value : undefined;
|
||||
const botOnline = isBotOnlineFromState(this.props.bot);
|
||||
return <DesignerPanel panelName={"settings"} panel={Panel.Settings}>
|
||||
<DesignerNavTabs />
|
||||
<DesignerPanelContent panelName={"settings"}>
|
||||
<SearchField
|
||||
placeholder={t("Search settings...")}
|
||||
searchTerm={this.props.searchTerm}
|
||||
onChange={searchTerm => {
|
||||
dispatch(bulkToggleControlPanel(true, true));
|
||||
dispatch({
|
||||
type: Actions.SET_SETTINGS_SEARCH_TERM,
|
||||
payload: searchTerm
|
||||
});
|
||||
}} />
|
||||
{DevSettings.futureFeaturesEnabled() ?
|
||||
<div className="all-settings">
|
||||
<div className="bulk-expand-controls">
|
||||
<button
|
||||
className={"fb-button gray no-float"}
|
||||
onClick={() => dispatch(bulkToggleControlPanel(true, true))}>
|
||||
{t("Expand All")}
|
||||
</button>
|
||||
<button
|
||||
className={"fb-button gray no-float"}
|
||||
onClick={() => dispatch(bulkToggleControlPanel(false, true))}>
|
||||
{t("Collapse All")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="all-settings-content">
|
||||
<FarmBotSettings
|
||||
bot={this.props.bot}
|
||||
env={this.props.env}
|
||||
alerts={this.props.alerts}
|
||||
saveFarmwareEnv={this.props.saveFarmwareEnv}
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
botOnline={botOnline}
|
||||
timeSettings={this.props.timeSettings}
|
||||
device={this.props.deviceAccount} />
|
||||
<Firmware
|
||||
bot={this.props.bot}
|
||||
alerts={this.props.alerts}
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
botOnline={botOnline}
|
||||
timeSettings={this.props.timeSettings} />
|
||||
<PowerAndReset {...commonProps}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
botOnline={botOnline} />
|
||||
<HomingAndCalibration {...commonProps}
|
||||
bot={this.props.bot}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
firmwareConfig={firmwareConfig}
|
||||
firmwareHardware={firmwareHardware}
|
||||
botOnline={botOnline} />
|
||||
<Motors {...commonProps}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
firmwareHardware={firmwareHardware} />
|
||||
<Encoders {...commonProps}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
firmwareHardware={firmwareHardware} />
|
||||
<EndStops {...commonProps}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<ErrorHandling {...commonProps}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinBindings {...commonProps}
|
||||
resources={resources}
|
||||
firmwareHardware={firmwareHardware} />
|
||||
<PinGuard {...commonProps}
|
||||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<DangerZone {...commonProps}
|
||||
onReset={MCUFactoryReset}
|
||||
botOnline={botOnline} />
|
||||
<Designer {...commonProps}
|
||||
getConfigValue={getConfigValue} />
|
||||
</div>
|
||||
</div>
|
||||
: <div className="designer-settings">
|
||||
{PlainDesignerSettings(settingsProps)}
|
||||
</div>}
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
}
|
||||
|
||||
export const DesignerSettings = connect(mapStateToProps)(RawDesignerSettings);
|
|
@ -0,0 +1,53 @@
|
|||
import { GetWebAppConfigValue } from "../../config_storage/actions";
|
||||
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
||||
import {
|
||||
SourceFwConfig, SourceFbosConfig, UserEnv, ShouldDisplay,
|
||||
SaveFarmwareEnv, BotState, ControlPanelState,
|
||||
} from "../../devices/interfaces";
|
||||
import { ResourceIndex } from "../../resources/interfaces";
|
||||
import { TaggedDevice, Alert } from "farmbot";
|
||||
import { TimeSettings } from "../../interfaces";
|
||||
import { DeviceSetting } from "../../constants";
|
||||
import {
|
||||
BooleanConfigKey as WebAppBooleanConfigKey,
|
||||
} from "farmbot/dist/resources/configs/web_app";
|
||||
|
||||
export interface DesignerSettingsPropsBase {
|
||||
dispatch: Function;
|
||||
getConfigValue: GetWebAppConfigValue;
|
||||
}
|
||||
|
||||
export interface DesignerSettingsProps extends DesignerSettingsPropsBase {
|
||||
firmwareConfig: FirmwareConfig | undefined;
|
||||
sourceFwConfig: SourceFwConfig;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
resources: ResourceIndex;
|
||||
deviceAccount: TaggedDevice;
|
||||
env: UserEnv;
|
||||
alerts: Alert[];
|
||||
shouldDisplay: ShouldDisplay;
|
||||
saveFarmwareEnv: SaveFarmwareEnv;
|
||||
timeSettings: TimeSettings;
|
||||
bot: BotState;
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
export interface DesignerSettingsSectionProps {
|
||||
dispatch: Function;
|
||||
controlPanelState: ControlPanelState;
|
||||
getConfigValue: GetWebAppConfigValue;
|
||||
}
|
||||
|
||||
export interface SettingDescriptionProps {
|
||||
setting?: WebAppBooleanConfigKey;
|
||||
title: DeviceSetting;
|
||||
description: string;
|
||||
invert?: boolean;
|
||||
callback?: () => void;
|
||||
children?: React.ReactChild;
|
||||
defaultOn?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface SettingProps
|
||||
extends DesignerSettingsPropsBase, SettingDescriptionProps { }
|
|
@ -0,0 +1,35 @@
|
|||
import { Everything } from "../../interfaces";
|
||||
import { getWebAppConfigValue } from "../../config_storage/actions";
|
||||
import { validFwConfig, validFbosConfig } from "../../util";
|
||||
import { getFirmwareConfig, getFbosConfig } from "../../resources/getters";
|
||||
import {
|
||||
sourceFwConfigValue, sourceFbosConfigValue,
|
||||
} from "../../devices/components/source_config_value";
|
||||
import {
|
||||
getDeviceAccountSettings, maybeGetTimeSettings,
|
||||
} from "../../resources/selectors";
|
||||
import {
|
||||
saveOrEditFarmwareEnv, getShouldDisplayFn, getEnv,
|
||||
} from "../../farmware/state_to_props";
|
||||
import { getAllAlerts } from "../../messages/state_to_props";
|
||||
import { DesignerSettingsProps } from "./interfaces";
|
||||
|
||||
export const mapStateToProps = (props: Everything): DesignerSettingsProps => ({
|
||||
dispatch: props.dispatch,
|
||||
getConfigValue: getWebAppConfigValue(() => props),
|
||||
firmwareConfig: validFwConfig(getFirmwareConfig(props.resources.index)),
|
||||
sourceFwConfig: sourceFwConfigValue(validFwConfig(getFirmwareConfig(
|
||||
props.resources.index)), props.bot.hardware.mcu_params),
|
||||
sourceFbosConfig: sourceFbosConfigValue(validFbosConfig(getFbosConfig(
|
||||
props.resources.index)), props.bot.hardware.configuration),
|
||||
resources: props.resources.index,
|
||||
deviceAccount: getDeviceAccountSettings(props.resources.index),
|
||||
shouldDisplay: getShouldDisplayFn(props.resources.index, props.bot),
|
||||
env: getEnv(props.resources.index, getShouldDisplayFn(
|
||||
props.resources.index, props.bot), props.bot),
|
||||
saveFarmwareEnv: saveOrEditFarmwareEnv(props.resources.index),
|
||||
timeSettings: maybeGetTimeSettings(props.resources.index),
|
||||
alerts: getAllAlerts(props.resources),
|
||||
bot: props.bot,
|
||||
searchTerm: props.resources.consumers.farm_designer.settingsSearchTerm,
|
||||
});
|
|
@ -40,4 +40,12 @@ describe("<SearchField />", () => {
|
|||
wrapper.find("input").simulate("KeyPress", e);
|
||||
expect(p.onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("clears search term", () => {
|
||||
const p = fakeProps();
|
||||
p.searchTerm = "old";
|
||||
const wrapper = shallow(<SearchField {...p} />);
|
||||
wrapper.find("i").last().simulate("click");
|
||||
expect(p.onChange).toHaveBeenCalledWith("");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,8 @@ export const SearchField = (props: SearchFieldProps) =>
|
|||
onChange={e => props.onChange(e.currentTarget.value)}
|
||||
onKeyPress={e => props.onKeyPress?.(e.currentTarget.value)}
|
||||
placeholder={props.placeholder} />
|
||||
{props.searchTerm && props.customRightIcon}
|
||||
{props.searchTerm && (props.customRightIcon ||
|
||||
<i className="fa fa-times" onClick={() => props.onChange("")} />)}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue