commit
3677fb8a37
|
@ -8,6 +8,7 @@ export const fakeDesignerState = (): DesignerState => ({
|
|||
},
|
||||
hoveredPoint: undefined,
|
||||
hoveredPlantListItem: undefined,
|
||||
hoveredToolSlot: undefined,
|
||||
cropSearchQuery: "",
|
||||
cropSearchResults: [],
|
||||
cropSearchInProgress: false,
|
||||
|
|
|
@ -785,6 +785,10 @@ export namespace Content {
|
|||
export const NO_TOOLS =
|
||||
trim(`Press "+" to add a new tool.`);
|
||||
|
||||
export const MOUNTED_TOOL =
|
||||
trim(`The tool currently mounted to the UTM can be set here or by using
|
||||
a MARK AS step in a sequence.`);
|
||||
|
||||
// Farm Events
|
||||
export const NOTHING_SCHEDULED =
|
||||
trim(`Press "+" to schedule an event.`);
|
||||
|
@ -979,6 +983,7 @@ export enum Actions {
|
|||
TOGGLE_HOVERED_PLANT = "TOGGLE_HOVERED_PLANT",
|
||||
TOGGLE_HOVERED_POINT = "TOGGLE_HOVERED_POINT",
|
||||
HOVER_PLANT_LIST_ITEM = "HOVER_PLANT_LIST_ITEM",
|
||||
HOVER_TOOL_SLOT = "HOVER_TOOL_SLOT",
|
||||
OF_SEARCH_RESULTS_START = "OF_SEARCH_RESULTS_START",
|
||||
OF_SEARCH_RESULTS_OK = "OF_SEARCH_RESULTS_OK",
|
||||
OF_SEARCH_RESULTS_NO = "OF_SEARCH_RESULTS_NO",
|
||||
|
|
|
@ -509,9 +509,125 @@
|
|||
}
|
||||
}
|
||||
|
||||
.tools-panel-content {
|
||||
.tool-slots-panel,
|
||||
.tools-panel {
|
||||
.panel-top {
|
||||
display: flex;
|
||||
margin-top: 5rem;
|
||||
}
|
||||
.tool-slots-panel-content,
|
||||
.tools-panel-content {
|
||||
.tool-search-item,
|
||||
.tool-slot-search-item {
|
||||
cursor: pointer;
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
.row {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
p {
|
||||
line-height: 3rem;
|
||||
}
|
||||
}
|
||||
.mounted-tool-header {
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
label {
|
||||
margin: 0;
|
||||
}
|
||||
.help-icon {
|
||||
margin-left: 1rem;
|
||||
vertical-align: top;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
.tool-slots-header {
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
label {
|
||||
margin: 0;
|
||||
line-height: 2.1rem;
|
||||
}
|
||||
a {
|
||||
margin-left: auto;
|
||||
}
|
||||
.fa-plus {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
button:not(.bp3-button) {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
float: none;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.tool-verification-status {
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
button {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-tool-panel-content,
|
||||
.edit-tool-panel-content {
|
||||
button {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
float: none;
|
||||
margin-top: 1rem;
|
||||
&.red {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
.add-stock-tools {
|
||||
ul {
|
||||
font-size: 1.2rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
button {
|
||||
.fa-plus {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-tool-slot-panel-content,
|
||||
.edit-tool-slot-panel-content {
|
||||
label {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.row, fieldset {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
fieldset button {
|
||||
margin: 0;
|
||||
}
|
||||
.direction-icon {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.use-current-location-input {
|
||||
button {
|
||||
margin: 0;
|
||||
float: none;
|
||||
margin-left: 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
.gantry-mounted-input {
|
||||
label {
|
||||
margin-top: 0;
|
||||
}
|
||||
input[type="checkbox"] {
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -160,17 +160,17 @@
|
|||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.step-button-cluster,
|
||||
.sequence-list,
|
||||
.regimen-list {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: calc(100vh - 21rem);
|
||||
max-height: calc(100vh - 19rem);
|
||||
}
|
||||
|
||||
.step-button-cluster,
|
||||
.sequence-list {
|
||||
.step-button-cluster {
|
||||
margin-right: -15px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: calc(100vh - 16rem);
|
||||
}
|
||||
|
||||
.farmware-info-panel,
|
||||
|
@ -236,7 +236,7 @@
|
|||
margin-left: -15px;
|
||||
}
|
||||
.non-empty-state {
|
||||
height: calc(100vh - 15rem);
|
||||
height: calc(100vh - 19rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
@ -453,7 +453,6 @@
|
|||
.sequence-list-panel,
|
||||
.regimen-list-panel {
|
||||
padding-top: 0.4rem;
|
||||
margin-bottom: 3rem;
|
||||
margin-right: 5px;
|
||||
@media screen and (max-width: 1075px) {
|
||||
margin-left: 15px;
|
||||
|
@ -484,7 +483,15 @@
|
|||
}
|
||||
|
||||
.farmware-list-panel {
|
||||
margin-bottom: 5rem;
|
||||
.farmware-list-panel-contents {
|
||||
height: calc(100vh - 15rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin-right: -20px;
|
||||
margin-left: -15px;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
label {
|
||||
font-weight: bold;
|
||||
font-size: 1.4rem;
|
||||
|
|
|
@ -63,6 +63,15 @@ describe("designer reducer", () => {
|
|||
expect(newState.hoveredPoint).toEqual("uuid");
|
||||
});
|
||||
|
||||
it("sets hovered tool slot", () => {
|
||||
const action: ReduxAction<string> = {
|
||||
type: Actions.HOVER_TOOL_SLOT,
|
||||
payload: "toolSlotUuid"
|
||||
};
|
||||
const newState = designer(oldState(), action);
|
||||
expect(newState.hoveredToolSlot).toEqual("toolSlotUuid");
|
||||
});
|
||||
|
||||
it("sets chosen location", () => {
|
||||
const action: ReduxAction<BotPosition> = {
|
||||
type: Actions.CHOOSE_LOCATION,
|
||||
|
|
|
@ -103,6 +103,7 @@ export interface DesignerState {
|
|||
hoveredPlant: HoveredPlantPayl;
|
||||
hoveredPoint: string | undefined;
|
||||
hoveredPlantListItem: string | undefined;
|
||||
hoveredToolSlot: string | undefined;
|
||||
cropSearchQuery: string;
|
||||
cropSearchResults: CropLiveSearchResult[];
|
||||
cropSearchInProgress: boolean;
|
||||
|
|
|
@ -340,6 +340,8 @@ export class GardenMap extends
|
|||
ToolSlotLayer = () => <ToolSlotLayer
|
||||
mapTransformProps={this.mapTransformProps}
|
||||
visible={!!this.props.showFarmbot}
|
||||
dispatch={this.props.dispatch}
|
||||
hoveredToolSlot={this.props.designer.hoveredToolSlot}
|
||||
botPositionX={this.props.botLocationData.position.x}
|
||||
slots={this.props.toolSlots} />
|
||||
FarmBotLayer = () => <FarmBotLayer
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
import { BotOriginQuadrant } from "../../../../interfaces";
|
||||
import { Color } from "../../../../../ui";
|
||||
import { svgMount } from "../../../../../__test_support__/svg_mount";
|
||||
import { Actions } from "../../../../../constants";
|
||||
|
||||
describe("<ToolbaySlot />", () => {
|
||||
const fakeProps = (): ToolSlotGraphicProps => ({
|
||||
|
@ -61,7 +62,8 @@ describe("<Tool/>", () => {
|
|||
x: 10,
|
||||
y: 20,
|
||||
hovered: false,
|
||||
setHoverState: jest.fn(),
|
||||
dispatch: jest.fn(),
|
||||
uuid: "fakeUuid",
|
||||
xySwap: false,
|
||||
});
|
||||
|
||||
|
@ -75,9 +77,13 @@ describe("<Tool/>", () => {
|
|||
p.tool = toolName;
|
||||
const wrapper = svgMount(<Tool {...p} />);
|
||||
wrapper.find("g").simulate("mouseOver");
|
||||
expect(p.toolProps.setHoverState).toHaveBeenCalledWith(true);
|
||||
expect(p.toolProps.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.HOVER_TOOL_SLOT, payload: "fakeUuid"
|
||||
});
|
||||
wrapper.find("g").simulate("mouseLeave");
|
||||
expect(p.toolProps.setHoverState).toHaveBeenCalledWith(false);
|
||||
expect(p.toolProps.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.HOVER_TOOL_SLOT, payload: undefined
|
||||
});
|
||||
};
|
||||
|
||||
it("renders standard tool styling", () => {
|
||||
|
|
|
@ -14,6 +14,7 @@ import { shallow } from "enzyme";
|
|||
import { history } from "../../../../../history";
|
||||
import { ToolSlotPointer } from "farmbot/dist/resources/api_resources";
|
||||
import { TaggedToolSlotPointer } from "farmbot";
|
||||
import { ToolSlotPoint } from "../tool_slot_point";
|
||||
|
||||
describe("<ToolSlotLayer/>", () => {
|
||||
function fakeProps(): ToolSlotLayerProps {
|
||||
|
@ -35,18 +36,20 @@ describe("<ToolSlotLayer/>", () => {
|
|||
slots: [{ toolSlot, tool: undefined }],
|
||||
botPositionX: undefined,
|
||||
mapTransformProps: fakeMapTransformProps(),
|
||||
dispatch: jest.fn(),
|
||||
hoveredToolSlot: undefined,
|
||||
};
|
||||
}
|
||||
it("toggles visibility off", () => {
|
||||
const result = shallow(<ToolSlotLayer {...fakeProps()} />);
|
||||
expect(result.find("ToolSlotPoint").length).toEqual(0);
|
||||
expect(result.find(ToolSlotPoint).length).toEqual(0);
|
||||
});
|
||||
|
||||
it("toggles visibility on", () => {
|
||||
const p = fakeProps();
|
||||
p.visible = true;
|
||||
const result = shallow(<ToolSlotLayer {...p} />);
|
||||
expect(result.find("ToolSlotPoint").length).toEqual(1);
|
||||
expect(result.find(ToolSlotPoint).length).toEqual(1);
|
||||
});
|
||||
|
||||
it("navigates to tools page", async () => {
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
let mockDev = false;
|
||||
jest.mock("../../../../../account/dev/dev_support", () => ({
|
||||
DevSettings: { futureFeaturesEnabled: () => mockDev, }
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../history", () => ({ history: { push: jest.fn() } }));
|
||||
|
||||
import * as React from "react";
|
||||
import { ToolSlotPoint, TSPProps } from "../tool_slot_point";
|
||||
import {
|
||||
|
@ -7,12 +14,19 @@ import {
|
|||
fakeMapTransformProps
|
||||
} from "../../../../../__test_support__/map_transform_props";
|
||||
import { svgMount } from "../../../../../__test_support__/svg_mount";
|
||||
import { history } from "../../../../../history";
|
||||
|
||||
describe("<ToolSlotPoint/>", () => {
|
||||
beforeEach(() => {
|
||||
mockDev = false;
|
||||
});
|
||||
|
||||
const fakeProps = (): TSPProps => ({
|
||||
mapTransformProps: fakeMapTransformProps(),
|
||||
botPositionX: undefined,
|
||||
slot: { toolSlot: fakeToolSlot(), tool: fakeTool() }
|
||||
slot: { toolSlot: fakeToolSlot(), tool: fakeTool() },
|
||||
dispatch: jest.fn(),
|
||||
hoveredToolSlot: undefined,
|
||||
});
|
||||
|
||||
const testToolSlotGraphics = (tool: 0 | 1, slot: 0 | 1) => {
|
||||
|
@ -31,11 +45,23 @@ describe("<ToolSlotPoint/>", () => {
|
|||
testToolSlotGraphics(1, 0);
|
||||
testToolSlotGraphics(1, 1);
|
||||
|
||||
it("opens tool info", () => {
|
||||
const p = fakeProps();
|
||||
p.slot.toolSlot.body.id = 1;
|
||||
const wrapper = svgMount(<ToolSlotPoint {...p} />);
|
||||
mockDev = false;
|
||||
wrapper.find("g").first().simulate("click");
|
||||
expect(history.push).not.toHaveBeenCalled();
|
||||
mockDev = true;
|
||||
wrapper.find("g").first().simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tool-slots/1");
|
||||
});
|
||||
|
||||
it("displays tool name", () => {
|
||||
const p = fakeProps();
|
||||
p.slot.toolSlot.body.pullout_direction = 2;
|
||||
p.hoveredToolSlot = p.slot.toolSlot.uuid;
|
||||
const wrapper = svgMount(<ToolSlotPoint {...p} />);
|
||||
wrapper.find(ToolSlotPoint).setState({ hovered: true });
|
||||
expect(wrapper.find("text").props().visibility).toEqual("visible");
|
||||
expect(wrapper.find("text").text()).toEqual("Foo");
|
||||
expect(wrapper.find("text").props().dx).toEqual(-40);
|
||||
|
@ -44,8 +70,8 @@ describe("<ToolSlotPoint/>", () => {
|
|||
it("displays 'no tool'", () => {
|
||||
const p = fakeProps();
|
||||
p.slot.tool = undefined;
|
||||
p.hoveredToolSlot = p.slot.toolSlot.uuid;
|
||||
const wrapper = svgMount(<ToolSlotPoint {...p} />);
|
||||
wrapper.find(ToolSlotPoint).setState({ hovered: true });
|
||||
expect(wrapper.find("text").text()).toEqual("no tool");
|
||||
expect(wrapper.find("text").props().dx).toEqual(40);
|
||||
});
|
||||
|
@ -74,13 +100,21 @@ describe("<ToolSlotPoint/>", () => {
|
|||
p.slot.toolSlot.body.gantry_mounted = true;
|
||||
if (p.slot.tool) { p.slot.tool.body.name = "seed trough"; }
|
||||
const wrapper = svgMount(<ToolSlotPoint {...p} />);
|
||||
expect(wrapper.find("#seed-trough").length).toEqual(1);
|
||||
expect(wrapper.find("#seed-trough").find("rect").props().width)
|
||||
.toEqual(45);
|
||||
expect(wrapper.find("#gantry-toolbay-slot").find("rect").props().width)
|
||||
.toEqual(49);
|
||||
});
|
||||
|
||||
it("sets hover", () => {
|
||||
const wrapper = svgMount(<ToolSlotPoint {...fakeProps()} />);
|
||||
expect(wrapper.find(ToolSlotPoint).state().hovered).toBeFalsy();
|
||||
(wrapper.find(ToolSlotPoint).instance() as ToolSlotPoint).setHover(true);
|
||||
expect(wrapper.find(ToolSlotPoint).state().hovered).toBeTruthy();
|
||||
it("renders rotated trough", () => {
|
||||
const p = fakeProps();
|
||||
p.mapTransformProps.xySwap = true;
|
||||
p.slot.toolSlot.body.gantry_mounted = true;
|
||||
if (p.slot.tool) { p.slot.tool.body.name = "seed trough"; }
|
||||
const wrapper = svgMount(<ToolSlotPoint {...p} />);
|
||||
expect(wrapper.find("#seed-trough").find("rect").props().width)
|
||||
.toEqual(20);
|
||||
expect(wrapper.find("#gantry-toolbay-slot").find("rect").props().width)
|
||||
.toEqual(24);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,13 +3,16 @@ import { Color } from "../../../../ui/index";
|
|||
import { trim } from "../../../../util";
|
||||
import { BotOriginQuadrant } from "../../../interfaces";
|
||||
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
|
||||
import { Actions } from "../../../../constants";
|
||||
import { UUID } from "../../../../resources/interfaces";
|
||||
|
||||
export interface ToolGraphicProps {
|
||||
x: number;
|
||||
y: number;
|
||||
hovered: boolean;
|
||||
setHoverState(hoverState: boolean): void;
|
||||
dispatch: Function;
|
||||
xySwap: boolean;
|
||||
uuid: UUID | undefined;
|
||||
}
|
||||
|
||||
export interface ToolProps {
|
||||
|
@ -95,11 +98,14 @@ export const Tool = (props: ToolProps) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const setToolHover = (payload: string | undefined) =>
|
||||
({ type: Actions.HOVER_TOOL_SLOT, payload });
|
||||
|
||||
const StandardTool = (props: ToolGraphicProps) => {
|
||||
const { x, y, hovered, setHoverState } = props;
|
||||
const { x, y, hovered, dispatch, uuid } = props;
|
||||
return <g id={"tool"}
|
||||
onMouseOver={() => setHoverState(true)}
|
||||
onMouseLeave={() => setHoverState(false)}>
|
||||
onMouseOver={() => dispatch(setToolHover(uuid))}
|
||||
onMouseLeave={() => dispatch(setToolHover(undefined))}>
|
||||
<circle
|
||||
cx={x}
|
||||
cy={y}
|
||||
|
@ -116,10 +122,10 @@ const seedBinGradient =
|
|||
</radialGradient>;
|
||||
|
||||
const SeedBin = (props: ToolGraphicProps) => {
|
||||
const { x, y, hovered, setHoverState } = props;
|
||||
const { x, y, hovered, dispatch, uuid } = props;
|
||||
return <g id={"seed-bin"}
|
||||
onMouseOver={() => setHoverState(true)}
|
||||
onMouseLeave={() => setHoverState(false)}>
|
||||
onMouseOver={() => dispatch(setToolHover(uuid))}
|
||||
onMouseLeave={() => dispatch(setToolHover(undefined))}>
|
||||
|
||||
<defs>
|
||||
{seedBinGradient}
|
||||
|
@ -140,10 +146,10 @@ const SeedBin = (props: ToolGraphicProps) => {
|
|||
};
|
||||
|
||||
const SeedTray = (props: ToolGraphicProps) => {
|
||||
const { x, y, hovered, setHoverState } = props;
|
||||
const { x, y, hovered, dispatch, uuid } = props;
|
||||
return <g id={"seed-tray"}
|
||||
onMouseOver={() => setHoverState(true)}
|
||||
onMouseLeave={() => setHoverState(false)}>
|
||||
onMouseOver={() => dispatch(setToolHover(uuid))}
|
||||
onMouseLeave={() => dispatch(setToolHover(undefined))}>
|
||||
|
||||
<defs>
|
||||
{seedBinGradient}
|
||||
|
@ -188,12 +194,12 @@ export const GantryToolSlot = (props: GantryToolSlotGraphicProps) => {
|
|||
};
|
||||
|
||||
const SeedTrough = (props: ToolGraphicProps) => {
|
||||
const { x, y, hovered, setHoverState, xySwap } = props;
|
||||
const { x, y, hovered, dispatch, uuid, xySwap } = props;
|
||||
const slotLengthX = xySwap ? 20 : 45;
|
||||
const slotLengthY = xySwap ? 45 : 20;
|
||||
return <g id={"seed-trough"}
|
||||
onMouseOver={() => setHoverState(true)}
|
||||
onMouseLeave={() => setHoverState(false)}>
|
||||
onMouseOver={() => dispatch(setToolHover(uuid))}
|
||||
onMouseLeave={() => dispatch(setToolHover(undefined))}>
|
||||
<rect
|
||||
x={x - slotLengthX / 2} y={y - slotLengthY / 2}
|
||||
width={slotLengthX} height={slotLengthY}
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
import * as React from "react";
|
||||
import { SlotWithTool } from "../../../../resources/interfaces";
|
||||
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;
|
||||
slots: SlotWithTool[];
|
||||
botPositionX: number | undefined;
|
||||
mapTransformProps: MapTransformProps;
|
||||
dispatch: Function;
|
||||
hoveredToolSlot: UUID | undefined;
|
||||
}
|
||||
|
||||
export function ToolSlotLayer(props: ToolSlotLayerProps) {
|
||||
const pathArray = getPathArray();
|
||||
const canClickTool = !(pathArray[3] === "plants" && pathArray.length > 4);
|
||||
const goToToolsPage = () => canClickTool && history.push("/app/tools");
|
||||
const goToToolsPage = () => canClickTool &&
|
||||
!DevSettings.futureFeaturesEnabled() && history.push("/app/tools");
|
||||
const { slots, visible, mapTransformProps } = props;
|
||||
const cursor = canClickTool ? "pointer" : "default";
|
||||
|
||||
|
@ -28,6 +32,8 @@ export function ToolSlotLayer(props: ToolSlotLayerProps) {
|
|||
<ToolSlotPoint
|
||||
key={slot.toolSlot.uuid}
|
||||
slot={slot}
|
||||
hoveredToolSlot={props.hoveredToolSlot}
|
||||
dispatch={props.dispatch}
|
||||
botPositionX={props.botPositionX}
|
||||
mapTransformProps={mapTransformProps} />)}
|
||||
</g>;
|
||||
|
|
|
@ -1,78 +1,73 @@
|
|||
import * as React from "react";
|
||||
import { SlotWithTool } from "../../../../resources/interfaces";
|
||||
import { SlotWithTool, UUID } from "../../../../resources/interfaces";
|
||||
import { transformXY } from "../../util";
|
||||
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";
|
||||
|
||||
export interface TSPProps {
|
||||
slot: SlotWithTool;
|
||||
botPositionX: number | undefined;
|
||||
mapTransformProps: MapTransformProps;
|
||||
dispatch: Function;
|
||||
hoveredToolSlot: UUID | undefined;
|
||||
}
|
||||
|
||||
interface TSPState {
|
||||
hovered: boolean;
|
||||
}
|
||||
const reduceToolName = (raw: string | undefined) => {
|
||||
const lower = (raw || "").toLowerCase();
|
||||
if (includes(lower, "seed bin")) { return ToolNames.seedBin; }
|
||||
if (includes(lower, "seed tray")) { return ToolNames.seedTray; }
|
||||
if (includes(lower, "seed trough")) { return ToolNames.seedTrough; }
|
||||
return ToolNames.tool;
|
||||
};
|
||||
|
||||
export class ToolSlotPoint extends
|
||||
React.Component<TSPProps, Partial<TSPState>> {
|
||||
state: TSPState = { hovered: false };
|
||||
|
||||
setHover = (state: boolean) => this.setState({ hovered: state });
|
||||
|
||||
get slot() { return this.props.slot; }
|
||||
|
||||
reduceToolName = (raw: string | undefined) => {
|
||||
const lower = (raw || "").toLowerCase();
|
||||
if (includes(lower, "seed bin")) { return ToolNames.seedBin; }
|
||||
if (includes(lower, "seed tray")) { return ToolNames.seedTray; }
|
||||
if (includes(lower, "seed trough")) { return ToolNames.seedTrough; }
|
||||
return ToolNames.tool;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
id, x, y, pullout_direction, gantry_mounted
|
||||
} = this.slot.toolSlot.body;
|
||||
const { mapTransformProps, botPositionX } = this.props;
|
||||
const { quadrant, xySwap } = mapTransformProps;
|
||||
const xPosition = gantry_mounted ? (botPositionX || 0) : x;
|
||||
const { qx, qy } = transformXY(xPosition, y, this.props.mapTransformProps);
|
||||
const toolName = this.slot.tool ? this.slot.tool.body.name : "no tool";
|
||||
const toolProps = {
|
||||
x: qx,
|
||||
y: qy,
|
||||
hovered: this.state.hovered,
|
||||
setHoverState: this.setHover,
|
||||
xySwap,
|
||||
};
|
||||
return <g id={"toolslot-" + id}>
|
||||
{pullout_direction &&
|
||||
<ToolbaySlot
|
||||
id={id}
|
||||
x={qx}
|
||||
y={qy}
|
||||
pulloutDirection={pullout_direction}
|
||||
quadrant={quadrant}
|
||||
xySwap={xySwap} />}
|
||||
|
||||
{gantry_mounted && <GantryToolSlot x={qx} y={qy} xySwap={xySwap} />}
|
||||
|
||||
{(this.slot.tool || (!pullout_direction && !gantry_mounted)) &&
|
||||
<Tool
|
||||
tool={this.reduceToolName(toolName)}
|
||||
toolProps={toolProps} />}
|
||||
|
||||
<ToolLabel
|
||||
toolName={toolName}
|
||||
hovered={this.state.hovered}
|
||||
export const ToolSlotPoint = (props: TSPProps) => {
|
||||
const {
|
||||
id, x, y, pullout_direction, gantry_mounted
|
||||
} = props.slot.toolSlot.body;
|
||||
const { mapTransformProps, botPositionX } = props;
|
||||
const { quadrant, xySwap } = mapTransformProps;
|
||||
const xPosition = gantry_mounted ? (botPositionX || 0) : x;
|
||||
const { qx, qy } = transformXY(xPosition, y, props.mapTransformProps);
|
||||
const toolName = props.slot.tool ? props.slot.tool.body.name : "no tool";
|
||||
const hovered = props.slot.toolSlot.uuid === props.hoveredToolSlot;
|
||||
const toolProps = {
|
||||
x: qx,
|
||||
y: qy,
|
||||
hovered,
|
||||
dispatch: props.dispatch,
|
||||
uuid: props.slot.toolSlot.uuid,
|
||||
xySwap,
|
||||
};
|
||||
return <g id={"toolslot-" + id}
|
||||
onClick={() => DevSettings.futureFeaturesEnabled() &&
|
||||
history.push(`/app/designer/tool-slots/${id}`)}>
|
||||
{pullout_direction &&
|
||||
<ToolbaySlot
|
||||
id={id}
|
||||
x={qx}
|
||||
y={qy}
|
||||
pulloutDirection={pullout_direction}
|
||||
quadrant={quadrant}
|
||||
xySwap={xySwap} />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
xySwap={xySwap} />}
|
||||
|
||||
{gantry_mounted && <GantryToolSlot x={qx} y={qy} xySwap={xySwap} />}
|
||||
|
||||
{(props.slot.tool || (!pullout_direction && !gantry_mounted)) &&
|
||||
<Tool
|
||||
tool={reduceToolName(toolName)}
|
||||
toolProps={toolProps} />}
|
||||
|
||||
<ToolLabel
|
||||
toolName={toolName}
|
||||
hovered={hovered}
|
||||
x={qx}
|
||||
y={qy}
|
||||
pulloutDirection={pullout_direction}
|
||||
quadrant={quadrant}
|
||||
xySwap={xySwap} />
|
||||
</g>;
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ export const initialState: DesignerState = {
|
|||
},
|
||||
hoveredPoint: undefined,
|
||||
hoveredPlantListItem: undefined,
|
||||
hoveredToolSlot: undefined,
|
||||
cropSearchQuery: "",
|
||||
cropSearchResults: [],
|
||||
cropSearchInProgress: false,
|
||||
|
@ -55,6 +56,10 @@ export const designer = generateReducer<DesignerState>(initialState)
|
|||
s.hoveredPoint = payload;
|
||||
return s;
|
||||
})
|
||||
.add<string | undefined>(Actions.HOVER_TOOL_SLOT, (s, { payload }) => {
|
||||
s.hoveredToolSlot = payload;
|
||||
return s;
|
||||
})
|
||||
.add<CurrentPointPayl | undefined>(Actions.SET_CURRENT_POINT_DATA, (s, { payload }) => {
|
||||
const { color } =
|
||||
(!payload || !payload.color) ? (s.currentPoint || { color: "green" }) : payload;
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
jest.mock("../../../api/crud", () => ({
|
||||
init: jest.fn(() => ({ type: "", payload: { uuid: "fakeUuid" } })),
|
||||
save: jest.fn(),
|
||||
edit: jest.fn(),
|
||||
destroy: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../history", () => ({ history: { push: jest.fn() } }));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import {
|
||||
RawAddToolSlot as AddToolSlot, AddToolSlotProps, mapStateToProps
|
||||
} from "../add_tool_slot";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import {
|
||||
fakeTool, fakeToolSlot
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
buildResourceIndex
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { init, save, edit, destroy } from "../../../api/crud";
|
||||
import { history } from "../../../history";
|
||||
import { SpecialStatus } from "farmbot";
|
||||
|
||||
describe("<AddToolSlot />", () => {
|
||||
const fakeProps = (): AddToolSlotProps => ({
|
||||
tools: [],
|
||||
findTool: jest.fn(),
|
||||
botPosition: { x: undefined, y: undefined, z: undefined },
|
||||
dispatch: jest.fn(),
|
||||
findToolSlot: fakeToolSlot,
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<AddToolSlot {...fakeProps()} />);
|
||||
["add new tool slot", "x (mm)", "y (mm)", "z (mm)", "toolnone",
|
||||
"change slot direction", "use current location", "gantry-mounted"
|
||||
].map(string => expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
expect(init).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders while loading", () => {
|
||||
const p = fakeProps();
|
||||
p.findToolSlot = () => undefined;
|
||||
const wrapper = mount(<AddToolSlot {...p} />);
|
||||
expect(wrapper.text()).toContain("initializing");
|
||||
});
|
||||
|
||||
it("updates tool slot", () => {
|
||||
const toolSlot = fakeToolSlot();
|
||||
const p = fakeProps();
|
||||
const wrapper = mount<AddToolSlot>(<AddToolSlot {...p} />);
|
||||
wrapper.instance().updateSlot(toolSlot)({ x: 123 });
|
||||
expect(edit).toHaveBeenCalledWith(toolSlot, { x: 123 });
|
||||
});
|
||||
|
||||
it("saves tool slot", () => {
|
||||
const wrapper = shallow<AddToolSlot>(<AddToolSlot {...fakeProps()} />);
|
||||
wrapper.find("SaveBtn").simulate("click");
|
||||
expect(save).toHaveBeenCalled();
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tools");
|
||||
});
|
||||
|
||||
it("saves on unmount", () => {
|
||||
const toolSlot = fakeToolSlot();
|
||||
toolSlot.specialStatus = SpecialStatus.DIRTY;
|
||||
const p = fakeProps();
|
||||
p.findToolSlot = () => toolSlot;
|
||||
const wrapper = mount<AddToolSlot>(<AddToolSlot {...p} />);
|
||||
window.confirm = () => true;
|
||||
wrapper.unmount();
|
||||
expect(save).toHaveBeenCalledWith("fakeUuid");
|
||||
});
|
||||
|
||||
it("destroys on unmount", () => {
|
||||
const toolSlot = fakeToolSlot();
|
||||
toolSlot.specialStatus = SpecialStatus.DIRTY;
|
||||
const p = fakeProps();
|
||||
p.findToolSlot = () => toolSlot;
|
||||
const wrapper = mount<AddToolSlot>(<AddToolSlot {...p} />);
|
||||
window.confirm = () => false;
|
||||
wrapper.unmount();
|
||||
expect(destroy).toHaveBeenCalledWith("fakeUuid", true);
|
||||
});
|
||||
|
||||
it("doesn't confirm save", () => {
|
||||
const toolSlot = fakeToolSlot();
|
||||
toolSlot.specialStatus = SpecialStatus.SAVED;
|
||||
const p = fakeProps();
|
||||
p.findToolSlot = () => toolSlot;
|
||||
const wrapper = mount<AddToolSlot>(<AddToolSlot {...p} />);
|
||||
window.confirm = jest.fn();
|
||||
wrapper.unmount();
|
||||
expect(destroy).not.toHaveBeenCalled();
|
||||
expect(save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("can't find tool without tool slot", () => {
|
||||
const p = fakeProps();
|
||||
p.findToolSlot = () => undefined;
|
||||
const wrapper = mount<AddToolSlot>(<AddToolSlot {...p} />);
|
||||
expect(wrapper.instance().tool).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const tool = fakeTool();
|
||||
tool.body.id = 1;
|
||||
const toolSlot = fakeToolSlot();
|
||||
const state = fakeState();
|
||||
state.resources = buildResourceIndex([tool, toolSlot]);
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.findTool(1)).toEqual(tool);
|
||||
expect(props.findToolSlot(toolSlot.uuid)).toEqual(toolSlot);
|
||||
});
|
||||
});
|
|
@ -1,5 +1,7 @@
|
|||
jest.mock("../../../api/crud", () => ({ initSave: jest.fn() }));
|
||||
|
||||
jest.mock("../../../history", () => ({ history: { push: jest.fn() } }));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import {
|
||||
|
@ -8,6 +10,7 @@ import {
|
|||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import { SaveBtn } from "../../../ui";
|
||||
import { initSave } from "../../../api/crud";
|
||||
import { history } from "../../../history";
|
||||
|
||||
describe("<AddTool />", () => {
|
||||
const fakeProps = (): AddToolProps => ({
|
||||
|
@ -33,6 +36,13 @@ describe("<AddTool />", () => {
|
|||
wrapper.find(SaveBtn).simulate("click");
|
||||
expect(initSave).toHaveBeenCalledWith("Tool", { name: "Foo" });
|
||||
});
|
||||
|
||||
it("adds stock tools", () => {
|
||||
const wrapper = mount(<AddTool {...fakeProps()} />);
|
||||
wrapper.find("button").last().simulate("click");
|
||||
expect(initSave).toHaveBeenCalledTimes(6);
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tools");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
jest.mock("../../../api/crud", () => ({
|
||||
edit: jest.fn(),
|
||||
save: jest.fn(),
|
||||
destroy: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockDevice = { moveAbsolute: jest.fn(() => Promise.resolve()) };
|
||||
jest.mock("../../../device", () => ({ getDevice: () => mockDevice }));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import {
|
||||
RawEditToolSlot as EditToolSlot, EditToolSlotProps, mapStateToProps
|
||||
} from "../edit_tool_slot";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import {
|
||||
fakeToolSlot, fakeTool
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
buildResourceIndex
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { destroy, edit, save } from "../../../api/crud";
|
||||
|
||||
describe("<EditToolSlot />", () => {
|
||||
const fakeProps = (): EditToolSlotProps => ({
|
||||
findToolSlot: jest.fn(),
|
||||
tools: [],
|
||||
findTool: jest.fn(),
|
||||
botPosition: { x: undefined, y: undefined, z: undefined },
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("redirects", () => {
|
||||
const wrapper = mount(<EditToolSlot {...fakeProps()} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("redirecting");
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const p = fakeProps();
|
||||
p.findToolSlot = () => fakeToolSlot();
|
||||
const wrapper = mount(<EditToolSlot {...p} />);
|
||||
["edit tool slot", "x (mm)", "y (mm)", "z (mm)", "toolnone",
|
||||
"change slot direction", "use current location", "gantry-mounted"
|
||||
].map(string => expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
});
|
||||
|
||||
it("updates tool slot", () => {
|
||||
const slot = fakeToolSlot();
|
||||
const wrapper = mount<EditToolSlot>(<EditToolSlot {...fakeProps()} />);
|
||||
wrapper.instance().updateSlot(slot)({ x: 123 });
|
||||
expect(edit).toHaveBeenCalledWith(slot, { x: 123 });
|
||||
expect(save).toHaveBeenCalledWith(slot.uuid);
|
||||
});
|
||||
|
||||
it("moves to tool slot", () => {
|
||||
const p = fakeProps();
|
||||
const toolSlot = fakeToolSlot();
|
||||
toolSlot.body.x = 1;
|
||||
toolSlot.body.y = 2;
|
||||
toolSlot.body.z = 3;
|
||||
p.findToolSlot = () => toolSlot;
|
||||
const wrapper = shallow(<EditToolSlot {...p} />);
|
||||
wrapper.find(".gray").last().simulate("click");
|
||||
expect(mockDevice.moveAbsolute).toHaveBeenCalledWith({ x: 1, y: 2, z: 3 });
|
||||
});
|
||||
|
||||
it("removes tool slot", () => {
|
||||
const p = fakeProps();
|
||||
const toolSlot = fakeToolSlot();
|
||||
p.findToolSlot = () => toolSlot;
|
||||
const wrapper = shallow(<EditToolSlot {...p} />);
|
||||
wrapper.find("button").last().simulate("click");
|
||||
expect(destroy).toHaveBeenCalledWith(toolSlot.uuid);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const tool = fakeTool();
|
||||
tool.body.id = 1;
|
||||
const toolSlot = fakeToolSlot();
|
||||
toolSlot.body.id = 1;
|
||||
const state = fakeState();
|
||||
state.resources = buildResourceIndex([tool, toolSlot]);
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.findTool(1)).toEqual(tool);
|
||||
expect(props.findToolSlot("1")).toEqual(toolSlot);
|
||||
});
|
||||
|
||||
it("doesn't find tool slot", () => {
|
||||
const state = fakeState();
|
||||
state.resources = buildResourceIndex([]);
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.findToolSlot("1")).toEqual(undefined);
|
||||
});
|
||||
});
|
|
@ -1,8 +1,13 @@
|
|||
jest.mock("../../../api/crud", () => ({ edit: jest.fn() }));
|
||||
jest.mock("../../../api/crud", () => ({
|
||||
edit: jest.fn(),
|
||||
save: jest.fn(),
|
||||
destroy: jest.fn(),
|
||||
}));
|
||||
|
||||
let mockPath = "/app/designer/tools/1";
|
||||
jest.mock("../../../history", () => ({
|
||||
history: { push: jest.fn() },
|
||||
getPathArray: () => "/app/designer/tools/1".split("/"),
|
||||
getPathArray: () => mockPath.split("/"),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
|
@ -17,9 +22,13 @@ import {
|
|||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { SaveBtn } from "../../../ui";
|
||||
import { history } from "../../../history";
|
||||
import { edit } from "../../../api/crud";
|
||||
import { edit, destroy } from "../../../api/crud";
|
||||
|
||||
describe("<EditTool />", () => {
|
||||
beforeEach(() => {
|
||||
mockPath = "/app/designer/tools/1";
|
||||
});
|
||||
|
||||
const fakeProps = (): EditToolProps => ({
|
||||
findTool: jest.fn(() => fakeTool()),
|
||||
dispatch: jest.fn(),
|
||||
|
@ -27,14 +36,26 @@ describe("<EditTool />", () => {
|
|||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<EditTool {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("Edit Foo");
|
||||
expect(wrapper.text()).toContain("Edit tool");
|
||||
});
|
||||
|
||||
it("handles missing tool name", () => {
|
||||
const p = fakeProps();
|
||||
const tool = fakeTool();
|
||||
tool.body.name = undefined;
|
||||
p.findTool = () => tool;
|
||||
const wrapper = mount<EditTool>(<EditTool {...p} />);
|
||||
expect(wrapper.state().toolName).toEqual("");
|
||||
});
|
||||
|
||||
it("redirects", () => {
|
||||
mockPath = "/app/designer/tools/";
|
||||
const p = fakeProps();
|
||||
p.findTool = jest.fn(() => undefined);
|
||||
const wrapper = mount(<EditTool {...p} />);
|
||||
const wrapper = mount<EditTool>(<EditTool {...p} />);
|
||||
expect(wrapper.instance().stringyID).toEqual("");
|
||||
expect(wrapper.text()).toContain("Redirecting...");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tools");
|
||||
});
|
||||
|
||||
it("edits tool name", () => {
|
||||
|
@ -50,6 +71,15 @@ describe("<EditTool />", () => {
|
|||
expect(edit).toHaveBeenCalledWith(expect.any(Object), { name: "Foo" });
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tools");
|
||||
});
|
||||
|
||||
it("removes tool", () => {
|
||||
const p = fakeProps();
|
||||
const tool = fakeTool();
|
||||
p.findTool = () => tool;
|
||||
const wrapper = shallow(<EditTool {...p} />);
|
||||
wrapper.find("button").last().simulate("click");
|
||||
expect(destroy).toHaveBeenCalledWith(tool.uuid);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
|
|
|
@ -3,23 +3,42 @@ jest.mock("../../../history", () => ({
|
|||
getPathArray: () => "/app/designer/tools".split("/"),
|
||||
}));
|
||||
|
||||
jest.mock("../../../api/crud", () => ({
|
||||
edit: jest.fn(),
|
||||
save: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockDevice = { readPin: jest.fn(() => Promise.resolve()) };
|
||||
jest.mock("../../../device", () => ({ getDevice: () => mockDevice }));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { RawTools as Tools, ToolsProps, mapStateToProps } from "../index";
|
||||
import {
|
||||
fakeTool, fakeToolSlot
|
||||
fakeTool, fakeToolSlot, fakeSensor
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import { history } from "../../../history";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import {
|
||||
buildResourceIndex
|
||||
buildResourceIndex, fakeDevice
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||
import { error } from "../../../toast/toast";
|
||||
import { Content, Actions } from "../../../constants";
|
||||
import { edit, save } from "../../../api/crud";
|
||||
import { ToolSelection } from "../tool_slot_edit_components";
|
||||
|
||||
describe("<Tools />", () => {
|
||||
const fakeProps = (): ToolsProps => ({
|
||||
tools: [],
|
||||
toolSlots: [],
|
||||
dispatch: jest.fn(),
|
||||
findTool: () => fakeTool(),
|
||||
device: fakeDevice(),
|
||||
sensors: [fakeSensor()],
|
||||
bot,
|
||||
botToMqttStatus: "down",
|
||||
hoveredToolSlot: undefined,
|
||||
});
|
||||
|
||||
it("renders with no tools", () => {
|
||||
|
@ -29,14 +48,18 @@ describe("<Tools />", () => {
|
|||
|
||||
it("renders with tools", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool()];
|
||||
p.tools = [fakeTool(), fakeTool()];
|
||||
p.tools[0].body.id = 1;
|
||||
p.tools[0].body.status = "inactive";
|
||||
p.tools[0].body.name = undefined;
|
||||
p.tools[1].body.id = 2;
|
||||
p.tools[1].body.name = "my tool";
|
||||
p.toolSlots = [fakeToolSlot()];
|
||||
p.toolSlots[0].body.tool_id = 2;
|
||||
p.toolSlots[0].body.x = 1;
|
||||
const wrapper = mount(<Tools {...p} />);
|
||||
expect(wrapper.text()).toContain("Foo");
|
||||
expect(wrapper.text()).toContain("(1, 0, 0)");
|
||||
["foo", "my tool", "unnamed tool", "(1, 0, 0)", "unknown"].map(string =>
|
||||
expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
});
|
||||
|
||||
it("navigates to tool", () => {
|
||||
|
@ -45,14 +68,33 @@ describe("<Tools />", () => {
|
|||
p.tools[0].body.id = 1;
|
||||
p.tools[0].body.status = "inactive";
|
||||
p.toolSlots = [fakeToolSlot()];
|
||||
p.toolSlots[0].body.tool_id = 2;
|
||||
p.toolSlots[0].body.id = 2;
|
||||
p.toolSlots[0].body.tool_id = 3;
|
||||
const wrapper = mount(<Tools {...p} />);
|
||||
wrapper.find("p").first().simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tools/2");
|
||||
wrapper.find("p").last().simulate("click");
|
||||
wrapper.find(".tool-slot-search-item").first().simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tool-slots/2");
|
||||
wrapper.find(".tool-search-item").first().simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tools/1");
|
||||
});
|
||||
|
||||
it("hovers tool", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool()];
|
||||
p.tools[0].body.id = 1;
|
||||
p.toolSlots = [fakeToolSlot()];
|
||||
p.toolSlots[0].body.id = 1;
|
||||
p.hoveredToolSlot = p.toolSlots[0].uuid;
|
||||
const wrapper = mount(<Tools {...p} />);
|
||||
wrapper.find(".tool-slot-search-item").simulate("mouseEnter");
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.HOVER_TOOL_SLOT, payload: p.toolSlots[0].uuid
|
||||
});
|
||||
wrapper.find(".tool-slot-search-item").simulate("mouseLeave");
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.HOVER_TOOL_SLOT, payload: undefined
|
||||
});
|
||||
});
|
||||
|
||||
it("changes search term", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool(), fakeTool()];
|
||||
|
@ -73,14 +115,67 @@ describe("<Tools />", () => {
|
|||
wrapper.setState({ searchTerm: "0" });
|
||||
expect(wrapper.text()).not.toContain("tool 1");
|
||||
});
|
||||
|
||||
it("changes mounted tool", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool()];
|
||||
const wrapper = mount<Tools>(<Tools {...p} />);
|
||||
shallow(wrapper.instance().MountedToolInfo()).find(ToolSelection)
|
||||
.simulate("change", { tool_id: 123 });
|
||||
expect(edit).toHaveBeenCalledWith(p.device, { mounted_tool_id: 123 });
|
||||
expect(save).toHaveBeenCalledWith(p.device.uuid);
|
||||
});
|
||||
|
||||
it("displays tool verification result: disconnected", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool()];
|
||||
p.sensors[0].body.label = "tool verification";
|
||||
p.sensors[0].body.pin = undefined;
|
||||
p.bot.hardware.pins = { "63": { value: 1, mode: 0 } };
|
||||
const wrapper = mount(<Tools {...p} />);
|
||||
expect(wrapper.text()).toContain("disconnected");
|
||||
});
|
||||
|
||||
it("displays tool verification result: connected", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool()];
|
||||
p.sensors[0].body.label = "tool verification";
|
||||
p.sensors[0].body.pin = 64;
|
||||
p.bot.hardware.pins = { "64": { value: 0, mode: 0 } };
|
||||
const wrapper = mount(<Tools {...p} />);
|
||||
expect(wrapper.text()).toContain("connected");
|
||||
});
|
||||
|
||||
it("verifies tool attachment", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool()];
|
||||
p.bot.hardware.informational_settings.sync_status = "synced";
|
||||
p.botToMqttStatus = "up";
|
||||
const wrapper = mount(<Tools {...p} />);
|
||||
wrapper.find(".yellow").first().simulate("click");
|
||||
expect(mockDevice.readPin).toHaveBeenCalledWith({
|
||||
label: "pin63", pin_mode: 0, pin_number: 63
|
||||
});
|
||||
});
|
||||
|
||||
it("can't verify tool attachment when offline", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool()];
|
||||
p.botToMqttStatus = "down";
|
||||
const wrapper = mount(<Tools {...p} />);
|
||||
wrapper.find(".yellow").first().simulate("click");
|
||||
expect(mockDevice.readPin).not.toHaveBeenCalled();
|
||||
expect(error).toHaveBeenCalledWith(Content.NOT_AVAILABLE_WHEN_OFFLINE);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const state = fakeState();
|
||||
const tool = fakeTool();
|
||||
state.resources = buildResourceIndex([tool]);
|
||||
tool.body.id = 1;
|
||||
state.resources = buildResourceIndex([tool, fakeDevice()]);
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.tools).toEqual([tool]);
|
||||
expect(props.findTool(tool.body.id)).toEqual(tool);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
import * as React from "react";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import {
|
||||
GantryMountedInput, GantryMountedInputProps,
|
||||
UseCurrentLocationInputRow, UseCurrentLocationInputRowProps,
|
||||
SlotDirectionInputRow, SlotDirectionInputRowProps,
|
||||
ToolInputRow, ToolInputRowProps,
|
||||
SlotLocationInputRow, SlotLocationInputRowProps,
|
||||
ToolSelection, ToolSelectionProps,
|
||||
} from "../tool_slot_edit_components";
|
||||
import { fakeTool } from "../../../__test_support__/fake_state/resources";
|
||||
import { FBSelect } from "../../../ui";
|
||||
|
||||
describe("<GantryMountedInput />", () => {
|
||||
const fakeProps = (): GantryMountedInputProps => ({
|
||||
gantryMounted: false,
|
||||
onChange: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = shallow(<GantryMountedInput {...fakeProps()} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("gantry-mounted");
|
||||
});
|
||||
|
||||
it("changes value", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<GantryMountedInput {...p} />);
|
||||
wrapper.find("input").simulate("change");
|
||||
expect(p.onChange).toHaveBeenCalledWith({ gantry_mounted: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe("<UseCurrentLocationInputRow />", () => {
|
||||
const fakeProps = (): UseCurrentLocationInputRowProps => ({
|
||||
botPosition: { x: undefined, y: undefined, z: undefined },
|
||||
onChange: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<UseCurrentLocationInputRow {...fakeProps()} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("use current location");
|
||||
});
|
||||
|
||||
it("doesn't change value", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<UseCurrentLocationInputRow {...p} />);
|
||||
wrapper.find("button").simulate("click");
|
||||
expect(p.onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("changes value", () => {
|
||||
const p = fakeProps();
|
||||
p.botPosition = { x: 0, y: 1, z: 2 };
|
||||
const wrapper = shallow(<UseCurrentLocationInputRow {...p} />);
|
||||
wrapper.find("button").simulate("click");
|
||||
expect(p.onChange).toHaveBeenCalledWith(p.botPosition);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<SlotDirectionInputRow />", () => {
|
||||
const fakeProps = (): SlotDirectionInputRowProps => ({
|
||||
toolPulloutDirection: 0,
|
||||
onChange: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<SlotDirectionInputRow {...fakeProps()} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("change slot direction");
|
||||
});
|
||||
|
||||
it("changes value by click", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<SlotDirectionInputRow {...p} />);
|
||||
wrapper.find("i").first().simulate("click");
|
||||
expect(p.onChange).toHaveBeenCalledWith({ pullout_direction: 1 });
|
||||
});
|
||||
|
||||
it("changes value by selection", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<SlotDirectionInputRow {...p} />);
|
||||
wrapper.find("FBSelect").simulate("change", { label: "", value: 1 });
|
||||
expect(p.onChange).toHaveBeenCalledWith({ pullout_direction: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("<ToolSelection />", () => {
|
||||
const fakeProps = (): ToolSelectionProps => ({
|
||||
tools: [],
|
||||
selectedTool: undefined,
|
||||
onChange: jest.fn(),
|
||||
filterSelectedTool: false,
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<ToolSelection {...fakeProps()} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("none");
|
||||
});
|
||||
|
||||
it("handles missing tool data", () => {
|
||||
const p = fakeProps();
|
||||
const tool = fakeTool();
|
||||
tool.body.name = undefined;
|
||||
tool.body.id = undefined;
|
||||
p.tools = [tool];
|
||||
const wrapper = shallow(<ToolSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual([]);
|
||||
});
|
||||
|
||||
it("handles missing selected tool data", () => {
|
||||
const p = fakeProps();
|
||||
const tool = fakeTool();
|
||||
tool.body.name = undefined;
|
||||
p.selectedTool = tool;
|
||||
const wrapper = shallow(<ToolSelection {...p} />);
|
||||
expect(wrapper.find(FBSelect).props().selectedItem)
|
||||
.toEqual(expect.objectContaining({ label: "untitled" }));
|
||||
});
|
||||
|
||||
it("shows selected tool", () => {
|
||||
const p = fakeProps();
|
||||
p.selectedTool = fakeTool();
|
||||
const wrapper = mount(<ToolSelection {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("foo");
|
||||
});
|
||||
|
||||
it("changes value", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<ToolSelection {...p} />);
|
||||
wrapper.find("FBSelect").simulate("change", { label: "", value: 1 });
|
||||
expect(p.onChange).toHaveBeenCalledWith({ tool_id: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("<ToolInputRow />", () => {
|
||||
const fakeProps = (): ToolInputRowProps => ({
|
||||
tools: [],
|
||||
selectedTool: undefined,
|
||||
onChange: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<ToolInputRow {...fakeProps()} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("tool");
|
||||
});
|
||||
|
||||
it("shows selected tool", () => {
|
||||
const p = fakeProps();
|
||||
p.selectedTool = fakeTool();
|
||||
const wrapper = mount(<ToolInputRow {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("foo");
|
||||
});
|
||||
});
|
||||
|
||||
describe("<SlotLocationInputRow />", () => {
|
||||
const fakeProps = (): SlotLocationInputRowProps => ({
|
||||
slotLocation: { x: 0, y: 0, z: 0 },
|
||||
gantryMounted: false,
|
||||
onChange: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<SlotLocationInputRow {...fakeProps()} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("x (mm)y (mm)z (mm)");
|
||||
expect(wrapper.find("input").first().props().value).toEqual(0);
|
||||
});
|
||||
|
||||
it("renders gantry-mounted slot", () => {
|
||||
const p = fakeProps();
|
||||
p.gantryMounted = true;
|
||||
const wrapper = mount(<SlotLocationInputRow {...p} />);
|
||||
expect(wrapper.find("input").first().props().value).toEqual("Gantry");
|
||||
});
|
||||
|
||||
it("changes value", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<SlotLocationInputRow {...p} />);
|
||||
wrapper.find("BlurableInput").at(0).simulate("commit", {
|
||||
currentTarget: { value: 1 }
|
||||
});
|
||||
wrapper.find("BlurableInput").at(1).simulate("commit", {
|
||||
currentTarget: { value: 2 }
|
||||
});
|
||||
wrapper.find("BlurableInput").at(2).simulate("commit", {
|
||||
currentTarget: { value: 3 }
|
||||
});
|
||||
expect(p.onChange).toHaveBeenCalledWith({ x: 1 });
|
||||
expect(p.onChange).toHaveBeenCalledWith({ y: 2 });
|
||||
expect(p.onChange).toHaveBeenCalledWith({ z: 3 });
|
||||
});
|
||||
});
|
|
@ -9,6 +9,7 @@ import { SaveBtn } from "../../ui";
|
|||
import { SpecialStatus } from "farmbot";
|
||||
import { initSave } from "../../api/crud";
|
||||
import { Panel } from "../panel_header";
|
||||
import { history } from "../../history";
|
||||
|
||||
export interface AddToolProps {
|
||||
dispatch: Function;
|
||||
|
@ -24,21 +25,60 @@ export const mapStateToProps = (props: Everything): AddToolProps => ({
|
|||
|
||||
export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
||||
state: AddToolState = { toolName: "" };
|
||||
|
||||
newTool = (name: string) => {
|
||||
this.props.dispatch(initSave("Tool", { name }));
|
||||
};
|
||||
|
||||
save = () => {
|
||||
this.newTool(this.state.toolName);
|
||||
history.push("/app/designer/tools");
|
||||
}
|
||||
|
||||
get stockToolNames() {
|
||||
return [
|
||||
t("Seeder"),
|
||||
t("Watering Nozzle"),
|
||||
t("Weeder"),
|
||||
t("Soil Sensor"),
|
||||
t("Seed Bin"),
|
||||
t("Seed Tray"),
|
||||
];
|
||||
}
|
||||
|
||||
AddStockTools = () =>
|
||||
<div className="add-stock-tools">
|
||||
<label>{t("Add stock tools")}</label>
|
||||
<ul>
|
||||
{this.stockToolNames.map(n => <li key={n}>{n}</li>)}
|
||||
</ul>
|
||||
<button
|
||||
className="fb-button green"
|
||||
onClick={() => {
|
||||
this.stockToolNames.map(n => this.newTool(n));
|
||||
history.push("/app/designer/tools");
|
||||
}}>
|
||||
<i className="fa fa-plus" />
|
||||
{t("Stock Tools")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
render() {
|
||||
return <DesignerPanel panelName={"tool"} panel={Panel.Tools}>
|
||||
const panelName = "add-tool";
|
||||
return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"tool"}
|
||||
panelName={panelName}
|
||||
title={t("Add new tool")}
|
||||
backTo={"/app/designer/tools"}
|
||||
panel={Panel.Tools} />
|
||||
<DesignerPanelContent panelName={"tools"}>
|
||||
<label>{t("Tool Name")}</label>
|
||||
<input
|
||||
onChange={e => this.setState({ toolName: e.currentTarget.value })} />
|
||||
<SaveBtn
|
||||
onClick={() =>
|
||||
this.props.dispatch(initSave("Tool", { name: this.state.toolName }))}
|
||||
status={SpecialStatus.DIRTY} />
|
||||
<DesignerPanelContent panelName={panelName}>
|
||||
<div className="add-new-tool">
|
||||
<label>{t("Tool Name")}</label>
|
||||
<input onChange={e =>
|
||||
this.setState({ toolName: e.currentTarget.value })} />
|
||||
<SaveBtn onClick={this.save} status={SpecialStatus.DIRTY} />
|
||||
</div>
|
||||
<this.AddStockTools />
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelContent, DesignerPanelHeader
|
||||
} from "../designer_panel";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { SaveBtn } from "../../ui";
|
||||
import { SpecialStatus, TaggedTool, TaggedToolSlotPointer } from "farmbot";
|
||||
import { init, save, edit, destroy } from "../../api/crud";
|
||||
import { Panel } from "../panel_header";
|
||||
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
|
||||
import {
|
||||
selectAllTools, maybeFindToolById, maybeGetToolSlot
|
||||
} from "../../resources/selectors";
|
||||
import { BotPosition } from "../../devices/interfaces";
|
||||
import { validBotLocationData } from "../../util";
|
||||
import { history } from "../../history";
|
||||
import { SlotEditRows } from "./tool_slot_edit_components";
|
||||
import { UUID } from "../../resources/interfaces";
|
||||
|
||||
export interface AddToolSlotProps {
|
||||
tools: TaggedTool[];
|
||||
dispatch: Function;
|
||||
botPosition: BotPosition;
|
||||
findTool(id: number): TaggedTool | undefined;
|
||||
findToolSlot(uuid: UUID | undefined): TaggedToolSlotPointer | undefined;
|
||||
}
|
||||
|
||||
export interface AddToolSlotState {
|
||||
uuid: UUID | undefined;
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): AddToolSlotProps => ({
|
||||
tools: selectAllTools(props.resources.index),
|
||||
dispatch: props.dispatch,
|
||||
botPosition: validBotLocationData(props.bot.hardware.location_data).position,
|
||||
findTool: (id: number) => maybeFindToolById(props.resources.index, id),
|
||||
findToolSlot: (uuid: UUID | undefined) =>
|
||||
maybeGetToolSlot(props.resources.index, uuid),
|
||||
});
|
||||
|
||||
export class RawAddToolSlot
|
||||
extends React.Component<AddToolSlotProps, AddToolSlotState> {
|
||||
state: AddToolSlotState = { uuid: undefined };
|
||||
|
||||
componentDidMount() {
|
||||
const action = init("Point", {
|
||||
pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {},
|
||||
x: 0, y: 0, z: 0, tool_id: undefined,
|
||||
pullout_direction: ToolPulloutDirection.NONE, gantry_mounted: false
|
||||
});
|
||||
this.setState({ uuid: action.payload.uuid });
|
||||
this.props.dispatch(action);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.state.uuid && this.toolSlot
|
||||
&& this.toolSlot.specialStatus == SpecialStatus.DIRTY) {
|
||||
confirm(t("Save new tool?"))
|
||||
? this.props.dispatch(save(this.state.uuid))
|
||||
: this.props.dispatch(destroy(this.state.uuid, true));
|
||||
}
|
||||
}
|
||||
|
||||
get toolSlot() {
|
||||
return this.props.findToolSlot(this.state.uuid);
|
||||
}
|
||||
|
||||
get tool() {
|
||||
return this.toolSlot ?
|
||||
this.props.findTool(this.toolSlot.body.tool_id || 0) : undefined;
|
||||
}
|
||||
|
||||
updateSlot = (toolSlot: TaggedToolSlotPointer) =>
|
||||
(update: Partial<TaggedToolSlotPointer["body"]>) =>
|
||||
this.props.dispatch(edit(toolSlot, update));
|
||||
|
||||
save = () => {
|
||||
this.state.uuid && this.props.dispatch(save(this.state.uuid));
|
||||
history.push("/app/designer/tools");
|
||||
}
|
||||
|
||||
render() {
|
||||
const panelName = "add-tool-slot";
|
||||
return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
|
||||
<DesignerPanelHeader
|
||||
panelName={panelName}
|
||||
title={t("Add new tool slot")}
|
||||
backTo={"/app/designer/tools"}
|
||||
panel={Panel.Tools} />
|
||||
<DesignerPanelContent panelName={panelName}>
|
||||
{this.toolSlot
|
||||
? <SlotEditRows
|
||||
toolSlot={this.toolSlot}
|
||||
tools={this.props.tools}
|
||||
tool={this.tool}
|
||||
botPosition={this.props.botPosition}
|
||||
updateToolSlot={this.updateSlot(this.toolSlot)} />
|
||||
: "initializing"}
|
||||
<SaveBtn onClick={this.save} status={SpecialStatus.DIRTY} />
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel >;
|
||||
}
|
||||
}
|
||||
|
||||
export const AddToolSlot = connect(mapStateToProps)(RawAddToolSlot);
|
|
@ -9,7 +9,7 @@ import { getPathArray } from "../../history";
|
|||
import { TaggedTool, SpecialStatus } from "farmbot";
|
||||
import { maybeFindToolById } from "../../resources/selectors";
|
||||
import { SaveBtn } from "../../ui";
|
||||
import { edit } from "../../api/crud";
|
||||
import { edit, destroy } from "../../api/crud";
|
||||
import { history } from "../../history";
|
||||
import { Panel } from "../panel_header";
|
||||
|
||||
|
@ -40,26 +40,35 @@ export class RawEditTool extends React.Component<EditToolProps, EditToolState> {
|
|||
return <span>{t("Redirecting...")}</span>;
|
||||
}
|
||||
|
||||
default = (tool: TaggedTool) =>
|
||||
<DesignerPanel panelName={"tool"} panel={Panel.Tools}>
|
||||
default = (tool: TaggedTool) => {
|
||||
const { dispatch } = this.props;
|
||||
const { toolName } = this.state;
|
||||
const panelName = "edit-tool";
|
||||
return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"tool"}
|
||||
title={`${t("Edit")} ${tool.body.name}`}
|
||||
panelName={panelName}
|
||||
title={t("Edit tool")}
|
||||
backTo={"/app/designer/tools"}
|
||||
panel={Panel.Tools} />
|
||||
<DesignerPanelContent panelName={"tools"}>
|
||||
<DesignerPanelContent panelName={panelName}>
|
||||
<label>{t("Tool Name")}</label>
|
||||
<input
|
||||
value={this.state.toolName}
|
||||
value={toolName}
|
||||
onChange={e => this.setState({ toolName: e.currentTarget.value })} />
|
||||
<SaveBtn
|
||||
onClick={() => {
|
||||
this.props.dispatch(edit(tool, { name: this.state.toolName }));
|
||||
dispatch(edit(tool, { name: toolName }));
|
||||
history.push("/app/designer/tools");
|
||||
}}
|
||||
status={SpecialStatus.DIRTY} />
|
||||
<button
|
||||
className="fb-button red no-float"
|
||||
onClick={() => dispatch(destroy(tool.uuid))}>
|
||||
{t("Delete")}
|
||||
</button>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.tool ? this.default(this.tool) : this.fallback();
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelContent, DesignerPanelHeader
|
||||
} from "../designer_panel";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { getPathArray } from "../../history";
|
||||
import { TaggedToolSlotPointer, TaggedTool } from "farmbot";
|
||||
import { edit, save, destroy } from "../../api/crud";
|
||||
import { history } from "../../history";
|
||||
import { Panel } from "../panel_header";
|
||||
import {
|
||||
maybeFindToolSlotById, selectAllTools, maybeFindToolById
|
||||
} from "../../resources/selectors";
|
||||
import { BotPosition } from "../../devices/interfaces";
|
||||
import { validBotLocationData } from "../../util";
|
||||
import { SlotEditRows } from "./tool_slot_edit_components";
|
||||
import { moveAbs } from "../../devices/actions";
|
||||
|
||||
export interface EditToolSlotProps {
|
||||
findToolSlot(id: string): TaggedToolSlotPointer | undefined;
|
||||
tools: TaggedTool[];
|
||||
findTool(id: number): TaggedTool | undefined;
|
||||
dispatch: Function;
|
||||
botPosition: BotPosition;
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): EditToolSlotProps => ({
|
||||
findToolSlot: (id: string) =>
|
||||
maybeFindToolSlotById(props.resources.index, parseInt(id)),
|
||||
tools: selectAllTools(props.resources.index),
|
||||
findTool: (id: number) => maybeFindToolById(props.resources.index, id),
|
||||
dispatch: props.dispatch,
|
||||
botPosition: validBotLocationData(props.bot.hardware.location_data).position,
|
||||
});
|
||||
|
||||
export class RawEditToolSlot extends React.Component<EditToolSlotProps> {
|
||||
|
||||
get stringyID() { return getPathArray()[4] || ""; }
|
||||
get toolSlot() { return this.props.findToolSlot(this.stringyID); }
|
||||
get tool() {
|
||||
return this.toolSlot && this.props.findTool(this.toolSlot.body.tool_id || 0);
|
||||
}
|
||||
|
||||
fallback = () => {
|
||||
history.push("/app/designer/tools");
|
||||
return <span>{t("Redirecting...")}</span>;
|
||||
}
|
||||
|
||||
updateSlot = (toolSlot: TaggedToolSlotPointer) =>
|
||||
(update: Partial<TaggedToolSlotPointer["body"]>) => {
|
||||
this.props.dispatch(edit(toolSlot, update));
|
||||
this.props.dispatch(save(toolSlot.uuid));
|
||||
}
|
||||
|
||||
default = (toolSlot: TaggedToolSlotPointer) => {
|
||||
const panelName = "edit-tool-slot";
|
||||
return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
|
||||
<DesignerPanelHeader
|
||||
panelName={panelName}
|
||||
title={t("Edit tool slot")}
|
||||
backTo={"/app/designer/tools"}
|
||||
panel={Panel.Tools} />
|
||||
<DesignerPanelContent panelName={panelName}>
|
||||
<SlotEditRows
|
||||
toolSlot={toolSlot}
|
||||
tools={this.props.tools}
|
||||
tool={this.tool}
|
||||
botPosition={this.props.botPosition}
|
||||
updateToolSlot={this.updateSlot(toolSlot)} />
|
||||
<button
|
||||
className="fb-button gray no-float"
|
||||
onClick={() => {
|
||||
const { x, y, z } = toolSlot.body;
|
||||
moveAbs({ x, y, z });
|
||||
}}>
|
||||
{t("Move FarmBot to tool slot location")}
|
||||
</button>
|
||||
<button
|
||||
className="fb-button red no-float"
|
||||
onClick={() => this.props.dispatch(destroy(toolSlot.uuid))}>
|
||||
{t("Delete")}
|
||||
</button>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.toolSlot ? this.default(this.toolSlot) : this.fallback();
|
||||
}
|
||||
}
|
||||
|
||||
export const EditToolSlot = connect(mapStateToProps)(RawEditToolSlot);
|
|
@ -4,24 +4,44 @@ import {
|
|||
DesignerPanel, DesignerPanelTop, DesignerPanelContent
|
||||
} from "../designer_panel";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { DesignerNavTabs, Panel } from "../panel_header";
|
||||
import { DesignerNavTabs, Panel, TAB_COLOR } from "../panel_header";
|
||||
import {
|
||||
EmptyStateWrapper, EmptyStateGraphic
|
||||
} from "../../ui/empty_state_wrapper";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { TaggedTool, TaggedToolSlotPointer } from "farmbot";
|
||||
import {
|
||||
selectAllTools, selectAllToolSlotPointers
|
||||
TaggedTool, TaggedToolSlotPointer, TaggedDevice, TaggedSensor,
|
||||
} from "farmbot";
|
||||
import {
|
||||
selectAllTools, selectAllToolSlotPointers, getDeviceAccountSettings,
|
||||
maybeFindToolById,
|
||||
selectAllSensors
|
||||
} from "../../resources/selectors";
|
||||
import { Content } from "../../constants";
|
||||
import { history } from "../../history";
|
||||
import { Row, Col } from "../../ui";
|
||||
import { Row, Col, Help } from "../../ui";
|
||||
import { botPositionLabel } from "../map/layers/farmbot/bot_position_label";
|
||||
import { Link } from "../../link";
|
||||
import { edit, save } from "../../api/crud";
|
||||
import { readPin } from "../../devices/actions";
|
||||
import { isBotOnline } from "../../devices/must_be_online";
|
||||
import { BotState } from "../../devices/interfaces";
|
||||
import { NetworkState } from "../../connectivity/interfaces";
|
||||
import { getStatus } from "../../connectivity/reducer_support";
|
||||
import { setToolHover } from "../map/layers/tool_slots/tool_graphics";
|
||||
import { ToolSelection } from "./tool_slot_edit_components";
|
||||
import { error } from "../../toast/toast";
|
||||
|
||||
export interface ToolsProps {
|
||||
tools: TaggedTool[];
|
||||
toolSlots: TaggedToolSlotPointer[];
|
||||
dispatch: Function;
|
||||
findTool(id: number): TaggedTool | undefined;
|
||||
device: TaggedDevice;
|
||||
sensors: TaggedSensor[];
|
||||
bot: BotState;
|
||||
botToMqttStatus: NetworkState;
|
||||
hoveredToolSlot: string | undefined;
|
||||
}
|
||||
|
||||
export interface ToolsState {
|
||||
|
@ -32,8 +52,22 @@ export const mapStateToProps = (props: Everything): ToolsProps => ({
|
|||
tools: selectAllTools(props.resources.index),
|
||||
toolSlots: selectAllToolSlotPointers(props.resources.index),
|
||||
dispatch: props.dispatch,
|
||||
findTool: (id: number) => maybeFindToolById(props.resources.index, id),
|
||||
device: getDeviceAccountSettings(props.resources.index),
|
||||
sensors: selectAllSensors(props.resources.index),
|
||||
bot: props.bot,
|
||||
botToMqttStatus: getStatus(props.bot.connectivity.uptime["bot.mqtt"]),
|
||||
hoveredToolSlot: props.resources.consumers.farm_designer.hoveredToolSlot,
|
||||
});
|
||||
|
||||
const toolStatus = (value: number | undefined): string => {
|
||||
switch (value) {
|
||||
case 1: return t("disconnected");
|
||||
case 0: return t("connected");
|
||||
default: return t("unknown");
|
||||
}
|
||||
};
|
||||
|
||||
export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
||||
state: ToolsState = { searchTerm: "" };
|
||||
|
||||
|
@ -46,6 +80,99 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
|||
return foundTool ? foundTool.body.name : undefined;
|
||||
};
|
||||
|
||||
get mountedToolId() { return this.props.device.body.mounted_tool_id; }
|
||||
|
||||
get mountedTool() { return this.props.findTool(this.mountedToolId || 0); }
|
||||
|
||||
get toolVerificationPin() {
|
||||
const toolVerificationSensor =
|
||||
this.props.sensors.filter(sensor => sensor.body.label.toLowerCase()
|
||||
.includes("tool verification"))[0] as TaggedSensor | undefined;
|
||||
return toolVerificationSensor ? toolVerificationSensor.body.pin || 63 : 63;
|
||||
}
|
||||
|
||||
get pins() { return this.props.bot.hardware.pins; }
|
||||
|
||||
get toolVerificationValue() {
|
||||
const pinData = this.pins[this.toolVerificationPin];
|
||||
return pinData ? pinData.value : undefined;
|
||||
}
|
||||
|
||||
get arduinoBusy() {
|
||||
return !!this.props.bot.hardware.informational_settings.busy;
|
||||
}
|
||||
|
||||
get botOnline() {
|
||||
return isBotOnline(
|
||||
this.props.bot.hardware.informational_settings.sync_status,
|
||||
this.props.botToMqttStatus);
|
||||
}
|
||||
|
||||
MountedToolInfo = () =>
|
||||
<div className="mounted-tool">
|
||||
<div className="mounted-tool-header">
|
||||
<label>{t("mounted tool")}</label>
|
||||
<Help text={Content.MOUNTED_TOOL} />
|
||||
</div>
|
||||
<ToolSelection
|
||||
tools={this.props.tools}
|
||||
selectedTool={this.mountedTool}
|
||||
onChange={({ tool_id }) => {
|
||||
this.props.dispatch(edit(this.props.device,
|
||||
{ mounted_tool_id: tool_id }));
|
||||
this.props.dispatch(save(this.props.device.uuid));
|
||||
}}
|
||||
filterSelectedTool={true} />
|
||||
<div className="tool-verification-status">
|
||||
<p>{t("status")}: {toolStatus(this.toolVerificationValue)}</p>
|
||||
<button
|
||||
className={`fb-button yellow ${this.botOnline ? "" : "pseudo-disabled"}`}
|
||||
disabled={this.arduinoBusy}
|
||||
title={this.botOnline ? "" : t(Content.NOT_AVAILABLE_WHEN_OFFLINE)}
|
||||
onClick={() => this.botOnline
|
||||
? readPin(this.toolVerificationPin,
|
||||
`pin${this.toolVerificationPin}`, 0)
|
||||
: error(t(Content.NOT_AVAILABLE_WHEN_OFFLINE))}>
|
||||
{t("verify")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
ToolSlots = () =>
|
||||
<div className="tool-slots">
|
||||
<div className="tool-slots-header">
|
||||
<label>{t("tool slots")}</label>
|
||||
<Link to={"/app/designer/tool-slots/add"}>
|
||||
<div className={`fb-button panel-${TAB_COLOR[Panel.Tools]}`}>
|
||||
<i className="fa fa-plus" title={t("Add tool slot")} />
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
{this.props.toolSlots
|
||||
.filter(p => (this.getToolName(p.body.tool_id) || "").toLowerCase()
|
||||
.includes(this.state.searchTerm.toLowerCase()))
|
||||
.map(toolSlot =>
|
||||
<ToolSlotInventoryItem key={toolSlot.uuid}
|
||||
hovered={toolSlot.uuid === this.props.hoveredToolSlot}
|
||||
dispatch={this.props.dispatch}
|
||||
toolSlot={toolSlot}
|
||||
getToolName={this.getToolName} />)}
|
||||
</div>
|
||||
|
||||
InactiveTools = () =>
|
||||
<div className="inactive-tools">
|
||||
<label>{t("inactive tools")}</label>
|
||||
{this.props.tools
|
||||
.filter(tool => !tool.body.name ||
|
||||
tool.body.name && tool.body.name.toLowerCase()
|
||||
.includes(this.state.searchTerm.toLowerCase()))
|
||||
.filter(tool => tool.body.status === "inactive")
|
||||
.map(tool =>
|
||||
<ToolInventoryItem key={tool.uuid}
|
||||
toolId={tool.body.id}
|
||||
toolName={tool.body.name || t("Unnamed tool")} />)}
|
||||
</div>
|
||||
|
||||
render() {
|
||||
const panelName = "tools";
|
||||
return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
|
||||
|
@ -64,26 +191,9 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
|||
title={t("Add a tool")}
|
||||
text={Content.NO_TOOLS}
|
||||
colorScheme={"tools"}>
|
||||
<div>
|
||||
<label>{t("tool slots")}</label>
|
||||
{this.props.toolSlots
|
||||
.filter(p => (this.getToolName(p.body.tool_id) || "").toLowerCase()
|
||||
.includes(this.state.searchTerm.toLowerCase()))
|
||||
.map(toolSlot =>
|
||||
<ToolSlotInventoryItem key={toolSlot.uuid}
|
||||
toolSlot={toolSlot}
|
||||
getToolName={this.getToolName} />)}
|
||||
<br />
|
||||
<label>{t("inactive tools")}</label>
|
||||
{this.props.tools
|
||||
.filter(tool => tool.body.name && tool.body.name.toLowerCase()
|
||||
.includes(this.state.searchTerm.toLowerCase()))
|
||||
.filter(tool => tool.body.status === "inactive")
|
||||
.map(tool =>
|
||||
<ToolInventoryItem key={tool.uuid}
|
||||
toolId={tool.body.id}
|
||||
toolName={tool.body.name || t("Unnammed tool")} />)}
|
||||
</div>
|
||||
<this.MountedToolInfo />
|
||||
<this.ToolSlots />
|
||||
<this.InactiveTools />
|
||||
</EmptyStateWrapper>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
|
@ -93,20 +203,26 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
|||
interface ToolSlotInventoryItemProps {
|
||||
toolSlot: TaggedToolSlotPointer;
|
||||
getToolName(toolId: number | undefined): string | undefined;
|
||||
hovered: boolean;
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
const ToolSlotInventoryItem = (props: ToolSlotInventoryItemProps) => {
|
||||
const { x, y, z, tool_id } = props.toolSlot.body;
|
||||
return <Row>
|
||||
<Col xs={7}>
|
||||
<p onClick={() => history.push(`/app/designer/tools/${tool_id}`)}>
|
||||
{props.getToolName(tool_id) || t("No tool")}
|
||||
</p>
|
||||
</Col>
|
||||
<Col xs={5}>
|
||||
<p style={{ float: "right" }}>{botPositionLabel({ x, y, z })}</p>
|
||||
</Col>
|
||||
</Row>;
|
||||
const { x, y, z, id, tool_id } = props.toolSlot.body;
|
||||
return <div
|
||||
className={`tool-slot-search-item ${props.hovered ? "hovered" : ""}`}
|
||||
onClick={() => history.push(`/app/designer/tool-slots/${id}`)}
|
||||
onMouseEnter={() => props.dispatch(setToolHover(props.toolSlot.uuid))}
|
||||
onMouseLeave={() => props.dispatch(setToolHover(undefined))}>
|
||||
<Row>
|
||||
<Col xs={7}>
|
||||
<p>{props.getToolName(tool_id) || t("No tool")}</p>
|
||||
</Col>
|
||||
<Col xs={5}>
|
||||
<p style={{ float: "right" }}>{botPositionLabel({ x, y, z })}</p>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>;
|
||||
};
|
||||
|
||||
interface ToolInventoryItemProps {
|
||||
|
@ -115,12 +231,13 @@ interface ToolInventoryItemProps {
|
|||
}
|
||||
|
||||
const ToolInventoryItem = (props: ToolInventoryItemProps) =>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<p onClick={() => history.push(`/app/designer/tools/${props.toolId}`)}>
|
||||
{t(props.toolName)}
|
||||
</p>
|
||||
</Col>
|
||||
</Row>;
|
||||
<div className={"tool-search-item"}
|
||||
onClick={() => history.push(`/app/designer/tools/${props.toolId}`)}>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<p>{t(props.toolName)}</p>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>;
|
||||
|
||||
export const Tools = connect(mapStateToProps)(RawTools);
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
import React from "react";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { Xyz, TaggedTool, TaggedToolSlotPointer } from "farmbot";
|
||||
import { Row, Col, BlurableInput, FBSelect, NULL_CHOICE } 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";
|
||||
import { BotPosition } from "../../devices/interfaces";
|
||||
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
export interface GantryMountedInputProps {
|
||||
gantryMounted: boolean;
|
||||
onChange(update: { gantry_mounted: boolean }): void;
|
||||
}
|
||||
|
||||
export const GantryMountedInput = (props: GantryMountedInputProps) =>
|
||||
<fieldset className="gantry-mounted-input">
|
||||
<label>{t("Gantry-mounted")}</label>
|
||||
<input type="checkbox"
|
||||
onChange={() => props.onChange({ gantry_mounted: !props.gantryMounted })}
|
||||
checked={props.gantryMounted} />
|
||||
</fieldset>;
|
||||
|
||||
export interface UseCurrentLocationInputRowProps {
|
||||
botPosition: BotPosition;
|
||||
onChange(botPosition: BotPosition): void;
|
||||
}
|
||||
|
||||
export const UseCurrentLocationInputRow =
|
||||
(props: UseCurrentLocationInputRowProps) =>
|
||||
<fieldset className="use-current-location-input">
|
||||
<label>{t("Use current location")}</label>
|
||||
<button
|
||||
className="blue fb-button"
|
||||
title={positionButtonTitle(props.botPosition)}
|
||||
onClick={() => positionIsDefined(props.botPosition) &&
|
||||
props.onChange(props.botPosition)}>
|
||||
<i className="fa fa-crosshairs" />
|
||||
</button>
|
||||
<p>{positionButtonTitle(props.botPosition)}</p>
|
||||
</fieldset>;
|
||||
|
||||
export interface SlotDirectionInputRowProps {
|
||||
toolPulloutDirection: ToolPulloutDirection;
|
||||
onChange(update: { pullout_direction: ToolPulloutDirection }): void;
|
||||
}
|
||||
|
||||
export const SlotDirectionInputRow = (props: SlotDirectionInputRowProps) =>
|
||||
<fieldset className="tool-slot-direction-input">
|
||||
<label>
|
||||
{t("Change slot direction")}
|
||||
</label>
|
||||
<i className={"direction-icon "
|
||||
+ directionIconClass(props.toolPulloutDirection)}
|
||||
onClick={() => props.onChange({
|
||||
pullout_direction: newSlotDirection(props.toolPulloutDirection)
|
||||
})} />
|
||||
<FBSelect
|
||||
key={props.toolPulloutDirection}
|
||||
list={DIRECTION_CHOICES}
|
||||
selectedItem={DIRECTION_CHOICES_DDI[props.toolPulloutDirection]}
|
||||
onChange={ddi => props.onChange({
|
||||
pullout_direction: parseInt("" + ddi.value)
|
||||
})} />
|
||||
</fieldset>;
|
||||
|
||||
export interface ToolSelectionProps {
|
||||
tools: TaggedTool[];
|
||||
selectedTool: TaggedTool | undefined;
|
||||
onChange(update: { tool_id: number }): void;
|
||||
filterSelectedTool: boolean;
|
||||
}
|
||||
|
||||
export const ToolSelection = (props: ToolSelectionProps) =>
|
||||
<FBSelect
|
||||
list={props.tools
|
||||
.filter(tool => (!props.filterSelectedTool || !props.selectedTool)
|
||||
|| tool.body.id != props.selectedTool.body.id)
|
||||
.map(tool => ({
|
||||
label: tool.body.name || "untitled",
|
||||
value: tool.body.id || 0,
|
||||
}))
|
||||
.filter(ddi => ddi.value > 0)}
|
||||
selectedItem={props.selectedTool
|
||||
? {
|
||||
label: props.selectedTool.body.name || "untitled",
|
||||
value: "" + props.selectedTool.body.id
|
||||
} : NULL_CHOICE}
|
||||
allowEmpty={true}
|
||||
onChange={ddi =>
|
||||
props.onChange({ tool_id: parseInt("" + ddi.value) })} />;
|
||||
|
||||
export interface ToolInputRowProps {
|
||||
tools: TaggedTool[];
|
||||
selectedTool: TaggedTool | undefined;
|
||||
onChange(update: { tool_id: number }): void;
|
||||
}
|
||||
|
||||
export const ToolInputRow = (props: ToolInputRowProps) =>
|
||||
<div className="tool-slot-tool-input">
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<label>{t("Tool")}</label>
|
||||
<ToolSelection
|
||||
tools={props.tools}
|
||||
selectedTool={props.selectedTool}
|
||||
onChange={props.onChange}
|
||||
filterSelectedTool={false} />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>;
|
||||
|
||||
export interface SlotLocationInputRowProps {
|
||||
slotLocation: Record<Xyz, number>;
|
||||
gantryMounted: boolean;
|
||||
onChange(update: Partial<Record<Xyz, number>>): void;
|
||||
}
|
||||
|
||||
export const SlotLocationInputRow = (props: SlotLocationInputRowProps) =>
|
||||
<div className="tool-slot-location-input">
|
||||
<Row>
|
||||
{["x", "y", "z"].map((axis: Xyz) =>
|
||||
<Col xs={4} key={axis}>
|
||||
<label>{t("{{axis}} (mm)", { axis })}</label>
|
||||
{axis == "x" && props.gantryMounted
|
||||
? <input disabled value={t("Gantry")} />
|
||||
: <BlurableInput
|
||||
type="number"
|
||||
value={props.slotLocation[axis]}
|
||||
min={axis == "z" ? undefined : 0}
|
||||
onCommit={e => props.onChange({
|
||||
[axis]: parseFloat(e.currentTarget.value)
|
||||
})} />}
|
||||
</Col>)}
|
||||
</Row>
|
||||
</div>;
|
||||
|
||||
export interface SlotEditRowsProps {
|
||||
toolSlot: TaggedToolSlotPointer;
|
||||
tools: TaggedTool[];
|
||||
tool: TaggedTool | undefined;
|
||||
botPosition: BotPosition;
|
||||
updateToolSlot(update: Partial<TaggedToolSlotPointer["body"]>): void;
|
||||
}
|
||||
|
||||
export const SlotEditRows = (props: SlotEditRowsProps) =>
|
||||
<div className="tool-slot-edit-rows">
|
||||
<SlotLocationInputRow
|
||||
slotLocation={props.toolSlot.body}
|
||||
gantryMounted={props.toolSlot.body.gantry_mounted}
|
||||
onChange={props.updateToolSlot} />
|
||||
<ToolInputRow
|
||||
tools={props.tools}
|
||||
selectedTool={props.tool}
|
||||
onChange={props.updateToolSlot} />
|
||||
<SlotDirectionInputRow
|
||||
toolPulloutDirection={props.toolSlot.body.pullout_direction}
|
||||
onChange={props.updateToolSlot} />
|
||||
<UseCurrentLocationInputRow
|
||||
botPosition={props.botPosition}
|
||||
onChange={props.updateToolSlot} />
|
||||
<GantryMountedInput
|
||||
gantryMounted={props.toolSlot.body.gantry_mounted}
|
||||
onChange={props.updateToolSlot} />
|
||||
</div>;
|
|
@ -94,7 +94,7 @@ export class FarmwareList
|
|||
.filter(x => showFirstParty || !firstPartyFarmwareNames.includes(x))
|
||||
.filter(x => !listed1stPartyNames.includes(x));
|
||||
|
||||
return <div>
|
||||
return <div className="farmware-list-panel-contents">
|
||||
<div className="farmware-settings-menu">
|
||||
<Popover position={Position.BOTTOM_RIGHT}>
|
||||
<i className="fa fa-gear dark" />
|
||||
|
|
|
@ -178,6 +178,11 @@ export function maybeGetRegimen(index: ResourceIndex,
|
|||
if (tr && isTaggedRegimen(tr)) { return tr; }
|
||||
}
|
||||
|
||||
export function maybeGetToolSlot(index: ResourceIndex,
|
||||
uuid: string | undefined): TaggedToolSlotPointer | undefined {
|
||||
return selectAllToolSlotPointers(index).filter(x => x.uuid === uuid)[0];
|
||||
}
|
||||
|
||||
/** Return the UTC offset of current bot if possible. If not, use UTC (0). */
|
||||
export function maybeGetTimeOffset(index: ResourceIndex): number {
|
||||
const dev = maybeGetDevice(index);
|
||||
|
|
|
@ -50,6 +50,16 @@ export const findFarmEventById = (ri: ResourceIndex, fe_id: number) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const maybeFindToolSlotById = (ri: ResourceIndex, tool_slot_id?: number):
|
||||
TaggedToolSlotPointer | undefined => {
|
||||
const toolSlot = tool_slot_id && byId("Point")(ri, tool_slot_id);
|
||||
if (toolSlot && isTaggedToolSlotPointer(toolSlot) && sanityCheck(toolSlot)) {
|
||||
return toolSlot;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const maybeFindToolById = (ri: ResourceIndex, tool_id?: number):
|
||||
TaggedTool | undefined => {
|
||||
const tool = tool_id && byId("Tool")(ri, tool_id);
|
||||
|
|
|
@ -345,6 +345,22 @@ export const UNBOUND_ROUTES = [
|
|||
getChild: () => import("./farm_designer/tools/edit_tool"),
|
||||
childKey: "EditTool"
|
||||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/tool-slots/add",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/tools/add_tool_slot"),
|
||||
childKey: "AddToolSlot"
|
||||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/tool-slots/:tool_id",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/tools/edit_tool_slot"),
|
||||
childKey: "EditToolSlot"
|
||||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/groups",
|
||||
|
|
|
@ -6,7 +6,7 @@ import { isNumber } from "lodash";
|
|||
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
const DIRECTION_CHOICES_DDI: { [index: number]: DropDownItem } = {
|
||||
export const DIRECTION_CHOICES_DDI: { [index: number]: DropDownItem } = {
|
||||
[ToolPulloutDirection.NONE]:
|
||||
{ label: t("None"), value: ToolPulloutDirection.NONE },
|
||||
[ToolPulloutDirection.POSITIVE_X]:
|
||||
|
@ -19,7 +19,7 @@ const DIRECTION_CHOICES_DDI: { [index: number]: DropDownItem } = {
|
|||
{ label: t("Negative Y"), value: ToolPulloutDirection.NEGATIVE_Y },
|
||||
};
|
||||
|
||||
const DIRECTION_CHOICES: DropDownItem[] = [
|
||||
export const DIRECTION_CHOICES: DropDownItem[] = [
|
||||
DIRECTION_CHOICES_DDI[ToolPulloutDirection.NONE],
|
||||
DIRECTION_CHOICES_DDI[ToolPulloutDirection.POSITIVE_X],
|
||||
DIRECTION_CHOICES_DDI[ToolPulloutDirection.NEGATIVE_X],
|
||||
|
|
|
@ -7,31 +7,32 @@ import { SlotDirectionSelect } from "./toolbay_slot_direction_selection";
|
|||
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
const positionIsDefined = (position: BotPosition): boolean =>
|
||||
export const positionIsDefined = (position: BotPosition): boolean =>
|
||||
isNumber(position.x) && isNumber(position.y) && isNumber(position.z);
|
||||
|
||||
const useCurrentPosition = (
|
||||
export const useCurrentPosition = (
|
||||
dispatch: Function, slot: TaggedToolSlotPointer, position: BotPosition) => {
|
||||
if (positionIsDefined(position)) {
|
||||
dispatch(edit(slot, { x: position.x, y: position.y, z: position.z }));
|
||||
}
|
||||
};
|
||||
|
||||
const positionButtonTitle = (position: BotPosition): string =>
|
||||
export const positionButtonTitle = (position: BotPosition): string =>
|
||||
positionIsDefined(position)
|
||||
? `(${position.x}, ${position.y}, ${position.z})`
|
||||
: t("(unknown)");
|
||||
|
||||
const changePulloutDirection =
|
||||
export const newSlotDirection =
|
||||
(old: ToolPulloutDirection | undefined): ToolPulloutDirection =>
|
||||
isNumber(old) && old < 4 ? old + 1 : ToolPulloutDirection.NONE;
|
||||
|
||||
export const changePulloutDirection =
|
||||
(dispatch: Function, slot: TaggedToolSlotPointer) => () => {
|
||||
const newDirection =
|
||||
(old: ToolPulloutDirection | undefined): ToolPulloutDirection =>
|
||||
isNumber(old) && old < 4 ? old + 1 : ToolPulloutDirection.NONE;
|
||||
dispatch(edit(slot,
|
||||
{ pullout_direction: newDirection(slot.body.pullout_direction) }));
|
||||
{ pullout_direction: newSlotDirection(slot.body.pullout_direction) }));
|
||||
};
|
||||
|
||||
const directionIconClass = (slotDirection: ToolPulloutDirection) => {
|
||||
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";
|
||||
|
|
Loading…
Reference in New Issue