tools fixes and refactoring

pull/1002/head
gabrielburnworth 2018-10-01 17:03:11 -07:00
parent fa56cc137b
commit f165cd19b0
21 changed files with 413 additions and 285 deletions

View File

@ -836,6 +836,7 @@ ul {
}
}
.tools-widget,
.toolbay-widget {
.fa-gear {
color: $dark_gray;
@ -844,6 +845,9 @@ ul {
.del-button {
margin-top: 0.5rem;
}
.fb-button {
margin-left: 1rem;
}
}
.toolbay-slot-menu {

View File

@ -3,26 +3,26 @@ jest.mock("react-redux", () => ({
}));
import * as React from "react";
import { mount } from "enzyme";
import { mount, shallow } from "enzyme";
import { Tools } from "../index";
import { Props } from "../interfaces";
import { fakeToolSlot, fakeTool } from "../../__test_support__/fake_state/resources";
import {
fakeToolSlot, fakeTool
} from "../../__test_support__/fake_state/resources";
describe("<Tools />", () => {
function fakeProps(): Props {
return {
toolSlots: [],
tools: [fakeTool()],
getToolSlots: () => [fakeToolSlot()],
getToolOptions: () => [],
getChosenToolOption: () => { return { label: "None", value: "" }; },
getToolByToolSlotUUID: () => fakeTool(),
changeToolSlot: jest.fn(),
isActive: () => true,
dispatch: jest.fn(),
botPosition: { x: undefined, y: undefined, z: undefined }
};
}
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()} />);
@ -36,4 +36,17 @@ describe("<Tools />", () => {
"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

@ -0,0 +1,30 @@
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,12 +1,20 @@
jest.mock("../../../api/crud", () => ({
init: jest.fn(),
saveAll: jest.fn(),
destroy: jest.fn(),
edit: jest.fn(),
}));
import * as React from "react";
import { ToolForm } from "../tool_form";
import { mount } from "enzyme";
import { mount, shallow } from "enzyme";
import { fakeTool } from "../../../__test_support__/fake_state/resources";
import { ToolFormProps } from "../../interfaces";
import { ToolListAndFormProps } from "../../interfaces";
import { clickButton } from "../../../__test_support__/helpers";
import { init, saveAll, destroy, edit } from "../../../api/crud";
describe("<ToolForm/>", () => {
function fakeProps(): ToolFormProps {
function fakeProps(): ToolListAndFormProps {
return {
dispatch: jest.fn(),
toggle: jest.fn(),
@ -21,11 +29,34 @@ describe("<ToolForm/>", () => {
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(expect.objectContaining({
body: { name: "Tool 3" }
}));
});
it("adds stock tools", () => {
const p = fakeProps();
const wrapper = mount(<ToolForm {...p} />);
const wrapper = mount(<ToolForm {...fakeProps()} />);
clickButton(wrapper, 3, "stock tools");
expect(p.dispatch).toHaveBeenCalledTimes(6);
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", () => {
@ -36,6 +67,15 @@ describe("<ToolForm/>", () => {
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;

View File

@ -3,25 +3,22 @@ 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 />", () => {
function bootstrapTest() {
const state = fakeState();
const toggle = jest.fn();
const props = mapStateToProps(state);
const fakeProps = (): ToolListAndFormProps => {
const props = mapStateToProps(fakeState());
return {
state,
toggle,
props,
component: mount(<ToolList dispatch={state.dispatch}
tools={props.tools}
toggle={toggle}
isActive={props.isActive} />)
dispatch: jest.fn(),
tools: props.tools,
toggle: jest.fn(),
isActive: props.isActive,
};
}
};
it("renders tool names and statuses", () => {
const test = bootstrapTest();
expect(test.component.text()).toContain("Trench Digging Toolactive");
expect(test.component.text()).toContain("Berry Picking Toolinactive");
const wrapper = mount(<ToolList {...fakeProps()} />);
expect(wrapper.text()).toContain("Trench Digging Toolactive");
expect(wrapper.text()).toContain("Berry Picking Toolinactive");
});
});

View File

@ -0,0 +1,25 @@
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(),
});
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,37 +1,50 @@
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 { emptyToolSlot } from "../empty_tool_slot";
describe("<ToolBayForm/>", () => {
function bootstrapTest() {
const state = fakeState();
const toggle = jest.fn();
const props = mapStateToProps(state);
const dispatch = jest.fn();
const fakeProps = (): ToolBayFormProps => {
const props = mapStateToProps(fakeState());
return {
state,
toggle,
props,
dispatch,
component: mount(<ToolBayForm
toggle={toggle}
dispatch={dispatch}
toolSlots={props.toolSlots}
getToolSlots={props.getToolSlots}
getChosenToolOption={props.getChosenToolOption}
getToolOptions={props.getToolOptions}
changeToolSlot={props.changeToolSlot}
botPosition={{ x: 1, y: 2, z: 3 }} />)
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 test = bootstrapTest();
const inputs = test.component.find("input");
const wrapper = mount(<ToolBayForm {...fakeProps()} />);
const inputs = wrapper.find("input");
expect(inputs.length).toEqual(3);
expect(test.component.text()).toContain("Trench Digging Tool");
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(emptyToolSlot());
});
});

View File

@ -3,24 +3,20 @@ 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 />", () => {
function bootstrapTest() {
const state = fakeState();
const toggle = jest.fn();
const props = mapStateToProps(state);
const fakeProps = (): ToolBayListProps => {
const props = mapStateToProps(fakeState());
return {
state,
toggle,
props,
component: mount(<ToolBayList dispatch={state.dispatch}
getToolByToolSlotUUID={props.getToolByToolSlotUUID}
getToolSlots={props.getToolSlots}
toggle={toggle} />)
getToolByToolSlotUUID: props.getToolByToolSlotUUID,
getToolSlots: props.getToolSlots,
toggle: jest.fn(),
};
}
};
it("renders", () => {
const test = bootstrapTest();
expect(test.component.text()).toContain("1101010Trench Digging Tool");
const wrapper = mount(<ToolBayList {...fakeProps()} />);
expect(wrapper.text()).toContain("1101010Trench Digging Tool");
});
});

View File

@ -0,0 +1,25 @@
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,5 +1,5 @@
import * as React from "react";
import { ToolFormProps } from "../interfaces";
import { ToolListAndFormProps } from "../interfaces";
import { t } from "i18next";
import {
Row,
@ -9,30 +9,25 @@ import {
WidgetHeader,
BlurableInput,
SaveBtn
} from "../../ui/index";
} from "../../ui";
import { getArrayStatus } from "../../resources/tagged_resources";
import { edit, destroy, init, saveAll } from "../../api/crud";
import { ToolTips } from "../../constants";
import { TaggedTool, SpecialStatus } from "farmbot";
export class ToolForm extends React.Component<ToolFormProps, {}> {
taggedTool = (name: string): TaggedTool => {
return {
uuid: "ERROR: GENERATED BY REDUCER - UUID SHOULD BE UNSEEN",
specialStatus: SpecialStatus.SAVED,
kind: "Tool",
body: { name }
};
}
export class ToolForm extends React.Component<ToolListAndFormProps, {}> {
taggedTool = (name: string): TaggedTool => ({
uuid: "ERROR: GENERATED BY REDUCER - UUID SHOULD BE UNSEEN",
specialStatus: SpecialStatus.SAVED,
kind: "Tool",
body: { name }
});
emptyTool = (): TaggedTool => {
return this.taggedTool(t("Tool ") + (this.props.tools.length + 1));
}
emptyTool = (): TaggedTool =>
this.taggedTool(t("Tool ") + (this.props.tools.length + 1))
stockTools = (dispatch: Function) => {
const newTool = (name: string) => {
dispatch(init(this.taggedTool(name)));
};
const newTool = (name: string) => dispatch(init(this.taggedTool(name)));
newTool(t("Seeder"));
newTool(t("Watering Nozzle"));
@ -42,34 +37,59 @@ export class ToolForm extends React.Component<ToolFormProps, {}> {
newTool(t("Seed Tray"));
}
render() {
const toggle = () => this.props.toggle();
const { dispatch, tools, isActive } = this.props;
HeaderButtons = () => {
const { dispatch, tools, toggle } = this.props;
const specialStatus = getArrayStatus(tools);
return <Widget>
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={() => dispatch(init(this.emptyTool()))}>
<i className="fa fa-plus" />
</button>
<button
className="fb-button green"
onClick={() => this.stockTools(dispatch)}>
<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") : ""}
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">
<button
className="fb-button gray"
onClick={() => { toggle(); }}
hidden={!!specialStatus}>
{t("Back")}
</button>
<SaveBtn
status={specialStatus}
onClick={() => {
dispatch(saveAll(tools, () => { toggle(); }));
}} />
<button
className="fb-button green"
onClick={() => { dispatch(init(this.emptyTool())); }}>
<i className="fa fa-plus" />
</button>
<button
className="fb-button green"
onClick={() => { this.stockTools(dispatch); }}>
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
{t("Stock Tools")}
</button>
<this.HeaderButtons />
</WidgetHeader>
<WidgetBody>
<Row>
@ -77,27 +97,7 @@ export class ToolForm extends React.Component<ToolFormProps, {}> {
<label>{t("Tool Name")}</label>
</Col>
</Row>
{tools.map((tool: TaggedTool, index: number) => {
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}`}
title={isActive(tool) ? t("in slot") : ""}
onClick={() => { dispatch(destroy(tool.uuid)); }}>
<i className="fa fa-times"></i>
</button>
</Col>
</Row>;
})}
{this.props.tools.map(this.ToolForm)}
</WidgetBody>
</Widget>;
}

View File

@ -1,14 +1,30 @@
import * as React from "react";
import { Row, Col, Widget, WidgetBody, WidgetHeader } from "../../ui/index";
import { Row, Col, Widget, WidgetBody, WidgetHeader } from "../../ui";
import { t } from "i18next";
import { ToolListProps } from "../interfaces";
import { ToolListAndFormProps } from "../interfaces";
import { TaggedTool } from "farmbot";
import { ToolTips } from "../../constants";
export class ToolList extends React.Component<ToolListProps, {}> {
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 toggle = () => this.props.toggle();
const { tools } = this.props;
const { tools, toggle } = this.props;
return <Widget>
<WidgetHeader helpText={ToolTips.TOOL_LIST} title={t("Tools")}>
@ -20,23 +36,14 @@ export class ToolList extends React.Component<ToolListProps, {}> {
</WidgetHeader>
<WidgetBody>
<Row>
<Col xs={8}>
<Col xs={ColWidth.toolName}>
<label>{t("Tool Name")}</label>
</Col>
<Col xs={4}>
<Col xs={ColWidth.status}>
<label>{t("Status")}</label>
</Col>
</Row>
{tools.map((tool: TaggedTool) => {
return <Row key={tool.uuid}>
<Col xs={8}>
{tool.body.name || "Name not found"}
</Col>
<Col xs={4}>
{this.props.isActive(tool) ? t("active") : t("inactive")}
</Col>
</Row>;
})}
{tools.map(this.ToolListItem)}
</WidgetBody>
</Widget>;
}

View File

@ -7,7 +7,7 @@ import { destroy } from "../../api/crud";
import { Xyz } from "../../devices/interfaces";
import { ToolBayNumberCol } from "./toolbay_number_column";
interface ToolSlotRowProps {
export interface ToolSlotRowProps {
dispatch: Function;
slot: TaggedToolSlotPointer;
botPosition: Record<"x" | "y" | "z", number | undefined>;
@ -22,7 +22,7 @@ interface ToolSlotRowProps {
type Axis = Xyz & keyof (TaggedToolSlotPointer["body"]);
const axes: Axis[] = ["x", "y", "z"];
export function ToolSlotRow(p: ToolSlotRowProps) {
export function ToolSlotRow(props: ToolSlotRowProps) {
const {
dispatch,
slot,
@ -30,7 +30,7 @@ export function ToolSlotRow(p: ToolSlotRowProps) {
toolOptions,
onToolSlotChange,
chosenToolOption
} = p;
} = props;
return <Row>
<Col xs={1}>
@ -42,12 +42,10 @@ export function ToolSlotRow(p: ToolSlotRowProps) {
botPosition={botPosition} />
</Popover>
</Col>
{
axes
.map(axis => ({ axis, dispatch, slot, value: (slot.body[axis] || 0) }))
.map((props) =>
<ToolBayNumberCol key={slot.uuid + props.axis} {...props} />)
}
{axes
.map(axis => ({ axis, dispatch, slot, value: (slot.body[axis] || 0) }))
.map(axisProps =>
<ToolBayNumberCol key={slot.uuid + axisProps.axis} {...axisProps} />)}
<Col xs={4}>
<FBSelect
list={toolOptions}

View File

@ -5,7 +5,7 @@ import {
WidgetBody,
WidgetHeader,
SaveBtn,
} from "../../ui/index";
} from "../../ui";
import { t } from "i18next";
import {
getArrayStatus
@ -19,42 +19,48 @@ import { TaggedToolSlotPointer } from "farmbot";
export class ToolBayForm extends React.Component<ToolBayFormProps, {}> {
render() {
const { toggle, dispatch, toolSlots, botPosition } = this.props;
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(emptyToolSlot()))}>
<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()}
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")}>
<button
className="gray fb-button"
hidden={!!toolSlotStatus}
onClick={() => { toggle(); }}>
{t("Back")}
</button>
<SaveBtn
status={toolSlotStatus}
onClick={() => {
dispatch(saveAll(toolSlots, () => { toggle(); }));
}} />
<button
className="green fb-button"
onClick={() => { dispatch(init(emptyToolSlot())); }}>
<i className="fa fa-plus" />
</button>
<this.HeaderButtons />
</WidgetHeader>
<WidgetBody>
<ToolBayHeader />
{this.props.getToolSlots().map(
(slot: TaggedToolSlotPointer) => {
return <ToolSlotRow
key={slot.uuid}
dispatch={dispatch}
slot={slot}
botPosition={botPosition}
toolOptions={this.props.getToolOptions()}
onToolSlotChange={this.props.changeToolSlot(slot, this.props.dispatch)}
chosenToolOption={this.props.getChosenToolOption(slot.uuid)} />;
})}
{this.props.getToolSlots().map(this.ToolbayForm)}
</WidgetBody>
</Widget>
</div>;

View File

@ -1,8 +1,8 @@
import * as React from "react";
import { Col, Row } from "../../ui/index";
import { Col, Row } from "../../ui";
import { t } from "i18next";
export function ToolBayHeader(_: {}) {
export function ToolBayHeader() {
return <Row>
<Col xs={1}>
<label>{t("Slot")}</label>

View File

@ -1,41 +1,40 @@
import * as React from "react";
import { t } from "i18next";
import { Row, Col, Widget, WidgetBody, WidgetHeader } from "../../ui/index";
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";
export class ToolBayList extends React.Component<ToolBayListProps, {}> {
render() {
const toggle = () => this.props.toggle();
const { getToolSlots, getToolByToolSlotUUID } = this.props;
ToolSlotListItem = (slot: TaggedToolSlotPointer, index: number) => {
const { getToolByToolSlotUUID } = this.props;
const tool = getToolByToolSlotUUID(slot.uuid);
const name = (tool && tool.body.name) || t("None");
return <Row key={slot.uuid}>
<Col xs={1}><label>{index + 1}</label></Col>
<Col xs={2}>{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>
<WidgetHeader helpText={ToolTips.TOOLBAY_LIST} title={t("ToolBay ") + "1"}>
<WidgetHeader
helpText={ToolTips.TOOLBAY_LIST}
title={t("ToolBay ") + "1"}>
<button
className="gray fb-button"
onClick={toggle}>
onClick={this.props.toggle}>
{t("Edit")}
</button>
</WidgetHeader>
<WidgetBody>
<ToolBayHeader />
{getToolSlots().map((slot: TaggedToolSlotPointer, index: number) => {
const tool = getToolByToolSlotUUID(slot.uuid);
const name = (tool && tool.body.name) || t("None");
return <Row key={slot.uuid}>
<Col xs={1}>
<label>{index + 1}</label>
</Col>
<Col xs={2}>{slot.body.x}</Col>
<Col xs={2}>{slot.body.y}</Col>
<Col xs={2}>{slot.body.z}</Col>
<Col xs={4}>
{name}
</Col>
</Row>;
})}
{this.props.getToolSlots().map(this.ToolSlotListItem)}
</WidgetBody>
</Widget>;
}

View File

@ -1,9 +1,9 @@
import * as React from "react";
import { TaggedToolSlotPointer } from "farmbot";
import { Col, BlurableInput } from "../../ui/index";
import { Col, BlurableInput } from "../../ui";
import { edit } from "../../api/crud";
interface NumColProps {
export interface TBNumColProps {
axis: "x" | "y" | "z";
value: number;
dispatch: Function;
@ -11,13 +11,13 @@ interface NumColProps {
}
/** Used to display and edit the X/Y/Z numeric values in the tool bay form. */
export function ToolBayNumberCol({ axis, value, dispatch, slot }: NumColProps) {
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) }));
}}
onCommit={e =>
dispatch(edit(slot, { [axis]: parseFloat(e.currentTarget.value) }))}
type="number" />
</Col>;
}

View File

@ -1,6 +1,6 @@
import * as React from "react";
import { t } from "i18next";
import { FBSelect, DropDownItem } from "../../ui/index";
import { FBSelect, DropDownItem } from "../../ui";
import { TaggedToolSlotPointer } from "farmbot";
import { edit } from "../../api/crud";
import { isNumber } from "lodash";

View File

@ -7,9 +7,8 @@ import { edit } from "../../api/crud";
import { SlotDirectionSelect } from "./toolbay_slot_direction_selection";
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
const positionIsDefined = (position: BotPosition): boolean => {
return isNumber(position.x) && isNumber(position.y) && isNumber(position.z);
};
const positionIsDefined = (position: BotPosition): boolean =>
isNumber(position.x) && isNumber(position.y) && isNumber(position.z);
const useCurrentPosition = (
dispatch: Function, slot: TaggedToolSlotPointer, position: BotPosition) => {
@ -18,21 +17,16 @@ const useCurrentPosition = (
}
};
const positionButtonTitle = (position: BotPosition): string => {
if (positionIsDefined(position)) {
return `(${position.x}, ${position.y}, ${position.z})`;
} else {
return t("(unknown)");
}
};
const positionButtonTitle = (position: BotPosition): string =>
positionIsDefined(position)
? `(${position.x}, ${position.y}, ${position.z})`
: t("(unknown)");
const changePulloutDirection =
(dispatch: Function, slot: TaggedToolSlotPointer) => () => {
const newDirection = (
old: ToolPulloutDirection | undefined): ToolPulloutDirection => {
if (isNumber(old) && old < 4) { return old + 1; }
return ToolPulloutDirection.NONE;
};
const newDirection =
(old: ToolPulloutDirection | undefined): ToolPulloutDirection =>
isNumber(old) && old < 4 ? old + 1 : ToolPulloutDirection.NONE;
dispatch(edit(slot,
{ pullout_direction: newDirection(slot.body.pullout_direction) }));
};
@ -61,29 +55,22 @@ export const SlotMenu = (props: SlotMenuProps) => {
<label>
{t("Change slot direction")}
</label>
<i className={"direction-icon " +
directionIconClass(pullout_direction)}
onClick={
changePulloutDirection(dispatch, slot)} />
<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>
<label>{t("Use current location")}</label>
<button
className="blue fb-button"
title={positionButtonTitle(botPosition)}
onClick={() =>
useCurrentPosition(dispatch, slot, botPosition)}>
onClick={() => useCurrentPosition(dispatch, slot, botPosition)}>
<i className="fa fa-crosshairs" />
</button>
<p>
{positionButtonTitle(botPosition)}
</p>
<p>{positionButtonTitle(botPosition)}</p>
</fieldset>
</div>;
};

View File

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

View File

@ -14,7 +14,7 @@ export interface Props {
toolSlots: TaggedToolSlotPointer[];
tools: TaggedTool[];
getToolOptions(): DropDownItem[];
getChosenToolOption(toolSlotUuid: string): DropDownItem;
getChosenToolOption(toolSlotUuid: string | undefined): DropDownItem;
getToolByToolSlotUUID(uuid: string): TaggedTool | undefined;
getToolSlots(): TaggedToolSlotPointer[];
dispatch: Function;
@ -30,7 +30,6 @@ export interface Tool {
}
export interface ToolBayListProps {
dispatch: Function;
toggle(): void;
getToolByToolSlotUUID(uuid: string): TaggedTool | undefined;
getToolSlots(): TaggedToolSlotPointer[];
@ -42,19 +41,12 @@ export interface ToolBayFormProps {
botPosition: BotPosition;
toggle(): void;
getToolOptions(): DropDownItem[];
getChosenToolOption(uuid: string): DropDownItem;
getChosenToolOption(uuid: string | undefined): DropDownItem;
getToolSlots(): TaggedToolSlotPointer[];
changeToolSlot(t: TaggedToolSlotPointer, dispatch: Function): (d: DropDownItem) => void;
}
export interface ToolListProps {
tools: TaggedTool[];
dispatch: Function;
toggle(): void;
isActive(tool: TaggedTool): boolean;
}
export interface ToolFormProps {
export interface ToolListAndFormProps {
dispatch: Function;
tools: TaggedTool[];
toggle(): void;

View File

@ -10,7 +10,7 @@ import {
isTaggedTool,
} from "../resources/tagged_resources";
import { edit } from "../api/crud";
import { DropDownItem, NULL_CHOICE } from "../ui/index";
import { DropDownItem, NULL_CHOICE } from "../ui";
import { validBotLocationData } from "../util";
import { TaggedTool, TaggedToolSlotPointer } from "farmbot";
@ -44,11 +44,9 @@ export function mapStateToProps(props: Everything): Props {
* and in an <FBSelect /> compatible format. */
const getChosenToolOption = (toolSlotUUID: string | undefined) => {
const chosenTool = toolSlotUUID && getToolByToolSlotUUID(toolSlotUUID);
if (chosenTool && isTaggedTool(chosenTool) && chosenTool.body.id) {
return { label: chosenTool.body.name || "untitled", value: chosenTool.uuid };
} else {
return NULL_CHOICE;
}
return (chosenTool && isTaggedTool(chosenTool) && chosenTool.body.id)
? { label: chosenTool.body.name || "untitled", value: chosenTool.uuid }
: NULL_CHOICE;
};
const changeToolSlot = (t: TaggedToolSlotPointer,
@ -62,6 +60,9 @@ export function mapStateToProps(props: Everything): Props {
dispatch(edit(t, { tool_id }));
};
const botPosition =
validBotLocationData(props.bot.hardware.location_data).position;
return {
toolSlots,
tools,
@ -72,7 +73,7 @@ export function mapStateToProps(props: Everything): Props {
changeToolSlot,
isActive,
dispatch: _.noop,
botPosition: validBotLocationData(props.bot.hardware.location_data).position
botPosition,
};
}