remove old tools page

pull/1720/head
gabrielburnworth 2020-02-26 10:11:31 -08:00
parent 9bd98aca1e
commit 90ddd78bb8
28 changed files with 46 additions and 1178 deletions

View File

@ -30,7 +30,6 @@ import "../regimens/editor/interfaces";
import "../regimens/interfaces";
import "../resources/interfaces";
import "../sequences/interfaces";
import "../tools/interfaces";
describe("interfaces", () => {
it("cant explain why coverage is 0 for interface files", () => {

View File

@ -1,18 +1,15 @@
import React from "react";
import { t } from "../../i18next_wrapper";
import { Xyz, TaggedTool, TaggedToolSlotPointer } from "farmbot";
import { Row, Col, BlurableInput, FBSelect, NULL_CHOICE, DropDownItem } from "../../ui";
import {
directionIconClass, positionButtonTitle, newSlotDirection, positionIsDefined
} from "../../tools/components/toolbay_slot_menu";
import {
DIRECTION_CHOICES, DIRECTION_CHOICES_DDI
} from "../../tools/components/toolbay_slot_direction_selection";
Row, Col, BlurableInput, FBSelect, NULL_CHOICE, DropDownItem
} from "../../ui";
import { BotPosition } from "../../devices/interfaces";
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
import { Popover } from "@blueprintjs/core";
import { ToolSlotSVG } from "../map/layers/tool_slots/tool_graphics";
import { BotOriginQuadrant } from "../interfaces";
import { isNumber } from "lodash";
export interface GantryMountedInputProps {
gantryMounted: boolean;
@ -189,3 +186,46 @@ export const SlotEditRows = (props: SlotEditRowsProps) =>
gantryMounted={props.toolSlot.body.gantry_mounted}
onChange={props.updateToolSlot} />}
</div>;
const directionIconClass = (slotDirection: ToolPulloutDirection) => {
switch (slotDirection) {
case ToolPulloutDirection.POSITIVE_X: return "fa fa-arrow-circle-right";
case ToolPulloutDirection.NEGATIVE_X: return "fa fa-arrow-circle-left";
case ToolPulloutDirection.POSITIVE_Y: return "fa fa-arrow-circle-up";
case ToolPulloutDirection.NEGATIVE_Y: return "fa fa-arrow-circle-down";
case ToolPulloutDirection.NONE: return "fa fa-dot-circle-o";
}
};
export const positionButtonTitle = (position: BotPosition): string =>
positionIsDefined(position)
? `(${position.x}, ${position.y}, ${position.z})`
: t("(unknown)");
export const newSlotDirection =
(old: ToolPulloutDirection | undefined): ToolPulloutDirection =>
isNumber(old) && old < 4 ? old + 1 : ToolPulloutDirection.NONE;
export const positionIsDefined = (position: BotPosition): boolean =>
isNumber(position.x) && isNumber(position.y) && isNumber(position.z);
export const DIRECTION_CHOICES_DDI: { [index: number]: DropDownItem } = {
[ToolPulloutDirection.NONE]:
{ label: t("None"), value: ToolPulloutDirection.NONE },
[ToolPulloutDirection.POSITIVE_X]:
{ label: t("Positive X"), value: ToolPulloutDirection.POSITIVE_X },
[ToolPulloutDirection.NEGATIVE_X]:
{ label: t("Negative X"), value: ToolPulloutDirection.NEGATIVE_X },
[ToolPulloutDirection.POSITIVE_Y]:
{ label: t("Positive Y"), value: ToolPulloutDirection.POSITIVE_Y },
[ToolPulloutDirection.NEGATIVE_Y]:
{ label: t("Negative Y"), value: ToolPulloutDirection.NEGATIVE_Y },
};
export const DIRECTION_CHOICES: DropDownItem[] = [
DIRECTION_CHOICES_DDI[ToolPulloutDirection.NONE],
DIRECTION_CHOICES_DDI[ToolPulloutDirection.POSITIVE_X],
DIRECTION_CHOICES_DDI[ToolPulloutDirection.NEGATIVE_X],
DIRECTION_CHOICES_DDI[ToolPulloutDirection.POSITIVE_Y],
DIRECTION_CHOICES_DDI[ToolPulloutDirection.NEGATIVE_Y],
];

View File

@ -149,12 +149,6 @@ export const UNBOUND_ROUTES = [
getModule: () => import("./sequences/sequences"),
key: "Sequences",
}),
route({
children: false,
$: "/tools",
getModule: () => import("./tools"),
key: "Tools",
}),
route({
children: false,
$: "/designer",

View File

@ -1,48 +0,0 @@
import * as React from "react";
import { mount, shallow } from "enzyme";
import { RawTools as Tools } from "../index";
import { Props } from "../interfaces";
import {
fakeToolSlot, fakeTool
} from "../../__test_support__/fake_state/resources";
describe("<Tools />", () => {
const fakeProps = (): Props => ({
toolSlots: [],
tools: [fakeTool()],
getToolSlots: () => [fakeToolSlot()],
getToolOptions: () => [],
getChosenToolOption: () => ({ label: "None", value: "" }),
getToolByToolSlotUUID: fakeTool,
changeToolSlot: jest.fn(),
isActive: () => true,
dispatch: jest.fn(),
botPosition: { x: undefined, y: undefined, z: undefined }
});
it("renders", () => {
const wrapper = mount(<Tools {...fakeProps()} />);
const txt = wrapper.text();
const strings = [
"Tool Slots",
"SlotXYZ",
"Tool or Seed Container",
"Tools",
"NameStatus",
"Fooactive"];
strings.map(string => expect(txt).toContain(string));
});
it("shows forms", () => {
const wrapper = shallow(<Tools {...fakeProps()} />);
expect(wrapper.find("ToolList").length).toEqual(1);
expect(wrapper.find("ToolBayList").length).toEqual(1);
expect(wrapper.find("ToolForm").length).toEqual(0);
expect(wrapper.find("ToolBayForm").length).toEqual(0);
wrapper.setState({ editingBays: true, editingTools: true });
expect(wrapper.find("ToolList").length).toEqual(0);
expect(wrapper.find("ToolBayList").length).toEqual(0);
expect(wrapper.find("ToolForm").length).toEqual(1);
expect(wrapper.find("ToolBayForm").length).toEqual(1);
});
});

View File

@ -1,30 +0,0 @@
jest.mock("../../api/crud", () => ({ edit: jest.fn() }));
import { mapStateToProps } from "../state_to_props";
import { fakeState } from "../../__test_support__/fake_state";
import { NULL_CHOICE } from "../../ui";
import { fakeToolSlot } from "../../__test_support__/fake_state/resources";
import { edit } from "../../api/crud";
describe("mapStateToProps()", () => {
it("getChosenToolOption()", () => {
const props = mapStateToProps(fakeState());
const result = props.getChosenToolOption(undefined);
expect(result).toEqual(NULL_CHOICE);
});
it("changeToolSlot(): no tool_id", () => {
const props = mapStateToProps(fakeState());
const tool = fakeToolSlot();
props.changeToolSlot(tool, jest.fn())({ label: "", value: "" });
// tslint:disable-next-line:no-null-keyword
expect(edit).toHaveBeenCalledWith(tool, { tool_id: null });
});
it("changeToolSlot(): tool_id", () => {
const props = mapStateToProps(fakeState());
const tool = fakeToolSlot();
props.changeToolSlot(tool, jest.fn())({ label: "", value: 1 });
expect(edit).toHaveBeenCalledWith(tool, { tool_id: 1 });
});
});

View File

@ -1,91 +0,0 @@
jest.mock("../../../api/crud", () => ({
init: jest.fn(),
saveAll: jest.fn(),
destroy: jest.fn(),
edit: jest.fn(),
}));
import { SpecialStatus } from "farmbot";
jest.mock("../../../resources/tagged_resources", () => ({
getArrayStatus: () => SpecialStatus.SAVED,
isTaggedResource: () => true
}));
import * as React from "react";
import { ToolForm } from "../tool_form";
import { mount, shallow } from "enzyme";
import { fakeTool } from "../../../__test_support__/fake_state/resources";
import { ToolListAndFormProps } from "../../interfaces";
import { clickButton } from "../../../__test_support__/helpers";
import { init, saveAll, destroy, edit } from "../../../api/crud";
describe("<ToolForm/>", () => {
function fakeProps(): ToolListAndFormProps {
return {
dispatch: jest.fn(),
toggle: jest.fn(),
tools: [fakeTool(), fakeTool()],
isActive: jest.fn(),
};
}
it("renders", () => {
const p = fakeProps();
const wrapper = mount(<ToolForm {...p} />);
expect(wrapper.find("input").length).toEqual(p.tools.length);
});
it("saves tools", () => {
const wrapper = mount(<ToolForm {...fakeProps()} />);
clickButton(wrapper, 1, "saved", { partial_match: true });
expect(saveAll).toHaveBeenCalledTimes(1);
});
it("adds new tool", () => {
const wrapper = mount(<ToolForm {...fakeProps()} />);
expect(wrapper.props().tools.length).toEqual(2);
clickButton(wrapper, 2, "");
expect(init).toHaveBeenCalledWith("Tool", { name: "Tool 3" });
});
it("adds stock tools", () => {
const wrapper = mount(<ToolForm {...fakeProps()} />);
clickButton(wrapper, 3, "stock tools");
expect(init).toHaveBeenCalledTimes(6);
});
it("changes tool name", () => {
const p = fakeProps();
const wrapper = shallow(<ToolForm {...p} />);
wrapper.find("BlurableInput").first().simulate("commit", {
currentTarget: { value: "New Tool Name" }
});
expect(edit).toHaveBeenCalledWith(p.tools[0], { name: "New Tool Name" });
});
it("has red delete button", () => {
const p = fakeProps();
p.isActive = () => false;
const wrapper = mount(<ToolForm {...p} />);
const delBtn = wrapper.find("button").last();
expect(delBtn.hasClass("red")).toBeTruthy();
});
it("deletes tool", () => {
const p = fakeProps();
p.isActive = () => false;
const wrapper = mount(<ToolForm {...p} />);
const delBtn = wrapper.find("button").last();
delBtn.simulate("click");
expect(destroy).toHaveBeenCalledWith(p.tools[1].uuid);
});
it("has gray delete button", () => {
const p = fakeProps();
p.isActive = () => true;
const wrapper = mount(<ToolForm {...p} />);
const delBtn = wrapper.find("button").last();
expect(delBtn.hasClass("pseudo-disabled")).toBeTruthy();
expect(delBtn.props().title).toContain("in slot");
});
});

View File

@ -1,24 +0,0 @@
import * as React from "react";
import { ToolList } from "../tool_list";
import { mount } from "enzyme";
import { mapStateToProps } from "../../state_to_props";
import { fakeState } from "../../../__test_support__/fake_state";
import { ToolListAndFormProps } from "../../interfaces";
describe("<ToolList />", () => {
const fakeProps = (): ToolListAndFormProps => {
const props = mapStateToProps(fakeState());
return {
dispatch: jest.fn(),
tools: props.tools,
toggle: jest.fn(),
isActive: props.isActive,
};
};
it("renders tool names and statuses", () => {
const wrapper = mount(<ToolList {...fakeProps()} />);
expect(wrapper.text()).toContain("Trench Digging Toolactive");
expect(wrapper.text()).toContain("Berry Picking Toolinactive");
});
});

View File

@ -1,26 +0,0 @@
jest.mock("../../../api/crud", () => ({ destroy: jest.fn() }));
import * as React from "react";
import { ToolSlotRowProps, ToolSlotRow } from "../tool_slot_row";
import { mount } from "enzyme";
import { destroy } from "../../../api/crud";
import { fakeToolSlot } from "../../../__test_support__/fake_state/resources";
describe("<ToolSlotRow />", () => {
const fakeProps = (): ToolSlotRowProps => ({
dispatch: jest.fn(),
slot: fakeToolSlot(),
botPosition: { x: undefined, y: undefined, z: undefined },
toolOptions: [],
chosenToolOption: { label: "", value: "" },
onToolSlotChange: jest.fn(),
gantryMounted: false,
});
it("deletes slot", () => {
const p = fakeProps();
const wrapper = mount(<ToolSlotRow {...p} />);
wrapper.find("button").last().simulate("click");
expect(destroy).toHaveBeenCalledWith(p.slot.uuid);
});
});

View File

@ -1,50 +0,0 @@
jest.mock("../../../api/crud", () => ({
init: jest.fn(),
saveAll: jest.fn(),
}));
import * as React from "react";
import { ToolBayForm } from "../toolbay_form";
import { mount } from "enzyme";
import { mapStateToProps } from "../../state_to_props";
import { fakeState } from "../../../__test_support__/fake_state";
import { ToolBayFormProps } from "../../interfaces";
import { clickButton } from "../../../__test_support__/helpers";
import { saveAll, init } from "../../../api/crud";
import { emptyToolSlotBody } from "../empty_tool_slot";
describe("<ToolBayForm/>", () => {
const fakeProps = (): ToolBayFormProps => {
const props = mapStateToProps(fakeState());
return {
toggle: jest.fn(),
dispatch: jest.fn(),
toolSlots: props.toolSlots,
getToolSlots: props.getToolSlots,
getChosenToolOption: props.getChosenToolOption,
getToolOptions: props.getToolOptions,
changeToolSlot: props.changeToolSlot,
botPosition: { x: 1, y: 2, z: 3 },
};
};
it("renders ToolSlot", () => {
const wrapper = mount(<ToolBayForm {...fakeProps()} />);
const inputs = wrapper.find("input");
expect(inputs.length).toEqual(3);
expect(wrapper.text()).toContain("Trench Digging Tool");
[0, 1, 2].map(i => expect(inputs.at(i).props().value).toEqual("10"));
});
it("saves tool slots", () => {
const wrapper = mount(<ToolBayForm {...fakeProps()} />);
clickButton(wrapper, 1, "saved", { partial_match: true });
expect(saveAll).toHaveBeenCalledTimes(1);
});
it("adds new tool slot", () => {
const wrapper = mount(<ToolBayForm {...fakeProps()} />);
clickButton(wrapper, 2, "");
expect(init).toHaveBeenCalledWith("Point", emptyToolSlotBody());
});
});

View File

@ -1,10 +0,0 @@
import * as React from "react";
import { ToolBayHeader } from "../toolbay_header";
import { mount } from "enzyme";
describe("<ToolBayHeader />", () => {
it("renders", () => {
const header = mount(<ToolBayHeader />);
expect(header.text()).toEqual("SlotXYZTool or Seed Container");
});
});

View File

@ -1,31 +0,0 @@
import * as React from "react";
import { ToolBayList } from "../toolbay_list";
import { mount } from "enzyme";
import { mapStateToProps } from "../../state_to_props";
import { fakeState } from "../../../__test_support__/fake_state";
import { ToolBayListProps } from "../../interfaces";
describe("<ToolBayList />", () => {
const fakeProps = (): ToolBayListProps => {
const props = mapStateToProps(fakeState());
return {
getToolByToolSlotUUID: props.getToolByToolSlotUUID,
getToolSlots: props.getToolSlots,
toggle: jest.fn(),
};
};
it("renders", () => {
const wrapper = mount(<ToolBayList {...fakeProps()} />);
expect(wrapper.text()).toContain("1101010Trench Digging Tool");
});
it("renders gantry mounted slot", () => {
const p = fakeProps();
const slots = p.getToolSlots();
slots[0].body.gantry_mounted = true;
p.getToolSlots = () => slots;
const wrapper = mount(<ToolBayList {...fakeProps()} />);
expect(wrapper.text()).toContain("1Gantry1010Trench Digging Tool");
});
});

View File

@ -1,25 +0,0 @@
jest.mock("../../../api/crud", () => ({ edit: jest.fn() }));
import * as React from "react";
import { shallow } from "enzyme";
import { ToolBayNumberCol, TBNumColProps } from "../toolbay_number_column";
import { edit } from "../../../api/crud";
import { fakeToolSlot } from "../../../__test_support__/fake_state/resources";
describe("<ToolBayNumberCol />", () => {
const fakeProps = (): TBNumColProps => ({
axis: "x",
value: 0,
dispatch: jest.fn(),
slot: fakeToolSlot(),
});
it("edits value", () => {
const p = fakeProps();
const wrapper = shallow(<ToolBayNumberCol {...p} />);
wrapper.find("BlurableInput").simulate("commit", {
currentTarget: { value: "1.23" }
});
expect(edit).toHaveBeenCalledWith(p.slot, { x: 1.23 });
});
});

View File

@ -1,31 +0,0 @@
import * as React from "react";
import { shallow } from "enzyme";
import {
SlotDirectionSelect, SlotDirectionSelectProps
} from "../toolbay_slot_direction_selection";
import { fakeToolSlot } from "../../../__test_support__/fake_state/resources";
import { Actions } from "../../../constants";
import { SpecialStatus } from "farmbot";
describe("<SlotDirectionSelect />", () => {
const fakeProps = (): SlotDirectionSelectProps => {
return {
dispatch: jest.fn(),
slot: fakeToolSlot()
};
};
it("changes slot direction", () => {
const p = fakeProps();
const wrapper = shallow(<SlotDirectionSelect {...p} />);
wrapper.simulate("change", { value: 1 });
expect(p.dispatch).toHaveBeenCalledWith({
payload: {
specialStatus: SpecialStatus.DIRTY,
update: { pullout_direction: 1 },
uuid: expect.any(String)
},
type: Actions.EDIT_RESOURCE
});
});
});

View File

@ -1,95 +0,0 @@
import * as React from "react";
import { mount } from "enzyme";
import { SlotMenu, SlotMenuProps } from "../toolbay_slot_menu";
import { fakeToolSlot } from "../../../__test_support__/fake_state/resources";
import { Actions } from "../../../constants";
import { SpecialStatus } from "farmbot";
describe("<SlotMenu />", () => {
const fakeProps = (): SlotMenuProps => {
return {
dispatch: jest.fn(),
slot: fakeToolSlot(),
botPosition: { x: 1, y: 2, z: 3 }
};
};
it("changes slot direction", () => {
const p = fakeProps();
const wrapper = mount(<SlotMenu {...p} />);
wrapper.find("i").first().simulate("click");
expect(p.dispatch).toHaveBeenCalledWith({
payload: {
specialStatus: SpecialStatus.DIRTY,
update: { pullout_direction: 1 },
uuid: expect.any(String)
},
type: Actions.EDIT_RESOURCE
});
});
it("changes slot direction: reset", () => {
const p = fakeProps();
p.slot.body.pullout_direction = 4;
const wrapper = mount(<SlotMenu {...p} />);
wrapper.find("i").first().simulate("click");
expect(p.dispatch).toHaveBeenCalledWith({
payload: {
specialStatus: SpecialStatus.DIRTY,
update: { pullout_direction: 0 },
uuid: expect.any(String)
},
type: Actions.EDIT_RESOURCE
});
});
const checkDirection = (direction: number, expected: string) => {
it("icon shows direction", () => {
const p = fakeProps();
p.slot.body.pullout_direction = direction;
const wrapper = mount(<SlotMenu {...p} />);
expect(wrapper.html()).toContain(expected);
});
};
checkDirection(1, "right");
checkDirection(2, "left");
checkDirection(3, "up");
checkDirection(4, "down");
it("fills inputs with bot position", () => {
const p = fakeProps();
const wrapper = mount(<SlotMenu {...p} />);
const buttons = wrapper.find("button");
buttons.last().simulate("click");
expect(p.dispatch).toHaveBeenCalledWith({
type: Actions.EDIT_RESOURCE,
payload: expect.objectContaining({
update: { x: 1, y: 2, z: 3 }
})
});
});
it("doesn't fills inputs with bot position unknown", () => {
const p = fakeProps();
p.botPosition = { x: undefined, y: undefined, z: undefined };
const wrapper = mount(<SlotMenu {...p} />);
const buttons = wrapper.find("button");
buttons.last().simulate("click");
expect(p.dispatch).not.toHaveBeenCalled();
});
it("sets gantry_mounted", () => {
const p = fakeProps();
p.slot.body.gantry_mounted = false;
const wrapper = mount(<SlotMenu {...p} />);
wrapper.find("input").last().simulate("change");
expect(p.dispatch).toHaveBeenCalledWith({
payload: {
specialStatus: SpecialStatus.DIRTY,
update: { gantry_mounted: true },
uuid: expect.any(String)
},
type: Actions.EDIT_RESOURCE
});
});
});

View File

@ -1,14 +0,0 @@
import { ToolSlotPointer } from "farmbot/dist/resources/api_resources";
export const emptyToolSlotBody = (): ToolSlotPointer => ({
x: 0,
y: 0,
z: 0,
radius: 25,
pointer_type: "ToolSlot",
meta: {},
tool_id: undefined,
name: "Tool Slot",
pullout_direction: 0,
gantry_mounted: false,
});

View File

@ -1,4 +0,0 @@
export * from "./toolbay_form";
export * from "./toolbay_list";
export * from "./tool_list";
export * from "./tool_form";

View File

@ -1,98 +0,0 @@
import * as React from "react";
import { ToolListAndFormProps } from "../interfaces";
import {
Row,
Col,
Widget,
WidgetBody,
WidgetHeader,
BlurableInput,
SaveBtn
} from "../../ui";
import { getArrayStatus } from "../../resources/tagged_resources";
import { edit, destroy, init, saveAll } from "../../api/crud";
import { ToolTips } from "../../constants";
import { TaggedTool } from "farmbot";
import { t } from "../../i18next_wrapper";
export class ToolForm extends React.Component<ToolListAndFormProps, {}> {
get newToolName() { return t("Tool ") + (this.props.tools.length + 1); }
newTool = (name = this.newToolName) => {
this.props.dispatch(init("Tool", { name }));
};
stockTools = () => {
this.newTool(t("Seeder"));
this.newTool(t("Watering Nozzle"));
this.newTool(t("Weeder"));
this.newTool(t("Soil Sensor"));
this.newTool(t("Seed Bin"));
this.newTool(t("Seed Tray"));
}
HeaderButtons = () => {
const { dispatch, tools, toggle } = this.props;
const specialStatus = getArrayStatus(tools);
return <div>
<button
className="fb-button gray"
onClick={toggle}
disabled={!!specialStatus}>
{t("Back")}
</button>
<SaveBtn
status={specialStatus}
onClick={() => dispatch(saveAll(tools, toggle))} />
<button
className="fb-button green"
onClick={() => this.newTool()}>
<i className="fa fa-plus" />
</button>
<button
className="fb-button green"
onClick={this.stockTools}>
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
{t("Stock Tools")}
</button>
</div>;
}
ToolForm = (tool: TaggedTool, index: number) => {
const { dispatch, isActive } = this.props;
const inSlotClass = isActive(tool) ? "pseudo-disabled" : "";
return <Row key={index}>
<Col xs={10}>
<BlurableInput
id={(tool.body.id || "Error getting ID").toString()}
value={tool.body.name || "Error getting Name"}
onCommit={e =>
dispatch(edit(tool, { name: e.currentTarget.value }))} />
</Col>
<Col xs={2}>
<button
className={`fb-button red ${inSlotClass} del-button`}
title={isActive(tool) ? t("in slot") : t("Delete")}
onClick={() => dispatch(destroy(tool.uuid))}>
<i className="fa fa-times"></i>
</button>
</Col>
</Row>;
}
render() {
return <Widget className="tools-widget">
<WidgetHeader helpText={ToolTips.TOOL_LIST} title="Tools and Seed Containers">
<this.HeaderButtons />
</WidgetHeader>
<WidgetBody>
<Row>
<Col xs={12}>
<label>{t("Name")}</label>
</Col>
</Row>
{this.props.tools.map(this.ToolForm)}
</WidgetBody>
</Widget>;
}
}

View File

@ -1,50 +0,0 @@
import * as React from "react";
import { Row, Col, Widget, WidgetBody, WidgetHeader } from "../../ui";
import { ToolListAndFormProps } from "../interfaces";
import { TaggedTool } from "farmbot";
import { ToolTips } from "../../constants";
import { t } from "../../i18next_wrapper";
enum ColWidth {
toolName = 8,
status = 4,
}
export class ToolList extends React.Component<ToolListAndFormProps, {}> {
ToolListItem = (tool: TaggedTool) => {
return <Row key={tool.uuid}>
<Col xs={ColWidth.toolName}>
{tool.body.name || "Name not found"}
</Col>
<Col xs={ColWidth.status}>
{this.props.isActive(tool) ? t("active") : t("inactive")}
</Col>
</Row>;
}
render() {
const { tools, toggle } = this.props;
return <Widget className="tool-list">
<WidgetHeader helpText={ToolTips.TOOL_LIST} title={t("Tools and Seed Containers")}>
<button
className="fb-button gray"
onClick={toggle}>
{t("Edit")}
</button>
</WidgetHeader>
<WidgetBody>
<Row>
<Col xs={ColWidth.toolName}>
<label>{t("Name")}</label>
</Col>
<Col xs={ColWidth.status}>
<label>{t("Status")}</label>
</Col>
</Row>
{tools.map(this.ToolListItem)}
</WidgetBody>
</Widget>;
}
}

View File

@ -1,65 +0,0 @@
import * as React from "react";
import { Row, Col, FBSelect, DropDownItem } from "../../ui";
import { Popover, Position } from "@blueprintjs/core";
import { SlotMenu } from "./toolbay_slot_menu";
import { TaggedToolSlotPointer } from "farmbot";
import { destroy } from "../../api/crud";
import { Xyz } from "../../devices/interfaces";
import { ToolBayNumberCol } from "./toolbay_number_column";
import { t } from "../../i18next_wrapper";
export interface ToolSlotRowProps {
dispatch: Function;
slot: TaggedToolSlotPointer;
botPosition: Record<"x" | "y" | "z", number | undefined>;
/** List of all legal tool options for the current tool slot. */
toolOptions: DropDownItem[];
/** The current tool (if any) in the slot. */
chosenToolOption: DropDownItem;
/** Broadcast tool change back up to parent. */
onToolSlotChange(item: DropDownItem): void;
/** Gantry-mounted tool slot. */
gantryMounted: boolean;
}
type Axis = Xyz & keyof (TaggedToolSlotPointer["body"]);
const axes: Axis[] = ["x", "y", "z"];
export function ToolSlotRow(props: ToolSlotRowProps) {
const { dispatch, slot, botPosition, toolOptions, onToolSlotChange,
chosenToolOption, gantryMounted } = props;
return <Row>
<Col xs={1}>
<Popover position={Position.BOTTOM_LEFT}>
<i className="fa fa-gear" />
<SlotMenu
dispatch={dispatch}
slot={slot}
botPosition={botPosition} />
</Popover>
</Col>
{axes
.map(axis => ({ axis, dispatch, slot, value: (slot.body[axis] || 0) }))
.map(axisProps => (axisProps.axis === "x" && gantryMounted)
? <Col xs={2} key={slot.uuid + axisProps.axis}>
<input disabled value={t("Gantry")} />
</Col>
: <ToolBayNumberCol key={slot.uuid + axisProps.axis} {...axisProps} />)}
<Col xs={4}>
<FBSelect
list={toolOptions}
selectedItem={chosenToolOption}
allowEmpty={true}
onChange={onToolSlotChange} />
</Col>
<Col xs={1}>
<button
className="red fb-button del-button"
title={t("Delete")}
onClick={() => dispatch(destroy(slot.uuid))}>
<i className="fa fa-times" />
</button>
</Col>
</Row>;
}

View File

@ -1,70 +0,0 @@
import * as React from "react";
import { ToolBayFormProps } from "../interfaces";
import {
Widget,
WidgetBody,
WidgetHeader,
SaveBtn,
} from "../../ui";
import {
getArrayStatus
} from "../../resources/tagged_resources";
import { saveAll, init } from "../../api/crud";
import { ToolBayHeader } from "./toolbay_header";
import { ToolTips } from "../../constants";
import { ToolSlotRow } from "./tool_slot_row";
import { emptyToolSlotBody } from "./empty_tool_slot";
import { TaggedToolSlotPointer } from "farmbot";
import { t } from "../../i18next_wrapper";
export class ToolBayForm extends React.Component<ToolBayFormProps, {}> {
HeaderButtons = () => {
const { toggle, dispatch, toolSlots } = this.props;
const toolSlotStatus = getArrayStatus(toolSlots);
return <div>
<button
className="gray fb-button"
disabled={!!toolSlotStatus}
onClick={toggle}>
{t("Back")}
</button>
<SaveBtn
status={toolSlotStatus}
onClick={() => dispatch(saveAll(toolSlots, toggle))} />
<button
className="green fb-button"
onClick={() => dispatch(init("Point", emptyToolSlotBody()))}>
<i className="fa fa-plus" />
</button>
</div>;
}
ToolbayForm = (slot: TaggedToolSlotPointer) => {
const { dispatch, botPosition } = this.props;
return <ToolSlotRow
key={slot.uuid}
dispatch={dispatch}
slot={slot}
botPosition={botPosition}
toolOptions={this.props.getToolOptions()}
gantryMounted={slot.body.gantry_mounted}
onToolSlotChange={this.props.changeToolSlot(slot, this.props.dispatch)}
chosenToolOption={this.props.getChosenToolOption(slot.uuid)} />;
}
render() {
return <div className={"toolbay-widget"}>
<Widget>
<WidgetHeader helpText={ToolTips.TOOLBAY_LIST} title={t("Tool Slots")}>
<this.HeaderButtons />
</WidgetHeader>
<WidgetBody>
<ToolBayHeader />
{this.props.getToolSlots().map(this.ToolbayForm)}
</WidgetBody>
</Widget>
</div>;
}
}

View File

@ -1,23 +0,0 @@
import * as React from "react";
import { Col, Row } from "../../ui";
import { t } from "../../i18next_wrapper";
export function ToolBayHeader() {
return <Row>
<Col xs={1}>
<label>{t("Slot")}</label>
</Col>
<Col xs={2}>
<label>{t("X")}</label>
</Col>
<Col xs={2}>
<label>{t("Y")}</label>
</Col>
<Col xs={2}>
<label>{t("Z")}</label>
</Col>
<Col xs={4}>
<label>{t("Tool or Seed Container")}</label>
</Col>
</Row>;
}

View File

@ -1,41 +0,0 @@
import * as React from "react";
import { Row, Col, Widget, WidgetBody, WidgetHeader } from "../../ui";
import { ToolBayListProps } from "../interfaces";
import { TaggedToolSlotPointer } from "farmbot";
import { ToolBayHeader } from "./toolbay_header";
import { ToolTips } from "../../constants";
import { t } from "../../i18next_wrapper";
export class ToolBayList extends React.Component<ToolBayListProps, {}> {
ToolSlotListItem = (slot: TaggedToolSlotPointer, index: number) => {
const { getToolByToolSlotUUID } = this.props;
const tool = getToolByToolSlotUUID(slot.uuid);
const name = (tool?.body.name) || t("None");
return <Row key={slot.uuid}>
<Col xs={1}><label>{index + 1}</label></Col>
<Col xs={2}>{slot.body.gantry_mounted ? t("Gantry") : slot.body.x}</Col>
<Col xs={2}>{slot.body.y}</Col>
<Col xs={2}>{slot.body.z}</Col>
<Col xs={4}>{name}</Col>
</Row>;
}
render() {
return <Widget className="toolbay-list">
<WidgetHeader
helpText={ToolTips.TOOLBAY_LIST}
title={t("Tool Slots")}>
<button
className="gray fb-button"
onClick={this.props.toggle}>
{t("Edit")}
</button>
</WidgetHeader>
<WidgetBody>
<ToolBayHeader />
{this.props.getToolSlots().map(this.ToolSlotListItem)}
</WidgetBody>
</Widget>;
}
}

View File

@ -1,23 +0,0 @@
import * as React from "react";
import { TaggedToolSlotPointer } from "farmbot";
import { Col, BlurableInput } from "../../ui";
import { edit } from "../../api/crud";
export interface TBNumColProps {
axis: "x" | "y" | "z";
value: number;
dispatch: Function;
slot: TaggedToolSlotPointer;
}
/** Used to display and edit the X/Y/Z numeric values in the tool bay form. */
export function ToolBayNumberCol(props: TBNumColProps) {
const { axis, value, dispatch, slot } = props;
return <Col xs={2}>
<BlurableInput
value={value.toString()}
onCommit={e =>
dispatch(edit(slot, { [axis]: parseFloat(e.currentTarget.value) }))}
type="number" />
</Col>;
}

View File

@ -1,50 +0,0 @@
import * as React from "react";
import { FBSelect, DropDownItem } from "../../ui";
import { TaggedToolSlotPointer } from "farmbot";
import { edit } from "../../api/crud";
import { isNumber } from "lodash";
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
import { t } from "../../i18next_wrapper";
export const DIRECTION_CHOICES_DDI: { [index: number]: DropDownItem } = {
[ToolPulloutDirection.NONE]:
{ label: t("None"), value: ToolPulloutDirection.NONE },
[ToolPulloutDirection.POSITIVE_X]:
{ label: t("Positive X"), value: ToolPulloutDirection.POSITIVE_X },
[ToolPulloutDirection.NEGATIVE_X]:
{ label: t("Negative X"), value: ToolPulloutDirection.NEGATIVE_X },
[ToolPulloutDirection.POSITIVE_Y]:
{ label: t("Positive Y"), value: ToolPulloutDirection.POSITIVE_Y },
[ToolPulloutDirection.NEGATIVE_Y]:
{ label: t("Negative Y"), value: ToolPulloutDirection.NEGATIVE_Y },
};
export const DIRECTION_CHOICES: DropDownItem[] = [
DIRECTION_CHOICES_DDI[ToolPulloutDirection.NONE],
DIRECTION_CHOICES_DDI[ToolPulloutDirection.POSITIVE_X],
DIRECTION_CHOICES_DDI[ToolPulloutDirection.NEGATIVE_X],
DIRECTION_CHOICES_DDI[ToolPulloutDirection.POSITIVE_Y],
DIRECTION_CHOICES_DDI[ToolPulloutDirection.NEGATIVE_Y],
];
export interface SlotDirectionSelectProps {
dispatch: Function;
slot: TaggedToolSlotPointer;
}
export function SlotDirectionSelect(props: SlotDirectionSelectProps) {
const { dispatch, slot } = props;
const direction = slot.body.pullout_direction;
const changePulloutDirection = (selectedDirection: DropDownItem) => {
const { value } = selectedDirection;
dispatch(edit(slot, {
pullout_direction: isNumber(value) ? value : parseInt(value)
}));
};
return <FBSelect
list={DIRECTION_CHOICES}
selectedItem={DIRECTION_CHOICES_DDI[direction]}
onChange={changePulloutDirection} />;
}

View File

@ -1,84 +0,0 @@
import * as React from "react";
import { isNumber } from "lodash";
import { BotPosition } from "../../devices/interfaces";
import { TaggedToolSlotPointer } from "farmbot";
import { edit } from "../../api/crud";
import { SlotDirectionSelect } from "./toolbay_slot_direction_selection";
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
import { t } from "../../i18next_wrapper";
export const positionIsDefined = (position: BotPosition): boolean =>
isNumber(position.x) && isNumber(position.y) && isNumber(position.z);
export const useCurrentPosition = (
dispatch: Function, slot: TaggedToolSlotPointer, position: BotPosition) => {
if (positionIsDefined(position)) {
dispatch(edit(slot, { x: position.x, y: position.y, z: position.z }));
}
};
export const positionButtonTitle = (position: BotPosition): string =>
positionIsDefined(position)
? `(${position.x}, ${position.y}, ${position.z})`
: t("(unknown)");
export const newSlotDirection =
(old: ToolPulloutDirection | undefined): ToolPulloutDirection =>
isNumber(old) && old < 4 ? old + 1 : ToolPulloutDirection.NONE;
export const changePulloutDirection =
(dispatch: Function, slot: TaggedToolSlotPointer) => () => {
dispatch(edit(slot,
{ pullout_direction: newSlotDirection(slot.body.pullout_direction) }));
};
export const directionIconClass = (slotDirection: ToolPulloutDirection) => {
switch (slotDirection) {
case ToolPulloutDirection.POSITIVE_X: return "fa fa-arrow-circle-right";
case ToolPulloutDirection.NEGATIVE_X: return "fa fa-arrow-circle-left";
case ToolPulloutDirection.POSITIVE_Y: return "fa fa-arrow-circle-up";
case ToolPulloutDirection.NEGATIVE_Y: return "fa fa-arrow-circle-down";
case ToolPulloutDirection.NONE: return "fa fa-dot-circle-o";
}
};
export interface SlotMenuProps {
dispatch: Function,
slot: TaggedToolSlotPointer,
botPosition: BotPosition
}
export const SlotMenu = (props: SlotMenuProps) => {
const { dispatch, slot, botPosition } = props;
const { pullout_direction, gantry_mounted } = slot.body;
return <div className="toolbay-slot-menu">
<fieldset>
<label>
{t("Change slot direction")}
</label>
<i className={"direction-icon " + directionIconClass(pullout_direction)}
onClick={changePulloutDirection(dispatch, slot)} />
<SlotDirectionSelect
key={pullout_direction}
dispatch={dispatch}
slot={slot} />
</fieldset>
<fieldset>
<label>{t("Use current location")}</label>
<button
className="blue fb-button"
title={positionButtonTitle(botPosition)}
onClick={() => useCurrentPosition(dispatch, slot, botPosition)}>
<i className="fa fa-crosshairs" />
</button>
<p>{positionButtonTitle(botPosition)}</p>
</fieldset>
<fieldset>
<label>{t("Gantry-mounted")}</label>
<input type="checkbox"
onChange={() =>
dispatch(edit(slot, { gantry_mounted: !gantry_mounted }))}
checked={gantry_mounted} />
</fieldset>
</div>;
};

View File

@ -1,51 +0,0 @@
import * as React from "react";
import { connect } from "react-redux";
import { ToolsState, Props } from "./interfaces";
import { Col, Row, Page } from "../ui";
import { ToolBayList, ToolBayForm, ToolList, ToolForm } from "./components";
import { mapStateToProps } from "./state_to_props";
export class RawTools extends React.Component<Props, Partial<ToolsState>> {
state: ToolsState = { editingBays: false, editingTools: false };
toggle = (name: keyof ToolsState) =>
() => this.setState({ [name]: !this.state[name] });
render() {
return <Page className="tools-page">
<Row>
<Col sm={7}>
{this.state.editingBays
? <ToolBayForm
toggle={this.toggle("editingBays")}
dispatch={this.props.dispatch}
botPosition={this.props.botPosition}
toolSlots={this.props.toolSlots}
getToolSlots={this.props.getToolSlots}
getChosenToolOption={this.props.getChosenToolOption}
getToolOptions={this.props.getToolOptions}
changeToolSlot={this.props.changeToolSlot} />
: <ToolBayList
toggle={this.toggle("editingBays")}
getToolByToolSlotUUID={this.props.getToolByToolSlotUUID}
getToolSlots={this.props.getToolSlots} />}
</Col>
<Col sm={5}>
{this.state.editingTools
? <ToolForm
isActive={this.props.isActive}
toggle={this.toggle("editingTools")}
dispatch={this.props.dispatch}
tools={this.props.tools} />
: <ToolList
isActive={this.props.isActive}
toggle={this.toggle("editingTools")}
dispatch={this.props.dispatch}
tools={this.props.tools} />}
</Col>
</Row>
</Page>;
}
}
export const Tools = connect(mapStateToProps)(RawTools);

View File

@ -1,56 +0,0 @@
import { DropDownItem } from "../ui/index";
import {
TaggedTool,
TaggedToolSlotPointer,
} from "farmbot";
import { BotPosition } from "../devices/interfaces";
export interface ToolsState {
editingTools: boolean;
editingBays: boolean;
}
export interface Props {
toolSlots: TaggedToolSlotPointer[];
tools: TaggedTool[];
getToolOptions(): DropDownItem[];
getChosenToolOption(toolSlotUuid: string | undefined): DropDownItem;
getToolByToolSlotUUID(uuid: string): TaggedTool | undefined;
getToolSlots(): TaggedToolSlotPointer[];
dispatch: Function;
isActive: (tool: TaggedTool) => boolean;
changeToolSlot(t: TaggedToolSlotPointer, dispatch: Function):
(d: DropDownItem) => void;
botPosition: BotPosition;
}
export interface Tool {
id?: number | undefined;
name?: string;
status?: string | undefined;
}
export interface ToolBayListProps {
toggle(): void;
getToolByToolSlotUUID(uuid: string): TaggedTool | undefined;
getToolSlots(): TaggedToolSlotPointer[];
}
export interface ToolBayFormProps {
dispatch: Function;
toolSlots: TaggedToolSlotPointer[];
botPosition: BotPosition;
toggle(): void;
getToolOptions(): DropDownItem[];
getChosenToolOption(uuid: string | undefined): DropDownItem;
getToolSlots(): TaggedToolSlotPointer[];
changeToolSlot(t: TaggedToolSlotPointer, dispatch: Function):
(d: DropDownItem) => void;
}
export interface ToolListAndFormProps {
dispatch: Function;
tools: TaggedTool[];
toggle(): void;
isActive(tool: TaggedTool): boolean;
}

View File

@ -1,75 +0,0 @@
import { Everything } from "../interfaces";
import { Props } from "./interfaces";
import {
selectAllToolSlotPointers,
selectAllTools,
currentToolInSlot,
} from "../resources/selectors";
import { isTaggedTool } from "../resources/tagged_resources";
import { edit } from "../api/crud";
import { DropDownItem, NULL_CHOICE } from "../ui";
import { validBotLocationData } from "../util";
import { TaggedTool, TaggedToolSlotPointer } from "farmbot";
import { isNumber, noop, compact } from "lodash";
export function mapStateToProps(props: Everything): Props {
const toolSlots = selectAllToolSlotPointers(props.resources.index);
const tools = selectAllTools(props.resources.index);
/** Returns sorted tool slots specific to the tool bay id passed. */
const getToolSlots = () => toolSlots;
/** Returns all tools in an <FBSelect /> compatible format. */
const getToolOptions = () => {
return compact(tools
.map(tool => ({
label: tool.body.name || "untitled",
value: tool.body.id || 0,
}))
.filter(ddi => isNumber(ddi.value) && ddi.value > 0));
};
const activeTools = compact(toolSlots.map(x => x.body.tool_id));
const isActive =
(t: TaggedTool) => !!(t.body.id && activeTools.includes(t.body.id));
const getToolByToolSlotUUID = currentToolInSlot(props.resources.index);
/** Returns the current tool chosen in a slot based off the slot's id
* and in an <FBSelect /> compatible format. */
const getChosenToolOption = (toolSlotUUID: string | undefined) => {
const chosenTool = toolSlotUUID && getToolByToolSlotUUID(toolSlotUUID);
return (chosenTool && isTaggedTool(chosenTool) && chosenTool.body.id)
? { label: chosenTool.body.name || "untitled", value: chosenTool.uuid }
: NULL_CHOICE;
};
const changeToolSlot = (t: TaggedToolSlotPointer,
dispatch: Function) =>
(d: DropDownItem) => {
// THIS IS IMPORTANT:
// If you remove the `any`, the tool will be serialized wrong and
// cause errors.
// tslint:disable-next-line:no-null-keyword no-any
const tool_id = d.value ? d.value : (null as any);
dispatch(edit(t, { tool_id }));
};
const botPosition =
validBotLocationData(props.bot.hardware.location_data).position;
return {
toolSlots,
tools,
getToolSlots,
getToolOptions,
getChosenToolOption,
getToolByToolSlotUUID,
changeToolSlot,
isActive,
dispatch: noop,
botPosition,
};
}