FarmBot/Farmbot-Web-App

View on GitHub
frontend/sequences/locals_list/variable_form_list.ts

Summary

Maintainability
C
1 day
Test Coverage
import { ResourceIndex, TaggedPointGroup } from "../../resources/interfaces";
import {
  selectAllToolSlotPointers,
  selectAllActivePoints,
  maybeFindToolById,
  selectAllPointGroups,
} from "../../resources/selectors";
import { betterCompact } from "../../util";
import {
  TaggedTool, TaggedPoint, TaggedToolSlotPointer, Xyz, Vector3,
  TaggedSequence,
  TaggedPeripheral,
  TaggedSensor,
} from "farmbot";
import { DropDownItem } from "../../ui";
import { capitalize, isNumber, sortBy } from "lodash";
import { Point } from "farmbot/dist/resources/api_resources";
import { t } from "../../i18next_wrapper";
import { SequenceMeta } from "../../resources/sequence_meta";
import { VariableType } from "./locals_list_support";

/** Return tool and location for all tools currently in tool slots. */
export function activeToolDDIs(resources: ResourceIndex): DropDownItem[] {
  const slots = selectAllToolSlotPointers(resources);
  return betterCompact(slots
    .map(slot => {
      const tool = maybeFindToolById(resources, slot.body.tool_id);
      if (tool) { return formatTool(tool, slot); }
    }))
    .filter(x => parseInt("" + x.value) > 0);
}

type PointerTypeName = Point["pointer_type"];
type DropdownHeadingId =
  | PointerTypeName
  | "Tool"
  | "PointGroup"
  | "Variable"
  | "Identifier"
  | "Sequence"
  | "Other";

/** Variable selection menu section names. */
const NAME_MAP: Record<DropdownHeadingId, string> = {
  "Variable": "Variables",
  "Identifier": "Variables",
  "GenericPointer": "Map Points",
  "Plant": "Plants",
  "ToolSlot": "Slots",
  "Tool": "Tools and Seed Containers",
  "PointGroup": "Groups",
  "Weed": "Weeds",
  "Sequence": "Sequence",
  "Other": "Other",
};

export const heading = (headingId: DropdownHeadingId): DropDownItem[] => ([{
  label: t(NAME_MAP[headingId]),
  heading: true,
  value: 0,
  headingId: headingId
}]);

const points2ddi = (allPoints: TaggedPoint[], pointerType: PointerTypeName) =>
  allPoints
    .filter(x => x.body.pointer_type === pointerType)
    .map(formatPoint)
    .filter(x => parseInt("" + x.value) > 0);

export const groups2Ddi = (
  groups: TaggedPointGroup[],
): DropDownItem[] => {
  return groups
    .filter(group => group.body.id)
    .map(group => {
      const count = group.body.member_count || 0;
      const label = `${group.body.name} (${count})`;
      return { label, value: "" + group.body.id, headingId: "PointGroup" };
    });
};

export const sequences2Ddi = (sequences: TaggedSequence[]): DropDownItem[] => {
  return sequences
    .filter(x => x.body.id)
    .map(x => {
      return { label: x.body.name, value: "" + x.body.id, headingId: "Sequence" };
    });
};

export const peripherals2Ddi =
  (peripherals: TaggedPeripheral[]): DropDownItem[] =>
    peripherals
      .filter(x => x.body.id)
      .map(x => ({
        label: x.body.label,
        value: "" + x.body.id,
        headingId: "Peripheral"
      }));

export const sensors2Ddi =
  (sensors: TaggedSensor[]): DropDownItem[] =>
    sensors
      .filter(x => x.body.id)
      .map(x => ({
        label: x.body.label,
        value: "" + x.body.id,
        headingId: "Sensor"
      }));

