FarmBot/Farmbot-Web-App

View on GitHub
frontend/farm_designer/map/layers/logs/logs_layer.tsx

Summary

Maintainability
A
1 hr
Test Coverage
import { isUndefined } from "lodash";
import React from "react";
import { BotPosition } from "../../../../devices/interfaces";
import { BooleanSetting } from "../../../../session_keys";
import { Color } from "../../../../ui";
import { BotFigure, CameraViewArea } from "../farmbot/bot_figure";
import {
  RenderedLog, AnimationClass, LogsLayerProps, LogVisualProps,
} from "./interfaces";

const LOG_MESSAGE_LOOKUP: Record<RenderedLog, string> = {
  [RenderedLog.imageCapture]: "Taking photo",
  [RenderedLog.imageCalibrate]: "Calibrating camera",
  [RenderedLog.imageDetect]: "Running weed detector",
  [RenderedLog.imageMeasure]: "Executing Measure Soil Height",
  [RenderedLog.findHomeAll]: "Finding home on all axes",
  [RenderedLog.findHomeX]: "Finding home on the X axis",
  [RenderedLog.findHomeY]: "Finding home on the Y axis",
  [RenderedLog.findHomeZ]: "Finding home on the Z axis",
  [RenderedLog.findLengthAll]: "Finding length of all axes",
  [RenderedLog.findLengthX]: "Determining length of the X axis",
  [RenderedLog.findLengthY]: "Determining length of the Y axis",
  [RenderedLog.findLengthZ]: "Determining length of the Z axis",
};

const LOG_VISUAL_LOOKUP: Record<string, RenderedLog> = {};
Object.entries(LOG_MESSAGE_LOOKUP)
  .map(([visual, message]: [RenderedLog, string]) =>
    LOG_VISUAL_LOOKUP[message] = visual);

const ANIMATION_CLASS_LOOKUP: Record<RenderedLog, AnimationClass> = {
  [RenderedLog.imageCapture]: AnimationClass.capture,
  [RenderedLog.imageCalibrate]: AnimationClass.scan,
  [RenderedLog.imageDetect]: AnimationClass.scan,
  [RenderedLog.imageMeasure]: AnimationClass.scan,
  [RenderedLog.findHomeAll]: AnimationClass.find,
  [RenderedLog.findHomeX]: AnimationClass.find,
  [RenderedLog.findHomeY]: AnimationClass.find,
  [RenderedLog.findHomeZ]: AnimationClass.find,
  [RenderedLog.findLengthAll]: AnimationClass.find,
  [RenderedLog.findLengthX]: AnimationClass.find,
  [RenderedLog.findLengthY]: AnimationClass.find,
  [RenderedLog.findLengthZ]: AnimationClass.find,
};

const ANIMATION_DURATION_LOOKUP =
  (target: string): Record<RenderedLog, number> => {
    const slow = target == "rpi";
    return {
      [RenderedLog.imageCapture]: slow ? 10 : 5,
      [RenderedLog.imageCalibrate]: slow ? 60 : 15,
      [RenderedLog.imageDetect]: slow ? 60 : 15,
      [RenderedLog.imageMeasure]: slow ? 60 : 15,
      [RenderedLog.findHomeAll]: 20,
      [RenderedLog.findHomeX]: 10,
      [RenderedLog.findHomeY]: 10,
      [RenderedLog.findHomeZ]: 10,
      [RenderedLog.findLengthAll]: 20,
      [RenderedLog.findLengthX]: 10,
      [RenderedLog.findLengthY]: 10,
      [RenderedLog.findLengthZ]: 10,
    };
  };

export const LogsLayer = (props: LogsLayerProps) =>
  <g id="logs-layer" className="logs-layer">
    {props.visible &&
      props.logs
        .filter(log => !log.body.id // new logs
          && Object.values(LOG_MESSAGE_LOOKUP).includes(log.body.message))
        .map(log =>
          <LogVisual key={log.uuid}
            log={log}
            visual={LOG_VISUAL_LOOKUP[log.body.message]}
            cropImage={!!props.getConfigValue(BooleanSetting.crop_images)}
            showUncroppedArea={!!props.getConfigValue(
              BooleanSetting.show_uncropped_camera_view_area)}
            animate={!props.getConfigValue(BooleanSetting.disable_animations)}
            cameraCalibrationData={props.cameraCalibrationData}
            deviceTarget={props.deviceTarget}
            botPosition={props.botPosition}
            plantAreaOffset={props.plantAreaOffset}
            mapTransformProps={props.mapTransformProps} />)}
  </g>;

