toolbay slot refactoring
This commit is contained in:
parent
556fc73fae
commit
0e93de6d65
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
113
webpack/farm_designer/map/__tests__/tool_graphics_test.tsx
Normal file
113
webpack/farm_designer/map/__tests__/tool_graphics_test.tsx
Normal 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);
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
163
webpack/farm_designer/map/tool_graphics.tsx
Normal file
163
webpack/farm_designer/map/tool_graphics.tsx
Normal 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>;
|
||||
};
|
|
@ -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}>
|
||||
|
|
|
@ -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 }
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -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
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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} />;
|
||||
}
|
89
webpack/tools/components/toolbay_slot_menu.tsx
Normal file
89
webpack/tools/components/toolbay_slot_menu.tsx
Normal 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>;
|
||||
};
|
Loading…
Reference in a new issue