FarmBot/Farmbot-Web-App

View on GitHub
frontend/settings/hardware_settings/mcu_input_box.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import React from "react";
import { warning } from "../../toast/toast";
import { McuInputBoxProps } from "./interfaces";
import { updateMCU } from "../../devices/actions";
import { BlurableInput } from "../../ui";
import {
  clampInteger, IntegerSize, getMaxInputFromIntSize,
} from "../../util";
import { isUndefined, some } from "lodash";
import { t } from "../../i18next_wrapper";
import { getModifiedClassName } from "./default_values";
import {
  initSettingStatusState,
  SettingStatusIndicator, SettingStatusState, SETTING_SYNC_TIMEOUT,
} from "./setting_status_indicator";
import { McuParamName } from "farmbot";

export class McuInputBox
  extends React.Component<McuInputBoxProps, SettingStatusState> {
  state: SettingStatusState = initSettingStatusState();

  get key() { return this.props.setting; }

  get config() {
    return this.props.sourceFwConfig(this.key);
  }

  get value() {
    const v = this.config.value;
    const { filter } = this.props;
    const goodValue = !isUndefined(v) && !(filter && v > filter);
    return goodValue ? (v || 0).toString() : "";
  }

  get wrapperClassName() {
    const { firmwareHardware } = this.props;
    const value = microstepScaledConfig(this.key)
      ? (this.config.value || 1) / (this.props.scale || 1)
      : this.config.value;
    return getModifiedClassName(this.key, value, firmwareHardware,
      this.props.toInput);
  }

  get showValue() {
    const { toInput } = this.props;
    const intValue = parseInt(this.value);
    if (toInput) { return "" + toInput(intValue); }
    return this.props.scale
      ? "" + intValue / this.props.scale
      : this.value;
  }

  get className() {
    const gray = this.props.gray ? "gray" : "";
    return [gray].join(" ");
  }

  clampInputAndWarn = (input: string, intSize: IntegerSize): number => {
    const result = clampInteger(input, intSize, {
      min: this.props.min, max: this.props.max
    });
    const min = result.min.toLocaleString();
    const max = result.max.toLocaleString();
    switch (result.outcome) {
      case "ok":
        break;
      case "high":
        warning(t(`Maximum input is ${max}. Rounding down.`));
        break;
      case "low":
        warning(t(`Minimum input is ${min}. Rounding up.`));
        break;
      default:
        warning(t(`Please enter a number between ${min} and ${max}`));
        throw new Error("Bad input in mcu_input_box. Impossible?");
    }
    return result.result;
  };

  commit = (e: React.SyntheticEvent<HTMLInputElement>) => {
    const { value } = e.currentTarget;
    const { fromInput } = this.props;
    const floatValue = parseFloat(value);
    const funcScaledValue = fromInput ? "" + fromInput(floatValue) : value;
    const scaledValue = this.props.scale
      ? "" + Math.round(floatValue * this.props.scale)
      : funcScaledValue;
    const actuallyDifferent = this.value !== scaledValue;
    if (actuallyDifferent) {
      const result = this.props.float
        ? Math.max(0, parseFloat(scaledValue))
        : this.clampInputAndWarn(scaledValue, this.props.intSize);
      this.props.dispatch(updateMCU(this.key, result.toString()));
    }
  };

  componentDidUpdate = () => {
    const inconsistent = !this.config.consistent;
    const changed = inconsistent != this.state.inconsistent;
    if (!isUndefined(inconsistent) && changed) {
      this.setState({ inconsistent, syncing: true });
      this.state.timeout && clearTimeout(this.state.timeout);
      const timeout = setTimeout(() => this.setState({ syncing: false }),
        SETTING_SYNC_TIMEOUT);
      this.setState({ timeout });
    }
  };

  render() {
    return <div className={"mcu-input-box"}>
      <BlurableInput
        type="number"
        className={this.className}
        wrapperClassName={this.wrapperClassName}
        title={this.props.title}
        value={this.showValue}
        onCommit={this.commit}
        disabled={this.props.disabled}
        error={this.props.warnMin && parseInt(this.showValue) &&
          (parseInt(this.showValue) < this.props.warnMin)
          ? t("Warning: low value")
          : this.props.warning}
        min={this.props.min || 0}
        max={this.props.inputMax || this.props.max
          || getMaxInputFromIntSize(this.props.intSize)} />
      <SettingStatusIndicator
        dispatch={this.props.dispatch}
        wasSyncing={this.state.syncing}
        isSyncing={!this.config.consistent} />
    </div>;
  }
}

export const microstepScaledConfig = (key: McuParamName) =>
  some(["step_per_mm", "encoder_scaling"].map(part => key.includes(part)));