export const LogVisual = (props: LogVisualProps) => {
  switch (props.visual) {
    case RenderedLog.imageCapture:
    case RenderedLog.imageCalibrate:
    case RenderedLog.imageDetect:
    case RenderedLog.imageMeasure:
      return <ImageVisual {...props} />;
    case RenderedLog.findHomeAll:
    case RenderedLog.findHomeX:
    case RenderedLog.findHomeY:
    case RenderedLog.findHomeZ:
    case RenderedLog.findLengthAll:
    case RenderedLog.findLengthX:
    case RenderedLog.findLengthY:
    case RenderedLog.findLengthZ:
      return <MovementVisual {...props} />;
  }
};

const ImageVisual = (props: LogVisualProps) => {
  const { x, y, z } = props.log.body;
  const fadeDelay = ANIMATION_DURATION_LOOKUP(props.deviceTarget)[props.visual];
  const fadeDuration = 0.5;
  const [display, setDisplay] = React.useState(true);
  setTimeout(() => setDisplay(false), (fadeDelay + fadeDuration) * 1000);
  const className = [
    ANIMATION_CLASS_LOOKUP[props.visual],
    props.animate ? "animate" : "",
  ].join(" ");
  const style = props.animate
    ? { animation: `fade-out ${fadeDuration}s ease ${fadeDelay}s forwards` }
    : {};
  return <g id={`image-log-${props.log.uuid}-visual`}
    className={className} style={style}>
    {display && <CameraViewArea logVisual={true}
      position={{ x, y, z }}
      cropPhotos={props.cropImage}
      showUncroppedArea={props.showUncroppedArea}
      cameraCalibrationData={props.cameraCalibrationData}
      mapTransformProps={props.mapTransformProps} />}
  </g>;
};

const MovementVisual = (props: LogVisualProps) => {
  const fadeDelay = ANIMATION_DURATION_LOOKUP(props.deviceTarget)[props.visual];
  const fadeDuration = 0.5;
  const [display, setDisplay] = React.useState(true);
  setTimeout(() => setDisplay(false), (fadeDelay + fadeDuration) * 1000);
  const className = [
    ANIMATION_CLASS_LOOKUP[props.visual],
    props.animate ? "animate" : "",
  ].join(" ");
  const style = props.animate
    ? { animation: `fade-out ${fadeDuration}s ease ${fadeDelay}s forwards` }
    : {};
  return <g id={`movement-log-${props.log.uuid}-visual`}
    className={className} style={style}>
    {display && awayFromHome(props.botPosition, 5)
      && <BotFigure figureName={"finding-home"}
        color={Color.yellow}
        position={{ x: 0, y: 0, z: 0 }}
        mapTransformProps={props.mapTransformProps}
        plantAreaOffset={props.plantAreaOffset} />}
  </g>;
};

export const positionDifferent =
  (botPosition0: BotPosition, botPosition1: BotPosition, threshold = 0) => {
    if (isUndefined(botPosition0.x)
      || isUndefined(botPosition0.y)
      || isUndefined(botPosition0.z)
      || isUndefined(botPosition1.x)
      || isUndefined(botPosition1.y)
      || isUndefined(botPosition1.z)) { return false; }
    const xDelta = Math.abs(botPosition1.x - botPosition0.x);
    const yDelta = Math.abs(botPosition1.y - botPosition0.y);
    const zDelta = Math.abs(botPosition1.z - botPosition0.z);
    return xDelta > threshold
      || yDelta > threshold
      || zDelta > threshold;
  };

export const awayFromHome = (botPosition: BotPosition, threshold = 0) =>
  positionDifferent(botPosition, { x: 0, y: 0, z: 0 }, threshold);