garden map display improvements
This commit is contained in:
parent
b2170180f7
commit
46c3c35a29
|
@ -217,6 +217,7 @@
|
|||
.spread {
|
||||
animation: spread-pop 0.2s cubic-bezier(0, 0, 0, 1);
|
||||
transform-origin: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.garden-map-legend {
|
||||
|
|
|
@ -65,6 +65,9 @@ describe("<GardenPlant/>", () => {
|
|||
it("starts drag and sets activeSpread", async () => {
|
||||
const wrapper = shallow(<GardenMap {...fakeProps() } />);
|
||||
expect(wrapper.state()).toEqual({});
|
||||
Object.defineProperty(location, "pathname", {
|
||||
value: "/edit/"
|
||||
});
|
||||
await wrapper.find("#drop-area-svg").simulate("mouseDown");
|
||||
expect(wrapper.state()).toEqual({
|
||||
activeDragSpread: 1000,
|
||||
|
|
|
@ -13,12 +13,10 @@ describe("<GardenPlant/>", () => {
|
|||
plant: fakePlant(),
|
||||
selected: false,
|
||||
dragging: false,
|
||||
onClick: jest.fn(),
|
||||
dispatch: jest.fn(),
|
||||
zoomLvl: 1.8,
|
||||
activeDragXY: { x: undefined, y: undefined, z: undefined },
|
||||
activeDragSpread: undefined,
|
||||
plantAreaOffset: { x: 100, y: 100 }
|
||||
uuid: ""
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,8 +24,6 @@ describe("<GardenPlant/>", () => {
|
|||
const wrapper = shallow(<GardenPlant {...fakeProps() } />);
|
||||
expect(wrapper.find("image").length).toEqual(1);
|
||||
expect(wrapper.find("image").props().opacity).toEqual(1);
|
||||
expect(wrapper.find("Circle").length).toEqual(1);
|
||||
expect(wrapper.find("Circle").props().selected).toBeFalsy();
|
||||
expect(wrapper.find("text").length).toEqual(0);
|
||||
expect(wrapper.find("rect").length).toBeLessThanOrEqual(1);
|
||||
expect(wrapper.find("use").length).toEqual(0);
|
||||
|
|
|
@ -25,6 +25,7 @@ import { ToolSlotLayer } from "./layers/tool_slot_layer";
|
|||
import { HoveredPlantLayer } from "./layers/hovered_plant_layer";
|
||||
import { FarmBotLayer } from "./layers/farmbot_layer";
|
||||
import { cachedCrop } from "../../open_farm/index";
|
||||
import { DragHelperLayer } from "./layers/drag_helper_layer";
|
||||
|
||||
const DRAG_ERROR = `ERROR - Couldn't get zoom level of garden map, check the
|
||||
handleDrop() or drag() method in garden_map.tsx`;
|
||||
|
@ -59,10 +60,12 @@ export class GardenMap extends
|
|||
}
|
||||
|
||||
startDrag = (): void => {
|
||||
this.setState({ isDragging: true });
|
||||
const plant = this.getPlant();
|
||||
if (plant) {
|
||||
this.setActiveSpread(plant.body.openfarm_slug);
|
||||
if (this.isEditing) {
|
||||
this.setState({ isDragging: true });
|
||||
const plant = this.getPlant();
|
||||
if (plant) {
|
||||
this.setActiveSpread(plant.body.openfarm_slug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,11 +74,18 @@ export class GardenMap extends
|
|||
getPlant = (): TaggedPlantPointer | undefined => this.props.selectedPlant;
|
||||
|
||||
handleDragOver = (e: React.DragEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "move";
|
||||
if (!this.isEditing &&
|
||||
history.getCurrentLocation().pathname.split("/")[4] == "crop_search") {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "move";
|
||||
}
|
||||
}
|
||||
|
||||
handleDragEnter = (e: React.DragEvent<HTMLElement>) => e.preventDefault();
|
||||
handleDragEnter = (e: React.DragEvent<HTMLElement>) => {
|
||||
if (history.getCurrentLocation().pathname.split("/")[4] == "crop_search") {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
findCrop(slug?: string) {
|
||||
return findBySlug(this.props.designer.cropSearchResults || [], slug);
|
||||
|
@ -115,7 +125,9 @@ export class GardenMap extends
|
|||
radius: DEFAULT_PLANT_RADIUS
|
||||
})
|
||||
};
|
||||
this.props.dispatch(initSave(p));
|
||||
if (p.body.name != "name" && p.body.openfarm_slug != "slug") {
|
||||
this.props.dispatch(initSave(p));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Missing 'drop-area-svg', 'farm-designer-map', or
|
||||
|
@ -162,7 +174,9 @@ export class GardenMap extends
|
|||
}}
|
||||
onDrop={this.handleDrop}
|
||||
onDragEnter={this.handleDragEnter}
|
||||
onDragOver={this.handleDragOver}>
|
||||
onDragOver={this.handleDragOver}
|
||||
onMouseLeave={this.endDrag}
|
||||
onDragEnd={this.endDrag}>
|
||||
<svg
|
||||
id="map-background-svg">
|
||||
<MapBackground
|
||||
|
@ -182,7 +196,12 @@ export class GardenMap extends
|
|||
mapTransformProps={mapTransformProps}
|
||||
plants={this.props.plants}
|
||||
currentPlant={this.getPlant()}
|
||||
visible={!!this.props.showSpread} />
|
||||
visible={!!this.props.showSpread}
|
||||
dragging={!!this.state.isDragging}
|
||||
zoomLvl={this.props.zoomLvl}
|
||||
activeDragXY={this.state.activeDragXY}
|
||||
activeDragSpread={this.state.activeDragSpread}
|
||||
editing={!!this.isEditing} />
|
||||
<PointLayer
|
||||
mapTransformProps={mapTransformProps}
|
||||
visible={!!this.props.showPoints}
|
||||
|
@ -197,9 +216,7 @@ export class GardenMap extends
|
|||
dragging={!!this.state.isDragging}
|
||||
editing={!!this.isEditing}
|
||||
zoomLvl={this.props.zoomLvl}
|
||||
activeDragXY={this.state.activeDragXY}
|
||||
activeDragSpread={this.state.activeDragSpread}
|
||||
plantAreaOffset={this.props.gridOffset} />
|
||||
activeDragXY={this.state.activeDragXY} />
|
||||
<ToolSlotLayer
|
||||
mapTransformProps={mapTransformProps}
|
||||
visible={!!this.props.showFarmbot}
|
||||
|
@ -212,12 +229,21 @@ export class GardenMap extends
|
|||
botSize={this.props.botSize}
|
||||
plantAreaOffset={this.props.gridOffset} />
|
||||
<HoveredPlantLayer
|
||||
visible={!!this.props.showPlants}
|
||||
isEditing={this.isEditing}
|
||||
mapTransformProps={mapTransformProps}
|
||||
currentPlant={this.getPlant()}
|
||||
designer={this.props.designer}
|
||||
dispatch={this.props.dispatch}
|
||||
hoveredPlant={this.props.hoveredPlant} />
|
||||
hoveredPlant={this.props.hoveredPlant}
|
||||
dragging={!!this.state.isDragging} />
|
||||
<DragHelperLayer
|
||||
mapTransformProps={mapTransformProps}
|
||||
currentPlant={this.getPlant()}
|
||||
dragging={!!this.state.isDragging}
|
||||
editing={!!this.isEditing}
|
||||
zoomLvl={this.props.zoomLvl}
|
||||
activeDragXY={this.state.activeDragXY}
|
||||
plantAreaOffset={this.props.gridOffset} />
|
||||
</svg>
|
||||
</svg>
|
||||
</div>;
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import * as React from "react";
|
||||
import { GardenPlantProps, GardenPlantState } from "./interfaces";
|
||||
import { cachedCrop, DEFAULT_ICON, svgToUrl } from "../../open_farm/index";
|
||||
import { Circle } from "./circle";
|
||||
import { round, getXYFromQuadrant } from "./util";
|
||||
import { DragHelpers } from "./drag_helpers";
|
||||
import { SpreadOverlapHelper } from "./spread_overlap_helper";
|
||||
import { SpreadCircle } from "./layers/spread_layer";
|
||||
|
||||
export class GardenPlant extends
|
||||
React.Component<GardenPlantProps, Partial<GardenPlantState>> {
|
||||
|
@ -20,15 +17,22 @@ export class GardenPlant extends
|
|||
});
|
||||
}
|
||||
|
||||
click = () => {
|
||||
this.props.dispatch({ type: "SELECT_PLANT", payload: this.props.uuid });
|
||||
this.props.dispatch({
|
||||
type: "TOGGLE_HOVERED_PLANT", payload: {
|
||||
plantUUID: this.props.uuid, icon: this.state.icon
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selected, dragging, plant, onClick, dispatch,
|
||||
zoomLvl, activeDragXY, mapTransformProps, plantAreaOffset,
|
||||
activeDragSpread } = this.props;
|
||||
const { selected, dragging, plant, mapTransformProps,
|
||||
activeDragXY, zoomLvl } = this.props;
|
||||
const { quadrant, gridSize } = mapTransformProps;
|
||||
const { id, radius, x, y } = plant.body;
|
||||
const { icon } = this.state;
|
||||
|
||||
const action = { type: "TOGGLE_HOVERED_PLANT", payload: { plant, icon } };
|
||||
const { qx, qy } = getXYFromQuadrant(round(x), round(y), quadrant, gridSize);
|
||||
const alpha = dragging ? 0.4 : 1.0;
|
||||
|
||||
|
@ -42,48 +46,26 @@ export class GardenPlant extends
|
|||
fill="#90612f"
|
||||
fillOpacity="0" />
|
||||
|
||||
<SpreadOverlapHelper
|
||||
dragging={dragging}
|
||||
plant={plant}
|
||||
mapTransformProps={mapTransformProps}
|
||||
zoomLvl={zoomLvl}
|
||||
activeDragXY={activeDragXY}
|
||||
activeDragSpread={activeDragSpread} />
|
||||
|
||||
{selected &&
|
||||
<SpreadCircle
|
||||
plant={plant}
|
||||
key={plant.uuid}
|
||||
mapTransformProps={mapTransformProps} />}
|
||||
|
||||
<Circle
|
||||
className="plant-indicator"
|
||||
x={qx}
|
||||
y={qy}
|
||||
r={radius}
|
||||
selected={selected} />
|
||||
|
||||
<g id="plant-icon">
|
||||
<image
|
||||
visibility={dragging ? "hidden" : "visible"}
|
||||
className={"plant-image is-chosen-" + selected}
|
||||
opacity={alpha}
|
||||
xlinkHref={this.state.icon}
|
||||
onClick={() => onClick(this.props.plant)}
|
||||
onMouseEnter={() => dispatch(action)}
|
||||
onMouseLeave={() => dispatch(action)}
|
||||
xlinkHref={icon}
|
||||
onClick={this.click}
|
||||
height={radius * 2}
|
||||
width={radius * 2}
|
||||
x={qx - radius}
|
||||
y={qy - radius} />
|
||||
</g>
|
||||
|
||||
<DragHelpers
|
||||
<DragHelpers // for inactive plants
|
||||
dragging={dragging}
|
||||
plant={plant}
|
||||
mapTransformProps={mapTransformProps}
|
||||
zoomLvl={zoomLvl}
|
||||
activeDragXY={activeDragXY}
|
||||
plantAreaOffset={plantAreaOffset} />
|
||||
plantAreaOffset={{ x: 0, y: 0 }} />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,6 @@ export interface PlantLayerProps {
|
|||
mapTransformProps: MapTransformProps;
|
||||
zoomLvl: number;
|
||||
activeDragXY: BotPosition | undefined;
|
||||
activeDragSpread: number | undefined;
|
||||
plantAreaOffset: AxisNumberProperty;
|
||||
}
|
||||
|
||||
export interface CropSpreadDict {
|
||||
|
@ -49,11 +47,9 @@ export interface GardenPlantProps {
|
|||
plant: Readonly<TaggedPlantPointer>;
|
||||
selected: boolean;
|
||||
dragging: boolean;
|
||||
onClick: (plant: Readonly<TaggedPlantPointer>) => void;
|
||||
zoomLvl: number;
|
||||
activeDragXY: BotPosition | undefined;
|
||||
activeDragSpread: number | undefined;
|
||||
plantAreaOffset: AxisNumberProperty;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export interface GardenPlantState {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import * as React from "react";
|
||||
import { DragHelperLayer, DragHelperLayerProps } from "../drag_helper_layer";
|
||||
import { shallow } from "enzyme";
|
||||
import { fakePlant } from "../../../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("<DragHelperLayer/>", () => {
|
||||
function fakeProps(): DragHelperLayerProps {
|
||||
return {
|
||||
currentPlant: fakePlant(),
|
||||
editing: true,
|
||||
mapTransformProps: {
|
||||
quadrant: 2, gridSize: { x: 3000, y: 1500 }
|
||||
},
|
||||
dragging: true,
|
||||
zoomLvl: 1.8,
|
||||
activeDragXY: { x: undefined, y: undefined, z: undefined },
|
||||
plantAreaOffset: { x: 100, y: 100 }
|
||||
};
|
||||
}
|
||||
|
||||
it("shows drag helpers", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<DragHelperLayer {...p } />);
|
||||
expect(wrapper.html()).toContain("drag-helpers");
|
||||
expect(wrapper.html()).toContain("coordinates-tooltip");
|
||||
expect(wrapper.html()).toContain("long-crosshair");
|
||||
expect(wrapper.html()).toContain("short-crosshair");
|
||||
});
|
||||
|
||||
it("doesn't show drag helpers", () => {
|
||||
const p = fakeProps();
|
||||
p.editing = false;
|
||||
const wrapper = shallow(<DragHelperLayer {...p } />);
|
||||
expect(wrapper.html()).toEqual("<g id=\"drag-helper-layer\"></g>");
|
||||
});
|
||||
});
|
|
@ -1,10 +1,13 @@
|
|||
import * as React from "react";
|
||||
import { HoveredPlantLayer, HoveredPlantLayerProps } from "../hovered_plant_layer";
|
||||
import { shallow } from "enzyme";
|
||||
import { fakePlant } from "../../../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("<HoveredPlantLayer/>", () => {
|
||||
function fakeProps(): HoveredPlantLayerProps {
|
||||
return {
|
||||
visible: true,
|
||||
dragging: false,
|
||||
currentPlant: undefined,
|
||||
designer: {
|
||||
selectedPlant: undefined,
|
||||
|
@ -15,27 +18,41 @@ describe("<HoveredPlantLayer/>", () => {
|
|||
cropSearchQuery: "",
|
||||
cropSearchResults: []
|
||||
},
|
||||
hoveredPlant: undefined,
|
||||
dispatch: jest.fn(),
|
||||
hoveredPlant: fakePlant(),
|
||||
isEditing: false,
|
||||
mapTransformProps: {
|
||||
quadrant: 1, gridSize: { x: 3000, y: 1500 }
|
||||
quadrant: 2, gridSize: { x: 3000, y: 1500 }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
it("shows hovered plant icon", () => {
|
||||
const p = fakeProps();
|
||||
p.designer.hoveredPlant.icon = "fake icon";
|
||||
const wrapper = shallow(<HoveredPlantLayer {...p } />);
|
||||
wrapper.setState({ isHovered: true });
|
||||
const icon = wrapper.find("image");
|
||||
expect(icon.props().style).toEqual({ "transform": "scale(1.3, 1.3)" });
|
||||
const icon = wrapper.find("image").props();
|
||||
expect(icon.visibility).toBeTruthy();
|
||||
expect(icon.opacity).toEqual(1);
|
||||
expect(icon.x).toEqual(67.5);
|
||||
expect(icon.width).toEqual(65);
|
||||
});
|
||||
|
||||
it("shows selected plant indicators", () => {
|
||||
const p = fakeProps();
|
||||
p.designer.hoveredPlant.icon = "fake icon";
|
||||
p.currentPlant = fakePlant();
|
||||
const wrapper = shallow(<HoveredPlantLayer {...p } />);
|
||||
expect(wrapper.find("#selected-plant-indicators").length).toEqual(1);
|
||||
expect(wrapper.find("Circle").length).toEqual(1);
|
||||
expect(wrapper.find("Circle").props().selected).toBeTruthy();
|
||||
expect(wrapper.find("SpreadCircle").length).toEqual(1);
|
||||
expect(wrapper.find("SpreadCircle").html())
|
||||
.toContain("cx=\"100\" cy=\"200\" r=\"125\"");
|
||||
});
|
||||
|
||||
it("doesn't show hovered plant icon", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<HoveredPlantLayer {...p } />);
|
||||
const icon = wrapper.find("image").props();
|
||||
expect(icon.style).toEqual({ "transform": "scale(1, 1)" });
|
||||
expect(wrapper.html()).toEqual("<g id=\"hovered-plant-layer\"></g>");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,7 +11,12 @@ describe("<SpreadLayer/>", () => {
|
|||
currentPlant: undefined,
|
||||
mapTransformProps: {
|
||||
quadrant: 2, gridSize: { x: 3000, y: 1500 }
|
||||
}
|
||||
},
|
||||
dragging: false,
|
||||
zoomLvl: 1.8,
|
||||
activeDragXY: { x: undefined, y: undefined, z: undefined },
|
||||
activeDragSpread: undefined,
|
||||
editing: false
|
||||
};
|
||||
}
|
||||
|
||||
|
|
48
webpack/farm_designer/map/layers/drag_helper_layer.tsx
Normal file
48
webpack/farm_designer/map/layers/drag_helper_layer.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import * as React from "react";
|
||||
import { TaggedPlantPointer } from "../../../resources/tagged_resources";
|
||||
import { MapTransformProps, AxisNumberProperty } from "../interfaces";
|
||||
import { DragHelpers } from "../drag_helpers";
|
||||
import { BotPosition } from "../../../devices/interfaces";
|
||||
|
||||
/**
|
||||
* For showing drag helpers for the selected plant.
|
||||
* This layer must be rendered after the hovered plant layer.
|
||||
*/
|
||||
|
||||
export interface DragHelperLayerProps {
|
||||
currentPlant: TaggedPlantPointer | undefined;
|
||||
mapTransformProps: MapTransformProps;
|
||||
dragging: boolean;
|
||||
editing: boolean;
|
||||
zoomLvl: number;
|
||||
activeDragXY: BotPosition | undefined;
|
||||
plantAreaOffset: AxisNumberProperty;
|
||||
}
|
||||
|
||||
export class DragHelperLayer extends
|
||||
React.Component<DragHelperLayerProps, {}> {
|
||||
|
||||
get plantInfo() {
|
||||
if (this.props.currentPlant) {
|
||||
const { x, y, radius } = this.props.currentPlant.body;
|
||||
return { x, y, radius };
|
||||
} else {
|
||||
return { x: 0, y: 0, radius: 0 };
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const { dragging, currentPlant, zoomLvl, activeDragXY,
|
||||
mapTransformProps, plantAreaOffset, editing } = this.props;
|
||||
|
||||
return <g id="drag-helper-layer">
|
||||
{currentPlant && editing &&
|
||||
<DragHelpers // for active plants
|
||||
dragging={dragging}
|
||||
plant={currentPlant}
|
||||
mapTransformProps={mapTransformProps}
|
||||
zoomLvl={zoomLvl}
|
||||
activeDragXY={activeDragXY}
|
||||
plantAreaOffset={plantAreaOffset} />}
|
||||
</g>;
|
||||
}
|
||||
}
|
|
@ -2,78 +2,84 @@ import * as React from "react";
|
|||
import { TaggedPlantPointer } from "../../../resources/tagged_resources";
|
||||
import { DesignerState } from "../../interfaces";
|
||||
import { getXYFromQuadrant, round } from "../util";
|
||||
import { push } from "../../../history";
|
||||
import { MapTransformProps } from "../interfaces";
|
||||
import { SpreadCircle } from "./spread_layer";
|
||||
import { Circle } from "../circle";
|
||||
import * as _ from "lodash";
|
||||
|
||||
/**
|
||||
* PROBLEM: The plants are rendered via svg in a certain order. When a user
|
||||
* hovers over part of a plant they'd like to select that was rendered *prior*
|
||||
* to a different plant, it will cause an overlap and a less-than-desirable ux.
|
||||
*
|
||||
* SOLUTION: Use props to tell this component what plant is currently being
|
||||
* hovered over and make a "copy" to display on top of the rest of the layers.
|
||||
*
|
||||
* NOTE: This layer MUST be rendered LAST in its parent component to properly
|
||||
* achieve this effect.
|
||||
* For showing the map plant hovered in the plant panel.
|
||||
* This layer must be rendered after the plant layer
|
||||
* and before the drag helper layer.
|
||||
*/
|
||||
|
||||
export interface HoveredPlantLayerProps {
|
||||
visible: boolean;
|
||||
currentPlant: TaggedPlantPointer | undefined;
|
||||
designer: DesignerState;
|
||||
hoveredPlant: TaggedPlantPointer | undefined;
|
||||
dispatch: Function;
|
||||
isEditing: boolean;
|
||||
mapTransformProps: MapTransformProps;
|
||||
dragging: boolean;
|
||||
}
|
||||
|
||||
interface HoveredPlantLayerState { isHovered: boolean; }
|
||||
|
||||
export class HoveredPlantLayer extends
|
||||
React.Component<HoveredPlantLayerProps, Partial<HoveredPlantLayerState>> {
|
||||
|
||||
state: HoveredPlantLayerState = { isHovered: false };
|
||||
|
||||
onClick = () => {
|
||||
const plant = this.props.hoveredPlant;
|
||||
if (plant) {
|
||||
push("/app/designer/plants/" + (plant.body.id));
|
||||
const action = { type: "SELECT_PLANT", payload: plant.uuid };
|
||||
this.props.dispatch(action);
|
||||
}
|
||||
}
|
||||
|
||||
toggle = (bool: keyof HoveredPlantLayerState) => () =>
|
||||
this.setState({ isHovered: !this.state.isHovered })
|
||||
React.Component<HoveredPlantLayerProps, {}> {
|
||||
|
||||
/** Safe fallbacks if no hovered plant is found. */
|
||||
get plantInfo() {
|
||||
if (this.props.hoveredPlant) {
|
||||
const { x, y, radius } = this.props.hoveredPlant.body;
|
||||
return { x, y, radius };
|
||||
const { id, x, y, radius } = this.props.hoveredPlant.body;
|
||||
return { id, x, y, radius };
|
||||
} else {
|
||||
return { x: 0, y: 0, radius: 1 };
|
||||
return { id: 0, x: 0, y: 0, radius: 1 };
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { icon } = this.props.designer.hoveredPlant;
|
||||
const { quadrant, gridSize } = this.props.mapTransformProps;
|
||||
const { x, y } = this.plantInfo;
|
||||
const { currentPlant, mapTransformProps, dragging, isEditing } = this.props;
|
||||
const { quadrant, gridSize } = mapTransformProps;
|
||||
const { id, x, y, radius } = this.plantInfo;
|
||||
const { qx, qy } = getXYFromQuadrant(round(x), round(y), quadrant, gridSize);
|
||||
const scaleFactor = (this.state.isHovered) ? "1.3, 1.3" : "1, 1";
|
||||
const hovered = !!this.props.designer.hoveredPlant.icon;
|
||||
const scaledRadius = radius * 1.3;
|
||||
const alpha = dragging ? 0.4 : 1.0;
|
||||
|
||||
return <g id="hovered-plant-icon">
|
||||
<image
|
||||
visibility={this.props.isEditing ? "visible" : "hidden"}
|
||||
style={{ transform: "scale(" + scaleFactor + ")" }}
|
||||
className={"hovered-plant-copy"}
|
||||
x={qx - (this.plantInfo.radius)}
|
||||
y={qy - (this.plantInfo.radius)}
|
||||
onMouseEnter={this.toggle("isHovered")}
|
||||
onMouseLeave={this.toggle("isHovered")}
|
||||
onClick={this.onClick}
|
||||
width={this.plantInfo.radius * 2}
|
||||
height={this.plantInfo.radius * 2}
|
||||
xlinkHref={icon} />
|
||||
return <g id="hovered-plant-layer">
|
||||
{this.props.visible && hovered &&
|
||||
<g id={"hovered-plant-" + id}>
|
||||
{currentPlant &&
|
||||
<g id="selected-plant-indicators">
|
||||
<SpreadCircle
|
||||
plant={currentPlant}
|
||||
key={currentPlant.uuid}
|
||||
mapTransformProps={mapTransformProps}
|
||||
selected={false} />
|
||||
|
||||
<Circle
|
||||
className="plant-indicator"
|
||||
x={qx}
|
||||
y={qy}
|
||||
r={radius}
|
||||
selected={true} />
|
||||
</g>}
|
||||
|
||||
<g id="hovered-plant-icon">
|
||||
<image
|
||||
visibility={hovered ? "visible" : "hidden"}
|
||||
style={isEditing ? {} : { pointerEvents: "none" }}
|
||||
onClick={_.noop}
|
||||
className="hovered-plant-copy"
|
||||
opacity={alpha}
|
||||
x={qx - scaledRadius}
|
||||
y={qy - scaledRadius}
|
||||
width={scaledRadius * 2}
|
||||
height={scaledRadius * 2}
|
||||
xlinkHref={icon} />
|
||||
</g>
|
||||
</g>
|
||||
}
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,7 @@ export function PlantLayer(props: PlantLayerProps) {
|
|||
currentPlant,
|
||||
dragging,
|
||||
editing,
|
||||
mapTransformProps,
|
||||
plantAreaOffset
|
||||
mapTransformProps
|
||||
} = props;
|
||||
|
||||
crops
|
||||
|
@ -45,7 +44,6 @@ export function PlantLayer(props: PlantLayerProps) {
|
|||
};
|
||||
})
|
||||
.map(p => {
|
||||
const action = { type: "SELECT_PLANT", payload: p.uuid };
|
||||
return <Link className="plant-link-wrapper"
|
||||
style={maybeNoPointer}
|
||||
to={"/app/designer/plants/" + p.plantId}
|
||||
|
@ -53,16 +51,14 @@ export function PlantLayer(props: PlantLayerProps) {
|
|||
onClick={_.noop}
|
||||
key={p.plantId}>
|
||||
<GardenPlant
|
||||
uuid={p.uuid}
|
||||
mapTransformProps={mapTransformProps}
|
||||
plant={p.plant}
|
||||
selected={p.selected}
|
||||
dragging={p.selected && dragging && editing}
|
||||
onClick={() => dispatch(action)}
|
||||
dispatch={props.dispatch}
|
||||
dispatch={dispatch}
|
||||
zoomLvl={props.zoomLvl}
|
||||
activeDragXY={props.activeDragXY}
|
||||
activeDragSpread={props.activeDragSpread}
|
||||
plantAreaOffset={plantAreaOffset} />
|
||||
activeDragXY={props.activeDragXY} />
|
||||
</Link>;
|
||||
})}
|
||||
</g>;
|
||||
|
|
|
@ -4,16 +4,24 @@ import { TaggedPlantPointer } from "../../../resources/tagged_resources";
|
|||
import { round, getXYFromQuadrant } from "../util";
|
||||
import { cachedCrop } from "../../../open_farm/index";
|
||||
import { MapTransformProps } from "../interfaces";
|
||||
import { SpreadOverlapHelper } from "../spread_overlap_helper";
|
||||
import { BotPosition } from "../../../devices/interfaces";
|
||||
|
||||
export interface SpreadLayerProps {
|
||||
visible: boolean;
|
||||
plants: TaggedPlantPointer[];
|
||||
currentPlant: TaggedPlantPointer | undefined;
|
||||
mapTransformProps: MapTransformProps;
|
||||
dragging: boolean;
|
||||
zoomLvl: number;
|
||||
activeDragXY: BotPosition | undefined;
|
||||
activeDragSpread: number | undefined;
|
||||
editing: boolean;
|
||||
}
|
||||
|
||||
export function SpreadLayer(props: SpreadLayerProps) {
|
||||
const { plants, visible, mapTransformProps } = props;
|
||||
const { plants, visible, mapTransformProps, currentPlant,
|
||||
dragging, zoomLvl, activeDragXY, activeDragSpread, editing } = props;
|
||||
return (
|
||||
<g id="spread-layer">
|
||||
<defs>
|
||||
|
@ -23,13 +31,26 @@ export function SpreadLayer(props: SpreadLayerProps) {
|
|||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
{visible &&
|
||||
plants.map((p, index) => {
|
||||
return <SpreadCircle
|
||||
|
||||
{plants.map((p, index) => {
|
||||
const selected = !!(currentPlant && (p.uuid === currentPlant.uuid));
|
||||
return <g id={"spread-components-" + p.body.id} key={p.uuid}>
|
||||
{visible &&
|
||||
<SpreadCircle
|
||||
plant={p}
|
||||
key={"spread-" + p.uuid}
|
||||
mapTransformProps={mapTransformProps}
|
||||
selected={selected} />}
|
||||
<SpreadOverlapHelper
|
||||
key={"overlap-" + p.uuid}
|
||||
dragging={selected && dragging && editing}
|
||||
plant={p}
|
||||
key={p.uuid}
|
||||
mapTransformProps={mapTransformProps} />;
|
||||
})
|
||||
mapTransformProps={mapTransformProps}
|
||||
zoomLvl={zoomLvl}
|
||||
activeDragXY={activeDragXY}
|
||||
activeDragSpread={activeDragSpread} />
|
||||
</g>;
|
||||
})
|
||||
}
|
||||
</g>
|
||||
);
|
||||
|
@ -38,6 +59,7 @@ export function SpreadLayer(props: SpreadLayerProps) {
|
|||
interface SpreadCircleProps {
|
||||
plant: TaggedPlantPointer;
|
||||
mapTransformProps: MapTransformProps;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
interface SpreadCircleState {
|
||||
|
@ -56,18 +78,21 @@ export class SpreadCircle extends
|
|||
|
||||
render() {
|
||||
const { radius, x, y, id } = this.props.plant.body;
|
||||
const { quadrant, gridSize } = this.props.mapTransformProps;
|
||||
const { selected, mapTransformProps } = this.props;
|
||||
const { quadrant, gridSize } = mapTransformProps;
|
||||
const { qx, qy } = getXYFromQuadrant(round(x), round(y), quadrant, gridSize);
|
||||
|
||||
return <g id={"spread-" + id}>
|
||||
<circle
|
||||
className="spread"
|
||||
id={"spread-" + id}
|
||||
cx={qx}
|
||||
cy={qy}
|
||||
// Convert `spread` from diameter in cm to radius in mm.
|
||||
// `radius * 10` is the default value for spread diameter (in mm).
|
||||
r={(this.state.spread || radius) / 2 * 10}
|
||||
fill={"url(#SpreadGradient)"} />
|
||||
{!selected &&
|
||||
<circle
|
||||
className="spread"
|
||||
id={"spread-" + id}
|
||||
cx={qx}
|
||||
cy={qy}
|
||||
// Convert `spread` from diameter in cm to radius in mm.
|
||||
// `radius * 10` is the default value for spread diameter (in mm).
|
||||
r={(this.state.spread || radius) / 2 * 10}
|
||||
fill={"url(#SpreadGradient)"} />}
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ export class SpreadOverlapHelper extends
|
|||
// should be thought of as a tool checking the inactive plants, not
|
||||
// the plant being edited. Dragging a plant with a small spread into
|
||||
// the area of a plant with large spread will illustrate this point.
|
||||
return evaluateOverlap(overlap, inactiveSpreadRadius);
|
||||
return evaluateOverlap(overlap, comparisonRadius);
|
||||
}
|
||||
return { color: Overlap.NONE, value: 0 };
|
||||
}
|
||||
|
@ -108,14 +108,20 @@ export class SpreadOverlapHelper extends
|
|||
const inactiveSpreadRadius = (this.state.inactiveSpread || (radius * 10)) / 2;
|
||||
|
||||
const overlapData = getOverlap(activeDragXY, gardenCoord);
|
||||
// Radius to use to evaluate overlap severity.
|
||||
// For impact of active plant on inactive plants, use `inactiveSpreadRadius`
|
||||
// For impact of inactive plants on active plant, use `activeSpreadRadius`
|
||||
// For worst case, use `Math.min(activeSpreadRadius, inactiveSpreadRadius)`
|
||||
// For lesser case, use `Math.max(activeSpreadRadius, inactiveSpreadRadius)`
|
||||
const comparisonRadius = inactiveSpreadRadius;
|
||||
const debug = false; // change to true to show % overlap values
|
||||
|
||||
function getColor() {
|
||||
// Smoothly vary color based on overlap from dark green > yellow > orange > red
|
||||
if (overlapData.value > 0) {
|
||||
const normalized = Math.round(
|
||||
Math.max(0, Math.min(inactiveSpreadRadius, overlapData.value))
|
||||
/ (inactiveSpreadRadius) * 255 * 2);
|
||||
Math.max(0, Math.min(comparisonRadius, overlapData.value))
|
||||
/ (comparisonRadius) * 255 * 2);
|
||||
if (normalized < 255) { // green to yellow
|
||||
const r = Math.min(normalized, 255);
|
||||
const g = Math.min(100 + normalized, 255); // dark instead of bright green
|
||||
|
@ -130,7 +136,7 @@ export class SpreadOverlapHelper extends
|
|||
}
|
||||
}
|
||||
|
||||
return <g id="overlap-circle">
|
||||
return <g id="overlap-indicator">
|
||||
{!dragging && // Non-active plants
|
||||
<circle
|
||||
className="overlap-circle"
|
||||
|
|
|
@ -11,7 +11,14 @@ import { PlantPanel } from "./plant_panel";
|
|||
export class PlantInfo extends PlantInfoBase {
|
||||
|
||||
default = (plant_info: TaggedPlantPointer) => {
|
||||
const action = { type: "SELECT_PLANT", payload: undefined };
|
||||
const click = () => {
|
||||
this.props.dispatch({ type: "SELECT_PLANT", payload: undefined });
|
||||
this.props.dispatch({
|
||||
type: "TOGGLE_HOVERED_PLANT", payload: {
|
||||
plantUUID: undefined, icon: undefined
|
||||
}
|
||||
});
|
||||
};
|
||||
const info = formatPlantInfo(plant_info);
|
||||
const { name, id } = info;
|
||||
return <div className="panel-container green-panel" >
|
||||
|
@ -20,7 +27,7 @@ export class PlantInfo extends PlantInfoBase {
|
|||
<Link to="/app/designer/plants" className="back-arrow">
|
||||
<i
|
||||
className="fa fa-arrow-left"
|
||||
onClick={() => this.props.dispatch(action)} />
|
||||
onClick={click} />
|
||||
</Link>
|
||||
<span className="title">
|
||||
{name}
|
||||
|
|
|
@ -27,9 +27,24 @@ export class PlantInventoryItem extends
|
|||
const { tpp, dispatch } = this.props;
|
||||
const plantId = (plant.id || "ERR_NO_PLANT_ID").toString();
|
||||
|
||||
const toggle = () => {
|
||||
const { icon } = this.state;
|
||||
dispatch({ type: "TOGGLE_HOVERED_PLANT", payload: { plant: tpp, icon } });
|
||||
const toggle = (action: "enter" | "leave") => {
|
||||
switch (action) {
|
||||
case "enter":
|
||||
const { icon } = this.state;
|
||||
dispatch({
|
||||
type: "TOGGLE_HOVERED_PLANT", payload: {
|
||||
plantUUID: tpp.uuid, icon
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "leave":
|
||||
dispatch({
|
||||
type: "TOGGLE_HOVERED_PLANT", payload: {
|
||||
plantUUID: undefined, icon: undefined
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const click = () => {
|
||||
|
@ -60,8 +75,8 @@ export class PlantInventoryItem extends
|
|||
return <div
|
||||
className="plant-search-item"
|
||||
key={plantId}
|
||||
onMouseEnter={toggle}
|
||||
onMouseLeave={toggle}
|
||||
onMouseEnter={() => toggle("enter")}
|
||||
onMouseLeave={() => toggle("leave")}
|
||||
onClick={click}>
|
||||
<img
|
||||
className="plant-search-item-image"
|
||||
|
|
Loading…
Reference in a new issue