Merge branch 'staging' of github.com:FarmBot/Farmbot-Web-App into mark_as
commit
88b20a73ea
|
@ -19,4 +19,5 @@ export const fakeDesignerState = (): DesignerState => ({
|
||||||
openedSavedGarden: undefined,
|
openedSavedGarden: undefined,
|
||||||
tryGroupSortType: undefined,
|
tryGroupSortType: undefined,
|
||||||
editGroupAreaInMap: false,
|
editGroupAreaInMap: false,
|
||||||
|
settingsSearchTerm: "",
|
||||||
});
|
});
|
||||||
|
|
|
@ -923,6 +923,8 @@ export namespace TourContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DeviceSetting {
|
export enum DeviceSetting {
|
||||||
|
axisHeadingLabels = ``,
|
||||||
|
|
||||||
// Homing and calibration
|
// Homing and calibration
|
||||||
homingAndCalibration = `Homing and Calibration`,
|
homingAndCalibration = `Homing and Calibration`,
|
||||||
homing = `Homing`,
|
homing = `Homing`,
|
||||||
|
@ -974,6 +976,11 @@ export enum DeviceSetting {
|
||||||
|
|
||||||
// Pin Guard
|
// Pin Guard
|
||||||
pinGuard = `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
|
// Danger Zone
|
||||||
dangerZone = `Danger Zone`,
|
dangerZone = `Danger Zone`,
|
||||||
|
@ -981,6 +988,8 @@ export enum DeviceSetting {
|
||||||
|
|
||||||
// Pin Bindings
|
// Pin Bindings
|
||||||
pinBindings = `Pin Bindings`,
|
pinBindings = `Pin Bindings`,
|
||||||
|
savedPinBindings = `Saved pin bindings`,
|
||||||
|
addNewPinBinding = `Add new pin binding`,
|
||||||
|
|
||||||
// FarmBot OS
|
// FarmBot OS
|
||||||
farmbot = `FarmBot`,
|
farmbot = `FarmBot`,
|
||||||
|
@ -1131,6 +1140,7 @@ export enum Actions {
|
||||||
SET_DRAWN_WEED_DATA = "SET_DRAWN_WEED_DATA",
|
SET_DRAWN_WEED_DATA = "SET_DRAWN_WEED_DATA",
|
||||||
CHOOSE_SAVED_GARDEN = "CHOOSE_SAVED_GARDEN",
|
CHOOSE_SAVED_GARDEN = "CHOOSE_SAVED_GARDEN",
|
||||||
TRY_SORT_TYPE = "TRY_SORT_TYPE",
|
TRY_SORT_TYPE = "TRY_SORT_TYPE",
|
||||||
|
SET_SETTINGS_SEARCH_TERM = "SET_SETTINGS_SEARCH_TERM",
|
||||||
EDIT_GROUP_AREA_IN_MAP = "EDIT_GROUP_AREA_IN_MAP",
|
EDIT_GROUP_AREA_IN_MAP = "EDIT_GROUP_AREA_IN_MAP",
|
||||||
|
|
||||||
// Regimens
|
// Regimens
|
||||||
|
|
|
@ -38,6 +38,7 @@ $pink: #ebb;
|
||||||
$light_red: #e99;
|
$light_red: #e99;
|
||||||
$red: #e66;
|
$red: #e66;
|
||||||
$dark_red: #f00;
|
$dark_red: #f00;
|
||||||
|
$medium_dark_red: #c00;
|
||||||
$darkest_red: #900;
|
$darkest_red: #900;
|
||||||
$panel_green: #35761b;
|
$panel_green: #35761b;
|
||||||
$panel_light_green: #f3f9f1;
|
$panel_light_green: #f3f9f1;
|
||||||
|
|
|
@ -158,6 +158,17 @@
|
||||||
left: 1rem;
|
left: 1rem;
|
||||||
cursor: default !important;
|
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 {
|
input {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
|
@ -773,19 +773,101 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-pad {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.settings-panel-content {
|
.settings-panel-content {
|
||||||
max-height: calc(100vh - 15rem);
|
padding: 0;
|
||||||
overflow-y: auto;
|
margin-top: 6rem;
|
||||||
overflow-x: hidden;
|
|
||||||
margin-top: 5rem;
|
|
||||||
padding-bottom: 5rem;
|
padding-bottom: 5rem;
|
||||||
button {
|
.section {
|
||||||
margin-top: 1.75rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
p {
|
.bulk-expand-controls {
|
||||||
padding: 0.5rem;
|
|
||||||
margin-left: 1rem;
|
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;
|
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 {
|
.map-size-inputs {
|
||||||
.row {
|
.row {
|
||||||
|
@ -795,6 +877,31 @@
|
||||||
margin-top: 0.5rem;
|
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 {
|
.designer-setting {
|
||||||
&.disabled {
|
&.disabled {
|
||||||
input {
|
input {
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
jest.mock("../../actions", () => ({
|
jest.mock("../../actions", () => ({
|
||||||
toggleControlPanel: jest.fn(),
|
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";
|
import * as React from "react";
|
||||||
|
@ -9,7 +16,7 @@ import {
|
||||||
} from "../maybe_highlight";
|
} from "../maybe_highlight";
|
||||||
import { DeviceSetting } from "../../../constants";
|
import { DeviceSetting } from "../../../constants";
|
||||||
import { panelState } from "../../../__test_support__/control_panel_state";
|
import { panelState } from "../../../__test_support__/control_panel_state";
|
||||||
import { toggleControlPanel } from "../../actions";
|
import { toggleControlPanel, bulkToggleControlPanel } from "../../actions";
|
||||||
|
|
||||||
describe("<Highlight />", () => {
|
describe("<Highlight />", () => {
|
||||||
const fakeProps = (): HighlightProps => ({
|
const fakeProps = (): HighlightProps => ({
|
||||||
|
@ -25,6 +32,24 @@ describe("<Highlight />", () => {
|
||||||
wrapper.instance().componentDidMount();
|
wrapper.instance().componentDidMount();
|
||||||
expect(wrapper.state().className).toEqual("unhighlight");
|
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()", () => {
|
describe("maybeHighlight()", () => {
|
||||||
|
@ -78,4 +103,11 @@ describe("maybeOpenPanel()", () => {
|
||||||
maybeOpenPanel(panelState())(jest.fn());
|
maybeOpenPanel(panelState())(jest.fn());
|
||||||
expect(toggleControlPanel).not.toHaveBeenCalled();
|
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 {
|
import {
|
||||||
buildResourceIndex,
|
buildResourceIndex,
|
||||||
} from "../../../__test_support__/resource_index_builder";
|
} from "../../../__test_support__/resource_index_builder";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
|
||||||
describe("<PinGuardMCUInputGroup/>", () => {
|
describe("<PinGuardMCUInputGroup/>", () => {
|
||||||
const fakeProps = (): PinGuardMCUInputGroupProps => {
|
const fakeProps = (): PinGuardMCUInputGroupProps => {
|
||||||
return {
|
return {
|
||||||
label: "Pin Guard 1",
|
label: DeviceSetting.pinGuard1,
|
||||||
pinNumKey: "pin_guard_1_pin_nr",
|
pinNumKey: "pin_guard_1_pin_nr",
|
||||||
timeoutKey: "pin_guard_1_time_out",
|
timeoutKey: "pin_guard_1_time_out",
|
||||||
activeStateKey: "pin_guard_1_active_state",
|
activeStateKey: "pin_guard_1_active_state",
|
||||||
|
|
|
@ -13,11 +13,12 @@ import {
|
||||||
import { TaggedFirmwareConfig } from "farmbot";
|
import { TaggedFirmwareConfig } from "farmbot";
|
||||||
import { FBSelect } from "../../../ui";
|
import { FBSelect } from "../../../ui";
|
||||||
import { updateMCU } from "../../actions";
|
import { updateMCU } from "../../actions";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
|
||||||
describe("<PinNumberDropdown />", () => {
|
describe("<PinNumberDropdown />", () => {
|
||||||
const fakeProps =
|
const fakeProps =
|
||||||
(firmwareConfig?: TaggedFirmwareConfig): PinGuardMCUInputGroupProps => ({
|
(firmwareConfig?: TaggedFirmwareConfig): PinGuardMCUInputGroupProps => ({
|
||||||
label: "Pin Guard 1",
|
label: DeviceSetting.pinGuard1,
|
||||||
pinNumKey: "pin_guard_1_pin_nr",
|
pinNumKey: "pin_guard_1_pin_nr",
|
||||||
timeoutKey: "pin_guard_1_time_out",
|
timeoutKey: "pin_guard_1_time_out",
|
||||||
activeStateKey: "pin_guard_1_active_state",
|
activeStateKey: "pin_guard_1_active_state",
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function DangerZone(props: DangerZoneProps) {
|
||||||
<Highlight settingName={DeviceSetting.resetHardwareParams}>
|
<Highlight settingName={DeviceSetting.resetHardwareParams}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={newFormat ? 8 : 4}>
|
<Col xs={newFormat ? 8 : 4}>
|
||||||
<label>
|
<label style={{ lineHeight: "1.5rem" }}>
|
||||||
{t(DeviceSetting.resetHardwareParams)}
|
{t(DeviceSetting.resetHardwareParams)}
|
||||||
</label>
|
</label>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -44,7 +44,7 @@ export function PinGuard(props: PinGuardProps) {
|
||||||
</Col>
|
</Col>
|
||||||
</Row>}
|
</Row>}
|
||||||
<PinGuardMCUInputGroup
|
<PinGuardMCUInputGroup
|
||||||
label={t("Pin Guard {{ num }}", { num: 1 })}
|
label={DeviceSetting.pinGuard1}
|
||||||
pinNumKey={"pin_guard_1_pin_nr"}
|
pinNumKey={"pin_guard_1_pin_nr"}
|
||||||
timeoutKey={"pin_guard_1_time_out"}
|
timeoutKey={"pin_guard_1_time_out"}
|
||||||
activeStateKey={"pin_guard_1_active_state"}
|
activeStateKey={"pin_guard_1_active_state"}
|
||||||
|
@ -52,7 +52,7 @@ export function PinGuard(props: PinGuardProps) {
|
||||||
resources={resources}
|
resources={resources}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
<PinGuardMCUInputGroup
|
<PinGuardMCUInputGroup
|
||||||
label={t("Pin Guard {{ num }}", { num: 2 })}
|
label={DeviceSetting.pinGuard2}
|
||||||
pinNumKey={"pin_guard_2_pin_nr"}
|
pinNumKey={"pin_guard_2_pin_nr"}
|
||||||
timeoutKey={"pin_guard_2_time_out"}
|
timeoutKey={"pin_guard_2_time_out"}
|
||||||
activeStateKey={"pin_guard_2_active_state"}
|
activeStateKey={"pin_guard_2_active_state"}
|
||||||
|
@ -60,7 +60,7 @@ export function PinGuard(props: PinGuardProps) {
|
||||||
resources={resources}
|
resources={resources}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
<PinGuardMCUInputGroup
|
<PinGuardMCUInputGroup
|
||||||
label={t("Pin Guard {{ num }}", { num: 3 })}
|
label={DeviceSetting.pinGuard3}
|
||||||
pinNumKey={"pin_guard_3_pin_nr"}
|
pinNumKey={"pin_guard_3_pin_nr"}
|
||||||
timeoutKey={"pin_guard_3_time_out"}
|
timeoutKey={"pin_guard_3_time_out"}
|
||||||
activeStateKey={"pin_guard_3_active_state"}
|
activeStateKey={"pin_guard_3_active_state"}
|
||||||
|
@ -68,7 +68,7 @@ export function PinGuard(props: PinGuardProps) {
|
||||||
resources={resources}
|
resources={resources}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
<PinGuardMCUInputGroup
|
<PinGuardMCUInputGroup
|
||||||
label={t("Pin Guard {{ num }}", { num: 4 })}
|
label={DeviceSetting.pinGuard4}
|
||||||
pinNumKey={"pin_guard_4_pin_nr"}
|
pinNumKey={"pin_guard_4_pin_nr"}
|
||||||
timeoutKey={"pin_guard_4_time_out"}
|
timeoutKey={"pin_guard_4_time_out"}
|
||||||
activeStateKey={"pin_guard_4_active_state"}
|
activeStateKey={"pin_guard_4_active_state"}
|
||||||
|
@ -76,7 +76,7 @@ export function PinGuard(props: PinGuardProps) {
|
||||||
resources={resources}
|
resources={resources}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
<PinGuardMCUInputGroup
|
<PinGuardMCUInputGroup
|
||||||
label={t("Pin Guard {{ num }}", { num: 5 })}
|
label={DeviceSetting.pinGuard5}
|
||||||
pinNumKey={"pin_guard_5_pin_nr"}
|
pinNumKey={"pin_guard_5_pin_nr"}
|
||||||
timeoutKey={"pin_guard_5_time_out"}
|
timeoutKey={"pin_guard_5_time_out"}
|
||||||
activeStateKey={"pin_guard_5_active_state"}
|
activeStateKey={"pin_guard_5_active_state"}
|
||||||
|
|
|
@ -2,28 +2,32 @@ import * as React from "react";
|
||||||
import { Row, Col } from "../../../ui/index";
|
import { Row, Col } from "../../../ui/index";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { DevSettings } from "../../../account/dev/dev_support";
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
|
||||||
export function SpacePanelHeader() {
|
export function SpacePanelHeader() {
|
||||||
const newFormat = DevSettings.futureFeaturesEnabled();
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
const width = newFormat ? 4 : 2;
|
const width = newFormat ? 4 : 2;
|
||||||
const offset = newFormat ? 0 : 6;
|
const offset = newFormat ? 0 : 6;
|
||||||
return <div className="label-headings">
|
return <div className="label-headings">
|
||||||
<Row>
|
<Highlight settingName={DeviceSetting.axisHeadingLabels}>
|
||||||
<Col xs={width} xsOffset={offset} className={"centered-button-div"}>
|
<Row>
|
||||||
<label>
|
<Col xs={width} xsOffset={offset} className={"centered-button-div"}>
|
||||||
{t("X AXIS")}
|
<label>
|
||||||
</label>
|
{t("X AXIS")}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={width} className={"centered-button-div"}>
|
</Col>
|
||||||
<label>
|
<Col xs={width} className={"centered-button-div"}>
|
||||||
{t("Y AXIS")}
|
<label>
|
||||||
</label>
|
{t("Y AXIS")}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={width} className={"centered-button-div"}>
|
</Col>
|
||||||
<label>
|
<Col xs={width} className={"centered-button-div"}>
|
||||||
{t("Z AXIS")}
|
<label>
|
||||||
</label>
|
{t("Z AXIS")}
|
||||||
</Col>
|
</label>
|
||||||
</Row>
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Highlight>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ export interface NumericMCUInputGroupProps {
|
||||||
export interface PinGuardMCUInputGroupProps {
|
export interface PinGuardMCUInputGroupProps {
|
||||||
sourceFwConfig: SourceFwConfig;
|
sourceFwConfig: SourceFwConfig;
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
label: string;
|
label: DeviceSetting;
|
||||||
pinNumKey: McuParamName;
|
pinNumKey: McuParamName;
|
||||||
timeoutKey: McuParamName;
|
timeoutKey: McuParamName;
|
||||||
activeStateKey: McuParamName;
|
activeStateKey: McuParamName;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { store } from "../../redux/store";
|
||||||
import { ControlPanelState } from "../interfaces";
|
import { ControlPanelState } from "../interfaces";
|
||||||
import { toggleControlPanel, bulkToggleControlPanel } from "../actions";
|
import { toggleControlPanel, bulkToggleControlPanel } from "../actions";
|
||||||
import { urlFriendly } from "../../util";
|
import { urlFriendly } from "../../util";
|
||||||
|
@ -56,6 +57,11 @@ const ERROR_HANDLING_PANEL = [
|
||||||
];
|
];
|
||||||
const PIN_GUARD_PANEL = [
|
const PIN_GUARD_PANEL = [
|
||||||
DeviceSetting.pinGuard,
|
DeviceSetting.pinGuard,
|
||||||
|
DeviceSetting.pinGuard1,
|
||||||
|
DeviceSetting.pinGuard2,
|
||||||
|
DeviceSetting.pinGuard3,
|
||||||
|
DeviceSetting.pinGuard4,
|
||||||
|
DeviceSetting.pinGuard5,
|
||||||
];
|
];
|
||||||
const DANGER_ZONE_PANEL = [
|
const DANGER_ZONE_PANEL = [
|
||||||
DeviceSetting.dangerZone,
|
DeviceSetting.dangerZone,
|
||||||
|
@ -63,6 +69,8 @@ const DANGER_ZONE_PANEL = [
|
||||||
];
|
];
|
||||||
const PIN_BINDINGS_PANEL = [
|
const PIN_BINDINGS_PANEL = [
|
||||||
DeviceSetting.pinBindings,
|
DeviceSetting.pinBindings,
|
||||||
|
DeviceSetting.savedPinBindings,
|
||||||
|
DeviceSetting.addNewPinBinding,
|
||||||
];
|
];
|
||||||
const POWER_AND_RESET_PANEL = [
|
const POWER_AND_RESET_PANEL = [
|
||||||
DeviceSetting.powerAndReset,
|
DeviceSetting.powerAndReset,
|
||||||
|
@ -183,6 +191,7 @@ export interface HighlightProps {
|
||||||
| (React.ReactChild | false)[]
|
| (React.ReactChild | false)[]
|
||||||
| (React.ReactChild | React.ReactChild[])[];
|
| (React.ReactChild | React.ReactChild[])[];
|
||||||
className?: string;
|
className?: string;
|
||||||
|
searchTerm?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HighlightState {
|
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() {
|
render() {
|
||||||
|
const show = !this.searchTerm ||
|
||||||
|
this.props.settingName.toLowerCase().includes(this.searchTerm);
|
||||||
return <div className={[
|
return <div className={[
|
||||||
this.props.className,
|
this.props.className,
|
||||||
this.state.className,
|
this.state.className,
|
||||||
].join(" ")}>
|
].join(" ")}
|
||||||
|
hidden={!show}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { PinNumberDropdown } from "./pin_number_dropdown";
|
||||||
import { DevSettings } from "../../account/dev/dev_support";
|
import { DevSettings } from "../../account/dev/dev_support";
|
||||||
import { ToolTips } from "../../constants";
|
import { ToolTips } from "../../constants";
|
||||||
import { Position } from "@blueprintjs/core";
|
import { Position } from "@blueprintjs/core";
|
||||||
|
import { Highlight } from "./maybe_highlight";
|
||||||
|
|
||||||
export class PinGuardMCUInputGroup
|
export class PinGuardMCUInputGroup
|
||||||
extends React.Component<PinGuardMCUInputGroupProps> {
|
extends React.Component<PinGuardMCUInputGroupProps> {
|
||||||
|
@ -50,7 +51,7 @@ export class PinGuardMCUInputGroup
|
||||||
? <Row>
|
? <Row>
|
||||||
<Col xs={3}>
|
<Col xs={3}>
|
||||||
<label>
|
<label>
|
||||||
{label}
|
{t(label)}
|
||||||
</label>
|
</label>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={3}>
|
<Col xs={3}>
|
||||||
|
@ -63,46 +64,48 @@ export class PinGuardMCUInputGroup
|
||||||
<this.State />
|
<this.State />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
: <div className={"pin-guard-input-row"}>
|
: <Highlight settingName={label}>
|
||||||
<Row>
|
<div className={"pin-guard-input-row"}>
|
||||||
<Col xs={12}>
|
<Row>
|
||||||
<label>
|
<Col xs={12}>
|
||||||
{label}
|
<label>
|
||||||
</label>
|
{t(label)}
|
||||||
</Col>
|
</label>
|
||||||
</Row>
|
</Col>
|
||||||
<Row>
|
</Row>
|
||||||
<Col xs={5} xsOffset={1} className="no-pad">
|
<Row>
|
||||||
<label>
|
<Col xs={5} xsOffset={1} className="no-pad">
|
||||||
{t("Pin Number")}
|
<label>
|
||||||
</label>
|
{t("Pin Number")}
|
||||||
<Help text={ToolTips.PIN_GUARD_PIN_NUMBER}
|
</label>
|
||||||
position={Position.TOP_RIGHT} />
|
<Help text={ToolTips.PIN_GUARD_PIN_NUMBER}
|
||||||
</Col>
|
position={Position.TOP_RIGHT} />
|
||||||
<Col xs={5} className="no-pad">
|
</Col>
|
||||||
<this.Number />
|
<Col xs={5} className="no-pad">
|
||||||
</Col>
|
<this.Number />
|
||||||
</Row>
|
</Col>
|
||||||
<Row>
|
</Row>
|
||||||
<Col xs={5} xsOffset={1} className="no-pad">
|
<Row>
|
||||||
<label>
|
<Col xs={5} xsOffset={1} className="no-pad">
|
||||||
{t("Timeout (sec)")}
|
<label>
|
||||||
</label>
|
{t("Timeout (sec)")}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={5} className="no-pad">
|
</Col>
|
||||||
<this.Timeout />
|
<Col xs={5} className="no-pad">
|
||||||
</Col>
|
<this.Timeout />
|
||||||
</Row>
|
</Col>
|
||||||
<Row>
|
</Row>
|
||||||
<Col xs={5} xsOffset={1} className="no-pad">
|
<Row>
|
||||||
<label>
|
<Col xs={5} xsOffset={1} className="no-pad">
|
||||||
{t("To State")}
|
<label>
|
||||||
</label>
|
{t("To State")}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={5} className="no-pad">
|
</Col>
|
||||||
<this.State />
|
<Col xs={5} className="no-pad">
|
||||||
</Col>
|
<this.State />
|
||||||
</Row>
|
</Col>
|
||||||
</div>;
|
</Row>
|
||||||
|
</div>
|
||||||
|
</Highlight>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
} from "farmbot/dist/resources/api_resources";
|
} from "farmbot/dist/resources/api_resources";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { DevSettings } from "../../account/dev/dev_support";
|
import { DevSettings } from "../../account/dev/dev_support";
|
||||||
|
import { DeviceSetting } from "../../constants";
|
||||||
|
|
||||||
export class PinBindingInputGroup
|
export class PinBindingInputGroup
|
||||||
extends React.Component<PinBindingInputGroupProps, PinBindingInputGroupState> {
|
extends React.Component<PinBindingInputGroupProps, PinBindingInputGroupState> {
|
||||||
|
@ -129,7 +130,7 @@ export class PinBindingInputGroup
|
||||||
render() {
|
render() {
|
||||||
const newFormat = DevSettings.futureFeaturesEnabled();
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
return <div className="pin-binding-input-rows">
|
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 && <this.Number />}
|
||||||
{newFormat && <Row>
|
{newFormat && <Row>
|
||||||
<Col xs={5}>
|
<Col xs={5}>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Row, Col, Help } from "../../ui";
|
import { Row, Col, Help } from "../../ui";
|
||||||
import { ToolTips } from "../../constants";
|
import { ToolTips, DeviceSetting } from "../../constants";
|
||||||
import { selectAllPinBindings } from "../../resources/selectors";
|
import { selectAllPinBindings } from "../../resources/selectors";
|
||||||
import { PinBindingsContentProps, PinBindingListItems } from "./interfaces";
|
import { PinBindingsContentProps, PinBindingListItems } from "./interfaces";
|
||||||
import { PinBindingsList } from "./pin_bindings_list";
|
import { PinBindingsList } from "./pin_bindings_list";
|
||||||
|
@ -17,6 +17,7 @@ import {
|
||||||
} from "farmbot/dist/resources/api_resources";
|
} from "farmbot/dist/resources/api_resources";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { DevSettings } from "../../account/dev/dev_support";
|
import { DevSettings } from "../../account/dev/dev_support";
|
||||||
|
import { Highlight } from "../components/maybe_highlight";
|
||||||
|
|
||||||
/** Width of UI columns in Pin Bindings widget. */
|
/** Width of UI columns in Pin Bindings widget. */
|
||||||
export enum PinBindingColWidth {
|
export enum PinBindingColWidth {
|
||||||
|
@ -73,32 +74,38 @@ export const PinBindingsContent = (props: PinBindingsContentProps) => {
|
||||||
const pinBindings = apiPinBindings(resources);
|
const pinBindings = apiPinBindings(resources);
|
||||||
const newFormat = DevSettings.futureFeaturesEnabled();
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
return <div className="pin-bindings">
|
return <div className="pin-bindings">
|
||||||
<Row>
|
<Highlight settingName={DeviceSetting.pinBindings}>
|
||||||
{newFormat && <Help text={ToolTips.PIN_BINDINGS}
|
<Row>
|
||||||
position={Position.TOP_RIGHT} />}
|
{newFormat && <Help text={ToolTips.PIN_BINDINGS}
|
||||||
<StockPinBindingsButton
|
position={Position.TOP_RIGHT} />}
|
||||||
dispatch={dispatch} firmwareHardware={firmwareHardware} />
|
<StockPinBindingsButton
|
||||||
<Popover
|
dispatch={dispatch} firmwareHardware={firmwareHardware} />
|
||||||
position={Position.TOP_RIGHT}
|
<Popover
|
||||||
interactionKind={PopoverInteractionKind.HOVER}
|
position={Position.TOP_RIGHT}
|
||||||
portalClassName={"bindings-warning-icon"}
|
interactionKind={PopoverInteractionKind.HOVER}
|
||||||
popoverClassName={"help"}>
|
portalClassName={"bindings-warning-icon"}
|
||||||
<i className="fa fa-exclamation-triangle" />
|
popoverClassName={"help"}>
|
||||||
<div className={"pin-binding-warning"}>
|
<i className="fa fa-exclamation-triangle" />
|
||||||
{t(ToolTips.PIN_BINDING_WARNING)}
|
<div className={"pin-binding-warning"}>
|
||||||
</div>
|
{t(ToolTips.PIN_BINDING_WARNING)}
|
||||||
</Popover>
|
</div>
|
||||||
</Row>
|
</Popover>
|
||||||
|
</Row>
|
||||||
|
</Highlight>
|
||||||
<div className={"pin-bindings-list-and-input"}>
|
<div className={"pin-bindings-list-and-input"}>
|
||||||
{!newFormat && <PinBindingsListHeader />}
|
{!newFormat && <PinBindingsListHeader />}
|
||||||
<PinBindingsList
|
<Highlight settingName={DeviceSetting.savedPinBindings}>
|
||||||
pinBindings={pinBindings}
|
<PinBindingsList
|
||||||
dispatch={dispatch}
|
pinBindings={pinBindings}
|
||||||
resources={resources} />
|
dispatch={dispatch}
|
||||||
<PinBindingInputGroup
|
resources={resources} />
|
||||||
pinBindings={pinBindings}
|
</Highlight>
|
||||||
dispatch={dispatch}
|
<Highlight settingName={DeviceSetting.addNewPinBinding}>
|
||||||
resources={resources} />
|
<PinBindingInputGroup
|
||||||
|
pinBindings={pinBindings}
|
||||||
|
dispatch={dispatch}
|
||||||
|
resources={resources} />
|
||||||
|
</Highlight>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { DevSettings } from "../../account/dev/dev_support";
|
||||||
import {
|
import {
|
||||||
PinBindingType, PinBindingSpecialAction,
|
PinBindingType, PinBindingSpecialAction,
|
||||||
} from "farmbot/dist/resources/api_resources";
|
} from "farmbot/dist/resources/api_resources";
|
||||||
|
import { DeviceSetting } from "../../constants";
|
||||||
|
|
||||||
export const PinBindingsList = (props: PinBindingsListProps) => {
|
export const PinBindingsList = (props: PinBindingsListProps) => {
|
||||||
const { pinBindings, resources, dispatch } = props;
|
const { pinBindings, resources, dispatch } = props;
|
||||||
|
@ -41,7 +42,7 @@ export const PinBindingsList = (props: PinBindingsListProps) => {
|
||||||
|
|
||||||
const newFormat = DevSettings.futureFeaturesEnabled();
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
return <div className={"bindings-list"}>
|
return <div className={"bindings-list"}>
|
||||||
{newFormat && <Row><label>{t("saved pin bindings")}</label></Row>}
|
{newFormat && <Row><label>{t(DeviceSetting.savedPinBindings)}</label></Row>}
|
||||||
{pinBindings
|
{pinBindings
|
||||||
.sort((a, b) => sortByNameAndPin(a.pin_number, b.pin_number))
|
.sort((a, b) => sortByNameAndPin(a.pin_number, b.pin_number))
|
||||||
.map(x => {
|
.map(x => {
|
||||||
|
|
|
@ -191,6 +191,16 @@ describe("designer reducer", () => {
|
||||||
expect(newState.tryGroupSortType).toEqual("random");
|
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", () => {
|
it("enables edit group area in map mode", () => {
|
||||||
const state = oldState();
|
const state = oldState();
|
||||||
state.editGroupAreaInMap = false;
|
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;
|
openedSavedGarden: string | undefined;
|
||||||
tryGroupSortType: PointGroupSortType | "nn" | undefined;
|
tryGroupSortType: PointGroupSortType | "nn" | undefined;
|
||||||
editGroupAreaInMap: boolean;
|
editGroupAreaInMap: boolean;
|
||||||
|
settingsSearchTerm: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TaggedExecutable = TaggedSequence | TaggedRegimen;
|
export type TaggedExecutable = TaggedSequence | TaggedRegimen;
|
||||||
|
|
|
@ -27,6 +27,7 @@ export const initialState: DesignerState = {
|
||||||
openedSavedGarden: undefined,
|
openedSavedGarden: undefined,
|
||||||
tryGroupSortType: undefined,
|
tryGroupSortType: undefined,
|
||||||
editGroupAreaInMap: false,
|
editGroupAreaInMap: false,
|
||||||
|
settingsSearchTerm: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const designer = generateReducer<DesignerState>(initialState)
|
export const designer = generateReducer<DesignerState>(initialState)
|
||||||
|
@ -107,6 +108,10 @@ export const designer = generateReducer<DesignerState>(initialState)
|
||||||
s.tryGroupSortType = payload;
|
s.tryGroupSortType = payload;
|
||||||
return s;
|
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 }) => {
|
.add<boolean>(Actions.EDIT_GROUP_AREA_IN_MAP, (s, { payload }) => {
|
||||||
s.editGroupAreaInMap = payload;
|
s.editGroupAreaInMap = payload;
|
||||||
return s;
|
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);
|
wrapper.find("input").simulate("KeyPress", e);
|
||||||
expect(p.onChange).not.toHaveBeenCalled();
|
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)}
|
onChange={e => props.onChange(e.currentTarget.value)}
|
||||||
onKeyPress={e => props.onKeyPress?.(e.currentTarget.value)}
|
onKeyPress={e => props.onKeyPress?.(e.currentTarget.value)}
|
||||||
placeholder={props.placeholder} />
|
placeholder={props.placeholder} />
|
||||||
{props.searchTerm && props.customRightIcon}
|
{props.searchTerm && (props.customRightIcon ||
|
||||||
|
<i className="fa fa-times" onClick={() => props.onChange("")} />)}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
class BroadcastToAll < Mutations::Command
|
class BroadcastToAll < Mutations::Command
|
||||||
RELEVANT_TIMEFRAME = 7.months.ago
|
|
||||||
|
|
||||||
required do
|
required do
|
||||||
string :title
|
string :title
|
||||||
string :content
|
string :content
|
||||||
|
@ -31,7 +29,7 @@ class BroadcastToAll < Mutations::Command
|
||||||
end
|
end
|
||||||
|
|
||||||
def devices
|
def devices
|
||||||
@devices ||= Device.where("updated_at > ?", RELEVANT_TIMEFRAME)
|
@devices ||= Device.all
|
||||||
end
|
end
|
||||||
|
|
||||||
def attach_alerts
|
def attach_alerts
|
||||||
|
|
30
package.json
30
package.json
|
@ -24,20 +24,20 @@
|
||||||
"author": "farmbot.io",
|
"author": "farmbot.io",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "7.8.7",
|
"@babel/core": "7.9.0",
|
||||||
"@blueprintjs/core": "3.24.0",
|
"@blueprintjs/core": "3.25.0",
|
||||||
"@blueprintjs/datetime": "3.15.2",
|
"@blueprintjs/datetime": "3.16.0",
|
||||||
"@blueprintjs/select": "3.12.0",
|
"@blueprintjs/select": "3.12.1",
|
||||||
"@types/enzyme": "3.10.5",
|
"@types/enzyme": "3.10.5",
|
||||||
"@types/jest": "25.1.4",
|
"@types/jest": "25.2.1",
|
||||||
"@types/lodash": "4.14.149",
|
"@types/lodash": "4.14.149",
|
||||||
"@types/markdown-it": "0.0.9",
|
"@types/markdown-it": "10.0.0",
|
||||||
"@types/moxios": "0.4.9",
|
"@types/moxios": "0.4.9",
|
||||||
"@types/node": "13.9.2",
|
"@types/node": "13.11.1",
|
||||||
"@types/promise-timeout": "1.3.0",
|
"@types/promise-timeout": "1.3.0",
|
||||||
"@types/react": "16.9.23",
|
"@types/react": "16.9.34",
|
||||||
"@types/react-color": "3.0.1",
|
"@types/react-color": "3.0.1",
|
||||||
"@types/react-dom": "16.9.5",
|
"@types/react-dom": "16.9.6",
|
||||||
"@types/react-redux": "7.1.7",
|
"@types/react-redux": "7.1.7",
|
||||||
"axios": "0.19.2",
|
"axios": "0.19.2",
|
||||||
"boxed_value": "1.0.0",
|
"boxed_value": "1.0.0",
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
"enzyme": "3.11.0",
|
"enzyme": "3.11.0",
|
||||||
"enzyme-adapter-react-16": "1.15.2",
|
"enzyme-adapter-react-16": "1.15.2",
|
||||||
"farmbot": "9.2.3",
|
"farmbot": "9.2.3",
|
||||||
"i18next": "19.3.3",
|
"i18next": "19.4.1",
|
||||||
"install": "0.13.0",
|
"install": "0.13.0",
|
||||||
"lodash": "4.17.15",
|
"lodash": "4.17.15",
|
||||||
"markdown-it": "10.0.0",
|
"markdown-it": "10.0.0",
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
"moment": "2.24.0",
|
"moment": "2.24.0",
|
||||||
"moxios": "0.4.0",
|
"moxios": "0.4.0",
|
||||||
"mqtt": "3.0.0",
|
"mqtt": "3.0.0",
|
||||||
"npm": "6.14.3",
|
"npm": "6.14.4",
|
||||||
"parcel-bundler": "1.12.4",
|
"parcel-bundler": "1.12.4",
|
||||||
"promise-timeout": "1.3.0",
|
"promise-timeout": "1.3.0",
|
||||||
"raf": "3.4.1",
|
"raf": "3.4.1",
|
||||||
|
@ -71,15 +71,15 @@
|
||||||
"redux-thunk": "2.3.0",
|
"redux-thunk": "2.3.0",
|
||||||
"sass-lint": "1.13.1",
|
"sass-lint": "1.13.1",
|
||||||
"takeme": "0.11.3",
|
"takeme": "0.11.3",
|
||||||
"ts-jest": "25.2.1",
|
"ts-jest": "25.3.1",
|
||||||
"ts-lint": "4.5.1",
|
"ts-lint": "4.5.1",
|
||||||
"tslint": "6.1.0",
|
"tslint": "6.1.1",
|
||||||
"typescript": "3.8.3",
|
"typescript": "3.8.3",
|
||||||
"which": "2.0.2"
|
"which": "2.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "25.1.0",
|
"jest": "25.3.0",
|
||||||
"jest-cli": "25.1.0",
|
"jest-cli": "25.3.0",
|
||||||
"jest-junit": "10.0.0",
|
"jest-junit": "10.0.0",
|
||||||
"jest-skipped-reporter": "0.0.5",
|
"jest-skipped-reporter": "0.0.5",
|
||||||
"jshint": "2.11.0",
|
"jshint": "2.11.0",
|
||||||
|
|
Loading…
Reference in New Issue