FarmBot/Farmbot-Web-App

View on GitHub
frontend/tools/add_tool.tsx

Summary

Maintainability
B
4 hrs
Test Coverage
import React from "react";
import { connect } from "react-redux";
import {
  DesignerPanel, DesignerPanelContent, DesignerPanelHeader,
} from "../farm_designer/designer_panel";
import { Everything } from "../interfaces";
import { t } from "../i18next_wrapper";
import { SaveBtn } from "../ui";
import { SpecialStatus } from "farmbot";
import { initSave, destroy, init, save } from "../api/crud";
import { Panel } from "../farm_designer/panel_header";
import { useNavigate } from "react-router-dom";
import { selectAllTools } from "../resources/selectors";
import { betterCompact } from "../util";
import {
  getFwHardwareValue,
} from "../settings/firmware/firmware_hardware_support";
import { getFbosConfig } from "../resources/getters";
import { ToolSVG } from "../farm_designer/map/layers/tool_slots/tool_graphics";
import { AddToolProps, AddToolState } from "./interfaces";
import {
  reduceFarmwareEnv, saveOrEditFarmwareEnv,
} from "../farmware/state_to_props";
import { CustomToolGraphicsInput } from "./custom_tool_graphics";
import { Path } from "../internal_urls";
import {
  reduceToolName, ToolName,
} from "../farm_designer/map/tool_graphics/all_tools";
import { WaterFlowRateInput } from "./edit_tool";

export const mapStateToProps = (props: Everything): AddToolProps => ({
  dispatch: props.dispatch,
  existingToolNames: betterCompact(selectAllTools(props.resources.index)
    .map(tool => tool.body.name)),
  firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)),
  saveFarmwareEnv: saveOrEditFarmwareEnv(props.resources.index),
  env: reduceFarmwareEnv(props.resources.index),
});

export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
  state: AddToolState = { toolName: "", toAdd: [], uuid: undefined, flowRate: 0 };

  filterExisting = (n: string) => !this.props.existingToolNames.includes(n);

  add = (n: string) => this.filterExisting(n) && !this.state.toAdd.includes(n) &&
    this.setState({ toAdd: this.state.toAdd.concat([n]) });

  remove = (n: string) =>
    this.setState({ toAdd: this.state.toAdd.filter(name => name != n) });

  componentDidMount = () => this.setState({
    toAdd: this.stockToolNames().filter(this.filterExisting)
  });

  newTool = (name: string) => this.props.dispatch(initSave("Tool", { name }));

  back = () => {
    const navigate = useNavigate();
    navigate(Path.tools());
  };

  save = () => {
    const initTool = init("Tool", {
      name: this.state.toolName,
      flow_rate_ml_per_s: this.state.flowRate,
    });
    this.props.dispatch(initTool);
    const { uuid } = initTool.payload;
    this.setState({ uuid });
    this.props.dispatch(save(uuid))
      .then(() => this.setState({ uuid: undefined }, this.back))
      .catch(() => { });
  };

  componentWillUnmount = () =>
    this.state.uuid && this.props.dispatch(destroy(this.state.uuid));

  stockToolNames = () => {
    const TROUGHS = [
      t("Seed Trough 1"),
      t("Seed Trough 2"),
    ];
    const BASE_TOOLS = [
      t("Watering Nozzle"),
    ];
    const GENESIS_TOOLS = [
      t("Seeder"),
      t("Weeder"),
      t("Soil Sensor"),
      t("Seed Bin"),
      t("Seed Tray"),
    ];
    switch (this.props.firmwareHardware) {
      case "arduino":
      case "farmduino":
      case "farmduino_k14":
      default:
        return [
          ...BASE_TOOLS,
          ...GENESIS_TOOLS,
        ];
      case "farmduino_k15":
        return [
          ...BASE_TOOLS,
          ...GENESIS_TOOLS,
          ...TROUGHS,
        ];
      case "farmduino_k16":
      case "farmduino_k17":
        return [
          ...BASE_TOOLS,
          t("Rotary Tool"),
          ...GENESIS_TOOLS,
          ...TROUGHS,
        ];
      case "express_k10":
      case "express_k11":
      case "express_k12":
        return [
          ...BASE_TOOLS,
          ...TROUGHS,
        ];
    }
  };

  StockToolCheckbox = ({ toolName }: { toolName: string }) => {
    const alreadyAdded = !this.filterExisting(toolName);
    const checked = this.state.toAdd.includes(toolName) || alreadyAdded;
    return <div className={`fb-checkbox ${alreadyAdded ? "disabled" : ""}`}>
      <input type="checkbox" key={JSON.stringify(this.state.toAdd)}
        title={alreadyAdded ? t("Already added.") : ""}
        name="toolName"
        checked={checked}
        onChange={() => checked
          ? this.remove(toolName)
          : this.add(toolName)} />
    </div>;
  };

  AddStockTools = () => {
    const add = this.state.toAdd.filter(this.filterExisting);
    const navigate = useNavigate();
    return <div className="add-stock-tools"
      hidden={this.props.firmwareHardware == "none"}>
      <label>{t("stock names")}</label>
      <ul>
        {this.stockToolNames().map(n =>
          <li key={n}>
            <this.StockToolCheckbox toolName={n} />
            <p onClick={() => this.setState({ toolName: n })}>{n}</p>
          </li>)}
      </ul>
      <button
        className={`fb-button green ${add.length > 0 ? "" : "pseudo-disabled"}`}
        title={add.length > 0 ? t("Add selected") : t("None to add")}
        onClick={() => {
          add.map(n => this.newTool(n));
          navigate(Path.tools());
        }}>
        <i className="fa fa-plus" />
        {t("selected")}
      </button>
    </div>;
  };

  changeFlowRate = (flowRate: number) => this.setState({ flowRate });

  render() {
    const { toolName, uuid } = this.state;
    const alreadyAdded = !uuid && !this.filterExisting(toolName);
    const panelName = "add-tool";
    return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
      <DesignerPanelHeader
        panelName={panelName}
        title={t("Add new")}
        backTo={Path.tools()}
        panel={Panel.Tools}>
        <div className={"tool-action-btn-group"}>
          <SaveBtn
            onClick={this.save}
            disabled={!this.state.toolName || alreadyAdded}
            status={SpecialStatus.DIRTY} />
        </div>
      </DesignerPanelHeader>
      <DesignerPanelContent panelName={panelName}>
        <div className="add-new-tool">
          <ToolSVG toolName={this.state.toolName} profile={true} />
          <CustomToolGraphicsInput
            toolName={this.state.toolName}
            dispatch={this.props.dispatch}
            saveFarmwareEnv={this.props.saveFarmwareEnv}
            env={this.props.env} />
          <label>{t("Name")}</label>
          <input defaultValue={this.state.toolName}
            name="toolName"
            onChange={e =>
              this.setState({ toolName: e.currentTarget.value })} />
          {reduceToolName(toolName) == ToolName.wateringNozzle &&
            <WaterFlowRateInput value={this.state.flowRate}
              onChange={this.changeFlowRate} />}
          <p className="name-error">
            {alreadyAdded ? t("Already added.") : ""}
          </p>
        </div>
        <this.AddStockTools />
      </DesignerPanelContent>
    </DesignerPanel>;
  }
}

export const AddTool = connect(mapStateToProps)(RawAddTool);
// eslint-disable-next-line import/no-default-export
export default AddTool;