multiple plant selection and deletion ui
parent
6e37de86de
commit
a77e96c6bf
|
@ -121,6 +121,9 @@
|
|||
color: $white;
|
||||
}
|
||||
}
|
||||
button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.plant-inventory-panel {
|
||||
|
@ -129,6 +132,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.plant-selection-panel {
|
||||
.panel-content {
|
||||
padding: 10rem 1.4rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.crop-catalog-panel {
|
||||
.back-arrow {
|
||||
margin-top: 0rem;
|
||||
|
|
|
@ -12,7 +12,7 @@ describe("<FarmDesigner/>", () => {
|
|||
dispatch: jest.fn(),
|
||||
selectedPlant: undefined,
|
||||
designer: {
|
||||
selectedPlant: undefined,
|
||||
selectedPlants: undefined,
|
||||
hoveredPlant: {
|
||||
plantUUID: undefined,
|
||||
icon: ""
|
||||
|
|
|
@ -15,6 +15,7 @@ import { BotPosition, StepsPerMmXY } from "../devices/interfaces";
|
|||
import { isNumber } from "lodash";
|
||||
import { McuParams } from "farmbot/dist";
|
||||
import { AxisNumberProperty, BotSize } from "./map/interfaces";
|
||||
import { SelectionBoxData } from "./map/selection_box";
|
||||
|
||||
/** TODO: Use Enums */
|
||||
export type BotOriginQuadrant = 1 | 2 | 3 | 4;
|
||||
|
@ -94,7 +95,7 @@ export interface Crop {
|
|||
}
|
||||
|
||||
export interface DesignerState {
|
||||
selectedPlant: string | undefined;
|
||||
selectedPlants: string[] | undefined;
|
||||
hoveredPlant: HoveredPlantPayl;
|
||||
cropSearchQuery: string;
|
||||
cropSearchResults: CropLiveSearchResult[];
|
||||
|
@ -180,6 +181,7 @@ export interface GardenMapState {
|
|||
pageY: number | undefined;
|
||||
activeDragXY: BotPosition | undefined;
|
||||
activeDragSpread: number | undefined;
|
||||
selectionBox: SelectionBoxData | undefined;
|
||||
}
|
||||
|
||||
export type PlantOptions = Partial<PlantPointer>;
|
||||
|
|
|
@ -27,7 +27,7 @@ describe("<GardenPlant/>", () => {
|
|||
crops: [],
|
||||
dispatch: jest.fn(),
|
||||
designer: {
|
||||
selectedPlant: "",
|
||||
selectedPlants: undefined,
|
||||
hoveredPlant: {
|
||||
plantUUID: "",
|
||||
icon: ""
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import * as React from "react";
|
||||
import { SelectionBox, SelectionBoxProps } from "../selection_box";
|
||||
import { shallow } from "enzyme";
|
||||
|
||||
describe("<SelectionBox/>", () => {
|
||||
function fakeProps(): SelectionBoxProps {
|
||||
return {
|
||||
selectionBox: {
|
||||
x0: 40,
|
||||
y0: 30,
|
||||
x1: 240,
|
||||
y1: 130
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
it("renders selection box", () => {
|
||||
const wrapper = shallow(<SelectionBox {...fakeProps() } />);
|
||||
const boxProps = wrapper.find("rect").props();
|
||||
expect(boxProps.x).toEqual(40);
|
||||
expect(boxProps.y).toEqual(30);
|
||||
expect(boxProps.width).toEqual(200);
|
||||
expect(boxProps.height).toEqual(100);
|
||||
});
|
||||
|
||||
it("doesn't render selection box: partially undefined", () => {
|
||||
const p = fakeProps();
|
||||
p.selectionBox.x1 = undefined;
|
||||
const wrapper = shallow(<SelectionBox {...p } />);
|
||||
expect(wrapper.html()).toEqual("<g id=\"selection-box\"></g>");
|
||||
});
|
||||
|
||||
});
|
|
@ -26,6 +26,8 @@ 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";
|
||||
import { AxisNumberProperty } from "./interfaces";
|
||||
import { SelectionBox, SelectionBoxData } from "./selection_box";
|
||||
|
||||
const DRAG_ERROR = `ERROR - Couldn't get zoom level of garden map, check the
|
||||
handleDrop() or drag() method in garden_map.tsx`;
|
||||
|
@ -46,7 +48,8 @@ export class GardenMap extends
|
|||
this.setState({
|
||||
isDragging: false, pageX: 0, pageY: 0,
|
||||
activeDragXY: { x: undefined, y: undefined, z: undefined },
|
||||
activeDragSpread: undefined
|
||||
activeDragSpread: undefined,
|
||||
selectionBox: undefined
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -59,7 +62,29 @@ export class GardenMap extends
|
|||
);
|
||||
}
|
||||
|
||||
startDrag = (): void => {
|
||||
getGardenCoordinates(
|
||||
e: React.DragEvent<HTMLElement> | React.MouseEvent<SVGElement>
|
||||
): AxisNumberProperty | undefined {
|
||||
const el = document.querySelector("div.drop-area svg[id='drop-area-svg']");
|
||||
const map = document.querySelector(".farm-designer-map");
|
||||
const page = document.querySelector(".farm-designer");
|
||||
if (el && map && page) {
|
||||
const zoomLvl = parseFloat(window.getComputedStyle(map).zoom || DRAG_ERROR);
|
||||
const { pageX, pageY } = e;
|
||||
const params: ScreenToGardenParams = {
|
||||
quadrant: this.props.botOriginQuadrant,
|
||||
pageX: pageX + page.scrollLeft - this.props.gridOffset.x * zoomLvl,
|
||||
pageY: pageY + map.scrollTop * zoomLvl - this.props.gridOffset.y * zoomLvl,
|
||||
zoomLvl,
|
||||
gridSize: this.props.gridSize
|
||||
};
|
||||
return translateScreenToGarden(params);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
startDrag = (e: React.MouseEvent<SVGElement>): void => {
|
||||
if (this.isEditing) {
|
||||
this.setState({ isDragging: true });
|
||||
const plant = this.getPlant();
|
||||
|
@ -67,11 +92,25 @@ export class GardenMap extends
|
|||
this.setActiveSpread(plant.body.openfarm_slug);
|
||||
}
|
||||
}
|
||||
if (location.pathname.includes("select")) {
|
||||
const gardenCoords = this.getGardenCoordinates(e);
|
||||
if (gardenCoords) {
|
||||
this.setState({
|
||||
selectionBox: {
|
||||
x0: gardenCoords.x, y0: gardenCoords.y, x1: undefined, y1: undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
this.props.dispatch({ type: "SELECT_PLANT", payload: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
get isEditing(): boolean { return location.pathname.includes("edit"); }
|
||||
|
||||
getPlant = (): TaggedPlantPointer | undefined => this.props.selectedPlant;
|
||||
getPlant = (): TaggedPlantPointer | undefined =>
|
||||
history.getCurrentLocation().pathname.split("/")[4] != "select"
|
||||
? this.props.selectedPlant
|
||||
: undefined;
|
||||
|
||||
handleDragOver = (e: React.DragEvent<HTMLElement>) => {
|
||||
if (!this.isEditing &&
|
||||
|
@ -93,22 +132,11 @@ export class GardenMap extends
|
|||
|
||||
handleDrop = (e: React.DragEvent<HTMLElement> | React.MouseEvent<SVGElement>) => {
|
||||
e.preventDefault();
|
||||
const el = document.querySelector("div.drop-area svg[id='drop-area-svg']");
|
||||
const map = document.querySelector(".farm-designer-map");
|
||||
const page = document.querySelector(".farm-designer");
|
||||
if (el && map && page) {
|
||||
const zoomLvl = parseFloat(window.getComputedStyle(map).zoom || DRAG_ERROR);
|
||||
const { pageX, pageY } = e;
|
||||
const gardenCoords = this.getGardenCoordinates(e);
|
||||
if (gardenCoords) {
|
||||
const crop = history.getCurrentLocation().pathname.split("/")[5];
|
||||
const OFEntry = this.findCrop(crop);
|
||||
const params: ScreenToGardenParams = {
|
||||
quadrant: this.props.botOriginQuadrant,
|
||||
pageX: pageX + page.scrollLeft - this.props.gridOffset.x * zoomLvl,
|
||||
pageY: pageY + map.scrollTop * zoomLvl - this.props.gridOffset.y * zoomLvl,
|
||||
zoomLvl,
|
||||
gridSize: this.props.gridSize
|
||||
};
|
||||
const { x, y } = translateScreenToGarden(params);
|
||||
const { x, y } = gardenCoords;
|
||||
if (x < 0 || y < 0 || x > this.props.gridSize.x || y > this.props.gridSize.y) {
|
||||
error(t("Outside of planting area. Plants must be placed within the grid."));
|
||||
} else {
|
||||
|
@ -142,6 +170,20 @@ export class GardenMap extends
|
|||
}
|
||||
}
|
||||
|
||||
getSelected(box: SelectionBoxData) {
|
||||
const selected = this.props.plants.filter((p) => {
|
||||
if (box && box.x0 && box.y0 && box.x1 && box.y1) {
|
||||
return (
|
||||
p.body.x >= Math.min(box.x0, box.x1) &&
|
||||
p.body.x <= Math.max(box.x0, box.x1) &&
|
||||
p.body.y >= Math.min(box.y0, box.y1) &&
|
||||
p.body.y <= Math.max(box.y0, box.y1)
|
||||
);
|
||||
}
|
||||
}).map(p => { return p.uuid; });
|
||||
return selected.length > 0 ? selected : undefined;
|
||||
}
|
||||
|
||||
drag = (e: React.MouseEvent<SVGElement>) => {
|
||||
const plant = this.getPlant();
|
||||
const map = document.querySelector(".farm-designer-map");
|
||||
|
@ -158,6 +200,19 @@ export class GardenMap extends
|
|||
});
|
||||
this.props.dispatch(movePlant({ deltaX, deltaY, plant, gridSize }));
|
||||
}
|
||||
if (this.state.selectionBox && location.pathname.includes("select")) {
|
||||
const current = this.getGardenCoordinates(e);
|
||||
if (current) {
|
||||
const box = this.state.selectionBox;
|
||||
this.props.dispatch({
|
||||
type: "SELECT_PLANT",
|
||||
payload: this.getSelected(this.state.selectionBox)
|
||||
});
|
||||
this.setState({
|
||||
selectionBox: { x0: box.x0, y0: box.y0, x1: current.x, y1: current.y }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -245,6 +300,8 @@ export class GardenMap extends
|
|||
zoomLvl={this.props.zoomLvl}
|
||||
activeDragXY={this.state.activeDragXY}
|
||||
plantAreaOffset={this.props.gridOffset} />
|
||||
{this.state.selectionBox &&
|
||||
<SelectionBox selectionBox={this.state.selectionBox} />}
|
||||
</svg>
|
||||
</svg>
|
||||
</div>;
|
||||
|
|
|
@ -18,7 +18,7 @@ export class GardenPlant extends
|
|||
}
|
||||
|
||||
click = () => {
|
||||
this.props.dispatch({ type: "SELECT_PLANT", payload: this.props.uuid });
|
||||
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
|
||||
|
|
|
@ -10,7 +10,7 @@ describe("<HoveredPlantLayer/>", () => {
|
|||
dragging: false,
|
||||
currentPlant: undefined,
|
||||
designer: {
|
||||
selectedPlant: undefined,
|
||||
selectedPlants: undefined,
|
||||
hoveredPlant: {
|
||||
plantUUID: undefined,
|
||||
icon: ""
|
||||
|
@ -33,8 +33,8 @@ describe("<HoveredPlantLayer/>", () => {
|
|||
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);
|
||||
expect(icon.x).toEqual(70);
|
||||
expect(icon.width).toEqual(60);
|
||||
});
|
||||
|
||||
it("shows selected plant indicators", () => {
|
||||
|
|
|
@ -24,7 +24,10 @@ export function PlantLayer(props: PlantLayerProps) {
|
|||
.filter(c => !!c.body.spread)
|
||||
.map(c => cropSpreadDict[c.body.slug] = c.body.spread);
|
||||
|
||||
const maybeNoPointer = history.getCurrentLocation().pathname.split("/")[6] == "add"
|
||||
const pathName = history.getCurrentLocation().pathname;
|
||||
const clickToAddMode = pathName.split("/")[6] == "add";
|
||||
const selectMode = pathName.split("/")[4] == "select";
|
||||
const maybeNoPointer = (clickToAddMode || selectMode)
|
||||
? { "pointerEvents": "none" } : {};
|
||||
|
||||
return <g id="plant-layer">
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import * as React from "react";
|
||||
|
||||
export type SelectionBoxData =
|
||||
Record<"x0" | "y0" | "x1" | "y1", number | undefined>;
|
||||
|
||||
export interface SelectionBoxProps {
|
||||
selectionBox: SelectionBoxData
|
||||
}
|
||||
|
||||
export function SelectionBox(props: SelectionBoxProps) {
|
||||
const { x0, y0, x1, y1 } = props.selectionBox;
|
||||
if (x0 && y0 && x1 && y1) {
|
||||
const x = Math.min(x0, x1);
|
||||
const y = Math.min(y0, y1);
|
||||
const width = Math.max(x0, x1) - x;
|
||||
const height = Math.max(y0, y1) - y;
|
||||
return <g id="selection-box">
|
||||
<rect
|
||||
x={x}
|
||||
y={y}
|
||||
width={width}
|
||||
height={height}
|
||||
fill="rgba(256,256,256,0.1)"
|
||||
stroke="rgba(256,256,256,0.6)"
|
||||
strokeWidth={2} />
|
||||
</g>;
|
||||
} else {
|
||||
return <g id="selection-box" />;
|
||||
}
|
||||
}
|
|
@ -30,8 +30,10 @@ describe("<EditPlantInfo />", () => {
|
|||
push={jest.fn()}
|
||||
dispatch={dispatch}
|
||||
findPlant={fakePlant} />);
|
||||
expect(wrapper.find("button").props().hidden).toBeFalsy();
|
||||
wrapper.find("button").simulate("click");
|
||||
const deleteButton = wrapper.find("button").first();
|
||||
expect(deleteButton.props().hidden).toBeFalsy();
|
||||
expect(deleteButton.text()).toEqual("Delete");
|
||||
deleteButton.simulate("click");
|
||||
expect(dispatch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ describe("<PlantInfo />", () => {
|
|||
expect(wrapper.text()).toContain("Strawberry Plant 1");
|
||||
expect(wrapper.text().replace(/\s+/g, " "))
|
||||
.toContain("Plant Type: Strawberry");
|
||||
expect(wrapper.find("button").props().hidden).toBeTruthy();
|
||||
const buttons = wrapper.find("button");
|
||||
expect(buttons.first().props().hidden).toBeTruthy();
|
||||
expect(buttons.last().props().hidden).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ describe("<PlantPanel/>", () => {
|
|||
const txt = el.text().toLowerCase();
|
||||
expect(txt).toContain("1 days old");
|
||||
expect(txt).toContain("(10, 30)");
|
||||
el.find("button").simulate("click");
|
||||
el.find("button").first().simulate("click");
|
||||
expect(onDestroy.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
jest.mock("react-redux", () => ({
|
||||
connect: jest.fn()
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { SelectPlants, SelectPlantsProps } from "../select_plants";
|
||||
import { fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("<SelectPlants />", () => {
|
||||
beforeEach(function () {
|
||||
jest.clearAllMocks();
|
||||
Object.defineProperty(location, "pathname", {
|
||||
value: "//app/plants/select"
|
||||
});
|
||||
});
|
||||
|
||||
function fakeProps(): SelectPlantsProps {
|
||||
const plant1 = fakePlant();
|
||||
plant1.uuid = "plant.1";
|
||||
plant1.body.name = "Strawberry";
|
||||
const plant2 = fakePlant();
|
||||
plant2.uuid = "plant.2";
|
||||
plant2.body.name = "Blueberry";
|
||||
return {
|
||||
selected: ["plant.1"],
|
||||
plants: [plant1, plant2],
|
||||
dispatch: jest.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
it("displays selected plant", () => {
|
||||
const wrapper = mount(<SelectPlants {...fakeProps() } />);
|
||||
expect(wrapper.text()).toContain("Strawberry");
|
||||
expect(wrapper.text()).toContain("Delete");
|
||||
});
|
||||
|
||||
it("displays multiple selected plants", () => {
|
||||
const p = fakeProps();
|
||||
p.selected = ["plant.1", "plant.2"];
|
||||
const wrapper = mount(<SelectPlants {...p} />);
|
||||
expect(wrapper.text()).toContain("Strawberry");
|
||||
expect(wrapper.text()).toContain("Blueberry");
|
||||
expect(wrapper.text()).toContain("Delete");
|
||||
});
|
||||
|
||||
it("displays no selected plants: selection empty", () => {
|
||||
const p = fakeProps();
|
||||
p.selected = [];
|
||||
const wrapper = mount(<SelectPlants {...p} />);
|
||||
expect(wrapper.text()).not.toContain("Strawberry Plant");
|
||||
});
|
||||
|
||||
it("displays no selected plants: selection invalid", () => {
|
||||
const p = fakeProps();
|
||||
p.selected = ["not a uuid"];
|
||||
const wrapper = mount(<SelectPlants {...p} />);
|
||||
expect(wrapper.text()).not.toContain("Strawberry Plant");
|
||||
});
|
||||
|
||||
it("selects all", () => {
|
||||
const p = fakeProps();
|
||||
p.dispatch = jest.fn();
|
||||
const wrapper = mount(<SelectPlants {...p} />);
|
||||
const selectAllButton = wrapper.find("button").at(1);
|
||||
expect(selectAllButton.text()).toEqual("Select all");
|
||||
selectAllButton.simulate("click");
|
||||
expect(p.dispatch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("selects none", () => {
|
||||
const p = fakeProps();
|
||||
p.dispatch = jest.fn();
|
||||
const wrapper = mount(<SelectPlants {...p} />);
|
||||
const selectNoneButton = wrapper.find("button").at(2);
|
||||
expect(selectNoneButton.text()).toEqual("Select none");
|
||||
selectNoneButton.simulate("click");
|
||||
expect(p.dispatch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -49,7 +49,7 @@ export class PlantInventoryItem extends
|
|||
|
||||
const click = () => {
|
||||
push("/app/designer/plants/" + plantId);
|
||||
dispatch({ type: "SELECT_PLANT", payload: tpp.uuid });
|
||||
dispatch({ type: "SELECT_PLANT", payload: [tpp.uuid] });
|
||||
};
|
||||
|
||||
// See `cachedIcon` for more details on this.
|
||||
|
|
|
@ -4,6 +4,7 @@ import { t } from "i18next";
|
|||
import { Link } from "react-router";
|
||||
import { FormattedPlantInfo } from "./map_state_to_props";
|
||||
import { round } from "../map/util";
|
||||
import { history } from "../../history";
|
||||
|
||||
interface PlantPanelProps {
|
||||
info: FormattedPlantInfo;
|
||||
|
@ -75,5 +76,12 @@ export function PlantPanel({ info, onDestroy }: PlantPanelProps) {
|
|||
onClick={destroy} >
|
||||
{t("Delete")}
|
||||
</button>
|
||||
<button
|
||||
className="fb-button gray"
|
||||
style={{ marginRight: "10px" }}
|
||||
hidden={!onDestroy}
|
||||
onClick={() => history.push("/app/designer/plants/select")} >
|
||||
{t("Delete multiple")}
|
||||
</button>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
import * as React from "react";
|
||||
import { history } from "../../history";
|
||||
import { t } from "i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { TaggedPlantPointer } from "../../resources/tagged_resources";
|
||||
import { selectAllPlantPointers } from "../../resources/selectors";
|
||||
import { PlantInventoryItem } from "./plant_inventory_item";
|
||||
import { destroy } from "../../api/crud";
|
||||
import { error } from "farmbot-toastr";
|
||||
|
||||
export function mapStateToProps(props: Everything) {
|
||||
const plants = selectAllPlantPointers(props.resources.index);
|
||||
return {
|
||||
selected: props
|
||||
.resources
|
||||
.consumers
|
||||
.farm_designer
|
||||
.selectedPlants,
|
||||
plants,
|
||||
dispatch: props.dispatch
|
||||
};
|
||||
}
|
||||
|
||||
export interface SelectPlantsProps {
|
||||
plants: TaggedPlantPointer[];
|
||||
dispatch: Function;
|
||||
selected: string[];
|
||||
}
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class SelectPlants
|
||||
extends React.Component<SelectPlantsProps, {}> {
|
||||
|
||||
destroySelected = (plantUUIDs: string[]) => {
|
||||
if (plantUUIDs) {
|
||||
plantUUIDs.map(uuid => {
|
||||
this.props.dispatch(destroy(uuid))
|
||||
.catch(() => error(t("Could not delete plant."), t("Error")));
|
||||
});
|
||||
history.push("/app/designer/plants");
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selected, plants, dispatch } = this.props;
|
||||
const selectedPlantData = selected ? selected.map(uuid => {
|
||||
return plants.filter(p => { return p.uuid == uuid; })[0];
|
||||
}) : undefined;
|
||||
|
||||
return <div
|
||||
className="panel-container green-panel plant-selection-panel">
|
||||
<div className="panel-header green-panel">
|
||||
<p className="panel-title">
|
||||
<a className="back-arrow"
|
||||
onClick={() => history.push("/app/designer/plants")}>
|
||||
<i className="fa fa-arrow-left"></i>
|
||||
</a>
|
||||
Select plants
|
||||
</p>
|
||||
|
||||
<div className="panel-title">
|
||||
<button className="fb-button red"
|
||||
onClick={() => this.destroySelected(selected)}>
|
||||
{t("Delete selected")}
|
||||
</button>
|
||||
<button className="fb-button gray"
|
||||
onClick={() => this.props.dispatch({
|
||||
type: "SELECT_PLANT",
|
||||
payload: plants.map(p => p.uuid)
|
||||
})}>
|
||||
{t("Select all")}
|
||||
</button>
|
||||
<button className="fb-button gray"
|
||||
onClick={() => this.props.dispatch({
|
||||
type: "SELECT_PLANT",
|
||||
payload: undefined
|
||||
})}>
|
||||
{t("Select none")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="panel-content">
|
||||
<div className="thin-search-wrapper">
|
||||
{selectedPlantData && selectedPlantData[0] &&
|
||||
selectedPlantData.map(p =>
|
||||
<PlantInventoryItem
|
||||
key={p.uuid}
|
||||
tpp={p}
|
||||
dispatch={dispatch} />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import { TaggedResource } from "../resources/tagged_resources";
|
|||
import { Actions } from "../constants";
|
||||
|
||||
export let initialState: DesignerState = {
|
||||
selectedPlant: undefined,
|
||||
selectedPlants: undefined,
|
||||
hoveredPlant: {
|
||||
plantUUID: undefined,
|
||||
icon: ""
|
||||
|
@ -24,8 +24,8 @@ export let designer = generateReducer<DesignerState>(initialState)
|
|||
state.cropSearchQuery = payload;
|
||||
return state;
|
||||
})
|
||||
.add<string | undefined>(Actions.SELECT_PLANT, (s, { payload }) => {
|
||||
s.selectedPlant = payload;
|
||||
.add<string[] | undefined>(Actions.SELECT_PLANT, (s, { payload }) => {
|
||||
s.selectedPlants = payload;
|
||||
return s;
|
||||
})
|
||||
.add<HoveredPlantPayl>(Actions.TOGGLE_HOVERED_PLANT, (s, { payload }) => {
|
||||
|
@ -38,6 +38,6 @@ export let designer = generateReducer<DesignerState>(initialState)
|
|||
return state;
|
||||
})
|
||||
.add<TaggedResource>(Actions.DESTROY_RESOURCE_OK, (s, { payload }) => {
|
||||
if (payload.uuid === s.selectedPlant) { s.selectedPlant = undefined; }
|
||||
s.selectedPlants = undefined;
|
||||
return s;
|
||||
});
|
||||
|
|
|
@ -11,12 +11,10 @@ import { isNumber } from "lodash";
|
|||
export function mapStateToProps(props: Everything) {
|
||||
|
||||
const plants = selectAllPlantPointers(props.resources.index);
|
||||
const selectedPlant = plants
|
||||
.filter(x => x.uuid === props
|
||||
.resources
|
||||
.consumers
|
||||
.farm_designer
|
||||
.selectedPlant)[0];
|
||||
const maybeSelectedPlants = props.resources.consumers.farm_designer.selectedPlants;
|
||||
const selectedPlant = maybeSelectedPlants
|
||||
? plants.filter(x => x.uuid === maybeSelectedPlants[0])[0]
|
||||
: undefined;
|
||||
const { plantUUID } = props.resources.consumers.farm_designer.hoveredPlant;
|
||||
const hoveredPlant = plants.filter(x => x.uuid === plantUUID)[0];
|
||||
|
||||
|
|
|
@ -172,6 +172,14 @@ export class RootComponent extends React.Component<RootComponentProps, {}> {
|
|||
.catch(errorLoading(cb));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "plants/select",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
import("./farm_designer/plants/select_plants")
|
||||
.then(module => cb(undefined, module.SelectPlants))
|
||||
.catch(errorLoading(cb));
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "plants/:plant_id",
|
||||
getComponent(_discard: void, cb: Function) {
|
||||
|
|
Loading…
Reference in New Issue