implement xySwap in map
This commit is contained in:
parent
9cf19193bd
commit
b343d44192
|
@ -95,6 +95,33 @@ describe("<VirtualFarmBot/>", () => {
|
|||
expect(maxLines.at(1).props()).toEqual({ "x1": 2998, "x2": 2900, "y1": 100, "y2": 100 });
|
||||
});
|
||||
|
||||
it("renders max line in correct location", () => {
|
||||
const p = fakeProps();
|
||||
p.stopAtHome.x = false;
|
||||
p.stopAtHome.y = false;
|
||||
p.botSize = {
|
||||
x: { value: 100, isDefault: false },
|
||||
y: { value: 100, isDefault: true }
|
||||
};
|
||||
const wrapper = shallow(<BotExtents {...p} />);
|
||||
const maxLines = wrapper.find("#max-lines").find("line");
|
||||
expect(maxLines.at(0).props()).toEqual({ "x1": 100, "x2": 100, "y1": 2, "y2": 100 });
|
||||
});
|
||||
|
||||
it("renders max line in correct location with swapped axes", () => {
|
||||
const p = fakeProps();
|
||||
p.stopAtHome.x = false;
|
||||
p.stopAtHome.y = false;
|
||||
p.mapTransformProps.xySwap = true;
|
||||
p.botSize = {
|
||||
x: { value: 100, isDefault: false },
|
||||
y: { value: 100, isDefault: true }
|
||||
};
|
||||
const wrapper = shallow(<BotExtents {...p} />);
|
||||
const maxLines = wrapper.find("#max-lines").find("line");
|
||||
expect(maxLines.at(0).props()).toEqual({ "x1": 2, "x2": 100, "y1": 100, "y2": 100 });
|
||||
});
|
||||
|
||||
it("renders no lines", () => {
|
||||
const p = fakeProps();
|
||||
p.stopAtHome.x = false;
|
||||
|
|
|
@ -13,31 +13,49 @@ describe("<ToolbaySlot />", () => {
|
|||
x: 10,
|
||||
y: 20,
|
||||
pulloutDirection: 0,
|
||||
quadrant: 2
|
||||
quadrant: 2,
|
||||
xySwap: false,
|
||||
};
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
(direction: number,
|
||||
quadrant: BotOriginQuadrant,
|
||||
xySwap: boolean,
|
||||
expected: string) => {
|
||||
it(`renders slot, pullout: ${direction} quad: ${quadrant} yx: ${xySwap}`,
|
||||
() => {
|
||||
const p = fakeProps();
|
||||
p.pulloutDirection = direction;
|
||||
p.quadrant = quadrant;
|
||||
p.xySwap = xySwap;
|
||||
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)");
|
||||
checkSlotDirection(0, 2, false, "rotate(0, 10, 20)");
|
||||
checkSlotDirection(1, 1, false, "rotate(180, 10, 20)");
|
||||
checkSlotDirection(1, 2, false, "rotate(0, 10, 20)");
|
||||
checkSlotDirection(1, 3, false, "rotate(0, 10, 20)");
|
||||
checkSlotDirection(1, 4, false, "rotate(180, 10, 20)");
|
||||
checkSlotDirection(2, 3, false, "rotate(180, 10, 20)");
|
||||
checkSlotDirection(3, 1, false, "rotate(90, 10, 20)");
|
||||
checkSlotDirection(3, 2, false, "rotate(90, 10, 20)");
|
||||
checkSlotDirection(3, 3, false, "rotate(270, 10, 20)");
|
||||
checkSlotDirection(3, 4, false, "rotate(270, 10, 20)");
|
||||
checkSlotDirection(4, 3, false, "rotate(90, 10, 20)");
|
||||
|
||||
checkSlotDirection(0, 2, true, "rotate(180, 10, 20)");
|
||||
checkSlotDirection(1, 1, true, "rotate(90, 10, 20)");
|
||||
checkSlotDirection(1, 2, true, "rotate(90, 10, 20)");
|
||||
checkSlotDirection(1, 3, true, "rotate(270, 10, 20)");
|
||||
checkSlotDirection(1, 4, true, "rotate(270, 10, 20)");
|
||||
checkSlotDirection(2, 3, true, "rotate(90, 10, 20)");
|
||||
checkSlotDirection(3, 1, true, "rotate(180, 10, 20)");
|
||||
checkSlotDirection(3, 2, true, "rotate(0, 10, 20)");
|
||||
checkSlotDirection(3, 3, true, "rotate(0, 10, 20)");
|
||||
checkSlotDirection(3, 4, true, "rotate(180, 10, 20)");
|
||||
checkSlotDirection(4, 3, true, "rotate(180, 10, 20)");
|
||||
});
|
||||
|
||||
describe("<Tool/>", () => {
|
||||
|
@ -58,7 +76,7 @@ describe("<Tool/>", () => {
|
|||
};
|
||||
|
||||
it("renders standard tool styling", () => {
|
||||
const wrapper = mount(<Tool {...fakeProps() } />);
|
||||
const wrapper = mount(<Tool {...fakeProps()} />);
|
||||
const props = wrapper.find("circle").last().props();
|
||||
expect(props.r).toEqual(35);
|
||||
expect(props.cx).toEqual(10);
|
||||
|
@ -69,7 +87,7 @@ describe("<Tool/>", () => {
|
|||
it("tool hover", () => {
|
||||
const p = fakeProps();
|
||||
p.toolProps.hovered = true;
|
||||
const wrapper = mount(<Tool {...p } />);
|
||||
const wrapper = mount(<Tool {...p} />);
|
||||
const props = wrapper.find("circle").last().props();
|
||||
expect(props.fill).toEqual(Color.darkGray);
|
||||
});
|
||||
|
@ -77,7 +95,7 @@ describe("<Tool/>", () => {
|
|||
it("renders special tool styling: bin", () => {
|
||||
const p = fakeProps();
|
||||
p.tool = "seedBin";
|
||||
const wrapper = mount(<Tool {...p } />);
|
||||
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)");
|
||||
|
@ -87,7 +105,7 @@ describe("<Tool/>", () => {
|
|||
const p = fakeProps();
|
||||
p.tool = "seedBin";
|
||||
p.toolProps.hovered = true;
|
||||
const wrapper = mount(<Tool {...p } />);
|
||||
const wrapper = mount(<Tool {...p} />);
|
||||
p.toolProps.hovered = true;
|
||||
expect(wrapper.find("#seed-bin").find("circle").length).toEqual(3);
|
||||
});
|
||||
|
@ -95,7 +113,7 @@ describe("<Tool/>", () => {
|
|||
it("renders special tool styling: tray", () => {
|
||||
const p = fakeProps();
|
||||
p.tool = "seedTray";
|
||||
const wrapper = mount(<Tool {...p } />);
|
||||
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);
|
||||
|
@ -106,7 +124,7 @@ describe("<Tool/>", () => {
|
|||
const p = fakeProps();
|
||||
p.tool = "seedTray";
|
||||
p.toolProps.hovered = true;
|
||||
const wrapper = mount(<Tool {...p } />);
|
||||
const wrapper = mount(<Tool {...p} />);
|
||||
p.toolProps.hovered = true;
|
||||
expect(wrapper.find("#seed-tray").find("circle").length).toEqual(3);
|
||||
});
|
||||
|
|
|
@ -7,30 +7,46 @@ describe("textAnchorPosition()", () => {
|
|||
const MIDDLE_BOTTOM = { anchor: "middle", x: 0, y: -40 };
|
||||
|
||||
it("returns correct label position: positive x", () => {
|
||||
expect(textAnchorPosition(1, 1)).toEqual(END);
|
||||
expect(textAnchorPosition(1, 2)).toEqual(START);
|
||||
expect(textAnchorPosition(1, 3)).toEqual(START);
|
||||
expect(textAnchorPosition(1, 4)).toEqual(END);
|
||||
expect(textAnchorPosition(1, 1, false)).toEqual(END);
|
||||
expect(textAnchorPosition(1, 2, false)).toEqual(START);
|
||||
expect(textAnchorPosition(1, 3, false)).toEqual(START);
|
||||
expect(textAnchorPosition(1, 4, false)).toEqual(END);
|
||||
expect(textAnchorPosition(1, 1, true)).toEqual(MIDDLE_TOP);
|
||||
expect(textAnchorPosition(1, 2, true)).toEqual(MIDDLE_TOP);
|
||||
expect(textAnchorPosition(1, 3, true)).toEqual(MIDDLE_BOTTOM);
|
||||
expect(textAnchorPosition(1, 4, true)).toEqual(MIDDLE_BOTTOM);
|
||||
});
|
||||
|
||||
it("returns correct label position: negative x", () => {
|
||||
expect(textAnchorPosition(2, 1)).toEqual(START);
|
||||
expect(textAnchorPosition(2, 2)).toEqual(END);
|
||||
expect(textAnchorPosition(2, 3)).toEqual(END);
|
||||
expect(textAnchorPosition(2, 4)).toEqual(START);
|
||||
expect(textAnchorPosition(2, 1, false)).toEqual(START);
|
||||
expect(textAnchorPosition(2, 2, false)).toEqual(END);
|
||||
expect(textAnchorPosition(2, 3, false)).toEqual(END);
|
||||
expect(textAnchorPosition(2, 4, false)).toEqual(START);
|
||||
expect(textAnchorPosition(2, 1, true)).toEqual(MIDDLE_BOTTOM);
|
||||
expect(textAnchorPosition(2, 2, true)).toEqual(MIDDLE_BOTTOM);
|
||||
expect(textAnchorPosition(2, 3, true)).toEqual(MIDDLE_TOP);
|
||||
expect(textAnchorPosition(2, 4, true)).toEqual(MIDDLE_TOP);
|
||||
});
|
||||
|
||||
it("returns correct label position: positive y", () => {
|
||||
expect(textAnchorPosition(3, 1)).toEqual(MIDDLE_TOP);
|
||||
expect(textAnchorPosition(3, 2)).toEqual(MIDDLE_TOP);
|
||||
expect(textAnchorPosition(3, 3)).toEqual(MIDDLE_BOTTOM);
|
||||
expect(textAnchorPosition(3, 4)).toEqual(MIDDLE_BOTTOM);
|
||||
expect(textAnchorPosition(3, 1, false)).toEqual(MIDDLE_TOP);
|
||||
expect(textAnchorPosition(3, 2, false)).toEqual(MIDDLE_TOP);
|
||||
expect(textAnchorPosition(3, 3, false)).toEqual(MIDDLE_BOTTOM);
|
||||
expect(textAnchorPosition(3, 4, false)).toEqual(MIDDLE_BOTTOM);
|
||||
expect(textAnchorPosition(3, 1, true)).toEqual(END);
|
||||
expect(textAnchorPosition(3, 2, true)).toEqual(START);
|
||||
expect(textAnchorPosition(3, 3, true)).toEqual(START);
|
||||
expect(textAnchorPosition(3, 4, true)).toEqual(END);
|
||||
});
|
||||
|
||||
it("returns correct label position: negative y", () => {
|
||||
expect(textAnchorPosition(4, 1)).toEqual(MIDDLE_BOTTOM);
|
||||
expect(textAnchorPosition(4, 2)).toEqual(MIDDLE_BOTTOM);
|
||||
expect(textAnchorPosition(4, 3)).toEqual(MIDDLE_TOP);
|
||||
expect(textAnchorPosition(4, 4)).toEqual(MIDDLE_TOP);
|
||||
expect(textAnchorPosition(4, 1, false)).toEqual(MIDDLE_BOTTOM);
|
||||
expect(textAnchorPosition(4, 2, false)).toEqual(MIDDLE_BOTTOM);
|
||||
expect(textAnchorPosition(4, 3, false)).toEqual(MIDDLE_TOP);
|
||||
expect(textAnchorPosition(4, 4, false)).toEqual(MIDDLE_TOP);
|
||||
expect(textAnchorPosition(4, 1, true)).toEqual(START);
|
||||
expect(textAnchorPosition(4, 2, true)).toEqual(END);
|
||||
expect(textAnchorPosition(4, 3, true)).toEqual(END);
|
||||
expect(textAnchorPosition(4, 4, true)).toEqual(START);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -170,9 +170,18 @@ describe("getbotSize()", () => {
|
|||
describe("getMapSize()", () => {
|
||||
it("calculates map size", () => {
|
||||
const mapSize = getMapSize(
|
||||
{ x: 2000, y: 1000 },
|
||||
fakeMapTransformProps(),
|
||||
{ x: 100, y: 50 });
|
||||
expect(mapSize).toEqual({ x: 2200, y: 1100 });
|
||||
expect(mapSize).toEqual({ h: 1600, w: 3200 });
|
||||
});
|
||||
|
||||
it("calculates map size: X&Y Swapped", () => {
|
||||
const fakeMPT = fakeMapTransformProps();
|
||||
fakeMPT.xySwap = true;
|
||||
const mapSize = getMapSize(
|
||||
fakeMPT,
|
||||
{ x: 100, y: 50 });
|
||||
expect(mapSize).toEqual({ h: 3200, w: 1600 });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -184,10 +193,17 @@ describe("transformXY", () => {
|
|||
|
||||
const transformCheck =
|
||||
(original: QXY, transformed: QXY, transformProps: MapTransformProps) => {
|
||||
transformProps.xySwap = false;
|
||||
expect(transformXY(original.qx, original.qy, transformProps))
|
||||
.toEqual(transformed);
|
||||
expect(transformXY(transformed.qx, transformed.qy, transformProps))
|
||||
.toEqual(original);
|
||||
transformProps.xySwap = true;
|
||||
const transformedYX = { qx: transformed.qy, qy: transformed.qx };
|
||||
expect(transformXY(original.qx, original.qy, transformProps))
|
||||
.toEqual(transformedYX);
|
||||
expect(transformXY(transformed.qx, transformed.qy, transformProps))
|
||||
.toEqual({ qx: original.qy, qy: original.qx });
|
||||
};
|
||||
|
||||
it("calculates transformed coordinate: quadrant 2", () => {
|
||||
|
|
|
@ -4,6 +4,7 @@ import { BotExtentsProps } from "./interfaces";
|
|||
|
||||
export function BotExtents(props: BotExtentsProps) {
|
||||
const { stopAtHome, botSize, mapTransformProps } = props;
|
||||
const { xySwap } = mapTransformProps;
|
||||
const homeLength = transformXY(
|
||||
botSize.x.value, botSize.y.value, mapTransformProps);
|
||||
const homeZero = transformXY(2, 2, mapTransformProps);
|
||||
|
@ -27,12 +28,12 @@ export function BotExtents(props: BotExtentsProps) {
|
|||
}
|
||||
</g>
|
||||
<g id="max-lines">
|
||||
{!botSize.x.isDefault &&
|
||||
{(xySwap ? !botSize.y.isDefault : !botSize.x.isDefault) &&
|
||||
<line
|
||||
x1={homeLength.qx} y1={homeZero.qy}
|
||||
x2={homeLength.qx} y2={homeLength.qy} />
|
||||
}
|
||||
{!botSize.y.isDefault &&
|
||||
{(xySwap ? !botSize.x.isDefault : !botSize.y.isDefault) &&
|
||||
<line
|
||||
x1={homeZero.qx} y1={homeLength.qy}
|
||||
x2={homeLength.qx} y2={homeLength.qy} />
|
||||
|
|
|
@ -55,7 +55,7 @@ export function DragHelpers(props: DragHelpersProps) {
|
|||
const {
|
||||
dragging, plant, zoomLvl, activeDragXY, mapTransformProps, plantAreaOffset
|
||||
} = props;
|
||||
const mapSize = getMapSize(mapTransformProps.gridSize, plantAreaOffset);
|
||||
const mapSize = getMapSize(mapTransformProps, plantAreaOffset);
|
||||
const { radius, x, y } = plant.body;
|
||||
|
||||
const scale = 1 + Math.round(15 * (1.8 - zoomLvl)) / 10; // scale factor
|
||||
|
@ -71,8 +71,8 @@ export function DragHelpers(props: DragHelpersProps) {
|
|||
</text>}
|
||||
{dragging && // Active plant
|
||||
<g id="long-crosshair">
|
||||
<rect x={qx - 0.5} y={-plantAreaOffset.y} width={1} height={mapSize.y} />
|
||||
<rect x={-plantAreaOffset.x} y={qy - 0.5} width={mapSize.x} height={1} />
|
||||
<rect x={qx - 0.5} y={-plantAreaOffset.y} width={1} height={mapSize.h} />
|
||||
<rect x={-plantAreaOffset.x} y={qy - 0.5} width={mapSize.w} height={1} />
|
||||
</g>}
|
||||
{dragging && // Active plant
|
||||
<g id="short-crosshair">
|
||||
|
|
|
@ -263,16 +263,19 @@ export class GardenMap extends
|
|||
const plant = this.getPlant();
|
||||
const map = document.querySelector(".farm-designer-map");
|
||||
const { gridSize } = this.props;
|
||||
const { quadrant, xySwap } = this.mapTransformProps;
|
||||
if (this.state.isDragging && plant && map) {
|
||||
const zoomLvl = parseFloat(window.getComputedStyle(map).zoom || "1");
|
||||
const { qx, qy } = transformXY(e.pageX, e.pageY, this.mapTransformProps);
|
||||
const deltaX = Math.round((qx - (this.state.pageX || qx)) / zoomLvl);
|
||||
const deltaY = Math.round((qy - (this.state.pageY || qy)) / zoomLvl);
|
||||
const dX = xySwap && (quadrant % 2 === 1) ? -deltaX : deltaX;
|
||||
const dY = xySwap && (quadrant % 2 === 1) ? -deltaY : deltaY;
|
||||
this.setState({
|
||||
pageX: qx, pageY: qy,
|
||||
activeDragXY: { x: plant.body.x + deltaX, y: plant.body.y + deltaY, z: 0 }
|
||||
activeDragXY: { x: plant.body.x + dX, y: plant.body.y + dY, z: 0 }
|
||||
});
|
||||
this.props.dispatch(movePlant({ deltaX, deltaY, plant, gridSize }));
|
||||
this.props.dispatch(movePlant({ deltaX: dX, deltaY: dY, plant, gridSize }));
|
||||
}
|
||||
break;
|
||||
case Mode.boxSelect:
|
||||
|
@ -312,13 +315,14 @@ export class GardenMap extends
|
|||
|
||||
render() {
|
||||
const { gridSize } = this.props;
|
||||
const mapSize = getMapSize(gridSize, this.props.gridOffset);
|
||||
const mapSize = getMapSize(this.mapTransformProps, this.props.gridOffset);
|
||||
const mapTransformProps = this.mapTransformProps;
|
||||
const { xySwap } = mapTransformProps;
|
||||
return <div
|
||||
className="drop-area"
|
||||
style={{
|
||||
height: mapSize.y + "px", maxHeight: mapSize.y + "px",
|
||||
width: mapSize.x + "px", maxWidth: mapSize.x + "px"
|
||||
height: mapSize.h + "px", maxHeight: mapSize.h + "px",
|
||||
width: mapSize.w + "px", maxWidth: mapSize.w + "px"
|
||||
}}
|
||||
onDrop={this.handleDrop}
|
||||
onDragEnter={this.handleDragEnter}
|
||||
|
@ -334,7 +338,8 @@ export class GardenMap extends
|
|||
<svg
|
||||
id="drop-area-svg"
|
||||
x={this.props.gridOffset.x} y={this.props.gridOffset.y}
|
||||
width={gridSize.x} height={gridSize.y}
|
||||
width={xySwap ? gridSize.y : gridSize.x}
|
||||
height={xySwap ? gridSize.x : gridSize.y}
|
||||
onMouseUp={this.endDrag}
|
||||
onMouseDown={this.startDrag}
|
||||
onMouseMove={this.drag}
|
||||
|
|
|
@ -6,7 +6,9 @@ import { Color } from "../../ui/index";
|
|||
|
||||
export function Grid(props: GridProps) {
|
||||
const { mapTransformProps } = props;
|
||||
const { gridSize } = mapTransformProps;
|
||||
const { gridSize, xySwap } = mapTransformProps;
|
||||
const gridSizeW = xySwap ? gridSize.y : gridSize.x;
|
||||
const gridSizeH = xySwap ? gridSize.x : gridSize.y;
|
||||
const origin = transformXY(0, 0, mapTransformProps);
|
||||
const arrowEnd = transformXY(25, 25, mapTransformProps);
|
||||
const xLabel = transformXY(15, -10, mapTransformProps);
|
||||
|
@ -34,10 +36,10 @@ export function Grid(props: GridProps) {
|
|||
|
||||
<g id="grid">
|
||||
<rect id="minor-grid"
|
||||
width={gridSize.x} height={gridSize.y} fill="url(#minor_grid)" />
|
||||
width={gridSizeW} height={gridSizeH} fill="url(#minor_grid)" />
|
||||
<rect id="major-grid" transform={transformForQuadrant(mapTransformProps)}
|
||||
width={gridSize.x} height={gridSize.y} fill="url(#major_grid)" />
|
||||
<rect id="border" width={gridSize.x} height={gridSize.y} fill="none"
|
||||
width={gridSizeW} height={gridSizeH} fill="url(#major_grid)" />
|
||||
<rect id="border" width={gridSizeW} height={gridSizeH} fill="none"
|
||||
stroke="rgba(0,0,0,0.3)" strokeWidth={2} />
|
||||
</g>
|
||||
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import * as React from "react";
|
||||
import { MapBackgroundProps } from "./interfaces";
|
||||
import { Color } from "../../ui/index";
|
||||
import { getMapSize } from "./util";
|
||||
|
||||
export function MapBackground(props: MapBackgroundProps) {
|
||||
const { mapTransformProps, plantAreaOffset } = props;
|
||||
const { gridSize } = mapTransformProps;
|
||||
const { gridSize, xySwap } = mapTransformProps;
|
||||
const gridSizeW = xySwap ? gridSize.y : gridSize.x;
|
||||
const gridSizeH = xySwap ? gridSize.x : gridSize.y;
|
||||
const boardWidth = 20;
|
||||
const mapWidth = gridSize.x + plantAreaOffset.x * 2;
|
||||
const mapHeight = gridSize.y + plantAreaOffset.y * 2;
|
||||
const mapSize = getMapSize(mapTransformProps, plantAreaOffset);
|
||||
return <g id="map-background">
|
||||
<defs>
|
||||
<pattern id="diagonalHatch"
|
||||
|
@ -18,16 +20,16 @@ export function MapBackground(props: MapBackgroundProps) {
|
|||
</defs>
|
||||
|
||||
<rect id="bed-border"
|
||||
x={0} y={0} width={mapWidth} height={mapHeight}
|
||||
x={0} y={0} width={mapSize.w} height={mapSize.h}
|
||||
fill={Color.soilBackground} />
|
||||
<rect id="bed-interior" x={boardWidth / 2} y={boardWidth / 2}
|
||||
width={mapWidth - boardWidth} height={mapHeight - boardWidth}
|
||||
width={mapSize.w - boardWidth} height={mapSize.h - boardWidth}
|
||||
stroke="rgba(120,63,4,0.25)" strokeWidth={boardWidth}
|
||||
fill={Color.soilBackground} />
|
||||
<rect id="no-access-perimeter" x={boardWidth} y={boardWidth}
|
||||
width={mapWidth - boardWidth * 2} height={mapHeight - boardWidth * 2}
|
||||
width={mapSize.w - boardWidth * 2} height={mapSize.h - boardWidth * 2}
|
||||
fill="url(#diagonalHatch)" />
|
||||
<rect id="grid-fill" x={plantAreaOffset.x} y={plantAreaOffset.y}
|
||||
width={gridSize.x} height={gridSize.y} fill={Color.gridSoil} />
|
||||
width={gridSizeW} height={gridSizeH} fill={Color.gridSoil} />
|
||||
</g>;
|
||||
}
|
||||
|
|
|
@ -22,13 +22,16 @@ export interface ToolSlotGraphicProps {
|
|||
y: number;
|
||||
pulloutDirection: ToolPulloutDirection;
|
||||
quadrant: BotOriginQuadrant;
|
||||
xySwap: boolean;
|
||||
}
|
||||
|
||||
const toolbaySlotAngle = (
|
||||
pulloutDirection: ToolPulloutDirection,
|
||||
quadrant: BotOriginQuadrant) => {
|
||||
quadrant: BotOriginQuadrant,
|
||||
xySwap: boolean) => {
|
||||
const rawAngle = () => {
|
||||
switch (pulloutDirection) {
|
||||
const direction = pulloutDirection + (xySwap ? 2 : 0);
|
||||
switch (direction > 4 ? direction % 4 : direction) {
|
||||
case ToolPulloutDirection.POSITIVE_X: return 0;
|
||||
case ToolPulloutDirection.NEGATIVE_X: return 180;
|
||||
case ToolPulloutDirection.NEGATIVE_Y: return 90;
|
||||
|
@ -56,8 +59,8 @@ export enum ToolNames {
|
|||
}
|
||||
|
||||
export const ToolbaySlot = (props: ToolSlotGraphicProps) => {
|
||||
const { id, x, y, pulloutDirection, quadrant } = props;
|
||||
const angle = toolbaySlotAngle(pulloutDirection, quadrant);
|
||||
const { id, x, y, pulloutDirection, quadrant, xySwap } = props;
|
||||
const angle = toolbaySlotAngle(pulloutDirection, quadrant, xySwap);
|
||||
return <g id={"toolbay-slot"}>
|
||||
<defs>
|
||||
<g id={"toolbay-slot-" + id}
|
||||
|
|
|
@ -12,9 +12,11 @@ enum Anchor {
|
|||
|
||||
export const textAnchorPosition = (
|
||||
pulloutDirection: ToolPulloutDirection,
|
||||
quadrant: BotOriginQuadrant): { x: number, y: number, anchor: string } => {
|
||||
quadrant: BotOriginQuadrant,
|
||||
xySwap: boolean): { x: number, y: number, anchor: string } => {
|
||||
const rawAnchor = () => {
|
||||
switch (pulloutDirection) {
|
||||
const direction = pulloutDirection + (xySwap ? 2 : 0);
|
||||
switch (direction > 4 ? direction % 4 : direction) {
|
||||
case ToolPulloutDirection.POSITIVE_X: return Anchor.start;
|
||||
case ToolPulloutDirection.NEGATIVE_X: return Anchor.end;
|
||||
case ToolPulloutDirection.NEGATIVE_Y: return Anchor.middleTop;
|
||||
|
@ -48,11 +50,12 @@ interface ToolLabelProps {
|
|||
y: number;
|
||||
pulloutDirection: ToolPulloutDirection;
|
||||
quadrant: BotOriginQuadrant;
|
||||
xySwap: boolean;
|
||||
}
|
||||
|
||||
export const ToolLabel = (props: ToolLabelProps) => {
|
||||
const { toolName, hovered, x, y, pulloutDirection, quadrant } = props;
|
||||
const labelAnchor = textAnchorPosition(pulloutDirection, quadrant);
|
||||
const { toolName, hovered, x, y, pulloutDirection, quadrant, xySwap } = props;
|
||||
const labelAnchor = textAnchorPosition(pulloutDirection, quadrant, xySwap);
|
||||
|
||||
return <text textAnchor={labelAnchor.anchor}
|
||||
visibility={hovered ? "visible" : "hidden"}
|
||||
|
|
|
@ -36,7 +36,7 @@ export class ToolSlotPoint extends
|
|||
render() {
|
||||
const { id, x, y, pullout_direction } = this.slot.toolSlot.body;
|
||||
const { mapTransformProps } = this.props;
|
||||
const { quadrant } = mapTransformProps;
|
||||
const { quadrant, xySwap } = mapTransformProps;
|
||||
const { qx, qy } = transformXY(x, y, this.props.mapTransformProps);
|
||||
const toolName = this.slot.tool ? this.slot.tool.body.name : "no tool";
|
||||
const toolProps = {
|
||||
|
@ -52,7 +52,8 @@ export class ToolSlotPoint extends
|
|||
x={qx}
|
||||
y={qy}
|
||||
pulloutDirection={pullout_direction}
|
||||
quadrant={quadrant} />}
|
||||
quadrant={quadrant}
|
||||
xySwap={xySwap} />}
|
||||
|
||||
{(this.slot.tool || !pullout_direction) &&
|
||||
<Tool
|
||||
|
@ -65,7 +66,8 @@ export class ToolSlotPoint extends
|
|||
x={qx}
|
||||
y={qy}
|
||||
pulloutDirection={pullout_direction}
|
||||
quadrant={quadrant} />
|
||||
quadrant={quadrant}
|
||||
xySwap={xySwap} />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ export function round(num: number) {
|
|||
* zoom level
|
||||
* quadrant
|
||||
* grid size
|
||||
* XY swap (coming soon)
|
||||
* XY swap
|
||||
*
|
||||
*/
|
||||
|
||||
|
@ -69,6 +69,7 @@ export function translateScreenToGarden(
|
|||
params: ScreenToGardenParams
|
||||
): XYCoordinate {
|
||||
const { page, scroll, zoomLvl, mapTransformProps, gridOffset } = params;
|
||||
const { xySwap } = mapTransformProps;
|
||||
const screenXY = page;
|
||||
const mapXY = ["x", "y"].reduce<XYCoordinate>(
|
||||
(result: XYCoordinate, axis: "x" | "y") => {
|
||||
|
@ -79,8 +80,11 @@ export function translateScreenToGarden(
|
|||
result[axis] = unscaled;
|
||||
return result;
|
||||
}, { x: 0, y: 0 });
|
||||
const gardenXY = quadTransform({ coordinate: mapXY, mapTransformProps });
|
||||
return gardenXY;
|
||||
const coordinate = xySwap ? { x: mapXY.y, y: mapXY.x } : mapXY;
|
||||
const gardenXY = transformXY(coordinate.x, coordinate.y, mapTransformProps);
|
||||
return xySwap
|
||||
? { x: gardenXY.qy, y: gardenXY.qx }
|
||||
: { x: gardenXY.qx, y: gardenXY.qy };
|
||||
}
|
||||
|
||||
/* BotOriginQuadrant diagram
|
||||
|
@ -112,7 +116,7 @@ interface QuadTransformParams {
|
|||
/** Quadrant coordinate transformation */
|
||||
function quadTransform(params: QuadTransformParams): XYCoordinate {
|
||||
const { coordinate, mapTransformProps } = params;
|
||||
const { quadrant, gridSize } = mapTransformProps;
|
||||
const { gridSize, quadrant } = mapTransformProps;
|
||||
if (isBotOriginQuadrant(quadrant)) {
|
||||
return ["x", "y"].reduce<XYCoordinate>(
|
||||
(result: XYCoordinate, axis: "x" | "y") => {
|
||||
|
@ -146,9 +150,24 @@ function quadTransform(params: QuadTransformParams): XYCoordinate {
|
|||
export function transformXY(
|
||||
x: number,
|
||||
y: number,
|
||||
mapTransformProps: MapTransformProps
|
||||
rawMapTransformProps: MapTransformProps
|
||||
): { qx: number, qy: number } {
|
||||
const transformed = quadTransform({ coordinate: { x, y }, mapTransformProps });
|
||||
const { quadrant, gridSize, xySwap } = rawMapTransformProps;
|
||||
const coordinate = {
|
||||
x: xySwap ? y : x,
|
||||
y: xySwap ? x : y,
|
||||
};
|
||||
const transformed = quadTransform({
|
||||
coordinate,
|
||||
mapTransformProps: {
|
||||
quadrant,
|
||||
gridSize: {
|
||||
x: xySwap ? gridSize.y : gridSize.x,
|
||||
y: xySwap ? gridSize.x : gridSize.y,
|
||||
},
|
||||
xySwap
|
||||
}
|
||||
});
|
||||
return {
|
||||
qx: transformed.x,
|
||||
qy: transformed.y
|
||||
|
@ -184,13 +203,18 @@ export function getBotSize(
|
|||
|
||||
/** Calculate map dimensions */
|
||||
export function getMapSize(
|
||||
gridSize: AxisNumberProperty,
|
||||
mapTransformProps: MapTransformProps,
|
||||
gridOffset: AxisNumberProperty
|
||||
): AxisNumberProperty {
|
||||
return {
|
||||
): { w: number, h: number } {
|
||||
const { gridSize, xySwap } = mapTransformProps;
|
||||
const mapSize = {
|
||||
x: gridSize.x + gridOffset.x * 2,
|
||||
y: gridSize.y + gridOffset.y * 2
|
||||
};
|
||||
return {
|
||||
w: xySwap ? mapSize.y : mapSize.x,
|
||||
h: xySwap ? mapSize.x : mapSize.y
|
||||
};
|
||||
}
|
||||
|
||||
/** Transform object based on selected map quadrant and grid size. */
|
||||
|
|
|
@ -17,21 +17,23 @@ describe("<BotFigure/>", () => {
|
|||
|
||||
function checkPositionForQuadrant(
|
||||
quadrant: BotOriginQuadrant,
|
||||
xySwap: boolean,
|
||||
expected: { x: number, y: number },
|
||||
name: string,
|
||||
opacity: number) {
|
||||
it(`shows ${name} in correct location for quadrant ${quadrant}`, () => {
|
||||
const p = fakeProps();
|
||||
p.mapTransformProps.quadrant = quadrant;
|
||||
p.mapTransformProps.xySwap = xySwap;
|
||||
p.name = name;
|
||||
const result = shallow(<BotFigure {...p} />);
|
||||
|
||||
const expectedGantryProps = expect.objectContaining({
|
||||
id: "gantry",
|
||||
x: expected.x - 10,
|
||||
y: -100,
|
||||
width: 20,
|
||||
height: 1700,
|
||||
x: xySwap ? -100 : expected.x - 10,
|
||||
y: xySwap ? expected.x - 10 : -100,
|
||||
width: xySwap ? 1700 : 20,
|
||||
height: xySwap ? 20 : 1700,
|
||||
fill: Color.darkGray,
|
||||
fillOpacity: opacity
|
||||
});
|
||||
|
@ -40,8 +42,8 @@ describe("<BotFigure/>", () => {
|
|||
|
||||
const expectedUTMProps = expect.objectContaining({
|
||||
id: "UTM",
|
||||
cx: expected.x,
|
||||
cy: expected.y,
|
||||
cx: xySwap ? expected.y : expected.x,
|
||||
cy: xySwap ? expected.x : expected.y,
|
||||
r: 35,
|
||||
fill: Color.darkGray,
|
||||
fillOpacity: opacity
|
||||
|
@ -51,11 +53,15 @@ describe("<BotFigure/>", () => {
|
|||
});
|
||||
}
|
||||
|
||||
checkPositionForQuadrant(1, { x: 3000, y: 0 }, "motors", 0.75);
|
||||
checkPositionForQuadrant(2, { x: 0, y: 0 }, "motors", 0.75);
|
||||
checkPositionForQuadrant(3, { x: 0, y: 1500 }, "motors", 0.75);
|
||||
checkPositionForQuadrant(4, { x: 3000, y: 1500 }, "motors", 0.75);
|
||||
checkPositionForQuadrant(2, { x: 0, y: 0 }, "encoders", 0.25);
|
||||
checkPositionForQuadrant(1, false, { x: 3000, y: 0 }, "motors", 0.75);
|
||||
checkPositionForQuadrant(2, false, { x: 0, y: 0 }, "motors", 0.75);
|
||||
checkPositionForQuadrant(3, false, { x: 0, y: 1500 }, "motors", 0.75);
|
||||
checkPositionForQuadrant(4, false, { x: 3000, y: 1500 }, "motors", 0.75);
|
||||
checkPositionForQuadrant(1, true, { x: 0, y: 1500 }, "motors", 0.75);
|
||||
checkPositionForQuadrant(2, true, { x: 0, y: 0 }, "motors", 0.75);
|
||||
checkPositionForQuadrant(3, true, { x: 3000, y: 0 }, "motors", 0.75);
|
||||
checkPositionForQuadrant(4, true, { x: 3000, y: 1500 }, "motors", 0.75);
|
||||
checkPositionForQuadrant(2, false, { x: 0, y: 0 }, "encoders", 0.25);
|
||||
|
||||
it("changes location", () => {
|
||||
const p = fakeProps();
|
||||
|
|
|
@ -59,6 +59,35 @@ describe("<BotPeripherals/>", () => {
|
|||
fill: "url(#LightingGradient)",
|
||||
height: 1700, width: 400, x: 0, y: -100
|
||||
});
|
||||
expect(wrapper.find("use").first().props()).toEqual({
|
||||
xlinkHref: "#light-half",
|
||||
transform: "rotate(0, 0, 750)"
|
||||
});
|
||||
expect(wrapper.find("use").last().props()).toEqual({
|
||||
xlinkHref: "#light-half",
|
||||
transform: "rotate(180, 0, 750)"
|
||||
});
|
||||
});
|
||||
|
||||
it("displays light: X&Y swapped", () => {
|
||||
const p = fakeProps();
|
||||
p.peripherals[0].label = "lights";
|
||||
p.peripherals[0].value = true;
|
||||
p.mapTransformProps.xySwap = true;
|
||||
const wrapper = shallow(<BotPeripherals {...p} />);
|
||||
expect(wrapper.find("#lights").length).toEqual(1);
|
||||
expect(wrapper.find("rect").last().props()).toEqual({
|
||||
fill: "url(#LightingGradient)",
|
||||
height: 1700, width: 400, x: -100, y: 0
|
||||
});
|
||||
expect(wrapper.find("use").first().props()).toEqual({
|
||||
xlinkHref: "#light-half",
|
||||
transform: "rotate(90, 750, 850)"
|
||||
});
|
||||
expect(wrapper.find("use").last().props()).toEqual({
|
||||
xlinkHref: "#light-half",
|
||||
transform: "rotate(270, -100, 0)"
|
||||
});
|
||||
});
|
||||
|
||||
it("displays water", () => {
|
||||
|
|
|
@ -26,18 +26,18 @@ export class BotFigure extends
|
|||
render() {
|
||||
const { name, position, plantAreaOffset, eStopStatus, mapTransformProps
|
||||
} = this.props;
|
||||
const mapSize = getMapSize(mapTransformProps.gridSize, plantAreaOffset);
|
||||
const { xySwap } = mapTransformProps;
|
||||
const mapSize = getMapSize(mapTransformProps, plantAreaOffset);
|
||||
const positionQ = transformXY(
|
||||
(position.x || 0), (position.y || 0), mapTransformProps);
|
||||
const color = eStopStatus ? Color.virtualRed : Color.darkGray;
|
||||
const opacity = name.includes("encoder") ? 0.25 : 0.75;
|
||||
return <g id={name}>
|
||||
<rect id="gantry"
|
||||
x={positionQ.qx - 10}
|
||||
y={-plantAreaOffset.y
|
||||
}
|
||||
width={20}
|
||||
height={mapSize.y}
|
||||
x={xySwap ? -plantAreaOffset.x : positionQ.qx - 10}
|
||||
y={xySwap ? positionQ.qy - 10 : -plantAreaOffset.y}
|
||||
width={xySwap ? mapSize.w : 20}
|
||||
height={xySwap ? 20 : mapSize.h}
|
||||
fillOpacity={opacity}
|
||||
fill={color} />
|
||||
<circle id="UTM"
|
||||
|
|
|
@ -5,6 +5,7 @@ import { BotPosition } from "../../../devices/interfaces";
|
|||
import * as _ from "lodash";
|
||||
import { Session } from "../../../session";
|
||||
import { BooleanSetting } from "../../../session_keys";
|
||||
import { trim } from "../../../util";
|
||||
|
||||
export interface BotPeripheralsProps {
|
||||
position: BotPosition;
|
||||
|
@ -14,8 +15,8 @@ export interface BotPeripheralsProps {
|
|||
}
|
||||
|
||||
function lightsFigure(
|
||||
props: { i: number, x: number, y: number, height: number }) {
|
||||
const { i, x, y, height } = props;
|
||||
props: { i: number, x: number, y: number, height: number, xySwap: boolean }) {
|
||||
const { i, x, y, height, xySwap } = props;
|
||||
const mapHeightMid = height / 2 + y;
|
||||
return <g id="lights" key={`peripheral_${i}`}>
|
||||
<defs>
|
||||
|
@ -34,9 +35,13 @@ function lightsFigure(
|
|||
</defs>
|
||||
|
||||
<use xlinkHref="#light-half"
|
||||
transform={`rotate(0, ${x}, ${mapHeightMid})`} />
|
||||
transform={trim(`rotate(${xySwap ? 90 : 0},
|
||||
${xySwap ? height / 2 + x : x},
|
||||
${mapHeightMid})`)} />
|
||||
<use xlinkHref="#light-half"
|
||||
transform={`rotate(180, ${x}, ${mapHeightMid})`} />
|
||||
transform={trim(`rotate(${xySwap ? 270 : 180},
|
||||
${x},
|
||||
${xySwap ? y : mapHeightMid})`)} />
|
||||
</g>;
|
||||
}
|
||||
|
||||
|
@ -117,7 +122,8 @@ function vacuumFigure(
|
|||
|
||||
export function BotPeripherals(props: BotPeripheralsProps) {
|
||||
const { peripherals, position, plantAreaOffset, mapTransformProps } = props;
|
||||
const mapSize = getMapSize(mapTransformProps.gridSize, plantAreaOffset);
|
||||
const { xySwap } = mapTransformProps;
|
||||
const mapSize = getMapSize(mapTransformProps, plantAreaOffset);
|
||||
const positionQ = transformXY(
|
||||
(position.x || 0), (position.y || 0), mapTransformProps);
|
||||
|
||||
|
@ -126,9 +132,10 @@ export function BotPeripherals(props: BotPeripheralsProps) {
|
|||
if (x.label.toLowerCase().includes("light") && x.value) {
|
||||
return lightsFigure({
|
||||
i,
|
||||
x: positionQ.qx,
|
||||
y: -plantAreaOffset.y,
|
||||
height: mapSize.y
|
||||
x: xySwap ? -plantAreaOffset.y : positionQ.qx,
|
||||
y: xySwap ? positionQ.qy : -plantAreaOffset.y,
|
||||
height: xySwap ? mapSize.w : mapSize.h,
|
||||
xySwap,
|
||||
});
|
||||
} else if (x.label.toLowerCase().includes("water") && x.value) {
|
||||
return waterFigure({
|
||||
|
|
Loading…
Reference in a new issue