Farmbot-Web-App/frontend/sequences/step_tiles/tile_move_absolute.tsx

246 lines
7.6 KiB
TypeScript

import * as React from "react";
import { t } from "i18next";
import { Component } from "react";
import { StepParams } from "../interfaces";
import { MoveAbsState } from "../interfaces";
import {
Tool,
Coordinate,
Point,
Identifier,
MoveAbsolute,
TaggedTool,
TaggedToolSlotPointer,
ScopeDeclarationBodyItem,
VariableDeclaration
} from "farmbot";
import { Row, Col, BlurableInput } from "../../ui/index";
import {
isTaggedSequence,
} from "../../resources/tagged_resources";
import {
findToolById,
findSlotByToolId,
findPointerByTypeAndId
} from "../../resources/selectors";
import { defensiveClone, betterMerge } from "../../util";
import { overwrite } from "../../api/crud";
import { Xyz } from "../../devices/interfaces";
import { ToolTips } from "../../constants";
import {
StepWrapper,
StepHeader,
StepContent,
StepWarning,
conflictsString
} from "../step_ui/index";
import { StepInputBox } from "../inputs/step_input_box";
import {
determineDropdown, determineVector, findVariableByName
} from "../../resources/sequence_meta";
import { LocationForm } from "../locals_list/location_form";
import { AllowedDeclaration } from "../locals_list/locals_list_support";
import { merge, some } from "lodash";
/** Union of all types found in a move_abs "args" attribute. */
export type LocationData = MoveAbsolute["args"]["location"];
interface Args {
location: Tool | Coordinate | Point | Identifier;
speed: number;
offset: Coordinate;
}
type LocationArg = "location" | "offset";
export class TileMoveAbsolute extends Component<StepParams, MoveAbsState> {
get resources() { return this.props.resources; }
get step() { return this.props.currentStep; }
get tool(): TaggedTool | undefined {
const l = this.args.location;
if (l && l.kind === "tool" && l.args.tool_id) {
return findToolById(this.resources, l.args.tool_id);
}
}
get tool_id() { return this.tool && this.tool.body.id; }
get slot(): TaggedToolSlotPointer | undefined {
return this.tool_id ?
findSlotByToolId(this.resources, this.tool_id) : undefined;
}
get args(): MoveAbsolute["args"] { return (this.step as MoveAbsolute).args; }
getOffsetValue = (val: Xyz) => {
return (this.args.offset.args[val] || 0).toString();
}
/** Merge step args update into step args. */
updateArgs = (update: Partial<Args>) => {
const copy = defensiveClone(this.props.currentSequence).body;
const step = (copy.body || [])[this.props.index] as MoveAbsolute;
delete step.args.location.args;
step.args = betterMerge(step.args, update);
this.props.dispatch(overwrite(this.props.currentSequence, copy));
}
/** Get axis value for settingConflicts. */
getAxisValue = (axis: Xyz): string => {
let number: number | undefined;
const l = this.args.location;
switch (l.kind) {
case "coordinate":
number = l.args[axis];
break;
case "tool":
number = (this.slot) ? this.slot.body[axis] : undefined;
break;
case "point":
const { pointer_id } = l.args;
number = findPointerByTypeAndId(this.resources, "Point", pointer_id)
.body[axis];
break;
case "identifier":
const { resources, currentSequence } = this.props;
const v =
findVariableByName(resources, currentSequence.uuid, l.args.label);
if (v && v.vector) {
number = v.vector[axis];
break;
}
}
return (number || 0).toString();
}
/** Update offset value. */
updateInputValue = (axis: Xyz, place: LocationArg) =>
(e: React.SyntheticEvent<HTMLInputElement>) => {
const num = parseFloat(e.currentTarget.value);
const update = { [place]: { args: { [axis]: num } } };
this.updateArgs(merge({}, this.args, update));
}
/** Determine if location conflicts with bot settings. */
get settingConflicts(): Record<Xyz, boolean> {
const conflicts = { x: false, y: false, z: false };
if (this.props.hardwareFlags) {
const {
stopAtHome, stopAtMax, negativeOnly, axisLength
} = this.props.hardwareFlags;
const axes: Xyz[] = ["x", "y", "z"];
axes.map((axis: Xyz) => {
const coord = parseFloat(this.getAxisValue(axis));
const offset = parseFloat(this.getOffsetValue(axis));
const sum = coord + offset;
if (stopAtHome[axis]) {
conflicts[axis] = negativeOnly[axis] ? sum > 0 : sum < 0;
}
if (stopAtMax[axis] && axisLength[axis] !== 0) {
conflicts[axis] = conflicts[axis] || (negativeOnly[axis]
? sum < -axisLength[axis]
: sum > axisLength[axis]);
}
});
}
return conflicts;
}
get settingConflictWarning() {
return t("Movement out of bounds for: ")
+ conflictsString(this.settingConflicts);
}
/** Handle changes to step.args.location. */
updateLocation = (declaration: VariableDeclaration) => {
const location = declaration.args.data_value;
if (location.kind === "every_point") {
throw new Error("Can't put `every_point` into `move_abs");
} else {
this.updateArgs({ location });
}
}
/** Prepare step.args.location data for LocationForm. */
get celeryNode(): ScopeDeclarationBodyItem {
const { location } = this.args;
return {
kind: "variable_declaration",
args: {
label: location.kind === "identifier" ? location.args.label : "",
data_value: location
}
};
}
LocationForm = () =>
<LocationForm
variable={{
celeryNode: this.celeryNode,
dropdown: determineDropdown(this.celeryNode, this.resources),
vector: determineVector(
this.celeryNode, this.resources, this.props.currentSequence.uuid),
}}
sequenceUuid={this.props.currentSequence.uuid}
resources={this.resources}
onChange={this.updateLocation}
shouldDisplay={this.props.shouldDisplay || (() => false)}
hideVariableLabel={true}
locationDropdownKey={JSON.stringify(this.props.currentSequence)}
allowedDeclarations={AllowedDeclaration.identifier}
disallowGroups={true}
width={3} />
SpeedForm = () =>
<Col xs={3}>
<label>
{t("Speed (%)")}
</label>
<StepInputBox
field={"speed"}
step={this.step}
index={this.props.index}
dispatch={this.props.dispatch}
sequence={this.props.currentSequence} />
</Col>
OffsetForm = () =>
<Row>
{["x", "y", "z"].map((axis: Xyz) =>
<Col xs={3} key={axis}>
<label>
{t("{{axis}}-Offset", { axis })}
</label>
<BlurableInput type="number"
onCommit={this.updateInputValue(axis, "offset")}
name={`offset-${axis}`}
value={this.getOffsetValue(axis)} />
</Col>)}
<this.SpeedForm />
</Row>
render() {
const { currentStep, dispatch, index, currentSequence } = this.props;
if (currentSequence && !isTaggedSequence(currentSequence)) {
throw new Error("WHOOPS!");
}
const className = "move-absolute-step";
return <StepWrapper>
<StepHeader
className={className}
helpText={ToolTips.MOVE_ABSOLUTE}
currentSequence={currentSequence}
currentStep={currentStep}
dispatch={dispatch}
index={index}
confirmStepDeletion={this.props.confirmStepDeletion}>
{some(this.settingConflicts) &&
<StepWarning
warning={this.settingConflictWarning}
conflicts={this.settingConflicts} />}
</StepHeader>
<StepContent className={className}>
<this.LocationForm />
<this.OffsetForm />
</StepContent>
</StepWrapper>;
}
}