FarmBot/Farmbot-Web-App

View on GitHub
frontend/controls/move/direction_button.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import React from "react";
import { moveRelative } from "../../devices/actions";
import { ButtonDirection, DirectionButtonProps } from "./interfaces";
import { t } from "../../i18next_wrapper";
import { MoveRelProps } from "../../devices/interfaces";
import { lockedClass } from "../locked_class";
import { Popover } from "../../ui";
import { setMovementState } from "../../connectivity/log_handlers";
import { movementPercentRemaining } from "../../farm_designer/move_to";

export function directionDisabled(props: DirectionButtonProps): boolean {
  const {
    stopAtHome, stopAtMax, axisLength, position, isInverted, negativeOnly
  } = props.directionAxisProps;
  const { direction } = props;
  const loc = position || 0;
  const jog = calculateDistance(props);
  const directionDisableHome = stopAtHome && loc === 0 &&
    (negativeOnly ? jog > 0 : jog < 0);
  const directionDisableEnd = stopAtMax && axisLength > 0 &&
    Math.abs(loc) === axisLength && Math.abs(loc + jog) > axisLength;
  switch (direction) {
    case "left":
    case "down":
      return (isInverted === negativeOnly)
        ? directionDisableHome
        : directionDisableEnd;
    case "right":
    case "up":
      return (isInverted === !negativeOnly)
        ? directionDisableHome
        : directionDisableEnd;
  }
}

export function calculateDistance(props: DirectionButtonProps) {
  const { direction } = props;
  const { isInverted } = props.directionAxisProps;
  const isNegative = (direction === "down") || (direction === "left");
  const inverter = isInverted ? -1 : 1;
  const multiplier = isNegative ? -1 : 1;
  const distance = props.steps * multiplier * inverter;
  return distance;
}

export const calcBtnStyle = (
  direction: ButtonDirection,
  remaining: number | undefined,
): React.CSSProperties => ({
  [["left", "right"].includes(direction) ? "width" : "height"]: remaining + "%",
  [direction == "up" ? "bottom" : "top"]: 0,
  [direction == "left" ? "right" : "left"]: 0,
});

interface DirectionButtonState {
  popoverOpen: boolean;
  popoverText: string;
}

export class DirectionButton
  extends React.Component<DirectionButtonProps, DirectionButtonState> {
  state: DirectionButtonState = {
    popoverOpen: false,
    popoverText: "",
  };

  get btnActive() {
    const { axis, movementState } = this.props;
    const { distance } = movementState;
    return (distance[axis] > 0 && this.distance > 0)
      || (distance[axis] < 0 && this.distance < 0);
  }

  get popActive() {
    return this.props.popover == this.props.axis + this.props.direction;
  }

  sendCommand = () => {
    const { botPosition, locked, axis, arduinoBusy, botOnline } = this.props;
    const buttonId = axis + this.props.direction;
    this.props.setActivePopover(buttonId);
    const text = () => {
      if (locked) { return t("FarmBot is locked"); }
      if (arduinoBusy) { return t("FarmBot is busy"); }
      if (!botOnline) { return t("FarmBot is offline"); }
      if (directionDisabled(this.props) && this.distance > 0) {
        return t("Axis is already at maximum position");
      }
      if (directionDisabled(this.props) && this.distance < 0) {
        return t("Axis is already at minimum position");
      }
      return "";
    };
    if (arduinoBusy || !botOnline || locked || directionDisabled(this.props)) {
      this.setState({
        popoverOpen: !this.state.popoverOpen,
        popoverText: text(),
      });
      return;
    }
    this.setState({ popoverOpen: false, popoverText: text() });
    const payload: MoveRelProps = { x: 0, y: 0, z: 0 };
    payload[this.props.axis] = this.distance;
    moveRelative(payload);
    this.props.dispatch(setMovementState({
      start: botPosition,
      distance: { x: 0, y: 0, z: 0, [axis]: this.distance },
    }));
  };

  get distance() { return calculateDistance(this.props); }

  render() {
    const {
      direction, axis, locked, arduinoBusy, botOnline, botPosition, movementState,
    } = this.props;
    const title = `${t("move {{axis}} axis", { axis })} (${this.distance})`;
    const remaining = movementPercentRemaining(botPosition, movementState);
    const style = calcBtnStyle(direction, remaining);
    const disabled = arduinoBusy || !botOnline || directionDisabled(this.props);
    return <button
      onClick={this.sendCommand}
      className={[
        "fb-button arrow-button radius",
        `fa fa-2x fa-arrow-${direction}`,
        axis == "z" ? "z" : "",
        lockedClass(locked),
        disabled ? "pseudo-disabled" : "",
      ].join(" ")}
      title={title}>
      <p>{this.distance > 0 ? "+" : "-"}{axis}</p>
      {(this.btnActive && remaining && arduinoBusy)
        ? <div className={"movement-progress"} style={style} />
        : <i />}
      <Popover
        isOpen={this.popActive && this.state.popoverOpen}
        popoverClassName={"help movement-message"}
        target={<i />}
        content={<div className={"help-text-content"}>
          {t(this.state.popoverText)}
        </div>} />
    </button>;
  }
}