/** Variable and location selection menu items. */
export function variableFormList(
  resources: ResourceIndex,
  additionalItems: DropDownItem[],
  variableItems?: DropDownItem[],
  displayGroups?: boolean,
  variableType?: VariableType,
): DropDownItem[] {
  const allPoints = selectAllActivePoints(resources);
  const plantDDI = points2ddi(allPoints, "Plant");
  const genericPointerDDI = points2ddi(allPoints, "GenericPointer");
  const weedDDI = points2ddi(allPoints, "Weed");
  const toolDDI = activeToolDDIs(resources);
  const addItems = additionalItems
    .concat((variableItems && variableItems.length > 0)
      ? heading((variableItems[0].headingId as DropdownHeadingId) || "Variable")
      : [])
    .concat(variableItems || []);
  if (variableType == VariableType.Number) {
    return [NUMBER_DDI()].concat(addItems);
  }
  if (variableType == VariableType.Text) {
    return [TEXT_DDI()].concat(addItems);
  }
  if (variableType == VariableType.Resource) {
    return [];
  }
  const allGroups = selectAllPointGroups(resources);
  return [COORDINATE_DDI()]
    .concat(addItems)
    .concat(heading("Tool"))
    .concat(toolDDI)
    .concat(displayGroups ? heading("PointGroup") : [])
    .concat(displayGroups ? groups2Ddi(allGroups) : [])
    .concat(heading("Plant"))
    .concat(plantDDI)
    .concat(heading("GenericPointer"))
    .concat(genericPointerDDI)
    .concat(heading("Weed"))
    .concat(weedDDI);
}

/** Create drop down item with label; i.e., "Point/Plant (1, 2, 3)" */
export const formatPoint = (p: TaggedPoint): DropDownItem => {
  const { id, name, pointer_type, x, y, z } = p.body;
  return {
    label: dropDownName(name, { x, y, z }),
    value: "" + id,
    headingId: pointer_type
  };
};

/** Create drop down item with label; i.e., "Tool (1, 2, 3)" */
export const formatTool =
  (tool: TaggedTool, slot: TaggedToolSlotPointer | undefined): DropDownItem => {
    const { id, name } = tool.body;
    const coordinate = slot
      ? {
        x: slot.body.x,
        y: slot.body.y,
        z: slot.body.z
      }
      : undefined;
    const gantryMounted = !!slot?.body.gantry_mounted;
    return {
      label: dropDownName((name || "Untitled tool"), coordinate, gantryMounted),
      value: "" + id,
      headingId: "Tool",
    };
  };

/** Uniformly generate a label for things that have an X/Y/Z value. */
export function dropDownName(name: string, v?: Record<Xyz, number | undefined>,
  gantryMounted = false) {
  let label = name || "untitled";
  if (v) {
    const labelFor = (axis: number | undefined) => isNumber(axis) ? axis : "---";
    const xLabel = gantryMounted ? t("Gantry") : labelFor(v.x);
    label += ` (${xLabel}, ${labelFor(v.y)}, ${labelFor(v.z)})`;
  }
  return capitalize(label);
}

export const COORDINATE_DDI = (vector?: Vector3): DropDownItem => ({
  label: vector
    ? `${t("Coordinate")} (${vector.x}, ${vector.y}, ${vector.z})`
    : t("Custom coordinates"),
  value: vector ? JSON.stringify(vector) : "",
  headingId: "Coordinate"
});

const NUMBER_DDI = (): DropDownItem =>
  ({ label: t("Custom number"), value: 0, headingId: "Numeric" });

const TEXT_DDI = (): DropDownItem =>
  ({ label: t("Custom text"), value: "", headingId: "Text" });

export const NO_VALUE_SELECTED_DDI = (): DropDownItem =>
  ({ label: t("Select a location"), value: "", isNull: true });

export const LOCATION_PLACEHOLDER_DDI = (): DropDownItem =>
  ({ label: t("None"), value: "", headingId: "Location" });

export const sortVariables = (variables: (SequenceMeta | undefined)[]) =>
  sortBy(betterCompact(variables),
    v => v.celeryNode.args.label == "parent"
      ? t("Location")
      : v.celeryNode.args.label);