garden map display improvements

This commit is contained in:
gabrielburnworth 2017-09-14 01:02:08 -07:00
parent b2170180f7
commit 46c3c35a29
16 changed files with 316 additions and 151 deletions

View file

@ -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 {

View file

@ -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,

View file

@ -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);

View file

@ -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>;

View file

@ -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>;
}
}

View file

@ -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 {

View file

@ -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>");
});
});

View file

@ -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>");
});
});

View file

@ -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
};
}

View 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>;
}
}

View file

@ -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>;
}
}

View file

@ -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>;

View file

@ -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>;
}
}

View file

@ -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"

View file

@ -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}

View file

@ -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"