FarmBot/Farmbot-Web-App

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

Summary

Maintainability
A
35 mins
Test Coverage
import React from "react";
import { FBSelect, DropDownItem } from "../../ui";
import { updateMCU } from "../../devices/actions";
import { isNumber } from "lodash";
import { t } from "../../i18next_wrapper";
import {
  pinDropdowns, celery2DropDown, PinGroupName, PERIPHERAL_HEADING,
} from "../../sequences/step_tiles/pin_support";
import {
  selectAllPeripherals, selectAllSavedPeripherals,
} from "../../resources/selectors";
import { Dictionary, NamedPin, McuParamName } from "farmbot";
import { ResourceIndex } from "../../resources/interfaces";
import { NumberConfigKey } from "farmbot/dist/resources/configs/firmware";
import { SourceFwConfig } from "../../devices/interfaces";
import { getModifiedClassNameDefaultFalse } from "../default_values";

interface PinNumberDropdownProps {
  sourceFwConfig: SourceFwConfig;
  dispatch: Function;
  pinNumKey: McuParamName;
  resources: ResourceIndex;
  disabled: boolean;
}

/**
 * Dropdown of pin numbers values. Includes peripheral pin numbers.
 * Will show the peripheral name of a pin if the pin number value matches.
 */
export const PinNumberDropdown = (props: PinNumberDropdownProps) => {
  const { pinNumKey, resources, dispatch } = props;
  const pinNumberValue = props.sourceFwConfig(pinNumKey).value || 0;
  const peripheralIds = peripheralDictionary(resources);
  const pinNumberNode = pinNumOrNamedPin(pinNumberValue, peripheralIds);
  const classes = [
    props.sourceFwConfig(pinNumKey).consistent ? "" : "dim",
    props.disabled ? "disabled" : "",
    getModifiedClassNameDefaultFalse(pinNumberValue),
  ];
  return <FBSelect
    extraClass={classes.join(" ")}
    selectedItem={pinNumberValue
      ? celery2DropDown(pinNumberNode, resources)
      : undefined}
    customNullLabel={t("Select a pin")}
    onChange={onChange({ dispatch, oldValue: pinNumberValue, pinNumKey })}
    list={listItems(resources)} />;
};

const peripheralDictionary = (resources: ResourceIndex): Dictionary<number> => {
  const peripheralIds: Dictionary<number> = {};
  selectAllPeripherals(resources).map(p =>
    (p.body.pin && p.body.id) && (peripheralIds[p.body.pin] = p.body.id));
  return peripheralIds;
};

const pinNumOrNamedPin =
  (pin: number, lookup: Dictionary<number>): NamedPin | number =>
    lookup[pin]
      ? {
        kind: "named_pin",
        args: { pin_type: "Peripheral", pin_id: lookup[pin] }
      }
      : pin;

const DISABLE_DDI = (): DropDownItem => ({
  label: t("None"), value: 0
});

const listItems = (resources: ResourceIndex): DropDownItem[] =>
  [DISABLE_DDI(), ...peripheralItems(resources), ...pinDropdowns(n => n)];

const peripheralItems = (resources: ResourceIndex): DropDownItem[] => {
  const list = selectAllSavedPeripherals(resources)
    .filter(peripheral => isNumber(peripheral.body.pin))
    .map(peripheral => ({
      label: peripheral.body.label,
      value: "" + peripheral.body.pin,
      headingId: PinGroupName.Peripheral
    }));
  return list.length ? [PERIPHERAL_HEADING(), ...list] : [];
};

interface OnChangeProps {
  dispatch: Function;
  oldValue: number;
  pinNumKey: NumberConfigKey;
}

const onChange = (props: OnChangeProps) => (ddi: DropDownItem) => {
  const { oldValue, pinNumKey } = props;
  const newValue = parseInt("" + ddi.value);
  (isFinite(newValue) && (newValue !== oldValue)) &&
    props.dispatch(updateMCU(pinNumKey, "" + newValue));
};