cleanup and refactoring

pull/1721/head
gabrielburnworth 2020-02-26 12:08:49 -08:00
parent a49e5e67ba
commit edb96d3ca8
19 changed files with 85 additions and 167 deletions

View File

@ -17,6 +17,7 @@ import { PowerAndReset } from "./fbos_settings/power_and_reset";
import { BootSequenceSelector } from "./fbos_settings/boot_sequence_selector";
import { ExternalUrl } from "../../external_urls";
import { Highlight } from "./maybe_highlight";
import { OtaTimeSelectorRow } from "./fbos_settings/ota_time_selector";
export enum ColWidth {
label = 3,
@ -78,8 +79,6 @@ export class FarmbotOsSettings
const { bot, sourceFbosConfig, botToMqttStatus } = this.props;
const { sync_status } = bot.hardware.informational_settings;
const botOnline = isBotOnline(sync_status, botToMqttStatus);
const timeFormat = this.props.webAppConfig.body.time_format_24_hour ?
"24h" : "12h";
return <Widget className="device-widget">
<form onSubmit={(e) => e.preventDefault()}>
<WidgetHeader title="Device">
@ -133,11 +132,14 @@ export class FarmbotOsSettings
shouldDisplay={this.props.shouldDisplay}
timeSettings={this.props.timeSettings}
sourceFbosConfig={sourceFbosConfig} />
<AutoUpdateRow
timeFormat={timeFormat}
<OtaTimeSelectorRow
timeSettings={this.props.timeSettings}
device={this.props.deviceAccount}
dispatch={this.props.dispatch}
sourceFbosConfig={sourceFbosConfig} />
<AutoUpdateRow
dispatch={this.props.dispatch}
sourceFbosConfig={sourceFbosConfig} />
<FarmbotOsRow
bot={this.props.bot}
osReleaseNotesHeading={this.osReleaseNotes.heading}

View File

@ -11,7 +11,7 @@ import { fakeState } from "../../../../__test_support__/fake_state";
import { edit, save } from "../../../../api/crud";
import { fakeFbosConfig } from "../../../../__test_support__/fake_state/resources";
import {
buildResourceIndex, fakeDevice
buildResourceIndex
} from "../../../../__test_support__/resource_index_builder";
describe("<AutoUpdateRow/>", () => {
@ -20,10 +20,8 @@ describe("<AutoUpdateRow/>", () => {
state.resources = buildResourceIndex([fakeConfig]);
const fakeProps = (): AutoUpdateRowProps => ({
timeFormat: "12h",
device: fakeDevice(),
dispatch: jest.fn(x => x(jest.fn(), () => state)),
sourceFbosConfig: () => ({ value: 1, consistent: true })
sourceFbosConfig: () => ({ value: 1, consistent: true }),
});
it("renders", () => {
@ -35,7 +33,7 @@ describe("<AutoUpdateRow/>", () => {
const p = fakeProps();
p.sourceFbosConfig = () => ({ value: 0, consistent: true });
const wrapper = mount(<AutoUpdateRow {...p} />);
wrapper.find("button").at(1).simulate("click");
wrapper.find("button").first().simulate("click");
expect(edit).toHaveBeenCalledWith(fakeConfig, { os_auto_update: true });
expect(save).toHaveBeenCalledWith(fakeConfig.uuid);
});
@ -44,7 +42,7 @@ describe("<AutoUpdateRow/>", () => {
const p = fakeProps();
p.sourceFbosConfig = () => ({ value: 1, consistent: true });
const wrapper = mount(<AutoUpdateRow {...p} />);
wrapper.find("button").at(1).simulate("click");
wrapper.find("button").first().simulate("click");
expect(edit).toHaveBeenCalledWith(fakeConfig, { os_auto_update: false });
expect(save).toHaveBeenCalledWith(fakeConfig.uuid);
});

View File

@ -6,38 +6,30 @@ import { updateConfig } from "../../actions";
import { Content, DeviceSetting } from "../../../constants";
import { AutoUpdateRowProps } from "./interfaces";
import { t } from "../../../i18next_wrapper";
import { OtaTimeSelector, changeOtaHour } from "./ota_time_selector";
import { Highlight } from "../maybe_highlight";
export function AutoUpdateRow(props: AutoUpdateRowProps) {
const osAutoUpdate = props.sourceFbosConfig("os_auto_update");
return <div>
<OtaTimeSelector
timeFormat={props.timeFormat}
disabled={!osAutoUpdate.value}
value={props.device.body.ota_hour}
onChange={changeOtaHour(props.dispatch, props.device)} />
<Row>
<Highlight settingName={DeviceSetting.farmbotOSAutoUpdate}>
<Col xs={ColWidth.label}>
<label>
{t(DeviceSetting.farmbotOSAutoUpdate)}
</label>
</Col>
<Col xs={ColWidth.description}>
<p>
{t(Content.OS_AUTO_UPDATE)}
</p>
</Col>
<Col xs={ColWidth.button}>
<ToggleButton toggleValue={osAutoUpdate.value}
dim={!osAutoUpdate.consistent}
toggleAction={() => props.dispatch(updateConfig({
os_auto_update: !osAutoUpdate.value
}))} />
</Col>
</Highlight>
</Row>
</div>;
return <Row>
<Highlight settingName={DeviceSetting.farmbotOSAutoUpdate}>
<Col xs={ColWidth.label}>
<label>
{t(DeviceSetting.farmbotOSAutoUpdate)}
</label>
</Col>
<Col xs={ColWidth.description}>
<p>
{t(Content.OS_AUTO_UPDATE)}
</p>
</Col>
<Col xs={ColWidth.button}>
<ToggleButton toggleValue={osAutoUpdate.value}
dim={!osAutoUpdate.consistent}
toggleAction={() => props.dispatch(updateConfig({
os_auto_update: !osAutoUpdate.value
}))} />
</Col>
</Highlight>
</Row>;
}

View File

@ -12,7 +12,6 @@ import {
TaggedDevice,
} from "farmbot";
import { TimeSettings } from "../../../interfaces";
import { PreferredHourFormat } from "./ota_time_selector";
export interface AutoSyncRowProps {
dispatch: Function;
@ -21,9 +20,14 @@ export interface AutoSyncRowProps {
export interface AutoUpdateRowProps {
dispatch: Function;
timeFormat: PreferredHourFormat;
sourceFbosConfig: SourceFbosConfig;
}
export interface OtaTimeSelectorRowProps {
dispatch: Function;
sourceFbosConfig: SourceFbosConfig;
device: TaggedDevice;
timeSettings: TimeSettings;
}
export interface CameraSelectionProps {

View File

@ -6,11 +6,12 @@ import { edit, save } from "../../../api/crud";
import { ColWidth } from "../farmbot_os_settings";
import { DeviceSetting } from "../../../constants";
import { Highlight } from "../maybe_highlight";
import { OtaTimeSelectorRowProps } from "./interfaces";
// tslint:disable-next-line:no-null-keyword
const UNDEFINED = null as unknown as undefined;
const IMMEDIATELY = -1;
export type PreferredHourFormat = "12h" | "24h";
type PreferredHourFormat = "12h" | "24h";
type HOUR =
| typeof IMMEDIATELY
| 0
@ -163,3 +164,13 @@ export const OtaTimeSelector = (props: OtaTimeSelectorProps): JSX.Element => {
</Highlight>
</Row>;
};
export function OtaTimeSelectorRow(props: OtaTimeSelectorRowProps) {
const osAutoUpdate = props.sourceFbosConfig("os_auto_update");
const timeFormat = props.timeSettings.hour24 ? "24h" : "12h";
return <OtaTimeSelector
timeFormat={timeFormat}
disabled={!osAutoUpdate.value}
value={props.device.body.ota_hour}
onChange={changeOtaHour(props.dispatch, props.device)} />;
}

View File

@ -71,6 +71,14 @@ describe("<DesignerNavTabs />", () => {
expect(wrapper.html()).toContain("active");
});
it("renders for tools", () => {
mockPath = "/app/designer/tools";
mockDev = false;
const wrapper = shallow(<DesignerNavTabs />);
expect(wrapper.hasClass("gray-panel")).toBeTruthy();
expect(wrapper.html()).toContain("active");
});
it("renders for zones", () => {
mockPath = "/app/designer/zones";
mockDev = true;

View File

@ -4,11 +4,6 @@ jest.mock("../../../../../history", () => ({
getPathArray: jest.fn(() => { return mockPath.split("/"); })
}));
let mockDev = false;
jest.mock("../../../../../account/dev/dev_support", () => ({
DevSettings: { futureFeaturesEnabled: () => mockDev }
}));
import * as React from "react";
import { ToolSlotLayer, ToolSlotLayerProps } from "../tool_slot_layer";
import {
@ -57,16 +52,6 @@ describe("<ToolSlotLayer/>", () => {
expect(result.find(ToolSlotPoint).length).toEqual(1);
});
it("navigates to tools page", async () => {
mockDev = true;
mockPath = "/app/designer/plants";
const p = fakeProps();
const wrapper = shallow(<ToolSlotLayer {...p} />);
const tools = wrapper.find("g").first();
await tools.simulate("click");
expect(history.push).toHaveBeenCalledWith("/app/tools");
});
it("doesn't navigate to tools page", async () => {
mockPath = "/app/designer/plants/1";
const p = fakeProps();

View File

@ -1,8 +1,3 @@
let mockDev = false;
jest.mock("../../../../../account/dev/dev_support", () => ({
DevSettings: { futureFeaturesEnabled: () => mockDev }
}));
jest.mock("../../../../../history", () => ({ history: { push: jest.fn() } }));
import * as React from "react";
@ -17,10 +12,6 @@ import { svgMount } from "../../../../../__test_support__/svg_mount";
import { history } from "../../../../../history";
describe("<ToolSlotPoint/>", () => {
beforeEach(() => {
mockDev = false;
});
const fakeProps = (): TSPProps => ({
mapTransformProps: fakeMapTransformProps(),
botPositionX: undefined,
@ -48,10 +39,6 @@ describe("<ToolSlotPoint/>", () => {
const p = fakeProps();
p.slot.toolSlot.body.id = 1;
const wrapper = svgMount(<ToolSlotPoint {...p} />);
mockDev = true;
wrapper.find("g").first().simulate("click");
expect(history.push).not.toHaveBeenCalled();
mockDev = false;
wrapper.find("g").first().simulate("click");
expect(history.push).toHaveBeenCalledWith("/app/designer/tool-slots/1");
});

View File

@ -2,9 +2,7 @@ import * as React from "react";
import { SlotWithTool, UUID } from "../../../../resources/interfaces";
import { ToolSlotPoint } from "./tool_slot_point";
import { MapTransformProps } from "../../interfaces";
import { history, getPathArray } from "../../../../history";
import { maybeNoPointer } from "../../util";
import { DevSettings } from "../../../../account/dev/dev_support";
export interface ToolSlotLayerProps {
visible: boolean;
@ -16,17 +14,11 @@ export interface ToolSlotLayerProps {
}
export function ToolSlotLayer(props: ToolSlotLayerProps) {
const pathArray = getPathArray();
const canClickTool = !(pathArray[3] === "plants" && pathArray.length > 4);
const goToToolsPage = () => canClickTool &&
DevSettings.futureFeaturesEnabled() && history.push("/app/tools");
const { slots, visible, mapTransformProps } = props;
const cursor = canClickTool ? "pointer" : "default";
return <g
id="toolslot-layer"
onClick={goToToolsPage}
style={maybeNoPointer({ cursor: cursor })}>
style={maybeNoPointer({ cursor: "pointer" })}>
{visible &&
slots.map(slot =>
<ToolSlotPoint

View File

@ -5,7 +5,6 @@ import { MapTransformProps } from "../../interfaces";
import { ToolbaySlot, ToolNames, Tool, GantryToolSlot } from "./tool_graphics";
import { ToolLabel } from "./tool_label";
import { includes } from "lodash";
import { DevSettings } from "../../../../account/dev/dev_support";
import { history } from "../../../../history";
import { t } from "../../../../i18next_wrapper";
@ -49,8 +48,7 @@ export const ToolSlotPoint = (props: TSPProps) => {
xySwap,
};
return <g id={"toolslot-" + id}
onClick={() => !DevSettings.futureFeaturesEnabled() &&
history.push(`/app/designer/tool-slots/${id}`)}>
onClick={() => history.push(`/app/designer/tool-slots/${id}`)}>
{pullout_direction && !gantry_mounted &&
<ToolbaySlot
id={-(id || 1)}

View File

@ -143,11 +143,10 @@ export function DesignerNavTabs(props: { hidden?: boolean }) {
panel={Panel.Weeds}
linkTo={"/app/designer/weeds"}
title={t("Weeds")} />
{!DevSettings.futureFeaturesEnabled() &&
<NavTab
panel={Panel.Tools}
linkTo={"/app/designer/tools"}
title={t("Tools")} />}
<NavTab
panel={Panel.Tools}
linkTo={"/app/designer/tools"}
title={t("Tools")} />
<NavTab
panel={Panel.Settings}
icon={"fa fa-gear"}

View File

@ -9,32 +9,19 @@ import { UUID } from "../../resources/interfaces";
import { edit, save } from "../../api/crud";
import { EditPlantStatusProps } from "./plant_panel";
const PLANT_STAGES: DropDownItem[] = [
{ value: "planned", label: t("Planned") },
{ value: "planted", label: t("Planted") },
{ value: "sprouted", label: t("Sprouted") },
{ value: "harvested", label: t("Harvested") },
export const PLANT_STAGE_DDI_LOOKUP = (): { [x: string]: DropDownItem } => ({
planned: { label: t("Planned"), value: "planned" },
planted: { label: t("Planted"), value: "planted" },
sprouted: { label: t("Sprouted"), value: "sprouted" },
harvested: { label: t("Harvested"), value: "harvested" },
});
export const PLANT_STAGE_LIST = () => [
PLANT_STAGE_DDI_LOOKUP().planned,
PLANT_STAGE_DDI_LOOKUP().planted,
PLANT_STAGE_DDI_LOOKUP().sprouted,
PLANT_STAGE_DDI_LOOKUP().harvested,
];
const PLANT_STAGES_DDI = {
[PLANT_STAGES[0].value]: {
label: PLANT_STAGES[0].label,
value: PLANT_STAGES[0].value
},
[PLANT_STAGES[1].value]: {
label: PLANT_STAGES[1].label,
value: PLANT_STAGES[1].value
},
[PLANT_STAGES[2].value]: {
label: PLANT_STAGES[2].label,
value: PLANT_STAGES[2].value
},
[PLANT_STAGES[3].value]: {
label: PLANT_STAGES[3].label,
value: PLANT_STAGES[3].value
},
};
/** Change `planted_at` value based on `plant_stage` update. */
const getUpdateByPlantStage = (plant_stage: PlantStage): PlantOptions => {
const update: PlantOptions = { plant_stage };
@ -52,8 +39,8 @@ const getUpdateByPlantStage = (plant_stage: PlantStage): PlantOptions => {
export function EditPlantStatus(props: EditPlantStatusProps) {
const { plantStatus, updatePlant, uuid } = props;
return <FBSelect
list={PLANT_STAGES}
selectedItem={PLANT_STAGES_DDI[plantStatus]}
list={PLANT_STAGE_LIST()}
selectedItem={PLANT_STAGE_DDI_LOOKUP()[plantStatus]}
onChange={ddi =>
updatePlant(uuid, getUpdateByPlantStage(ddi.value as PlantStage))} />;
}
@ -70,7 +57,7 @@ export const PlantStatusBulkUpdate = (props: PlantStatusBulkUpdateProps) =>
<p>{t("update plant status to")}</p>
<FBSelect
key={JSON.stringify(props.selected)}
list={PLANT_STAGES}
list={PLANT_STAGE_LIST()}
selectedItem={undefined}
customNullLabel={t("Select a status")}
onChange={ddi => {

View File

@ -9,7 +9,6 @@ import {
AddEqCriteria, AddNumberCriteria, editCriteria, AddStringCriteria,
toggleStringCriteria,
POINTER_TYPE_LIST,
PLANT_STAGE_LIST
} from "..";
import {
AddEqCriteriaProps, NumberCriteriaProps, DEFAULT_CRITERIA,
@ -19,6 +18,7 @@ import {
fakePointGroup
} from "../../../../__test_support__/fake_state/resources";
import { PointGroup } from "farmbot/dist/resources/api_resources";
import { PLANT_STAGE_LIST } from "../../../plants/edit_plant_status";
describe("<AddEqCriteria<string> />", () => {
const fakeProps = (): AddEqCriteriaProps<string> => ({

View File

@ -10,6 +10,9 @@ import {
AddNumberCriteriaState,
AddStringCriteriaProps,
} from "./interfaces";
import {
PLANT_STAGE_DDI_LOOKUP, PLANT_STAGE_LIST
} from "../../plants/edit_plant_status";
export class AddEqCriteria<T extends string | number>
extends React.Component<AddEqCriteriaProps<T>, AddEqCriteriaState> {
@ -78,19 +81,6 @@ export const POINTER_TYPE_LIST = () => [
POINTER_TYPE_DDI_LOOKUP().ToolSlot,
];
export const PLANT_STAGE_DDI_LOOKUP = (): { [x: string]: DropDownItem } => ({
planned: { label: t("Planned"), value: "planned" },
planted: { label: t("Planted"), value: "planted" },
sprouted: { label: t("Sprouted"), value: "sprouted" },
harvested: { label: t("Harvested"), value: "harvested" },
});
export const PLANT_STAGE_LIST = () => [
PLANT_STAGE_DDI_LOOKUP().planned,
PLANT_STAGE_DDI_LOOKUP().planted,
PLANT_STAGE_DDI_LOOKUP().sprouted,
PLANT_STAGE_DDI_LOOKUP().harvested,
];
export class AddStringCriteria
extends React.Component<AddStringCriteriaProps, AddEqCriteriaState> {
state: AddEqCriteriaState = { key: "", value: "" };

View File

@ -3,7 +3,7 @@ import { cloneDeep, capitalize } from "lodash";
import { Row, Col, FBSelect, DropDownItem } from "../../../ui";
import {
AddEqCriteria, toggleEqCriteria, editCriteria, AddNumberCriteria,
POINTER_TYPE_DDI_LOOKUP, PLANT_STAGE_DDI_LOOKUP, AddStringCriteria,
POINTER_TYPE_DDI_LOOKUP, AddStringCriteria,
CRITERIA_TYPE_DDI_LOOKUP, toggleStringCriteria
} from ".";
import {
@ -14,6 +14,7 @@ import {
} from "./interfaces";
import { t } from "../../../i18next_wrapper";
import { PointGroup } from "farmbot/dist/resources/api_resources";
import { PLANT_STAGE_DDI_LOOKUP } from "../../plants/edit_plant_status";
export class EqCriteriaSelection<T extends string | number>
extends React.Component<EqCriteriaSelectionProps<T>> {

View File

@ -1,12 +1,5 @@
jest.mock("../../history", () => ({ history: { push: jest.fn() } }));
let mockDev = false;
jest.mock("../../account/dev/dev_support", () => ({
DevSettings: {
futureFeaturesEnabled: () => mockDev,
}
}));
import { fakeState } from "../../__test_support__/fake_state";
const mockState = fakeState();
jest.mock("../../redux/store", () => ({
@ -46,7 +39,6 @@ describe("tourPageNavigation()", () => {
it("includes steps based on tool count", () => {
const getTargets = () =>
Object.values(TOUR_STEPS()[Tours.gettingStarted]).map(t => t.target);
mockDev = false;
mockState.resources = buildResourceIndex([]);
expect(getTargets()).not.toContain(".tool-slots");
mockState.resources = buildResourceIndex([fakeTool()]);
@ -56,7 +48,6 @@ describe("tourPageNavigation()", () => {
it("has correct content based on board version", () => {
const getTitles = () =>
Object.values(TOUR_STEPS()[Tours.gettingStarted]).map(t => t.title);
mockDev = false;
mockState.resources = buildResourceIndex([]);
expect(getTitles()).toContain("Add tools and slots");
expect(getTitles()).not.toContain("Add seed containers");
@ -69,13 +60,4 @@ describe("tourPageNavigation()", () => {
expect(getTitles()).not.toContain("Add seed containers and slots");
expect(getTitles()).toContain("Add seed containers");
});
it("includes correct tour steps", () => {
mockDev = true;
const targets =
Object.values(TOUR_STEPS()[Tours.gettingStarted]).map(t => t.target);
expect(targets).not.toContain(".tools");
expect(targets).toContain(".tool-list");
expect(targets).toContain(".toolbay-list");
});
});

View File

@ -2,7 +2,6 @@ import { history } from "../history";
import { Step as TourStep } from "react-joyride";
import { TourContent } from "../constants";
import { t } from "../i18next_wrapper";
import { DevSettings } from "../account/dev/dev_support";
import { selectAllTools } from "../resources/selectors";
import { store } from "../redux/store";
import { getFbosConfig } from "../resources/getters";
@ -64,16 +63,8 @@ export const TOUR_STEPS = (): { [x: string]: TourStep[] } => ({
content: t(TourContent.ADD_PLANTS),
title: t("Add plants"),
},
...(DevSettings.futureFeaturesEnabled() ? [{
target: ".tool-list",
content: t(TourContent.ADD_TOOLS),
title: t("Add tools and seed containers"),
}] : toolsStep()),
...(DevSettings.futureFeaturesEnabled() ? [{
target: ".toolbay-list",
content: t(TourContent.ADD_TOOLS_SLOTS),
title: t("Add tools to tool bay"),
}] : toolSlotsStep()),
...toolsStep(),
...toolSlotsStep(),
{
target: ".peripherals-widget",
content: t(TourContent.ADD_PERIPHERALS),

View File

@ -3,11 +3,6 @@ jest.mock("../../history", () => ({
getPathArray: jest.fn(() => mockPath.split("/")),
}));
let mockDev = false;
jest.mock("../../account/dev/dev_support", () => ({
DevSettings: { futureFeaturesEnabled: () => mockDev }
}));
import * as React from "react";
import { shallow, mount } from "enzyme";
import { NavLinks } from "../nav_links";
@ -28,7 +23,6 @@ describe("<NavLinks />", () => {
});
it("shows links", () => {
mockDev = false;
const wrapper = mount(<NavLinks close={jest.fn()} alertCount={1} />);
expect(wrapper.text().toLowerCase()).not.toContain("tools");
});

View File

@ -7,7 +7,6 @@ import {
import { Link } from "../link";
import { t } from "../i18next_wrapper";
import { betterCompact } from "../util";
import { DevSettings } from "../account/dev/dev_support";
/** Uses a slug and a child path to compute the `href` of a navbar link. */
export type LinkComputeFn = (slug: string, childPath: string) => string;
@ -37,8 +36,6 @@ export const getLinks = (): NavLinkParams[] => betterCompact([
name: "Regimens", icon: "calendar-check-o", slug: "regimens",
computeHref: computeEditorUrlFromState("Regimen")
},
!DevSettings.futureFeaturesEnabled() ? undefined :
{ name: "Tools", icon: "wrench", slug: "tools" },
{
name: "Farmware", icon: "crosshairs", slug: "farmware",
computeHref: computeFarmwareUrlFromState