FarmBot/Farmbot-Web-App

View on GitHub
frontend/farm_designer/panel_header.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import React from "react";
import { Link } from "../link";
import { t } from "../i18next_wrapper";
import { DevSettings } from "../settings/dev/dev_support";
import { getWebAppConfigValue } from "../config_storage/actions";
import { store } from "../redux/store";
import { BooleanSetting } from "../session_keys";
import { computeEditorUrlFromState } from "../nav/compute_editor_url_from_state";
import { compact } from "lodash";
import { selectAllFarmwareInstallations } from "../resources/selectors";
import { FilePath, Icon, Path } from "../internal_urls";

export enum Panel {
  Map = "Map",
  Plants = "Plants",
  Weeds = "Weeds",
  Points = "Points",
  Groups = "Groups",
  Curves = "Curves",
  SavedGardens = "SavedGardens",
  Sequences = "Sequences",
  Regimens = "Regimens",
  FarmEvents = "FarmEvents",
  Zones = "Zones",
  Controls = "Controls",
  Sensors = "Sensors",
  Photos = "Photos",
  Farmware = "Farmware",
  Tools = "Tools",
  Messages = "Messages",
  Logs = "Logs",
  Help = "Help",
  Settings = "Settings",
  Shop = "Shop",
}

type Tabs = keyof typeof Panel;

export enum PanelColor {
  green = "green",
  cyan = "cyan",
  brown = "brown",
  magenta = "magenta",
  gray = "gray",
  lightGray = "light-gray",
  yellow = "yellow",
  blue = "blue",
  navy = "navy",
  teal = "teal",
  red = "red",
}

export const TAB_COLOR: Record<Panel, PanelColor> = {
  [Panel.Map]: PanelColor.gray,
  [Panel.Plants]: PanelColor.gray,
  [Panel.Weeds]: PanelColor.gray,
  [Panel.Points]: PanelColor.gray,
  [Panel.Groups]: PanelColor.gray,
  [Panel.Curves]: PanelColor.gray,
  [Panel.Sequences]: PanelColor.gray,
  [Panel.Regimens]: PanelColor.gray,
  [Panel.SavedGardens]: PanelColor.gray,
  [Panel.FarmEvents]: PanelColor.gray,
  [Panel.Zones]: PanelColor.gray,
  [Panel.Controls]: PanelColor.gray,
  [Panel.Sensors]: PanelColor.gray,
  [Panel.Photos]: PanelColor.gray,
  [Panel.Farmware]: PanelColor.gray,
  [Panel.Tools]: PanelColor.gray,
  [Panel.Messages]: PanelColor.gray,
  [Panel.Logs]: PanelColor.gray,
  [Panel.Help]: PanelColor.gray,
  [Panel.Settings]: PanelColor.gray,
  [Panel.Shop]: PanelColor.gray,
};

export const TAB_ICON: Record<Panel, string> = {
  [Panel.Map]: FilePath.icon(Icon.map),
  [Panel.Plants]: FilePath.icon(Icon.plant),
  [Panel.Weeds]: FilePath.icon(Icon.weeds),
  [Panel.Points]: FilePath.icon(Icon.point),
  [Panel.Groups]: FilePath.icon(Icon.groups),
  [Panel.Curves]: FilePath.icon(Icon.curves),
  [Panel.Sequences]: FilePath.icon(Icon.sequence),
  [Panel.Regimens]: FilePath.icon(Icon.regimens),
  [Panel.SavedGardens]: FilePath.icon(Icon.gardens),
  [Panel.FarmEvents]: FilePath.icon(Icon.calendar),
  [Panel.Zones]: FilePath.icon(Icon.zones),
  [Panel.Controls]: FilePath.icon(Icon.controls),
  [Panel.Sensors]: FilePath.icon(Icon.sensors),
  [Panel.Photos]: FilePath.icon(Icon.photos),
  [Panel.Farmware]: FilePath.icon(Icon.farmware),
  [Panel.Tools]: FilePath.icon(Icon.tool),
  [Panel.Messages]: FilePath.icon(Icon.messages),
  [Panel.Logs]: FilePath.icon(Icon.logs),
  [Panel.Help]: FilePath.icon(Icon.help),
  [Panel.Settings]: FilePath.icon(Icon.settings),
  [Panel.Shop]: FilePath.icon(Icon.shop),
};

export const PANEL_SLUG: Record<Panel, string> = {
  [Panel.Map]: "",
  [Panel.Plants]: "plants",
  [Panel.Weeds]: "weeds",
  [Panel.Points]: "points",
  [Panel.Groups]: "groups",
  [Panel.Curves]: "curves",
  [Panel.Sequences]: "sequences",
  [Panel.Regimens]: "regimens",
  [Panel.SavedGardens]: "gardens",
  [Panel.FarmEvents]: "events",
  [Panel.Zones]: "zones",
  [Panel.Controls]: "controls",
  [Panel.Sensors]: "sensors",
  [Panel.Photos]: "photos",
  [Panel.Farmware]: "farmware",
  [Panel.Tools]: "tools",
  [Panel.Messages]: "messages",
  [Panel.Logs]: "logs",
  [Panel.Help]: "help",
  [Panel.Settings]: "settings",
  [Panel.Shop]: "shop",
};

const ALT_PANEL_SLUG: Record<string, string[]> = {
  [PANEL_SLUG[Panel.Tools]]: ["tool-slots"],
  [PANEL_SLUG[Panel.Help]]: ["developer", "tours", "support"],
};

