toolbay slot refactoring

This commit is contained in:
gabrielburnworth 2018-01-25 16:50:17 -08:00
parent 556fc73fae
commit 0e93de6d65
13 changed files with 622 additions and 187 deletions

View file

@ -595,4 +595,27 @@ ul {
line-height: 1.75rem;
margin-bottom: 1rem;
}
}
}
.toolbay-widget {
.fa-gear {
color: $dark_gray;
margin-top: 0.5rem;
}
.del-button {
margin-top: 0.5rem;
}
}
.toolbay-slot-menu {
.direction-icon {
color: $dark_gray;
margin-left: 1rem;
}
label {
margin-top: 1rem;
}
.fb-button {
margin-top: 1rem;
}
}

View file

@ -0,0 +1,113 @@
import * as React from "react";
import { mount } from "enzyme";
import {
ToolbaySlot, Tool, ToolProps, ToolGraphicProps, ToolSlotGraphicProps
} from "../tool_graphics";
import { BotOriginQuadrant } from "../../interfaces";
import { Color } from "../../../ui/index";
describe("<ToolbaySlot />", () => {
const fakeProps = (): ToolSlotGraphicProps => {
return {
id: undefined,
x: 10,
y: 20,
pulloutDirection: 0,
quadrant: 2
};
};
const checkSlotDirection =
(direction: number, quadrant: BotOriginQuadrant, expected: string) => {
it(`renders slot, pullout: ${direction} quad: ${quadrant}`, () => {
const p = fakeProps();
p.pulloutDirection = direction;
p.quadrant = quadrant;
const wrapper = mount(<ToolbaySlot {...p } />);
expect(wrapper.find("use").props().transform).toEqual(expected);
});
};
checkSlotDirection(0, 2, "rotate(0, 10, 20)");
checkSlotDirection(1, 1, "rotate(180, 10, 20)");
checkSlotDirection(1, 2, "rotate(0, 10, 20)");
checkSlotDirection(1, 3, "rotate(0, 10, 20)");
checkSlotDirection(1, 4, "rotate(180, 10, 20)");
checkSlotDirection(2, 3, "rotate(180, 10, 20)");
checkSlotDirection(3, 1, "rotate(90, 10, 20)");
checkSlotDirection(3, 2, "rotate(90, 10, 20)");
checkSlotDirection(3, 3, "rotate(270, 10, 20)");
checkSlotDirection(3, 4, "rotate(270, 10, 20)");
checkSlotDirection(4, 3, "rotate(90, 10, 20)");
});
describe("<Tool/>", () => {
const fakeToolProps = (): ToolGraphicProps => {
return {
x: 10,
y: 20,
hovered: false,
setHoverState: jest.fn()
};
};
const fakeProps = (): ToolProps => {
return {
tool: "fake tool",
toolProps: fakeToolProps()
};
};
it("renders standard tool styling", () => {
const wrapper = mount(<Tool {...fakeProps() } />);
const props = wrapper.find("circle").last().props();
expect(props.r).toEqual(35);
expect(props.cx).toEqual(10);
expect(props.cy).toEqual(20);
expect(props.fill).toEqual(Color.mediumGray);
});
it("tool hover", () => {
const p = fakeProps();
p.toolProps.hovered = true;
const wrapper = mount(<Tool {...p } />);
const props = wrapper.find("circle").last().props();
expect(props.fill).toEqual(Color.darkGray);
});
it("renders special tool styling: bin", () => {
const p = fakeProps();
p.tool = "seedBin";
const wrapper = mount(<Tool {...p } />);
const elements = wrapper.find("#seed-bin").find("circle");
expect(elements.length).toEqual(2);
expect(elements.last().props().fill).toEqual("url(#SeedBinGradient)");
});
it("bin hover", () => {
const p = fakeProps();
p.tool = "seedBin";
p.toolProps.hovered = true;
const wrapper = mount(<Tool {...p } />);
p.toolProps.hovered = true;
expect(wrapper.find("#seed-bin").find("circle").length).toEqual(3);
});
it("renders special tool styling: tray", () => {
const p = fakeProps();
p.tool = "seedTray";
const wrapper = mount(<Tool {...p } />);
const elements = wrapper.find("#seed-tray");
expect(elements.find("circle").length).toEqual(2);
expect(elements.find("rect").length).toEqual(1);
expect(elements.find("rect").props().fill).toEqual("url(#SeedTrayPattern)");
});
it("tray hover", () => {
const p = fakeProps();
p.tool = "seedTray";
p.toolProps.hovered = true;
const wrapper = mount(<Tool {...p } />);
p.toolProps.hovered = true;
expect(wrapper.find("#seed-tray").find("circle").length).toEqual(3);
});
});

