frontend/points/create_points.tsx
import React from "react";
import { connect } from "react-redux";
import { Everything, ResourceColor } from "../interfaces";
import { initSave } from "../api/crud";
import { Row, BlurableInput, ColorPicker } from "../ui";
import { DrawnPointPayl } from "../farm_designer/interfaces";
import { Actions, Content } from "../constants";
import {
GenericPointer, WeedPointer,
} from "farmbot/dist/resources/api_resources";
import {
DesignerPanel,
DesignerPanelHeader,
DesignerPanelContent,
} from "../farm_designer/designer_panel";
import { parseIntInput } from "../util";
import { validBotLocationData } from "../util/location";
import { t } from "../i18next_wrapper";
import { Panel } from "../farm_designer/panel_header";
import { ListItem } from "../plants/plant_panel";
import { success } from "../toast/toast";
import { PlantGrid } from "../plants/grid/plant_grid";
import { getWebAppConfigValue } from "../config_storage/actions";
import { BooleanSetting } from "../session_keys";
import {
definedPosition, UseCurrentLocation,
} from "../tools/tool_slot_edit_components";
import { BotPosition } from "../devices/interfaces";
import { round } from "lodash";
import { Path } from "../internal_urls";
import { NavigationContext } from "../routes_helpers";
export function mapStateToProps(props: Everything): CreatePointsProps {
const { drawnPoint, drawnWeed } = props.resources.consumers.farm_designer;
return {
dispatch: props.dispatch,
drawnPoint: drawnPoint || drawnWeed,
xySwap: !!getWebAppConfigValue(() => props)(BooleanSetting.xy_swap),
botPosition: validBotLocationData(props.bot.hardware.location_data).position,
};
}
export interface CreatePointsProps {
dispatch: Function;
drawnPoint: DrawnPointPayl | undefined;
xySwap: boolean;
botPosition: BotPosition;
}
type CreatePointsState = Partial<DrawnPointPayl>;
const DEFAULTS: DrawnPointPayl = {
name: undefined,
cx: 1,
cy: 1,
z: 0,
r: 15,
color: undefined,
};
export class RawCreatePoints
extends React.Component<CreatePointsProps, Partial<CreatePointsState>> {
constructor(props: CreatePointsProps) {
super(props);
this.state = {};
}
attr = <T extends (keyof DrawnPointPayl)>(key: T,
fallback = DEFAULTS[key]): DrawnPointPayl[T] => {
const p = this.props.drawnPoint;
const userValue = this.state[key] as DrawnPointPayl[T] | undefined;
const propValue = p ? p[key] : fallback;
if (typeof userValue === "undefined") {
return propValue;
} else {
return userValue;
}
};
get defaultName() {
return this.panel == "weeds"
? t("Created Weed")
: t("Created Point");
}
get defaultColor() { return this.panel == "weeds" ? "red" : "green"; }
getPointData = (): DrawnPointPayl => {
return {
name: this.attr("name"),
cx: this.attr("cx"),
cy: this.attr("cy"),
z: this.attr("z"),
r: this.attr("r"),
color: this.attr("color") || this.defaultColor,
at_soil_level: this.attr("at_soil_level"),
};
};
cancel = () => {
this.props.dispatch({
type: this.panel == "weeds"
? Actions.SET_DRAWN_WEED_DATA
: Actions.SET_DRAWN_POINT_DATA,
payload: undefined
});
this.setState({
cx: undefined,
cy: undefined,
z: undefined,
r: undefined,
color: undefined
});
};
loadDefaultPoint = () => {
this.props.dispatch({
type: this.panel == "weeds"
? Actions.SET_DRAWN_WEED_DATA
: Actions.SET_DRAWN_POINT_DATA,
payload: {
name: this.defaultName,
cx: this.attr("cx") || DEFAULTS.cx,
cy: this.attr("cy") || DEFAULTS.cy,
z: DEFAULTS.z,
r: DEFAULTS.r,
color: this.defaultColor,
} as DrawnPointPayl
});
};
componentDidMount() {
this.loadDefaultPoint();
}
componentWillUnmount() {
this.cancel();
}
updateAttr = (key: keyof CreatePointsState, value: string | boolean) => {
if (this.props.drawnPoint) {
const point = this.getPointData();
switch (key) {
case "name":
case "color":
this.setState({ [key]: value });
point[key] = "" + value;
break;
case "at_soil_level":
this.setState({ [key]: !!value });
point[key] = !!value;
break;
default:
const intValue = parseIntInput("" + value);
this.setState({ [key]: intValue });
point[key] = intValue;
}
this.props.dispatch({
type: this.panel == "weeds"
? Actions.SET_DRAWN_WEED_DATA
: Actions.SET_DRAWN_POINT_DATA,
payload: point
});
}
};
/** Update fields. */
updateValue = (key: keyof CreatePointsState) => {
return (e: React.SyntheticEvent<HTMLInputElement>) => {
const { value } = e.currentTarget;
this.updateAttr(key, value);
};
};
changeColor = (color: ResourceColor) => {
this.setState({ color });
const point = this.getPointData();
point.color = color;
this.props.dispatch({
type: this.panel == "weeds"
? Actions.SET_DRAWN_WEED_DATA
: Actions.SET_DRAWN_POINT_DATA,
payload: point
});
};
get panel() { return Path.getSlug(Path.designer()) || "points"; }
createPoint = () => {
const body: GenericPointer | WeedPointer = {
pointer_type: this.panel == "weeds" ? "Weed" : "GenericPointer",
name: this.attr("name") || this.defaultName,
meta: {
color: this.attr("color") || this.defaultColor,
created_by: "farm-designer",
type: this.panel == "weeds" ? "weed" : "point",
...(this.attr("at_soil_level") ? { at_soil_level: "true" } : {}),
},
x: this.attr("cx"),
y: this.attr("cy"),
z: this.attr("z"),
plant_stage: "active",
radius: this.attr("r"),
};
this.props.dispatch(initSave("Point", body));
success(this.panel == "weeds"
? t("Weed created.")
: t("Point created."));
this.cancel();
this.closePanel();
};
static contextType = NavigationContext;
context!: React.ContextType<typeof NavigationContext>;
navigate = (url: string) => this.context(url);
closePanel = () => this.navigate(Path.designer(this.panel));
PointProperties = () =>
<ul className="grid">
<li>
<Row className="grid-exp-1">
<div className={"point-name-input"}>
<label>{t("Name")}</label>
<BlurableInput
name="pointName"
type="text"
onCommit={this.updateValue("name")}
value={this.attr("name") || this.defaultName} />
</div>
<ColorPicker
current={(this.attr("color") || this.defaultColor) as ResourceColor}
onChange={this.changeColor} />
</Row>
</li>
<ListItem>
<Row className="add-point-grid">
<div>
<label>{t("radius (mm)")}</label>
<BlurableInput
name="r"
type="number"
onCommit={this.updateValue("r")}
value={this.attr("r")}
min={0} />
</div>
<div>
<label>{t("X")}</label>
<BlurableInput
name="cx"
type="number"
onCommit={this.updateValue("cx")}
value={this.attr("cx", this.props.botPosition.x)} />
</div>
<div>
<label>{t("Y")}</label>
<BlurableInput
name="cy"
type="number"
onCommit={this.updateValue("cy")}
value={this.attr("cy", this.props.botPosition.y)} />
</div>
<div>
<label>{t("Z")}</label>
<BlurableInput
name="z"
type="number"
onCommit={this.updateValue("z")}
value={this.attr("z", this.props.botPosition.z)} />
</div>
<UseCurrentLocation botPosition={this.props.botPosition}
onChange={() => {
const position = definedPosition(this.props.botPosition);
if (position) {
const { x, y, z } = position;
this.setState({ cx: round(x), cy: round(y), z: round(z) }, () =>
this.props.dispatch({
type: this.panel == "weeds"
? Actions.SET_DRAWN_WEED_DATA
: Actions.SET_DRAWN_POINT_DATA,
payload: this.getPointData(),
}));
}
}} />
</Row>
</ListItem>
{this.panel == "points" &&
<ListItem>
<Row className="grid-exp-1">
<label>{t("at soil level")}</label>
<input
name="at_soil_level"
type="checkbox"
onChange={e =>
this.updateAttr("at_soil_level", e.currentTarget.checked)}
checked={!!this.attr("at_soil_level")} />
</Row>
</ListItem>}
</ul>;
PointActions = () =>
<Row>
<button className="fb-button green save"
title={t("save")}
onClick={this.createPoint}>
{t("Save")}
</button>
</Row>;
render() {
const panelType = this.panel == "weeds" ? Panel.Weeds : Panel.Points;
const panelDescription = this.panel == "weeds"
? Content.CREATE_WEEDS_DESCRIPTION
: Content.CREATE_POINTS_DESCRIPTION;
const point = this.getPointData();
const meta: Record<string, string | undefined> = { color: point.color };
point.at_soil_level && (meta.at_soil_level = "" + point.at_soil_level);
return <DesignerPanel panelName={"point-creation"} panel={panelType}>
<DesignerPanelHeader
panelName={"point-creation"}
panel={panelType}
title={this.panel == "weeds" ? t("Add weed") : t("Add point")}
backTo={Path.designer(this.panel)}
description={panelDescription} />
<DesignerPanelContent panelName={"point-creation"}>
<this.PointProperties />
<this.PointActions />
{panelType == Panel.Points && <hr />}
{panelType == Panel.Points &&
<PlantGrid
xy_swap={this.props.xySwap}
itemName={point.name || t("Grid point")}
radius={point.r}
dispatch={this.props.dispatch}
botPosition={this.props.botPosition}
z={this.attr("z", this.props.botPosition.z)}
meta={meta}
close={this.closePanel} />}
</DesignerPanelContent>
</DesignerPanel>;
}
}
export const CreatePoints = connect(mapStateToProps)(RawCreatePoints);
// eslint-disable-next-line import/no-default-export
export default CreatePoints;