export const PANEL_BY_SLUG: Record<string, Panel> = {};
Object.entries(PANEL_SLUG).map(([panel, slug]: [Panel, string]) =>
  PANEL_BY_SLUG[slug] = panel);

const PANEL_PATH: Partial<Record<Panel, () => string>> = {
  [Panel.Map]: Path.designer,
  [Panel.Sequences]: computeEditorUrlFromState("Sequence"),
  [Panel.Regimens]: computeEditorUrlFromState("Regimen"),
};

export const PANEL_TITLE = (): Record<Panel, string> => ({
  [Panel.Map]: t("Map"),
  [Panel.Plants]: t("Plants"),
  [Panel.Weeds]: t("Weeds"),
  [Panel.Points]: t("Points"),
  [Panel.Groups]: t("Groups"),
  [Panel.Curves]: t("Curves"),
  [Panel.Sequences]: t("Sequences"),
  [Panel.Regimens]: t("Regimens"),
  [Panel.SavedGardens]: t("Gardens"),
  [Panel.FarmEvents]: t("Events"),
  [Panel.Zones]: t("Zones"),
  [Panel.Controls]: t("Controls"),
  [Panel.Sensors]: t("Sensors"),
  [Panel.Photos]: t("Photos"),
  [Panel.Farmware]: t("Farmware"),
  [Panel.Tools]: t("Tools"),
  [Panel.Messages]: t("Messages"),
  [Panel.Logs]: t("Logs"),
  [Panel.Help]: t("Help"),
  [Panel.Settings]: t("Settings"),
  [Panel.Shop]: t("Shop"),
});

export const getCurrentPanel = (): Tabs | undefined => {
  if (Path.equals(Path.designer())) {
    return Panel.Map;
  } else if (Path.getSlug(Path.app()) == "sequences") {
    return Panel.Sequences;
  } else if (Path.getSlug(Path.app()) == "logs") {
    return undefined;
  } else if (Path.getSlug(Path.savedGardens()) == "templates") {
    return Panel.Plants;
  } else {
    const panelMatches = Object.values(PANEL_SLUG).map(slug => {
      const altSlugs = ALT_PANEL_SLUG[slug];
      if ([slug, ...(altSlugs || [])]
        .includes(Path.getSlug(Path.designer()))) {
        return PANEL_BY_SLUG[slug];
      }
    });
    return compact(panelMatches)[0];
  }
};

export const getPanelPath = (panel: Panel) => {
  const defaultLinkFn = () => Path.designer(PANEL_SLUG[panel]);
  const getPath = PANEL_PATH[panel] || defaultLinkFn;
  return getPath();
};

interface NavTabProps {
  panel: Panel;
}

const NavTab = (props: NavTabProps) =>
  <Link id={PANEL_SLUG[props.panel] || "map"}
    to={getPanelPath(props.panel)}
    style={{ flex: 0.3 }}
    className={[
      getCurrentPanel() === props.panel ? "active" : "",
    ].join(" ")}>
    <img width={35} height={30}
      src={TAB_ICON[props.panel]}
      title={PANEL_TITLE()[props.panel]} />
  </Link>;

const displayScrollIndicator = () => {
  const element = document.getElementsByClassName("panel-tabs")[1];
  const mobile = element?.clientWidth <= 500;
  const end = element?.scrollWidth - element?.scrollLeft == element?.clientWidth;
  return mobile && !end;
};

export const showSensors = () => {
  const getWebAppConfigVal = getWebAppConfigValue(store.getState);
  return !getWebAppConfigVal(BooleanSetting.hide_sensors);
};

export const showFarmware = () => {
  const { resources } = store.getState();
  const all = selectAllFarmwareInstallations(resources.index);
  const { firstPartyFarmwareNames } = resources.consumers.farmware;
  const installs = all
    .map(fw => fw.body.package || "")
    .filter(fwName => !firstPartyFarmwareNames.includes(fwName));
  return installs.length > 0;
};

interface DesignerNavTabsProps {
  hidden?: boolean;
}

interface DesignerNavTabsState {
  atEnd?: boolean;
}

export class DesignerNavTabs
  extends React.Component<DesignerNavTabsProps, DesignerNavTabsState> {
  state: DesignerNavTabsState = {};

  componentDidMount = () => this.updateScroll();

  updateScroll = () => this.setState({ atEnd: !displayScrollIndicator() });

  render() {
    const hidden = this.props.hidden ? "hidden" : "";
    return <div className={`panel-nav ${hidden}`}>
      {!this.state.atEnd && <div className={"scroll-indicator"} />}
      <div className={"panel-tabs"} onScroll={this.updateScroll}>
        <NavTab panel={Panel.Map} />
        <NavTab panel={Panel.Plants} />
        <NavTab panel={Panel.Weeds} />
        <NavTab panel={Panel.Points} />
        <NavTab panel={Panel.Curves} />
        <NavTab panel={Panel.Sequences} />
        <NavTab panel={Panel.Regimens} />
        <NavTab panel={Panel.FarmEvents} />
        {DevSettings.futureFeaturesEnabled() && <NavTab panel={Panel.Zones} />}
        {showSensors() && <NavTab panel={Panel.Sensors} />}
        <NavTab panel={Panel.Photos} />
        {showFarmware() && <NavTab panel={Panel.Farmware} />}
        <NavTab panel={Panel.Tools} />
        <NavTab panel={Panel.Messages} />
        <NavTab panel={Panel.Help} />
        <NavTab panel={Panel.Settings} />
      </div>
    </div>;
  }
}