View file

@ -1,6 +1,6 @@
import * as React from "react";
import { ToolSlotPoint, TSPProps } from "../tool_slot_point";
import { shallow } from "enzyme";
import { mount } from "enzyme";
import { fakeToolSlot, fakeTool } from "../../../__test_support__/fake_state/resources";
describe("<ToolSlotPoint/>", () => {
@ -13,68 +13,57 @@ describe("<ToolSlotPoint/>", () => {
};
}
it("renders tool slot point", () => {
const wrapper = shallow(<ToolSlotPoint {...fakeProps() } />);
const props = wrapper.find("circle").last().props();
expect(props.r).toEqual(35);
expect(props.cx).toEqual(10);
expect(props.cy).toEqual(10);
});
const testToolSlotGraphics = (tool: 0 | 1, slot: 0 | 1) => {
it(`renders ${tool ? "" : "no"} tool and ${slot ? "" : "no"} slot`, () => {
if (!tool && !slot) { tool = 1; }
const p = fakeProps();
if (!tool) { p.slot.tool = undefined; }
p.slot.toolSlot.body.pullout_direction = slot;
const wrapper = mount(<ToolSlotPoint {...p} />);
expect(wrapper.find("circle").length).toEqual(tool);
expect(wrapper.find("use").length).toEqual(slot);
});
};
testToolSlotGraphics(0, 0);
testToolSlotGraphics(0, 1);
testToolSlotGraphics(1, 0);
testToolSlotGraphics(1, 1);
it("displays tool name", () => {
const wrapper = shallow(<ToolSlotPoint {...fakeProps() } />);
const p = fakeProps();
p.slot.toolSlot.body.pullout_direction = 2;
const wrapper = mount(<ToolSlotPoint {...p} />);
wrapper.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);
});
it("displays 'no tool'", () => {
const p = fakeProps();
p.slot.tool = undefined;
const wrapper = shallow(<ToolSlotPoint {...p } />);
const wrapper = mount(<ToolSlotPoint {...p } />);
wrapper.setState({ hovered: true });
expect(wrapper.find("text").text()).toEqual("no tool");
expect(wrapper.find("text").props().dx).toEqual(40);
});
it("doesn't display tool name", () => {
const wrapper = shallow(<ToolSlotPoint {...fakeProps() } />);
const wrapper = mount(<ToolSlotPoint {...fakeProps() } />);
expect(wrapper.find("text").props().visibility).toEqual("hidden");
});
it("renders special tool styling: bin", () => {
it("renders bin", () => {
const p = fakeProps();
if (p.slot.tool) { p.slot.tool.body.name = "Seed Bin"; }
const wrapper = shallow(<ToolSlotPoint {...p } />);
const elements = wrapper.find("#seed-bin").find("circle");
expect(elements.length).toEqual(2);
expect(elements.last().props().fill).toEqual("url(#SeedBinGradient)");
wrapper.setState({ hovered: true });
expect(wrapper.find("#seed-bin").find("circle").length).toEqual(3);
if (p.slot.tool) { p.slot.tool.body.name = "seed bin"; }
const wrapper = mount(<ToolSlotPoint {...p } />);
expect(wrapper.find("#SeedBinGradient").length).toEqual(1);
});
it("renders special tool styling: tray", () => {
it("renders tray", () => {
const p = fakeProps();
if (p.slot.tool) { p.slot.tool.body.name = "Seed Tray"; }
const wrapper = shallow(<ToolSlotPoint {...p } />);
const elements = wrapper.find("#seed-tray");
expect(elements.find("circle").length).toEqual(1);
expect(elements.find("rect").length).toEqual(1);
expect(elements.find("rect").props().fill).toEqual("url(#SeedTrayPattern)");
wrapper.setState({ hovered: true });
expect(wrapper.find("#seed-tray").find("circle").length).toEqual(2);
});
it("doesn't render toolbay slot", () => {
const p = fakeProps();
p.slot.toolSlot.body.pullout_direction = 0;
const wrapper = shallow(<ToolSlotPoint {...p } />);
expect(wrapper.find("use").length).toEqual(0);
});
it("renders toolbay slot", () => {
const p = fakeProps();
p.slot.toolSlot.body.pullout_direction = 1;
const wrapper = shallow(<ToolSlotPoint {...p } />);
expect(wrapper.find("use").length).toEqual(1);
if (p.slot.tool) { p.slot.tool.body.name = "seed tray"; }
const wrapper = mount(<ToolSlotPoint {...p } />);
expect(wrapper.find("#SeedTrayPattern").length).toEqual(1);
});
});

View file

@ -0,0 +1,163 @@
import * as React from "react";
import { Color } from "../../ui/index";
import { trim } from "../../util";
import { ToolPulloutDirection } from "../../interfaces";
import { BotOriginQuadrant } from "../interfaces";
export interface ToolGraphicProps {
x: number;
y: number;
hovered: boolean;
setHoverState(hoverState: boolean): void;
}
export interface ToolProps {
tool: string;
toolProps: ToolGraphicProps;
}
export interface ToolSlotGraphicProps {
id: number | undefined;
x: number;
y: number;
pulloutDirection: ToolPulloutDirection;
quadrant: BotOriginQuadrant;
}
const toolbaySlotAngle = (
pulloutDirection: ToolPulloutDirection,
quadrant: BotOriginQuadrant) => {
const rawAngle = () => {
switch (pulloutDirection) {
case ToolPulloutDirection.POSITIVE_X: return 0;
case ToolPulloutDirection.NEGATIVE_X: return 180;
case ToolPulloutDirection.NEGATIVE_Y: return 90;
case ToolPulloutDirection.POSITIVE_Y: return 270;
default: return 0;
}
};
const adjustAngle = (angle: number) => {
const horizontal = angle % 180 === 0;
switch (quadrant) {
case 1: return angle + 180;
case 2: return horizontal ? angle : angle + 180;
case 3: return angle;
case 4: return horizontal ? angle + 180 : angle;
default: return angle;
}
};
return (adjustAngle(rawAngle())) % 360;
};
export enum ToolNames {
seedBin = "seedBin",
seedTray = "seedTray",
tool = "tool",
}
export const ToolbaySlot = (props: ToolSlotGraphicProps) => {
const { id, x, y, pulloutDirection, quadrant } = props;
const angle = toolbaySlotAngle(pulloutDirection, quadrant);
return <g id={"toolbay-slot"}>
<defs>
<g id={"toolbay-slot-" + id}
fillOpacity={0.25}
fill={Color.mediumGray}>
<path d={trim(`M${x + 50} ${y + 50}
h -150 v -100 h 150 v 15.5
a 5 5 0 0 1 -2.5 2.5
h -61.5
a 35 35 0 0 0 0 64
h 61.5
a 5 5 0 0 1 2.5 2.5
z`)} />
</g>
</defs>
<use style={{ pointerEvents: "none" }}
xlinkHref={"#toolbay-slot-" + id}
transform={
`rotate(${angle}, ${x}, ${y})`} />
</g>;
};
export const Tool = (props: ToolProps) => {
switch (props.tool) {
case ToolNames.seedBin: return <SeedBin {...props.toolProps} />;
case ToolNames.seedTray: return <SeedTray {...props.toolProps} />;
default: return <StandardTool {...props.toolProps} />;
}
};
const StandardTool = (props: ToolGraphicProps) => {
const { x, y, hovered, setHoverState } = props;
return <g id={"tool"}
onMouseOver={() => setHoverState(true)}
onMouseLeave={() => setHoverState(false)}>
<circle
cx={x}
cy={y}
r={35}
fillOpacity={0.5}
fill={hovered ? Color.darkGray : Color.mediumGray} />
</g>;
};
const seedBinGradient =
<radialGradient id="SeedBinGradient">
<stop offset="5%" stopColor="rgb(0, 0, 0)" stopOpacity={0.3} />
<stop offset="95%" stopColor="rgb(0, 0, 0)" stopOpacity={0.1} />
</radialGradient>;
const SeedBin = (props: ToolGraphicProps) => {
const { x, y, hovered, setHoverState } = props;
return <g id={"seed-bin"}
onMouseOver={() => setHoverState(true)}
onMouseLeave={() => setHoverState(false)}>
<defs>
{seedBinGradient}
</defs>
<circle
cx={x} cy={y} r={35}
fill="rgba(128, 128, 128, 0.8)" />
<circle
cx={x} cy={y} r={30}
fill="url(#SeedBinGradient)" />
{hovered &&
<circle
cx={x} cy={y} r={35}
fill="rgba(0, 0, 0, 0.1)" />}
</g>;
};
const SeedTray = (props: ToolGraphicProps) => {
const { x, y, hovered, setHoverState } = props;
return <g id={"seed-tray"}
onMouseOver={() => setHoverState(true)}
onMouseLeave={() => setHoverState(false)}>
<defs>
{seedBinGradient}
<pattern id="SeedTrayPattern"
x={0} y={0} width={0.25} height={0.25}>
<circle cx={6} cy={6} r={5} fill="url(#SeedBinGradient)" />
</pattern>
</defs>
<circle
cx={x} cy={y} r={35}
fill="rgba(128, 128, 128, 0.8)" />
<rect
x={x - 25} y={y - 25}
width={50} height={50}
fill="url(#SeedTrayPattern)" />
{hovered &&
<circle
cx={x} cy={y} r={35}
fill="rgba(0, 0, 0, 0.1)" />}
</g>;
};

View file

@ -4,8 +4,8 @@ import { getXYFromQuadrant } from "./util";
import { MapTransformProps } from "./interfaces";
import * as _ from "lodash";
import { Color } from "../../ui/index";
import { trim } from "../../util";
import { ToolPulloutDirection } from "../../interfaces";
import { ToolbaySlot, ToolNames, Tool } from "./tool_graphics";
export interface TSPProps {
slot: SlotWithTool;
@ -23,100 +23,50 @@ export class ToolSlotPoint extends
hovered: false
};
angles = [0, 0, 180, 270, 90];
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; }
return ToolNames.tool;
}
render() {
const { id, x, y, pullout_direction } = this.slot.toolSlot.body;
const { quadrant, gridSize } = this.props.mapTransformProps;
const { qx, qy } = getXYFromQuadrant(x, y, quadrant, gridSize);
const toolName = this.slot.tool ? this.slot.tool.body.name : "no tool";
const seedBin = _.includes((toolName || "").toLowerCase(), "seed bin");
const seedTray = _.includes((toolName || "").toLowerCase(), "seed tray");
const seedTrayRect = getXYFromQuadrant(x, y, quadrant, gridSize);
const toolProps = {
x: qx,
y: qy,
hovered: this.state.hovered,
setHoverState: this.setHover
};
const labelAnchor = pullout_direction === ToolPulloutDirection.NEGATIVE_X
? "end"
: "start";
return <g id={"toolslot-" + id}>
<defs>
<radialGradient id="SeedBinGradient">
<stop offset="5%" stopColor="rgb(0, 0, 0)" stopOpacity={0.3} />
<stop offset="95%" stopColor="rgb(0, 0, 0)" stopOpacity={0.1} />
</radialGradient>
<pattern id="SeedTrayPattern"
x={0} y={0} width={0.25} height={0.25}>
<circle cx={6} cy={6} r={5} fill="url(#SeedBinGradient)" />
</pattern>
<g id={"toolbay-slot-" + id}
fillOpacity={0.25}
fill={Color.mediumGray}>
<path d={trim(`M${qx + 50} ${qy + 50}
h -150 v -100 h 150 v 15.5
a 5 5 0 0 1 -2.5 2.5
h -61.5
a 35 35 0 0 0 0 64
h 61.5
a 5 5 0 0 1 2.5 2.5
z`)} />
</g>
</defs>
{pullout_direction &&
<use style={{ pointerEvents: "none" }}
xlinkHref={"#toolbay-slot-" + id}
transform={
`rotate(${this.angles[pullout_direction]}, ${qx}, ${qy})`} />}
<ToolbaySlot
id={id}
x={qx}
y={qy}
pulloutDirection={pullout_direction}
quadrant={quadrant} />}
{seedBin &&
<g id="seed-bin" key={this.slot.toolSlot.uuid}
onMouseOver={() => this.setState({ hovered: true })}
onMouseLeave={() => this.setState({ hovered: false })}>
<circle
cx={qx} cy={qy} r={35}
fill="rgba(128, 128, 128, 0.8)" />
<circle
cx={qx} cy={qy} r={30}
fill="url(#SeedBinGradient)" />
{this.state.hovered &&
<circle
cx={qx} cy={qy} r={35}
fill="rgba(0, 0, 0, 0.1)" />}
</g>
}
{seedTray &&
<g id="seed-tray" key={this.slot.toolSlot.uuid}
onMouseOver={() => this.setState({ hovered: true })}
onMouseLeave={() => this.setState({ hovered: false })}>
<circle
cx={qx} cy={qy} r={35}
fill="rgba(128, 128, 128, 0.8)" />
<rect
x={seedTrayRect.qx - 25} y={seedTrayRect.qy - 25}
width={50} height={50}
fill="url(#SeedTrayPattern)" />
{this.state.hovered &&
<circle
cx={qx} cy={qy} r={35}
fill="rgba(0, 0, 0, 0.1)" />}
</g>
}
{!seedBin && !seedTray &&
<circle key={this.slot.toolSlot.uuid}
onMouseOver={() => this.setState({ hovered: true })}
onMouseLeave={() => this.setState({ hovered: false })}
cx={qx}
cy={qy}
r={35}
fillOpacity={0.5}
fill={this.state.hovered ? Color.darkGray : Color.mediumGray} />
}
{(this.slot.tool || !pullout_direction) &&
<Tool
tool={this.reduceToolName(toolName)}
toolProps={toolProps} />}
<text textAnchor={labelAnchor}
visibility={this.state.hovered ? "visible" : "hidden"}
x={qx}
y={qy}
dx={40}
dx={labelAnchor === "start" ? 40 : -40}
dy={10}
fontSize={24}
fill={Color.darkGray}>

View file

@ -3,7 +3,6 @@ import { ToolBayForm } from "../toolbay_form";
import { mount } from "enzyme";
import { mapStateToProps } from "../../state_to_props";
import { fakeState } from "../../../__test_support__/fake_state";
import { Actions } from "../../../constants";
describe("<ToolBayForm/>", () => {
function bootstrapTest() {
@ -35,18 +34,4 @@ describe("<ToolBayForm/>", () => {
expect(test.component.text()).toContain("Trench Digging Tool");
[0, 1, 2].map(i => expect(inputs.at(i).props().value).toEqual("10"));
});
it("fills inputs with bot position", () => {
const test = bootstrapTest();
const buttons = test.component.find("button");
expect(buttons.length).toEqual(6);
buttons.at(3).simulate("click");
expect(test.dispatch).toHaveBeenCalledWith({
type: Actions.EDIT_RESOURCE,
payload: expect.objectContaining({
update: { x: 1, y: 2, z: 3 }
})
});
});
});

View file

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

View file

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

View file

@ -17,9 +17,8 @@ import {
import { edit, destroy, saveAll, init } from "../../api/crud";
import { ToolBayHeader } from "./toolbay_header";
import { ToolTips } from "../../constants";
import * as _ from "lodash";
import { BotPosition } from "../../devices/interfaces";
import { ToolPulloutDirection } from "../../interfaces";
import { Popover, Position } from "@blueprintjs/core";
import { SlotMenu } from "./toolbay_slot_menu";
export class ToolBayForm extends React.Component<ToolBayFormProps, {}> {
@ -42,42 +41,11 @@ export class ToolBayForm extends React.Component<ToolBayFormProps, {}> {
};
}
positionIsDefined = (position: BotPosition): boolean => {
return _.isNumber(position.x) && _.isNumber(position.y) && _.isNumber(position.z);
}
useCurrentPosition = (dispatch: Function, slot: TaggedToolSlotPointer, position: BotPosition) => {
if (this.positionIsDefined(position)) {
dispatch(edit(slot, { x: position.x, y: position.y, z: position.z }));
}
};
positionButtonTitle = (position: BotPosition): string => {
if (this.positionIsDefined(position)) {
return `use current location (${position.x}, ${position.y}, ${position.z})`;
} else {
return "use current location (unknown)";
}
}
changePulloutDirection = (dispatch: Function, slot: TaggedToolSlotPointer) =>
() => {
const newDirection = (
old: ToolPulloutDirection | undefined): ToolPulloutDirection => {
if (_.isNumber(old) && old < 4) { return old + 1; }
return ToolPulloutDirection.NONE;
};
dispatch(edit(slot,
{ pullout_direction: newDirection(slot.body.pullout_direction) }));
}
iconDirections = ["none", "right", "left", "up", "down"];
render() {
const { toggle, dispatch, toolSlots, botPosition } = this.props;
const toolSlotStatus = getArrayStatus(toolSlots);
return <div>
return <div className={"toolbay-widget"}>
<Widget>
<WidgetHeader helpText={ToolTips.TOOLBAY_LIST} title="Tool Slots">
<button
@ -101,21 +69,16 @@ export class ToolBayForm extends React.Component<ToolBayFormProps, {}> {
<ToolBayHeader />
{this.props.getToolSlots().map(
(slot: TaggedToolSlotPointer, index: number) => {
const { x, y, z, pullout_direction } = slot.body;
const { x, y, z } = slot.body;
return <Row key={index}>
<Col xs={2}>
<label onClick={this.changePulloutDirection(dispatch, slot)}>
{index + 1}
</label>
{_.isNumber(pullout_direction) &&
<i className={`fa fa-arrow-circle-${
this.iconDirections[pullout_direction]}`} />}
<button
className="blue fb-button"
title={this.positionButtonTitle(botPosition)}
onClick={() => this.useCurrentPosition(dispatch, slot, botPosition)}>
<i className="fa fa-crosshairs" />
</button>
<Col xs={1}>
<Popover position={Position.BOTTOM_LEFT}>
<i className="fa fa-gear" />
<SlotMenu
dispatch={dispatch}
slot={slot}
botPosition={botPosition} />
</Popover>
</Col>
<Col xs={2}>
<BlurableInput
@ -141,7 +104,7 @@ export class ToolBayForm extends React.Component<ToolBayFormProps, {}> {
}}
type="number" />
</Col>
<Col xs={3}>
<Col xs={4}>
<FBSelect
list={this.props.getToolOptions()}
selectedItem={this.props.getChosenToolOption(slot.uuid)}
@ -151,7 +114,7 @@ export class ToolBayForm extends React.Component<ToolBayFormProps, {}> {
</Col>
<Col xs={1}>
<button
className="red fb-button"
className="red fb-button del-button"
onClick={() => dispatch(destroy(slot.uuid))}>
<i className="fa fa-times" />
</button>

View file

@ -4,7 +4,7 @@ import { t } from "i18next";
export function ToolBayHeader(props: {}) {
return <Row>
<Col xs={2}>
<Col xs={1}>
<label>{t("Slot")}</label>
</Col>
<Col xs={2}>

View file

@ -25,7 +25,7 @@ export class ToolBayList extends React.Component<ToolBayListProps, {}> {
const tool = getToolByToolSlotUUID(slot.uuid);
const name = (tool && tool.body.name) || "None";
return <Row key={slot.uuid}>
<Col xs={2}>
<Col xs={1}>
<label>{index + 1}</label>
</Col>
<Col xs={2}>{slot.body.x}</Col>

View file

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

View file

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