code:
import { useBackend, useLocalState } from '../backend';
import { BlockQuote, Box, Button, Dimmer, Icon, LabeledList, Modal, ProgressBar, Section, Stack } from '../components';
import { Window } from '../layouts';
import { resolveAsset } from '../assets';
import { formatTime } from '../format';
import { capitalize } from 'common/string';
import nt_logo from '../assets/bg-zoomtech.svg';
import { Fragment } from 'inferno';

type ExplorationEventData = {
  name: string,
  ref: string
}

type FullEventData = {
  image: string,
  description: string,
  action_enabled: boolean,
  action_text: string,
  skippable: boolean,
  ignore_text: string,
  ref: string
}

type ChoiceData = {
  key: string,
  text: string
}

type AdventureData = {
  description: string,
  image: string,
  raw_image: string,
  choices: Array<ChoiceData>
}

type SiteData = {
  name: string,
  ref: string,
  description: string,
  coordinates: string,
  distance: number,
  band_info: Record<string, number>,
  revealed: boolean,
  point_scan_complete: boolean,
  deep_scan_complete: boolean,
  events: Array<ExplorationEventData>
}

enum DroneStatusEnum {
  Idle = "idle",
  Travel = "travel",
  Exploration = "exploration",
  Adventure = "adventure",
  Busy = "busy"
}

enum CargoType {
  Tool = "tool",
  Cargo = "cargo",
  Empty = "empty"
}

type CargoData = {
  type: CargoType,
  name: string
}

type DroneBasicData = {
  name: string,
  description: string,
  controlled: boolean,
  ref: string,
}

export type AdventureDataProvider = {
  adventure_data: AdventureData,
}

type DroneAdventure = AdventureDataProvider & {
  drone_status: DroneStatusEnum.Adventure,
};

type DroneData = {
  drone_name: string,
  drone_integrity: number,
  drone_max_integrity: number,
  drone_travel_coefficent: number,
  drone_log: Array<string>,
  configurable: boolean,
  cargo: Array<CargoData>,
  can_travel: boolean,
  travel_error: string,
};

type DroneBusy = {
  drone_status: DroneStatusEnum.Busy,
  wait_time_left: number,
  wait_message: string,
};

type DroneExploration = {
  drone_status: DroneStatusEnum.Exploration,
  sites: Array<SiteData>,
  site: SiteData,
  event?: FullEventData,
};

type DroneIdle = {
  drone_status: DroneStatusEnum.Idle,
  sites: Array<SiteData>,
  site: null,
};

type DroneTravel = {
  drone_status: DroneStatusEnum.Travel,
  travel_time: number,
  travel_time_left: number,
};

type ActiveDrone = DroneAdventure
  | DroneBusy
  | DroneExploration
  | DroneIdle
  | DroneTravel;

type ExodroneConsoleData = {
  signal_lost: boolean;

  // ui_static_data
  all_tools: Record<string, ToolData>;
  all_bands: Record<string, string>;
} & (
  | (({
      drone: true;
    } & DroneData) &
      ActiveDrone)
  | {
      all_drones: Array<DroneBasicData>;
      drone: undefined;
    }
);

type ToolData = {
  description: string,
  icon: string
}

export const ExodroneConsole = (props, context) => {
  const { data } = useBackend<ExodroneConsoleData>(context);
  const {
    signal_lost,
  } = data;

  const [
    choosingTools,
    setChoosingTools,
  ] = useLocalState(context, 'choosingTools', false);

  return (
    <Window width={650} height={500}>
      {!!signal_lost && <SignalLostModal />}
      {!!choosingTools && <ToolSelectionModal />}
      <Window.Content>
        <ExodroneConsoleContent />
      </Window.Content>
    </Window>
  );
};

const SignalLostModal = (props, context) => {
  const { act } = useBackend(context);
  return (
    <Modal
      backgroundColor="red"
      textAlign="center"
      width={30}
      height={22}
      p={0}
      style={{ "border-radius": "5%" }}>

      <Box
        backgroundColor="black"
        textColor="red"
        fontSize={2}
        style={{ "border-radius": "-10%" }}>
        CONNECTION LOST
      </Box>
      <Box p={2} italic>
        Connection to exploration drone interrupted.
        Please contact nearest Nanotrasen Exploration Division
        representative for further instructions.
      </Box>
      <Icon
        name="exclamation-triangle"
        textColor="black"
        size={5} />
      <Box>
        <Button
          content="Confirm"
          color="danger"
          style={{ "border": "1px solid black" }}
          onClick={() => act("confirm_signal_lost")} />
      </Box>
    </Modal>
  );
};

const DroneSelectionSection = (props: {
  all_drones: Array<DroneBasicData>,
}, context) => {
  const { act } = useBackend<ExodroneConsoleData>(context);
  const { all_drones } = props;

  return (

      <Stack vertical>
        {all_drones.map(drone => (
          <Fragment key={drone.ref}>
            <Stack.Item grow>
              <Stack fill>
                <Stack.Item basis={10} fontFamily="monospace" fontSize="18px">
                  {drone.name}
                </Stack.Item>
                <Stack.Divider />
                <Stack.Item fontFamily="monospace" mt={0.8}>
                  {drone.description}
                </Stack.Item>
                <Stack.Item grow />
                <Stack.Divider mr={1} />
                <Stack.Item ml={0}>
                  {drone.controlled && (
                    "Controlled by another console."
                  ) || (
                    <Button
                      content="Assume Control"
                      icon="plug"
                      onClick={() => act("select_drone", { "drone_ref": drone.ref })} />
                  )}
                </Stack.Item>
              </Stack>
            </Stack.Item>
            <Stack.Divider />
          </Fragment>
        ))}
      </Stack>

  );
};

const ToolSelectionModal = (props, context) => {
  const { act, data } = useBackend<ExodroneConsoleData>(context);
  const {
    all_tools = {},
  } = data;

  const [
    choosingTools,
    setChoosingTools,
  ] = useLocalState(context, 'choosingTools', false);

  const toolData = Object.keys(all_tools);
  return (
    <Modal>
      <Stack fill vertical pr={2}>
        <Stack.Item>
          Select Tool:
        </Stack.Item>
        <Stack.Item>
          <Stack textAlign="center">
            {!!toolData && toolData.map(tool_name => (
              <Stack.Item key={tool_name}>
                <Button
                  onClick={() => {
                    setChoosingTools(false);
                    act("add_tool", { tool_type: tool_name });
                  }}
                  width={6}
                  height={6}
                  tooltip={all_tools[tool_name].description}>
                  <Stack vertical>
                    <Stack.Item>
                      {capitalize(tool_name)}
                    </Stack.Item>
                    <Stack.Item ml={2.5}>
                      <Icon name={all_tools[tool_name].icon} size={3} />
                    </Stack.Item>
                  </Stack>
                </Button>
              </Stack.Item>
            )) || (
              <Stack.Item>
                <Button
                  content="Back" />
              </Stack.Item>
            )}
          </Stack>
        </Stack.Item>
      </Stack>
    </Modal>
  );
};

const EquipmentBox = (props: {
  cargo: CargoData,
  drone: DroneData,
}, context) => {
  const { act, data } = useBackend<ExodroneConsoleData>(context);
  const { all_tools = {} } = data;
  const { configurable } = props.drone;
  const cargo = props.cargo;
  const boxContents = cargo => {
    switch (cargo.type) {
      case "tool": // Tool icon+Remove button if configurable
        return (
          <Stack direction="column">
            <Stack.Item grow>
              <Button
                height={4.7}
                width={4.7}
                tooltip={capitalize(cargo.name)}
                tooltipPosition="right"
                color="transparent">
                <Icon
                  color="white"
                  name={all_tools[cargo.name].icon}
                  size={3}
                  pl={1.5}
                  pt={2} />
              </Button>
            </Stack.Item>
            {!!configurable && (
              <Stack.Item mt={-9.4} textAlign="right">
                <Button
                  onClick={() => act("remove_tool", { tool_type: cargo.name })}
                  color="danger"
                  icon="minus"
                  tooltipPosition="right"
                  tooltip="Remove Tool" />
              </Stack.Item>
            )}
          </Stack>
        );
      case "cargo":// Jettison button.
        return (
          <Stack direction="column">
            <Stack.Item>
              <Button
                mt={0}
                height={4.7}
                width={4.7}
                tooltip={capitalize(cargo.name)}
                tooltipPosition="right"
                color="transparent">
                <Icon
                  color="white"
                  name="box"
                  size={3}
                  pl={2.2}
                  pt={2} />
              </Button>
            </Stack.Item>
            <Stack.Item mt={-9.4} textAlign="right">
              <Button
                onClick={() => act("jettison", { target_ref: cargo.ref })}
                color="danger"
                icon="minus"
                tooltipPosition="right"
                tooltip={`Jettison ${cargo.name}`} />
            </Stack.Item>
          </Stack>
        );
      case "empty":
        return "";
    }
  };
  return (
    <Box
      width={5}
      height={5}
      style={{ border: '2px solid black' }}
      textAlign="center">
      {boxContents(cargo)}
    </Box>
  );
};

const EquipmentGrid = (props: {
  drone: ActiveDrone & DroneData,
}, context) => {
  const { act } = useBackend<ExodroneConsoleData>(context);
  const {
    cargo,
    configurable,
  } = props.drone;
  const [
    choosingTools,
    setChoosingTools,
  ] = useLocalState(context, 'choosingTools', false);
  return (
    <Stack vertical fill>
      <Stack.Item grow>

          <Stack vertical textAlign="center">
            <Stack.Item>
              <Button
                fluid
                icon="plug"
                content="Disconnect"
                onClick={() => act('end_control')} />
            </Stack.Item>
            <Stack.Divider />
            <Stack.Item>
              <Button.Confirm
                fluid
                icon="bomb"
                content="Self-Destruct"
                color="bad"
                onClick={() => act('self_destruct')} />
            </Stack.Item>
          </Stack>

      </Stack.Item>
      <Stack.Item>

          <Stack.Item>
            {!!configurable && (
              <Button
                fluid
                color="average"
                icon="wrench"
                content="Install Tool"
                onClick={() => setChoosingTools(true)} />
            )}
          </Stack.Item>
          <Stack.Item>
            <Stack wrap="wrap" width={10}>
              {cargo.map(cargo_element => (
                <EquipmentBox
                  drone={props.drone}
                  key={cargo_element.name}
                  cargo={cargo_element} />
              ))}
            </Stack>
          </Stack.Item>

      </Stack.Item>
    </Stack>
  );
};

const DroneStatus = (props: {
  drone_integrity: number,
  drone_max_integrity: number,
}, context) => {
  const {
    drone_integrity,
    drone_max_integrity,
  } = props;

  return (
    <Stack ml={-40}>
      <Stack.Item color="label" mt={0.2}>
        Integrity:
      </Stack.Item>
      <Stack.Item grow>
        <ProgressBar
          width="200px"
          ranges={{
            good: [0.7 * drone_max_integrity, drone_max_integrity],
            average: [0.4 * drone_max_integrity, 0.7 * drone_max_integrity],
            bad: [-Infinity, 0.4 * drone_max_integrity],
          }}
          value={drone_integrity}
          maxValue={drone_max_integrity} />
      </Stack.Item>
    </Stack>
  );
};

const NoSiteDimmer = () => {
  return (
    <Dimmer>
      <Stack textAlign="center" vertical>
        <Stack.Item>
          <Icon
            color="red"
            name="map"
            size={10}
          />
        </Stack.Item>
        <Stack.Item fontSize="18px" color="red">
          No Destinations.
        </Stack.Item>
        <Stack.Item basis={0} color="red">
          (Use the Scanner Array Console to find new locations.)
        </Stack.Item>
      </Stack>
    </Dimmer>
  );
};

const TravelTargetSelectionScreen = (props: {
  drone: (DroneExploration | DroneIdle | DroneTravel) & DroneData,
  showCancelButton?: boolean,
}, context) => {
  // List of sites and eta travel times to each
  const { act, data } = useBackend<ExodroneConsoleData>(context);
  const { drone } = props;
  const { all_bands } = data;
  const {
    can_travel,
    travel_error,
    drone_travel_coefficent,
  } = drone;

  const site = ("site" in drone) ? drone.site : null;
  const sites = ("sites" in drone) ? drone.sites : null;

  const travel_cost = target_site => {
    if (site) {
      return Math.max(Math.abs(site.distance - target_site.distance), 1)
        * drone_travel_coefficent;
    }
    else {
      return target_site.distance * drone_travel_coefficent;
    }
  };
  const [
    choosingTools,
    setChoosingTools,
  ] = useLocalState(context, 'choosingTools', false);
  const [
    TravelDimmerShown,
    setTravelDimmerShown,
  ] = useLocalState(context, 'TravelDimmerShown', false);

  const travel_to = ref => {
    setTravelDimmerShown(false);
    act("start_travel", { "target_site": ref });
  };

  const non_empty_bands = (dest : SiteData) => {
    const band_check = (s: string) => dest.band_info[s] !== undefined
      && dest.band_info[s] !== 0;
    return Object.keys(all_bands).filter(band_check);
  };
  const valid_destinations = sites && sites.filter(destination => (
    !site || destination.ref !== site.ref
  ));
  return (
    drone.drone_status === DroneStatusEnum.Travel && (
      <TravelDimmer drone={drone} />
    ) || (
      <Section
        title="Travel Destinations"
        fill
        scrollable
        buttons={
          <>
            {props.showCancelButton && (
              <Button
                ml={5}
                mr={0}
                content="Cancel"
                onClick={() => setTravelDimmerShown(false)} />
            )}
            <Box mt={props.showCancelButton && -3.5}>
              <DroneStatus
                drone_integrity={drone.drone_integrity}
                drone_max_integrity={drone.drone_max_integrity}
              />
            </Box>
          </>
        }>
        {((sites && !sites.length) && !choosingTools) && (
          <NoSiteDimmer />
        )}
        {site && (
          <Section
            mt={1}
            title="Home"
            buttons={
              <Box>
                ETA: {formatTime(site.distance * drone_travel_coefficent, "short")}
                <Button
                  ml={1}
                  content={can_travel ? "Launch!" : travel_error}
                  onClick={() => travel_to(null)}
                  disabled={!can_travel} />
              </Box>
            }
          />
        )}
        {valid_destinations?.map(destination => (
          <Section
            key={destination.ref}
            title={destination.name}
            buttons={
              <>
                ETA: {formatTime(travel_cost(destination), "short")}
                <Button
                  ml={1}
                  content={can_travel ? "Launch!" : travel_error}
                  onClick={() => travel_to(destination.ref)}
                  disabled={!can_travel} />
              </>
            }>
            <LabeledList>
              <LabeledList.Item label="Location">
                {destination.coordinates}
              </LabeledList.Item>
              <LabeledList.Item label="Description">
                {destination.description}
              </LabeledList.Item>
              <LabeledList.Divider />
              {non_empty_bands(destination).map(band => (
                <LabeledList.Item
                  key={band}
                  label={band}>
                  {destination.band_info[band]}
                </LabeledList.Item>
              ))}
            </LabeledList>

        ))}

    )
  );
};

const TravelDimmer = (props: {
  drone: DroneTravel,
}, context) => {
  const { travel_time_left } = props.drone;
  return (

      <Dimmer>
        <Stack textAlign="center" vertical>
          <Stack.Item>
            <Icon
              color="yellow"
              name="route"
              size={10}
            />
          </Stack.Item>
          <Stack.Item fontSize="18px" color="yellow">
            Travel Time: {formatTime(travel_time_left)}
          </Stack.Item>
        </Stack>
      </Dimmer>

  );
};

const TimeoutScreen = (props: {
  drone: DroneBusy,
}) => {
  const {
    wait_time_left,
    wait_message,
  } = props.drone;

  return (

      <Dimmer>
        <Stack textAlign="center" vertical>
          <Stack.Item>
            <Icon
              color="green"
              name="cog"
              size={10}
            />
          </Stack.Item>
          <Stack.Item fontSize="18px" color="green">
            {wait_message} ({formatTime(wait_time_left)})
          </Stack.Item>
        </Stack>
      </Dimmer>

  );
};

const ExplorationScreen = (props: {
  drone: DroneExploration & DroneData,
}, context) => {
  const { act } = useBackend(context);
  const { drone } = props;
  const { site } = drone;

  const [
    TravelDimmerShown,
    setTravelDimmerShown,
  ] = useLocalState(context, 'TravelDimmerShown', false);

  if (TravelDimmerShown) {
    return (<TravelTargetSelectionScreen
      drone={drone}
      showCancelButton
    />);
  }
  return (
    <Section
      fill
      title="Exploration"
      buttons={
        <DroneStatus
          drone_integrity={drone.drone_integrity}
          drone_max_integrity={drone.drone_max_integrity}
        />
      }>
      <Stack vertical fill>
        <Stack.Item grow>
          <LabeledList>
            <LabeledList.Item label="Site">{site.name}</LabeledList.Item>
            <LabeledList.Item label="Location">{site.coordinates}</LabeledList.Item>
            <LabeledList.Item label="Description">{site.description}</LabeledList.Item>
          </LabeledList>
        </Stack.Item>
        <Stack.Item align="center" grow>
          <Button
            content="Explore!"
            onClick={() => act("explore")} />
        </Stack.Item>
        {site.events.map(e => (
          <Stack.Item
            align="center"
            key={site.ref}
            grow>
            <Button
              content={capitalize(e.name)}
              onClick={() => act("explore_event", { target_event: e.ref })} />
          </Stack.Item>))}
        <Stack.Item align="center" grow>
          <Button
            content="Travel"
            onClick={() => setTravelDimmerShown(true)} />
        </Stack.Item>
      </Stack>

  );
};

const EventScreen = (props: {
  drone: DroneData,
  event: FullEventData,
}, context) => {
  const { act } = useBackend(context);
  const { drone, event } = props;

  return (
    <Section
      fill
      title="Exploration"
      buttons={
        <DroneStatus
          drone_integrity={drone.drone_integrity}
          drone_max_integrity={drone.drone_max_integrity}
        />
      }>
      <Stack vertical fill textAlign="center">
        <Stack.Item>
          <Stack fill>
            <Stack.Item>

            </Stack.Item>
            <Stack.Item >

                {event.description}

            </Stack.Item>
          </Stack>
        </Stack.Item>
        <Stack.Divider />
        <Stack.Item grow>
          <Stack vertical fill >
            <Stack.Item grow />
            <Stack.Item grow>
              <Button
                content={event.action_text}
                disabled={!event.action_enabled}
                onClick={() => act("start_event")} />
            </Stack.Item>
            {!!event.skippable && (
              <Stack.Item mt={2}>
                <Button
                  content={event.ignore_text}
                  onClick={() => act("skip_event")} />
              </Stack.Item>
            )}
            <Stack.Item grow />
          </Stack>
        </Stack.Item>
      </Stack>

  );
};

export const AdventureScreen = (props: {
  adventure_data: AdventureData,
  drone_integrity: number,
  drone_max_integrity: number,
  hide_status?: boolean,
}, context) => {
  const { act } = useBackend(context);
  const {
    adventure_data,
    drone_integrity,
    drone_max_integrity,
  } = props;
  const rawData = adventure_data.raw_image;
  const imgSource = rawData ? rawData : resolveAsset(adventure_data.image);
  return (
    <Section
      fill
      title="Exploration"
      buttons={!props.hide_status && <DroneStatus
        drone_integrity={drone_integrity}
        drone_max_integrity={drone_max_integrity}
      />}>
      <Stack>
        <Stack.Item>

            {adventure_data.description}

        </Stack.Item>
        <Stack.Divider />
        <Stack.Item>

          <Stack vertical>
            <Stack.Divider />
            <Stack.Item grow />
            {!!adventure_data.choices && adventure_data.choices.map(choice => (
              <Stack.Item key={choice.key}>
                <Button
                  fluid
                  content={choice.text}
                  textAlign="center"
                  onClick={() => act('adventure_choice', { choice: choice.key })} />
              </Stack.Item>
            ))}
            <Stack.Item grow />
          </Stack>
        </Stack.Item>
      </Stack>

  );
};

const DroneScreen = (props: {
  drone: ActiveDrone & DroneData,
}) => {
  const { drone } = props;

  switch (drone.drone_status) {
    case DroneStatusEnum.Busy:
      return <TimeoutScreen drone={drone} />;
    case DroneStatusEnum.Idle:
    case DroneStatusEnum.Travel:
      return <TravelTargetSelectionScreen drone={drone} />;
    case DroneStatusEnum.Adventure:
      return (<AdventureScreen
        adventure_data={drone.adventure_data}
        drone_integrity={drone.drone_integrity}
        drone_max_integrity={drone.drone_max_integrity}
      />);
    case DroneStatusEnum.Exploration:
      if (drone.event) {
        return <EventScreen drone={drone} event={drone.event} />;
      }
      else {
        return <ExplorationScreen drone={drone} />;
      }
  }
};

const ExodroneConsoleContent = (props, context) => {
  const { data } = useBackend<ExodroneConsoleData>(context);

  if (!data.drone) {
    return <DroneSelectionSection all_drones={data.all_drones} />;
  }

  const { drone_log } = data;

  return (
    <Stack fill vertical>
      <Stack.Item grow>
        <Stack vertical fill grow={2}>
          <Stack.Item grow>
            <Stack fill>
              <Stack.Item>
                <EquipmentGrid drone={data} />
              </Stack.Item>
              <Stack.Item grow basis={0}>
                <DroneScreen drone={data} />
              </Stack.Item>
            </Stack>
          </Stack.Item>
        </Stack>
      </Stack.Item>
      <Stack.Item height={10}>

          <LabeledList>
            {drone_log.map((log_line, ix) => (
              <LabeledList.Item key={log_line} label={`Entry ${ix + 1}`}>
                {log_line}
              </LabeledList.Item>
            ))}
          </LabeledList>

      </Stack.Item>
    </Stack>
  );
};


code:
import { useBackend } from '../backend';
import { BlockQuote, Box, Button, Flex, Icon, Modal, Section, LabeledList, NoticeBox, Stack } from '../components';
import { Window } from '../layouts';
import { formatTime } from '../format';

type SiteData = {
  name: string,
  ref: string,
  description: string,
  distance: number,
  band_info: Record<string, string>,
  revealed: boolean,
}

type ScanData = {
  scan_power: number,
  point_scan_eta: number,
  deep_scan_eta: number,
  point_scan_complete: boolean,
  deep_scan_complete: boolean
  site_data: SiteData
}

const ScanFailedModal = (props, context) => {
  const { act, data } = useBackend(context);
  return (
    <Modal>
      <Flex direction="column">
        <Flex.Item>
          <Box color="bad">SCAN FAILURE!</Box>
        </Flex.Item>
        <Flex.Item>
          <Button
            content="Confirm"
            onClick={() => act("confirm_fail")} />
        </Flex.Item>
      </Flex>
    </Modal>);
};

const ScanSelectionSection = (props, context) => {
  const { act, data } = useBackend<ScanData>(context);
  const {
    scan_power,
    point_scan_eta,
    deep_scan_eta,
    point_scan_complete,
    deep_scan_complete,
    site_data,
  } = data;
  const site = site_data;

  const point_cost = scan_power > 0 ? formatTime(point_scan_eta, "short") : "∞";
  const deep_cost = scan_power > 0 ? formatTime(deep_scan_eta, "short") : "∞";
  const scan_available = !point_scan_complete || !deep_scan_complete;
  return (
    <Stack vertical fill>
      <Stack.Item grow>
        <Section
          fill
          title="Site Data"
          buttons={
            <Button
              content="Back"
              onClick={() => act("select_site", { "site_ref": null })} />
          }>
          <LabeledList>
            <LabeledList.Item label="Name">{site.name}</LabeledList.Item>
            <LabeledList.Item label="Description">
              {site.revealed ? site.description : "No Data"}
            </LabeledList.Item>
            <LabeledList.Item label="Distance">{site.distance}</LabeledList.Item>
            <LabeledList.Divider />
            <LabeledList.Item label="Spectrography Data" />
            <LabeledList.Divider />
            {Object.keys(site.band_info).map(band => (
              <LabeledList.Item
                key={band}
                label={band}>
                {site.band_info[band]}
              </LabeledList.Item>
            ))}
          </LabeledList>

      </Stack.Item>
      {scan_available && (
        <Stack.Item>

            {!point_scan_complete && (

                  Point scan performs rudimentary scan of
                  the site, revealing its general characteristics.

                <Box>
                  <Button
                    content="Scan"
                    disabled={scan_power <= 0}
                    onClick={() => act("start_point_scan")} />
                  <Box
                    inline
                    pl={3}>
                    Estimated Time: {point_cost}.
                  </Box>
                </Box>

            )}
            {!deep_scan_complete && (

                  Deep scan performs full scan
                  of the site, revealing all details.

                <Box>
                  <Button
                    content="Scan"
                    disabled={scan_power <= 0}
                    onClick={() => act("start_deep_scan")} />
                  <Box
                    inline
                    pl={3}>
                    Estimated Time: {deep_cost}.
                  </Box>
                </Box>

            )}

        </Stack.Item>
      )}
    </Stack>
  );
};

type ScanInProgressData = {
  scan_time: number,
  scan_power: number,
  scan_description: string,
}

const ScanInProgressModal = (props, context) => {
  const { act, data } = useBackend<ScanInProgressData>(context);
  const {
    scan_time,
    scan_power,
    scan_description,
  } = data;

  return (
    <Modal ml={1}>
      <NoticeBox>Scan in Progress!</NoticeBox>
      <Box color="danger" />
      <LabeledList>
        <LabeledList.Item
          label="Scan summary">
          {scan_description}
        </LabeledList.Item>
        <LabeledList.Item
          label="Time left">
          {formatTime(scan_time)}
        </LabeledList.Item>
        <LabeledList.Item
          label="Scanning array power">
          {scan_power}
        </LabeledList.Item>
        <LabeledList.Item
          label="Emergency Stop">
          <Button.Confirm
            content="STOP SCAN"
            color="red"
            icon="times"
            onClick={() => act("stop_scan")} />
        </LabeledList.Item>
      </LabeledList>
    </Modal>
  );
};

type ExoscannerConsoleData = {
  scan_in_progress: boolean,
  scan_power: number,
  possible_sites: Array<SiteData>,
  wide_scan_eta: number,
  selected_site: string,
  failed: boolean,
  scan_conditions: Array<string>,
}

export const ExoscannerConsole = (props, context) => {
  const { act, data } = useBackend<ExoscannerConsoleData>(context);
  const {
    scan_in_progress,
    scan_power,
    possible_sites = [],
    wide_scan_eta,
    selected_site,
    failed,
    scan_conditions = [],
  } = data;

  const can_start_wide_scan = scan_power > 0;

  return (
    <Window>
      {!!scan_in_progress && (
        <ScanInProgressModal />
      )}
      {!!failed && (
        <ScanFailedModal />
      )}
      <Window.Content>
        <Stack vertical fill>
          <Stack.Item>

              <Stack>
                <Stack.Item grow>
                  {scan_power > 0 && (
                    <>
                      <Box pr={1} inline fontSize={2}>{scan_power}</Box>
                      <Icon
                        name="satellite-dish"
                        size={3} />
                    </>
                  ) || (
                    "No properly configured scanner arrays detected."
                  )}
                </Stack.Item>
              </Stack>

                {scan_conditions && scan_conditions.map(condition => (
                  <NoticeBox
                    key={condition}
                    warning>
                    {condition}
                  </NoticeBox>
                ))}

          </Stack.Item>
          {!!selected_site && (
            <Stack.Item grow>
              <ScanSelectionSection site_ref={selected_site} />
            </Stack.Item>
          )}
          {!selected_site && (
            <>
              <Stack.Item>

                  <Stack>
                    <Stack.Item>

                        Broad spectrum scan looking for
                        anything not matching known start charts.

                    </Stack.Item>
                    <Stack.Item>
                      Cost estimate: {scan_power > 0 ? formatTime(wide_scan_eta, "short") : "∞ minutes"}
                    </Stack.Item>
                    <Stack.Item>
                      <Button
                        mt={2}
                        content="Scan"
                        disabled={!can_start_wide_scan}
                        onClick={() => act("start_wide_scan")} />
                    </Stack.Item>
                  </Stack>

              </Stack.Item>
              <Stack.Item grow>
                <Section
                  fill
                  title="Configure Targeted Scans"
                  scrollable
                  buttons={
                    <Button
                      content="View Experiments"
                      onClick={() => act("open_experiments")}
                      icon="tasks" />
                  }>
                  <Stack vertical>
                    {possible_sites.map(site => (
                      <Stack.Item key={site.ref}>
                        <Button
                          content={site.name}
                          onClick={() => act("select_site", { "site_ref": site.ref })} />
                      </Stack.Item>
                    ))}
                  </Stack>

              </Stack.Item>
            </>
          )}
        </Stack>
      </Window.Content>
    </Window>
  );
};

repo_name: AnalWerewolf/sundown

license: mit


code:
import { toFixed } from 'common/math';
import { useBackend } from '../backend';
import { AnimatedNumber, Box, Button, LabeledList, NoticeBox, Section } from '../components';
import { Window } from '../layouts';

export const ExosuitControlConsole = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    mechs = [],
  } = data;
  return (
    <Window
      width={500}
      height={500}>
      <Window.Content scrollable>
        {mechs.length === 0 && (
          <NoticeBox>
            No exosuits detected
          </NoticeBox>
        )}
        {mechs.map(mech => (
          <Section
            key={mech.tracker_ref}
            title={mech.name}
            buttons={(
              <>
                <Button
                  icon="envelope"
                  content="Message"
                  disabled={!mech.pilot}
                  onClick={() => act('send_message', {
                    tracker_ref: mech.tracker_ref,
                  })} />
                <Button
                  icon="wifi"
                  content={mech.emp_recharging
                    ? 'Recharging...'
                    : 'EMP Burst'}
                  color="bad"
                  disabled={mech.emp_recharging}
                  onClick={() => act('shock', {
                    tracker_ref: mech.tracker_ref,
                  })} />
              </>
            )}>
            <LabeledList>
              <LabeledList.Item label="Integrity">
                <Box
                  color={(
                    mech.integrity <= 30 && 'bad'
                    || mech.integrity <= 70 && 'average'
                    || 'good'
                  )}>
                  {mech.integrity}%
                </Box>
              </LabeledList.Item>
              <LabeledList.Item label="Charge">
                <Box
                  color={(
                    mech.charge <= 30 && 'bad'
                    || mech.charge <= 70 && 'average'
                    || 'good'
                  )}>
                  {typeof mech.charge === 'number'
                    && mech.charge + '%'
                    || 'Not Found'}
                </Box>
              </LabeledList.Item>
              <LabeledList.Item label="Airtank">
                {typeof mech.airtank === 'number' && (
                  <AnimatedNumber
                    value={mech.airtank}
                    format={value => toFixed(value, 2) + ' kPa'} />
                ) || 'Not Equipped'}
              </LabeledList.Item>
              <LabeledList.Item label="Pilot">
                {mech.pilot.length > 0 && (
                  mech.pilot.map(pilot => (
                    <Box
                      key={pilot}
                      inline>
                      {pilot}{mech.pilot.length > 1 ? '|' : ''}
                    </Box>
                  ))) || 'None'}
              </LabeledList.Item>
              <LabeledList.Item label="Location">
                {mech.location || 'Unknown'}
              </LabeledList.Item>
              <LabeledList.Item label="Active Equipment">
                {mech.active_equipment || 'None'}
              </LabeledList.Item>
              {mech.cargo_space >= 0 && (
                <LabeledList.Item label="Used Cargo Space">
                  <Box
                    color={(
                      mech.cargo_space <= 30 && 'good'
                      || mech.cargo_space <= 70 && 'average'
                      || 'bad'
                    )}>
                    {mech.cargo_space}%
                  </Box>
                </LabeledList.Item>
              )}
            </LabeledList>
          </Section>
        ))}
      </Window.Content>
    </Window>
  );
};


code:
import { uniqBy } from 'common/collections';
import { createSearch } from 'common/string';
import { Fragment } from 'inferno';
import { useBackend, useSharedState } from '../backend';
import { Box, Button, Icon, Input, ProgressBar, Section, Stack } from '../components';
import { Materials, MaterialAmount, MaterialFormatting } from './common/Materials';
import { formatMoney } from '../format';
import { Window } from '../layouts';

const COLOR_NONE = 0;
const COLOR_AVERAGE = 1;
const COLOR_BAD = 2;

const COLOR_KEYS = {
  [COLOR_NONE]: false,
  [COLOR_AVERAGE]: "average",
  [COLOR_BAD]: "bad",
};

const materialArrayToObj = materials => {
  let materialObj = {};

  materials.forEach(m => {
    materialObj[m.name] = m.amount; });

  return materialObj;
};

const partBuildColor = (cost, tally, material) => {
  if (cost > material) {
    return { color: COLOR_BAD, deficit: (cost - material) };
  }

  if (tally > material) {
    return { color: COLOR_AVERAGE, deficit: cost };
  }

  if (cost + tally > material) {
    return { color: COLOR_AVERAGE, deficit: ((cost + tally) - material) };
  }

  return { color: COLOR_NONE, deficit: 0 };
};

const partCondFormat = (materials, tally, part) => {
  let format = { "textColor": COLOR_NONE };

  Object.keys(part.cost).forEach(mat => {
    format[mat] = partBuildColor(part.cost[mat], tally[mat], materials[mat]);

    if (format[mat].color > format["textColor"]) {
      format["textColor"] = format[mat].color;
    }
  });

  return format;
};

const queueCondFormat = (materials, queue) => {
  let materialTally = {};
  let matFormat = {};
  let missingMatTally = {};
  let textColors = {};

  queue.forEach((part, i) => {
    textColors[i] = COLOR_NONE;
    Object.keys(part.cost).forEach(mat => {
      materialTally[mat] = materialTally[mat] || 0;
      missingMatTally[mat] = missingMatTally[mat] || 0;

      matFormat[mat] = partBuildColor(
        part.cost[mat], materialTally[mat], materials[mat]
      );

      if (matFormat[mat].color !== COLOR_NONE) {
        if (textColors[i] < matFormat[mat].color) {
          textColors[i] = matFormat[mat].color;
        }
      }
      else {
        materialTally[mat] += part.cost[mat];
      }

      missingMatTally[mat] += matFormat[mat].deficit;
    });
  });
  return { materialTally, missingMatTally, textColors, matFormat };
};

const searchFilter = (search, allparts) => {
  if (!search.length) {
    return;
  }
  const resultFilter = createSearch(search, part => (
    (part.name || "")
    + (part.desc || "")
    + (part.searchMeta || "")
  ));
  let searchResults = [];
  Object.keys(allparts).forEach(category => {
    allparts[category]
      .filter(resultFilter)
      .forEach(e => {
        searchResults.push(e);
      });
  });
  searchResults = uniqBy(part => part.name)(searchResults);
  return searchResults;
};

export const ExosuitFabricator = (props, context) => {
  const { act, data } = useBackend(context);
  const queue = data.queue || [];
  const materialAsObj = materialArrayToObj(data.materials || []);
  const {
    materialTally,
    missingMatTally,
    textColors,
  } = queueCondFormat(materialAsObj, queue);
  const [displayMatCost, setDisplayMatCost] = useSharedState(
    context, 'display_mats', false);
  return (
    <Window
      title="Exosuit Fabricator"
      width={1100}
      height={640}>
      <Window.Content>
        <Stack fill vertical>
          <Stack.Item>
            <Stack fill>
              <Stack.Item grow>
                <Section fill title="Materials">
                  <Materials
                    materials={data.materials || []}
                    onEject={(ref, amount) => {
                      act("remove_mat", {
                        ref: ref,
                        amount: amount,
                      });
                    }}
                  />
                </Section>
              </Stack.Item>
              <Stack.Item>
                <Section fill title="Settings">
                  <Button.Checkbox
                    onClick={() => setDisplayMatCost(!displayMatCost)}
                    checked={displayMatCost}>
                    Display Material Costs
                  </Button.Checkbox>
                </Section>
              </Stack.Item>
            </Stack>
          </Stack.Item>
          <Stack.Item grow>
            <Stack fill>
              <Stack.Item>
                <Section
                  fill
                  title="Categories"
                  buttons={(
                    <Button
                      content="R&D Sync"
                      onClick={() => act("sync_rnd")} />
                  )}>
                  <PartSets />
                </Section>
              </Stack.Item>
              <Stack.Item grow>
                <PartLists
                  queueMaterials={materialTally}
                  materials={materialAsObj} />
              </Stack.Item>
              <Stack.Item width="420px">
                <Queue
                  queueMaterials={materialTally}
                  missingMaterials={missingMatTally}
                  textColors={textColors} />
              </Stack.Item>
            </Stack>
          </Stack.Item>
        </Stack>
      </Window.Content>
    </Window>
  );
};

const PartSets = (props, context) => {
  const { data } = useBackend(context);
  const partSets = data.partSets || [];
  const buildableParts = data.buildableParts || {};
  const [selectedPartTab, setSelectedPartTab] = useSharedState(
    context, 'part_tab', partSets.length ? buildableParts[0] : '');
  return partSets
    .filter(set => buildableParts[set])
    .map(set => (
      <Button
        key={set}
        fluid
        color="transparent"
        selected={set === selectedPartTab}
        onClick={() => setSelectedPartTab(set)}>
        {set}
      </Button>
    ));
};

const PartLists = (props, context) => {
  const { data } = useBackend(context);

  const getFirstValidPartSet = (sets => {
    for (let set of sets) {
      if (buildableParts[set]) {
        return set;
      }
    }
    return null;
  });

  const partSets = data.partSets || [];
  const buildableParts = data.buildableParts || [];

  const {
    queueMaterials,
    materials,
  } = props;

  const [
    selectedPartTab,
    setSelectedPartTab,
  ] = useSharedState(
    context,
    "part_tab",
    getFirstValidPartSet(partSets)
  );

  const [
    searchText,
    setSearchText,
  ] = useSharedState(context, "search_text", "");

  if (!selectedPartTab || !buildableParts[selectedPartTab]) {
    const validSet = getFirstValidPartSet(partSets);
    if (validSet) {
      setSelectedPartTab(validSet);
    }
    else {
      return;
    }
  }

  let partsList;
  // Build list of sub-categories if not using a search filter.
  if (!searchText) {
    partsList = { "Parts": [] };
    buildableParts[selectedPartTab].forEach(part => {
      part["format"] = partCondFormat(materials, queueMaterials, part);
      if (!part.subCategory) {
        partsList["Parts"].push(part);
        return;
      }
      if (!(part.subCategory in partsList)) {
        partsList[part.subCategory] = [];
      }
      partsList[part.subCategory].push(part);
    });
  }
  else {
    partsList = [];
    searchFilter(searchText, buildableParts).forEach(part => {
      part["format"] = partCondFormat(materials, queueMaterials, part);
      partsList.push(part);
    });
  }

  return (
    <Stack fill vertical>
      <Stack.Item>
        <Section fill>
          <Stack align="baseline">
            <Stack.Item>
              <Icon name="search" />
            </Stack.Item>
            <Stack.Item grow>
              <Input
                fluid
                placeholder="Search for..."
                onInput={(e, v) => setSearchText(v)} />
            </Stack.Item>
          </Stack>
        </Section>
      </Stack.Item>
      <Stack.Item grow>
        <Section fill scrollable>
          {!!searchText && (
            <PartCategory
              name={"Search Results"}
              parts={partsList}
              forceShow
              placeholder="No matching results..." />
          ) || (
            Object.keys(partsList).map(category => (
              <PartCategory
                key={category}
                name={category}
                parts={partsList[category]} />
            ))
          )}
        </Section>
      </Stack.Item>
    </Stack>
  );
};

const PartCategory = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    buildingPart,
  } = data;
  const {
    parts,
    name,
    forceShow,
    placeholder,
  } = props;
  const [
    displayMatCost,
  ] = useSharedState(context, 'display_mats', false);
  if (!forceShow && parts.length === 0) {
    return null;
  }
  return (
    <Section
      title={name}
      level={2}
      buttons={
        <Button
          disabled={!parts.length}
          color="good"
          content="Queue All"
          icon="plus-circle"
          onClick={() => act("add_queue_set", {
            part_list: parts.map(part => part.id),
          })} />
      }>
      {!parts.length && placeholder}
      {parts.map(part => (
        <Fragment key={part.name}>
          <Stack align="center">
            <Stack.Item>
              <Button
                disabled={(
                  buildingPart || part.format.textColor === COLOR_BAD
                )}
                color="good"
                icon="play"
                onClick={() => act("build_part", { id: part.id })} />
            </Stack.Item>
            <Stack.Item>
              <Button
                color="average"
                icon="plus-circle"
                onClick={() => act("add_queue_part", { id: part.id })} />
            </Stack.Item>
            <Stack.Item grow color={COLOR_KEYS[part.format.textColor]}>
              {part.name}
            </Stack.Item>
            <Stack.Item>
              <Button
                icon="question-circle"
                tooltip={
                  'Build Time: '
                  + part.printTime + 's. '
                  + (part.desc || '')
                }
                tooltipPosition="left" />
            </Stack.Item>
          </Stack>
          {displayMatCost && (
            <Stack mb={2}>
              {Object.keys(part.cost).map(material => (
                <Stack.Item
                  key={material}
                  width="50px"
                  color={COLOR_KEYS[part.format[material].color]}>
                  <MaterialAmount
                    formatting={MaterialFormatting.Money}
                    style={{
                      transform: 'scale(0.75) translate(0%, 10%)',
                    }}
                    name={material}
                    amount={part.cost[material]} />
                </Stack.Item>
              ))}
            </Stack>
          )}
        </Fragment>
      ))}
    </Section>
  );
};

const Queue = (props, context) => {
  const { act, data } = useBackend(context);
  const { isProcessingQueue } = data;
  const queue = data.queue || [];
  const {
    queueMaterials,
    missingMaterials,
    textColors,
  } = props;
  return (
    <Stack fill vertical>
      <Stack.Item grow>
        <Section
          fill
          scrollable
          title="Queue"
          buttons={(
            <>
              <Button.Confirm
                disabled={!queue.length}
                color="bad"
                icon="minus-circle"
                content="Clear Queue"
                onClick={() => act("clear_queue")} />
              {!!isProcessingQueue && (
                <Button
                  disabled={!queue.length}
                  content="Stop"
                  icon="stop"
                  onClick={() => act("stop_queue")} />
              ) || (
                <Button
                  disabled={!queue.length}
                  content="Build Queue"
                  icon="play"
                  onClick={() => act("build_queue")} />
              )}
            </>
          )}>
          <Stack fill vertical>
            <Stack.Item>
              <BeingBuilt />
            </Stack.Item>
            <Stack.Item>
              <QueueList textColors={textColors} />
            </Stack.Item>
          </Stack>
        </Section>
      </Stack.Item>
      {!!queue.length && (
        <Stack.Item>
          <Section title="Material Cost">
            <QueueMaterials
              queueMaterials={queueMaterials}
              missingMaterials={missingMaterials} />
          </Section>
        </Stack.Item>
      )}
    </Stack>
  );
};

const QueueMaterials = (props, context) => {
  const {
    queueMaterials,
    missingMaterials,
  } = props;
  return (
    <Stack wrap>
      {Object.keys(queueMaterials).map(material => (
        <Stack.Item key={material} textAlign="center">
          <MaterialAmount
            formatting={MaterialFormatting.Money}
            name={material}
            amount={queueMaterials[material]} />
          {!!missingMaterials[material] && (
            <Box color="bad">
              {formatMoney(missingMaterials[material])}
            </Box>
          )}
        </Stack.Item>
      ))}
    </Stack>
  );
};

const QueueList = (props, context) => {
  const { act, data } = useBackend(context);

  const {
    textColors,
  } = props;

  const queue = data.queue || [];

  if (!queue.length) {
    return (
      <>
        No parts in queue.
      </>
    );
  }

  return queue.map((part, index) => (
    <Stack key={part.name} wrap align="baseline">
      <Stack.Item>
        <Button
          icon="minus-circle"
          color="bad"
          onClick={() => act("del_queue_part", { index: index + 1 })} />
      </Stack.Item>
      <Stack.Item textColor={COLOR_KEYS[textColors[index]]}>
        {part.name}
      </Stack.Item>
    </Stack>
  ));
};

const BeingBuilt = (props, context) => {
  const { data } = useBackend(context);
  const {
    buildingPart,
    storedPart,
  } = data;

  if (storedPart) {
    const { name } = storedPart;
    return (
      <ProgressBar
        minValue={0}
        maxValue={1}
        value={1}
        color="average">
        <Stack>
          <Stack.Item grow>
            {name}
          </Stack.Item>
          <Stack.Item>
            Fabricator outlet obstructed...
          </Stack.Item>
        </Stack>
      </ProgressBar>
    );
  }

  if (buildingPart) {
    const {
      name,
      duration,
      printTime,
    } = buildingPart;
    const timeLeft = Math.ceil(duration/10);
    return (
      <ProgressBar
        minValue={0}
        maxValue={printTime}
        value={duration}>
        <Stack>
          <Stack.Item grow>
            {name}
          </Stack.Item>
          <Stack.Item>
            {timeLeft >= 0 && (timeLeft + 's') || 'Dispensing...'}
          </Stack.Item>
        </Stack>
      </ProgressBar>
    );
  }

  return null;
};


code:
import { Window } from '../layouts';
import { useBackend } from '../backend';
import { Section, Box, Button, Flex, Icon, LabeledList, Table, Tooltip } from '../components';
import { sortBy } from 'common/collections';

const ExperimentStages = props => {
  return (

      {props.children.map((stage, idx) =>
        (<ExperimentStageRow key={idx} {...stage} />))}

  );
};

const ExperimentStageRow = props => {
  const [type, description, value, altValue] = props;

  // Determine completion based on type of stage
  let completion = false;
  switch (type) {
    case "bool":
    case "detail":
      completion = value;
      break;
    case "integer":
      completion = value === altValue;
      break;
    case "float":
      completion = value >= 1;
      break;
  }

  return (

      <Table.Cell
        collapsing
        className={`ExperimentStage__Indicator ${type}`}
        color={completion ? "good" : "bad"}>
        {(type === "bool" && <Icon name={value ? "check" : "times"} />)
          || (type === "integer" && `${value}/${altValue}`)
          || (type === "float" && `${value * 100}%`)
          || (type === "detail" && "⤷")}

        {description}

  );
};

export const TechwebServer = (props, context) => {
  const { act, data } = useBackend(context);
  const { servers } = props;

  return (
    <Box m={1} className="ExperimentTechwebServer__Web">
      <Flex align="center" justify="space-between"
        className="ExperimentTechwebServer__WebHeader">
        <Flex.Item className="ExperimentTechwebServer__WebName">
          {servers[0].web_id} / {servers[0].web_org}
        </Flex.Item>
        <Flex.Item>
          <Button
            onClick={() => servers[0].selected
              ? act("clear_server")
              : act("select_server", { "ref": servers[0].ref })}
            content={servers[0].selected ? "Disconnect" : "Connect"}
            backgroundColor={servers[0].selected ? "good" : "rgba(0, 0, 0, 0.4)"}
            className="ExperimentTechwebServer__ConnectButton" />
        </Flex.Item>
      </Flex>
      <Box className="ExperimentTechwebServer__WebContent">

          Connectivity to this web is maintained by the following servers...

        <LabeledList>
          {servers.map((server, index) => {
            return (
              <LabeledList.Item
                key={index}
                label={server.name}>
                Located in {server.location}
              </LabeledList.Item>
            );
          })}
        </LabeledList>
      </Box>
    </Box>
  );
};

export const ExperimentConfigure = (props, context) => {
  const { act, data } = useBackend(context);
  const { always_active, has_start_callback } = data;
  let servers = data.servers ?? [];

  const experiments = sortBy(
    exp => exp.name
  )(data.experiments ?? []);

  // Group servers together by web
  let webs = new Map();
  servers.forEach(x => {
    if (x.web_id !== null) {
      if (!webs.has(x.web_id)) {
        webs.set(x.web_id, []);
      }
      webs.get(x.web_id).push(x);
    }
  });

  return (
    <Window
      resizable
      width={600}
      height={735}>
      <Window.Content>
        <Flex direction="column" height="100%">
          <Flex.Item mb={1}>

              <Box>
                {webs.size > 0
                  ? "Please select a techweb to connect to..."
                  : "Found no available techwebs!"}
              </Box>
              {webs.size > 0 && Array.from(webs, ([techweb, servers]) =>
                <TechwebServer key={techweb} servers={servers} />)}

          </Flex.Item>
          <Flex.Item mb={has_start_callback ? 1 : 0} grow={1}>
            {servers.some(e => e.selected) && (

                <Flex.Item mb={1}>
                  {experiments.length && always_active && (
                    "This device is configured to attempt to perform all available"
                    + " experiments, so no further configuration is necessary."
                  ) || experiments.length && (
                    "Select one of the following experiments..."
                  ) || (
                    "No experiments found on this web"
                  )}
                </Flex.Item>
                <Flex.Item>
                  {experiments.map((exp, i) => {
                    return (
                      <Experiment key={i} exp={exp} controllable />
                    );
                  })}
                </Flex.Item>

            )}
          </Flex.Item>
          {!!has_start_callback && (
            <Flex.Item>
              <Button
                fluid
                className="ExperimentConfigure__PerformExperiment"
                onClick={() => act("start_experiment_callback")}
                disabled={!experiments.some(e => e.selected)}
                icon="flask">
                Perform Experiment
              </Button>
            </Flex.Item>
          )}
        </Flex>
      </Window.Content>
    </Window>
  );
};

export const Experiment = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    exp,
    controllable,
  } = props;
  const {
    name,
    description,
    tag,
    selectable,
    selected,
    progress,
    performance_hint,
    ref,
  } = exp;

  return (
    <Box m={1} key={ref}
      className="ExperimentConfigure__ExperimentPanel">
      <Button fluid
        onClick={() => controllable && (selected
          ? act("clear_experiment")
          : act("select_experiment", { "ref": ref }))}
        backgroundColor={selected ? "good" : "#40628a"}
        className="ExperimentConfigure__ExperimentName"
        disabled={controllable && !selectable}>
        <Flex align="center" justify="space-between">
          <Flex.Item
            color={!controllable || selectable
              ? "white"
              : "rgba(0, 0, 0, 0.6)"}>
            {name}
          </Flex.Item>
          <Flex.Item
            color={!controllable || selectable
              ? "rgba(255, 255, 255, 0.5)"
              : "rgba(0, 0, 0, 0.5)"}>
            <Box className="ExperimentConfigure__TagContainer">
              {tag}
              <Tooltip
                content={performance_hint}
                position="bottom-start">
                <Icon
                  name="question-circle"
                  mx={0.5} />
                <Box className="ExperimentConfigure__PerformanceHint" />
              </Tooltip>
            </Box>
          </Flex.Item>
        </Flex>
      </Button>
      <Box className={"ExperimentConfigure__ExperimentContent"}>
        <Box mb={1}>
          {description}
        </Box>
        {props.children}
        <ExperimentStages>
          {progress}
        </ExperimentStages>
      </Box>
    </Box>
  );
};


code:
import { map } from 'common/collections';
import { toFixed } from 'common/math';
import { numberOfDecimalDigits } from '../../common/math';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Collapsible, ColorBox, Dropdown, Input, LabeledList, NoticeBox, NumberInput, Section } from '../components';
import { Window } from '../layouts';

const FilterIntegerEntry = (props, context) => {
  const { value, name, filterName } = props;
  const { act } = useBackend(context);
  return (
    <NumberInput
      value={value}
      minValue={-500}
      maxValue={500}
      stepPixelSize={5}
      width="39px"
      onDrag={(e, value) => act('modify_filter_value', {
        name: filterName,
        new_data: {
          [name]: value,
        },
      })} />
  );
};

const FilterFloatEntry = (props, context) => {
  const { value, name, filterName } = props;
  const { act } = useBackend(context);
  const [step, setStep] = useLocalState(context, `${filterName}-${name}`, 0.01);
  return (
    <>
      <NumberInput
        value={value}
        minValue={-500}
        maxValue={500}
        stepPixelSize={4}
        step={step}
        format={value => toFixed(value, numberOfDecimalDigits(step))}
        width="80px"
        onDrag={(e, value) => act('transition_filter_value', {
          name: filterName,
          new_data: {
            [name]: value,
          },
        })} />
      <Box
        inline
        ml={2}
        mr={1}>
        Step:
      </Box>
      <NumberInput
        value={step}
        step={0.001}
        format={value => toFixed(value, 4)}
        width="70px"
        onChange={(e, value) => setStep(value)} />
    </>
  );
};

const FilterTextEntry = (props, context) => {
  const { value, name, filterName } = props;
  const { act } = useBackend(context);

  return (
    <Input
      value={value}
      width="250px"
      onInput={(e, value) => act('modify_filter_value', {
        name: filterName,
        new_data: {
          [name]: value,
        },
      })} />
  );
};

const FilterColorEntry = (props, context) => {
  const { value, filterName, name } = props;
  const { act } = useBackend(context);
  return (
    <>
      <Button
        icon="pencil-alt"
        onClick={() => act('modify_color_value', {
          name: filterName,
        })} />
      <ColorBox
        color={value}
        mr={0.5} />
      <Input
        value={value}
        width="90px"
        onInput={(e, value) => act('transition_filter_value', {
          name: filterName,
          new_data: {
            [name]: value,
          },
        })} />
    </>
  );
};

const FilterIconEntry = (props, context) => {
  const { value, filterName } = props;
  const { act } = useBackend(context);
  return (
    <>
      <Button
        icon="pencil-alt"
        onClick={() => act('modify_icon_value', {
          name: filterName,
        })} />
      <Box inline ml={1}>
        {value}
      </Box>
    </>
  );
};

const FilterFlagsEntry = (props, context) => {
  const { name, value, filterName, filterType } = props;
  const { act, data } = useBackend(context);

  const filterInfo = data.filter_info;
  const flags = filterInfo[filterType]['flags'];
  return (
    map((bitField, flagName) => (
      <Button.Checkbox
        checked={value & bitField}
        content={flagName}
        onClick={() => act('modify_filter_value', {
          name: filterName,
          new_data: {
            [name]: value ^ bitField,
          },
        })} />
    ))(flags)
  );
};

const FilterDataEntry = (props, context) => {
  const { name, value, hasValue, filterName } = props;

  const filterEntryTypes = {
    int: <FilterIntegerEntry {...props} />,
    float: <FilterFloatEntry {...props} />,
    string: <FilterTextEntry {...props} />,
    color: <FilterColorEntry {...props} />,
    icon: <FilterIconEntry {...props} />,
    flags: <FilterFlagsEntry {...props} />,
  };

  const filterEntryMap = {
    x: 'float',
    y: 'float',
    icon: 'icon',
    render_source: 'string',
    flags: 'flags',
    size: 'float',
    color: 'color',
    offset: 'float',
    radius: 'float',
    falloff: 'float',
    density: 'int',
    threshold: 'float',
    factor: 'float',
    repeat: 'int',
  };

  return (
    <LabeledList.Item label={name}>
      {filterEntryTypes[filterEntryMap[name]] || "Not Found (This is an error)"}
      {' '}
      {!hasValue && <Box inline color="average">(Default)</Box>}
    </LabeledList.Item>
  );
};

const FilterEntry = (props, context) => {
  const { act, data } = useBackend(context);
  const { name, filterDataEntry } = props;
  const { type, priority, ...restOfProps } = filterDataEntry;

  const filterDefaults = data["filter_info"];

  const targetFilterPossibleKeys = Object.keys(filterDefaults[type]['defaults']);

  return (
    <Collapsible
      title={name + " (" + type + ")"}
      buttons={(
        <>
          <NumberInput
            value={priority}
            stepPixelSize={10}
            width="60px"
            onChange={(e, value) => act('change_priority', {
              name: name,
              new_priority: value,
            })}
          />
          <Button.Input
            content="Rename"
            placeholder={name}
            onCommit={(e, new_name) => act('rename_filter', {
              name: name,
              new_name: new_name,
            })}
            width="90px" />
          <Button.Confirm
            icon="minus"
            onClick={() => act("remove_filter", { name: name })} />
        </>
      )}>
      <Section level={2}>
        <LabeledList>
          {targetFilterPossibleKeys.map(entryName => {
            const defaults = filterDefaults[type]['defaults'];
            const value = restOfProps[entryName] || defaults[entryName];
            const hasValue = value !== defaults[entryName];
            return (
              <FilterDataEntry
                key={entryName}
                filterName={name}
                filterType={type}
                name={entryName}
                value={value}
                hasValue={hasValue} />
            );
          })}
        </LabeledList>
      </Section>
    </Collapsible>
  );
};

export const Filteriffic = (props, context) => {
  const { act, data } = useBackend(context);
  const name = data.target_name || "Unknown Object";
  const filters = data.target_filter_data || {};
  const hasFilters = Object.keys(filters).length !== 0;
  const filterDefaults = data["filter_info"];
  const [massApplyPath, setMassApplyPath] = useLocalState(context, 'massApplyPath', '');
  const [hiddenSecret, setHiddenSecret] = useLocalState(context, 'hidden', false);
  return (
    <Window
      title="Filteriffic"
      width={500}
      height={500}>
      <Window.Content scrollable>
        <NoticeBox danger>
          DO NOT MESS WITH EXISTING FILTERS IF YOU DO NOT KNOW THE CONSEQUENCES.
          YOU HAVE BEEN WARNED.
        </NoticeBox>
        <Section
          title={hiddenSecret ? (
            <>
              <Box mr={0.5} inline>
                MASS EDIT:
              </Box>
              <Input
                value={massApplyPath}
                width="100px"
                onInput={(e, value) => setMassApplyPath(value)} />
              <Button.Confirm
                content="Apply"
                confirmContent="ARE YOU SURE?"
                onClick={() => act('mass_apply', { path: massApplyPath })} />
            </>
          ) : (
            <Box
              inline
              onDblClick={() => setHiddenSecret(true)}>
              {name}
            </Box>
          )}
          buttons={(
            <Dropdown
              icon="plus"
              displayText="Add Filter"
              nochevron
              options={Object.keys(filterDefaults)}
              onSelected={value => act('add_filter', {
                name: 'default',
                priority: 10,
                type: value,
              })} />
          )} >
          {!hasFilters ? (
            <Box>
              No filters
            </Box>
          ) : (
            map((entry, key) => (
              <FilterEntry filterDataEntry={entry} name={key} key={key} />
            ))(filters)
          )}
        </Section>
      </Window.Content>
    </Window>
  );
};


code:
import { sortBy } from 'common/collections';
import { flow } from 'common/fp';
import { classes } from 'common/react';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, LabeledList, Section, Stack } from '../components';
import { Window } from '../layouts';
import { capitalize } from 'common/string';

export const FishCatalog = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    fish_info,
    sponsored_by,
  } = data;
  const fish_by_name = flow([
    sortBy(fish => fish.name),
  ])(data.fish_info || []);
  const [
    currentFish,
    setCurrentFish,
  ] = useLocalState(context, 'currentFish', null);
  return (
    <Window
      width={500}
      height={300}>
      <Window.Content>
        <Stack fill>
          <Stack.Item width="120px">
            <Section fill scrollable>
              {fish_by_name.map(f => (
                <Button
                  key={f.name}
                  fluid
                  color="transparent"
                  selected={f === currentFish}
                  onClick={() => { setCurrentFish(f); }}>
                  {f.name}
                </Button>
              ))}
            </Section>
          </Stack.Item>
          <Stack.Item grow basis={0}>
            <Section
              fill
              scrollable
              title={currentFish
                ? capitalize(currentFish.name)
                : sponsored_by + " Fish Index"}>
              {currentFish && (
                <LabeledList>
                  <LabeledList.Item label="Description">
                    {currentFish.desc}
                  </LabeledList.Item>
                  <LabeledList.Item label="Water type">
                    {currentFish.fluid}
                  </LabeledList.Item>
                  <LabeledList.Item label="Temperature">
                    {currentFish.temp_min} to {currentFish.temp_max}
                  </LabeledList.Item>
                  <LabeledList.Item label="Feeding">
                    {currentFish.feed}
                  </LabeledList.Item>
                  <LabeledList.Item label="Acquisition">
                    {currentFish.source}
                  </LabeledList.Item>
                  <LabeledList.Item label="Illustration">
                    <Box
                      className={classes([
                        'fish32x32',
                        currentFish.icon,
                      ])} />
                  </LabeledList.Item>
                </LabeledList>
              )}
            </Section>
          </Stack.Item>
        </Stack>
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend } from "../backend";
import { Box, Button, Flex, Section } from "../components";
import { Window } from "../layouts";

export const Folder = (props, context) => {
  const { act, data } = useBackend(context);
  const { theme, bg_color, folder_name, contents, contents_ref } = data;
  return (
    <Window
      title={folder_name || "Folder"}
      theme={theme}
      width={400}
      height={500}
    >
      <Window.Content backgroundColor={bg_color || "#7f7f7f"} scrollable>
        {contents.map((item, index) => (
          <Flex
            key={contents_ref[index]}
            color="black"
            backgroundColor="white"
            style={{ padding: "2px 2px 0 2px" }}
            mb={0.5}
          >
            <Flex.Item align="center" grow={1}>
              <Box align="center">{item}</Box>
            </Flex.Item>
            <Flex.Item>
              {
                <Button
                  icon="search"
                  onClick={() => act("examine", { ref: contents_ref[index] })}
                />
              }
              <Button
                icon="eject"
                onClick={() => act("remove", { ref: contents_ref[index] })}
              />
            </Flex.Item>
          </Flex>
        ))}
        {contents.length === 0 && (
          <Section>
            <Box color="lightgrey" align="center">
              This folder is empty!
            </Box>
          </Section>
        )}
      </Window.Content>
    </Window>
  );
};


code:
import { sortBy } from 'common/collections';
import { flow } from 'common/fp';
import { useBackend } from '../backend';
import { Box, Button, Section } from '../components';
import { Window } from '../layouts';

export const ForbiddenLore = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    charges,
  } = data;
  const to_know = flow([
    sortBy(to_know => to_know.state !== "Research",
      to_know => to_know.path === "Side"),
  ])(data.to_know || []);
  return (
    <Window
      width={500}
      height={900}>
      <Window.Content scrollable>
        <Section title="Research Eldritch Knowledge">
          Charges left : {charges}
          {to_know!== null ? (
            to_know.map(knowledge => (
              <Section
                key={knowledge.name}
                title={knowledge.name}
                level={2}>
                <Box bold my={1}>
                  {knowledge.path} path
                </Box>
                <Box my={1}>
                  <Button
                    content={knowledge.state}
                    disabled={knowledge.disabled}
                    onClick={() => act('research', {
                      name: knowledge.name,
                      cost: knowledge.cost,
                    })} />
                  {' '}
                  Cost : {knowledge.cost}
                </Box >
                <Box italic my={1}>
                  {knowledge.flavour}
                </Box>
                <Box my={1}>
                  {knowledge.desc}
                </Box>
              </Section>
            ))
          ) : (
            <Box>
              No more knowledge can be found
            </Box>
          )}
        </Section>
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend } from '../backend';
import { Box, Button, Icon, NoticeBox, ProgressBar, Section } from '../components';
import { Window } from '../layouts';

export const Gateway = () => {
  return (
    <Window>
      <Window.Content scrollable>
        <GatewayContent />
      </Window.Content>
    </Window>
  );
};

const GatewayContent = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    gateway_present = false,
    gateway_status = false,
    current_target = null,
    destinations = [],
  } = data;
  if (!gateway_present) {
    return (

        <NoticeBox>
          No linked gateway
        </NoticeBox>
        <Button
          fluid
          onClick={() => act('linkup')}>
          Linkup
        </Button>

    );
  }
  if (current_target) {
    return (

        <Icon name="rainbow" size={4} color="green" />
        <Button
          fluid
          onClick={() => act("deactivate")}>
          Deactivate
        </Button>

    );
  }
  if (!destinations.length) {
    return (

        No gateway nodes detected.

    );
  }
  return (
    <>
      {!gateway_status && (
        <NoticeBox>
          Gateway Unpowered
        </NoticeBox>
      )}
      {destinations.map(dest => (

          {dest.available && (
            <Button
              fluid
              onClick={() => act('activate', {
                destination: dest.ref,
              })}>
              Activate
            </Button>
          ) || (
            <>
              <Box m={1} textColor="bad">
                {dest.reason}
              </Box>
              {!!dest.timeout && (
                <ProgressBar
                  value={dest.timeout}>
                  Calibrating...
                </ProgressBar>
              )}
            </>
          )}

      ))}
    </>
  );
};


code:
import { useBackend } from '../backend';
import { Button, Flex, NoticeBox, Section } from '../components';
import { Window } from '../layouts';

export const GhostPoolProtection = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    events_or_midrounds,
    spawners,
    station_sentience,
    silicons,
    minigames,
  } = data;
  return (
    <Window
      title="Ghost Pool Protection"
      width={400}
      height={270}
      theme="admin">
      <Window.Content>
        <Flex grow={1} height="100%">
          <Section
            title="Options"
            buttons={
              <>
                <Button
                  color="good"
                  icon="plus-circle"
                  content="Enable Everything"
                  onClick={() => act("all_roles")} />
                <Button
                  color="bad"
                  icon="minus-circle"
                  content="Disable Everything"
                  onClick={() => act("no_roles")} />
              </>
            }>
            <NoticeBox danger>
              For people creating a sneaky event: If you
              toggle Station Created Sentience, people may
              catch on that admins have disabled roles for
              your event...
            </NoticeBox>
            <Flex.Item>
              <Button
                fluid
                textAlign="center"
                color={events_or_midrounds ? "good" : "bad"}
                icon="meteor"
                content="Events and Midround Rulesets"
                onClick={() => act("toggle_events_or_midrounds")} />
            </Flex.Item>
            <Flex.Item>
              <Button
                fluid
                textAlign="center"
                color={spawners ? "good" : "bad"}
                icon="pastafarianism"
                content="Ghost Role Spawners"
                onClick={() => act("toggle_spawners")} />
            </Flex.Item>
            <Flex.Item>
              <Button
                fluid
                textAlign="center"
                color={station_sentience ? "good" : "bad"}
                icon="user-astronaut"
                content="Station Created Sentience"
                onClick={() => act("toggle_station_sentience")} />
            </Flex.Item>
            <Flex.Item>
              <Button
                fluid
                textAlign="center"
                color={silicons ? "good" : "bad"}
                icon="robot"
                content="Silicons"
                onClick={() => act("toggle_silicons")} />
            </Flex.Item>
            <Flex.Item>
              <Button
                fluid
                textAlign="center"
                color={minigames ? "good" : "bad"}
                icon="gamepad"
                content="Minigames"
                onClick={() => act("toggle_minigames")} />
            </Flex.Item>
            <Flex.Item>
              <Button
                fluid
                textAlign="center"
                color="orange"
                icon="check"
                content="Apply Changes"
                onClick={() => act("apply_settings")} />
            </Flex.Item>
          </Section>
        </Flex>
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend } from '../backend';
import { Button, Section } from '../components';
import { Window } from '../layouts';

export const GlandDispenser = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    glands = [],
  } = data;
  return (
    <Window
      width={300}
      height={338}
      theme="abductor">
      <Window.Content>
        <Section>
          {glands.map(gland => (
            <Button
              key={gland.id}
              width="60px"
              height="60px"
              m={0.75}
              textAlign="center"
              lineHeight="55px"
              icon="eject"
              backgroundColor={gland.color}
              content={gland.amount || "0"}
              disabled={!gland.amount}
              onClick={() => act('dispense', {
                gland_id: gland.id,
              })} />
          ))}
        </Section>
      </Window.Content>
    </Window>
  );
};


code:
import { map, sortBy } from 'common/collections';
import { flow } from 'common/fp';
import { clamp } from 'common/math';
import { vecLength, vecSubtract } from 'common/vector';
import { useBackend } from '../backend';
import { Box, Button, Icon, LabeledList, Section, Table } from '../components';
import { Window } from '../layouts';

const coordsToVec = coords => map(parseFloat)(coords.split(', '));

export const Gps = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    currentArea,
    currentCoords,
    globalmode,
    power,
    tag,
    updating,
  } = data;
  const signals = flow([
    map((signal, index) => {
      // Calculate distance to the target. BYOND distance is capped to 127,
      // that's why we roll our own calculations here.
      const dist = signal.dist && (
        Math.round(vecLength(vecSubtract(
          coordsToVec(currentCoords),
          coordsToVec(signal.coords))))
      );
      return { ...signal, dist, index };
    }),
    sortBy(
      // Signals with distance metric go first
      signal => signal.dist === undefined,
      // Sort alphabetically
      signal => signal.entrytag),
  ])(data.signals || []);
  return (
    <Window
      title="Global Positioning System"
      width={470}
      height={700}>
      <Window.Content scrollable>
        <Section
          title="Control"
          buttons={(
            <Button
              icon="power-off"
              content={power ? "On" : "Off"}
              selected={power}
              onClick={() => act('power')} />
          )}>
          <LabeledList>
            <LabeledList.Item label="Tag">
              <Button
                icon="pencil-alt"
                content={tag}
                onClick={() => act('rename')} />
            </LabeledList.Item>
            <LabeledList.Item label="Scan Mode">
              <Button
                icon={updating ? "unlock" : "lock"}
                content={updating ? "AUTO" : "MANUAL"}
                color={!updating && "bad"}
                onClick={() => act('updating')} />
            </LabeledList.Item>
            <LabeledList.Item label="Range">
              <Button
                icon="sync"
                content={globalmode ? "MAXIMUM" : "LOCAL"}
                selected={!globalmode}
                onClick={() => act('globalmode')} />
            </LabeledList.Item>
          </LabeledList>

        {!!power && (
          <>

              <Box fontSize="18px">
                {currentArea} ({currentCoords})
              </Box>

                {signals.map(signal => (

                      {signal.entrytag}

                      {signal.degrees !== undefined && (
                        <Icon
                          mr={1}
                          size={1.2}
                          name="arrow-up"
                          rotation={signal.degrees} />
                      )}
                      {signal.dist !== undefined && (
                        signal.dist + 'm'
                      )}

                      {signal.coords}

                ))}

          </>
        )}
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend } from '../backend';
import { Box, Button, LabeledList, NoticeBox, ProgressBar, Section } from '../components';
import { Window } from '../layouts';

export const GravityGenerator = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    charging_state,
    operational,
  } = data;
  return (
    <Window
      width={400}
      height={155}>
      <Window.Content>
        {!operational && (
          <NoticeBox>
            No data available
          </NoticeBox>
        )}
        {!!operational && charging_state !== 0 && (
          <NoticeBox danger>
            WARNING - Radiation detected
          </NoticeBox>
        )}
        {!!operational && charging_state === 0 && (
          <NoticeBox success>
            No radiation detected
          </NoticeBox>
        )}
        {!!operational && (
          <GravityGeneratorContent />
        )}
      </Window.Content>
    </Window>
  );
};

const GravityGeneratorContent = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    breaker,
    charge_count,
    charging_state,
    on,
    operational,
  } = data;
  return (
    <Section>
      <LabeledList>
        <LabeledList.Item label="Power">
          <Button
            icon={breaker ? 'power-off' : 'times'}
            content={breaker ? 'On' : 'Off'}
            selected={breaker}
            disabled={!operational}
            onClick={() => act('gentoggle')} />
        </LabeledList.Item>
        <LabeledList.Item label="Gravity Charge">
          <ProgressBar
            value={charge_count / 100}
            ranges={{
              good: [0.7, Infinity],
              average: [0.3, 0.7],
              bad: [-Infinity, 0.3],
            }} />
        </LabeledList.Item>
        <LabeledList.Item label="Charge Mode">
          {charging_state === 0 && (
            on && (
              <Box color="good">
                Fully Charged
              </Box>
            ) || (
              <Box color="bad">
                Not Charging
              </Box>
            ))}
          {charging_state === 1 && (
            <Box color="average">
              Charging
            </Box>
          )}
          {charging_state === 2 && (
            <Box color="average">
              Discharging
            </Box>
          )}
        </LabeledList.Item>
      </LabeledList>
    </Section>
  );
};


code:
import { useBackend } from '../backend';
import { Box, Button, ColorBox, Flex, Stack, Icon, Input, LabeledList, Section, Table, Divider } from '../components';
import { Window } from '../layouts';

type ColorEntry = {
  index: Number;
  value: string;
}

type SpriteData = {
  icon_states: string[];
  finished: string;
  steps: SpriteEntry[];
  time_spent: Number;
}

type SpriteEntry = {
  layer: string;
  result: string;
  config_name: string;
}

type GreyscaleMenuData = {
  greyscale_config: string;
  colors: ColorEntry[];
  sprites: SpriteData;
  generate_full_preview: boolean;
  unlocked: boolean;
  monitoring_files: boolean;
  sprites_dir: string;
  icon_state: string;
  refreshing: boolean;
}

enum Direction {
  North = "north",
  NorthEast = "northeast",
  East = "east",
  SouthEast = "southeast",
  South = "south",
  SouthWest = "southwest",
  West = "west",
  NorthWest = "northwest"
}

const DirectionAbbreviation : Record<Direction, string> = {
  [Direction.North]: "N",
  [Direction.NorthEast]: "NE",
  [Direction.East]: "E",
  [Direction.SouthEast]: "SE",
  [Direction.South]: "S",
  [Direction.SouthWest]: "SW",
  [Direction.West]: "W",
  [Direction.NorthWest]: "NW",
};

const ConfigDisplay = (props, context) => {
  const { act, data } = useBackend<GreyscaleMenuData>(context);
  return (

      <LabeledList>
        <LabeledList.Item label="Design Type">
          <Button
            icon="cogs"
            onClick={() => act("select_config")}
          />
          <Input
            value={data.greyscale_config}
            onChange={(_, value) => act("load_config_from_string", { config_string: value })}
          />
        </LabeledList.Item>
      </LabeledList>

  );
};

const ColorDisplay = (props, context) => {
  const { act, data } = useBackend<GreyscaleMenuData>(context);
  const colors = (data.colors || []);
  return (

      <LabeledList>
        <LabeledList.Item
          label="Full Color String">
          <Button
            icon="dice"
            onClick={() => act("random_all_colors")}
            tooltip="Randomizes all color groups."
          />
          <Input
            value={colors.map(item => item.value).join('')}
            onChange={(_, value) => act("recolor_from_string", { color_string: value })}
          />
        </LabeledList.Item>
        {colors.map(item => (
          <LabeledList.Item
            key={`colorgroup${item.index}${item.value}`}
            label={`Color Group ${item.index}`}
            color={item.value}
          >
            <ColorBox
              color={item.value}
            />
            {" "}
            <Button
              icon="palette"
              onClick={() => act("pick_color", { color_index: item.index })}
              tooltip="Brings up a color pick window to replace this color group."
            />
            <Button
              icon="dice"
              onClick={() => act("random_color", { color_index: item.index })}
              tooltip="Randomizes the color for this color group."
            />
            <Input
              value={item.value}
              width={7}
              onChange={(_, value) => act("recolor", { color_index: item.index, new_color: value })}
            />
          </LabeledList.Item>
        ))}
      </LabeledList>

  );
};

const PreviewCompassSelect = (props, context) => {
  const { act, data } = useBackend<GreyscaleMenuData>(context);
  return (
    <Box>
      <Stack vertical>
        <Flex>
          <SingleDirection dir={Direction.NorthWest} />
          <SingleDirection dir={Direction.North} />
          <SingleDirection dir={Direction.NorthEast} />
        </Flex>
        <Flex>
          <SingleDirection dir={Direction.West} />
          <Flex.Item grow={1} basis={0}>
            <Button lineHeight={3} m={-0.2} fluid>
              <Icon name="arrows-alt" size={1.5} m="20%" />
            </Button>
          </Flex.Item>
          <SingleDirection dir={Direction.East} />
        </Flex>
        <Flex>
          <SingleDirection dir={Direction.SouthWest} />
          <SingleDirection dir={Direction.South} />
          <SingleDirection dir={Direction.SouthEast} />
        </Flex>
      </Stack>
    </Box>
  );
};

const SingleDirection = (props, context) => {
  const { dir } = props;
  const { data, act } = useBackend<GreyscaleMenuData>(context);
  return (
    <Flex.Item grow={1} basis={0}>
      <Button
        content={DirectionAbbreviation[dir]}
        tooltip={`Sets the direction of the preview sprite to ${dir}`}
        disabled={`${dir}` === data.sprites_dir ? true : false}
        textAlign="center"
        onClick={() => act("change_dir", { new_sprite_dir: dir })}
        lineHeight={3}
        m={-0.2}
        fluid
      />
    </Flex.Item>
  );
};

const IconStatesDisplay = (props, context) => {
  const { data, act } = useBackend<GreyscaleMenuData>(context);
  return (

      <Flex>
        {
          data.sprites.icon_states.map(item => (
            <Flex.Item key={item}>
              <Button
                mx={0.5}
                content={item ? item : "Blank State"}
                disabled={item === data.icon_state}
                onClick={() => act("select_icon_state", { new_icon_state: item })}
              />
            </Flex.Item>
          ))
        }
      </Flex>

  );
};

const PreviewDisplay = (props, context) => {
  const { data } = useBackend<GreyscaleMenuData>(context);
  return (
    <Section title={`Preview (${data.sprites_dir})`}>

            <PreviewCompassSelect />

          {
            data.sprites?.finished
              ? (

                  <Box as="img" src={data.sprites.finished} m={0} width="75%" mx="10%" style={{ "-ms-interpolation-mode": "nearest-neighbor" }} />

              )
              : (

                  <Box grow>
                    <Icon name="image" ml="25%" size={5} style={{ "-ms-interpolation-mode": "nearest-neighbor" }} />
                  </Box>

              )
          }

      {
        !!data.generate_full_preview
          && `Time Spent: ${data.sprites.time_spent}ms`
      }
      <Divider />
      {
        !data.refreshing
          && (

              {
                !!data.generate_full_preview && data.sprites.steps !== null
                  && (

Layer Source

Step Layer

Step Result

                  )
              }
              {
                !!data.generate_full_preview && data.sprites.steps !== null
                  && data.sprites.steps.map(item => (
                    <Table.Row key={`${item.result}|${item.layer}`}>

{item.config_name}

                        <SingleSprite source={item.layer} />

                        <SingleSprite source={item.result} />

                  ))
              }

          )
      }

  );
};

const SingleSprite = (props) => {
  const {
    source,
  } = props;
  return (
    <Box
      as="img"
      src={source}
      width="100%"
      style={{ "-ms-interpolation-mode": "nearest-neighbor" }}
    />
  );
};

const LoadingAnimation = () => {
  return (
    <Box height={0} mt="-100%">
      <Icon name="cog" height={22.7} opacity={0.5} size={25} spin />
    </Box>
  );
};

export const GreyscaleModifyMenu = (props, context) => {
  const { act, data } = useBackend<GreyscaleMenuData>(context);
  return (
    <Window
      title="Color Configuration"
      width={325}
      height={800}>
      <Window.Content scrollable>
        <ConfigDisplay />
        <ColorDisplay />
        <IconStatesDisplay />
        <Flex direction="column">
          {
            !!data.unlocked
              && (
                <Flex.Item justify="flex-start">
                  <Button
                    content={<Icon name="file-image-o" spin={data.monitoring_files} />}
                    tooltip="Continuously checks files for changes and reloads when necessary. WARNING: Very expensive"
                    selected={data.monitoring_files}
                    onClick={() => act("toggle_mass_refresh")}
                    width={1.9}
                    mr={-0.2}
                  />
                  <Button
                    content="Refresh Icon File"
                    tooltip="Loads the json configuration and icon file fresh from disk. This is useful to avoid restarting the server to see changes. WARNING: Expensive"
                    onClick={() => act("refresh_file")}
                  />
                  <Button
                    content="Save Icon File"
                    tooltip="Saves the icon to a temp file in tmp/. This is useful if you want to use a generated icon elsewhere or just view a more accurate representation"
                    onClick={() => act("save_dmi")}
                  />
                </Flex.Item>
              )
          }
          <Flex.Item>
            <Button
              content="Apply"
              tooltip="Applies changes made to the object this menu was created from."
              color="red"
              onClick={() => act("apply")}
            />
            <Button.Checkbox
              content="Full Preview"
              tooltip="Generates and displays the full sprite generation process instead of just the final output."
              disabled={!data.generate_full_preview && !data.unlocked}
              checked={data.generate_full_preview}
              onClick={() => act("toggle_full_preview")}
            />
          </Flex.Item>
        </Flex>
        <PreviewDisplay />
        {
          !!data.refreshing && <LoadingAnimation />
        }
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend } from '../backend';
import { Button, NoticeBox, Section, Table } from '../components';
import { Window } from '../layouts';

export const GulagItemReclaimer = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    mobs = [],
  } = data;
  return (
    <Window
      width={325}
      height={400}>
      <Window.Content scrollable>
        {mobs.length === 0 && (
          <NoticeBox>
            No stored items
          </NoticeBox>
        )}
        {mobs.length > 0 && (

              {mobs.map(mob => (

                    {mob.name}

                    <Button
                      content="Retrieve Items"
                      disabled={!data.can_reclaim}
                      onClick={() => act('release_items', {
                        mobref: mob.mob,
                      })} />

              ))}

        )}
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend } from '../backend';
import { Button, LabeledList, NumberInput, Section } from '../components';
import { Window } from '../layouts';

export const GulagTeleporterConsole = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    teleporter,
    teleporter_lock,
    teleporter_state_open,
    teleporter_location,
    beacon,
    beacon_location,
    id,
    id_name,
    can_teleport,
    goal = 0,
    prisoner = {},
  } = data;
  return (
    <Window
      width={350}
      height={295}>
      <Window.Content>
        <Section
          title="Teleporter Console"
          buttons={(
            <>
              <Button
                content={teleporter_state_open ? 'Open' : 'Closed'}
                disabled={teleporter_lock}
                selected={teleporter_state_open}
                onClick={() => act('toggle_open')} />
              <Button
                icon={teleporter_lock ? 'lock' : 'unlock'}
                content={teleporter_lock ? 'Locked' : 'Unlocked'}
                selected={teleporter_lock}
                disabled={teleporter_state_open}
                onClick={() => act('teleporter_lock')} />
            </>
          )}>
          <LabeledList>
            <LabeledList.Item
              label="Teleporter Unit"
              color={teleporter ? 'good' : 'bad'}
              buttons={!teleporter && (
                <Button
                  content="Reconnect"
                  onClick={() => act('scan_teleporter')}
                />
              )}>
              {teleporter ? teleporter_location : 'Not Connected'}
            </LabeledList.Item>
            <LabeledList.Item
              label="Receiver Beacon"
              color={beacon ? 'good' : 'bad'}
              buttons={!beacon && (
                <Button
                  content="Reconnect"
                  onClick={() => act('scan_beacon')} />
              )}>
              {beacon ? beacon_location : 'Not Connected'}
            </LabeledList.Item>
          </LabeledList>
        </Section>
        <Section title="Prisoner Details">
          <LabeledList>
            <LabeledList.Item label="Prisoner ID">
              <Button
                fluid
                content={id ? id_name : 'No ID'}
                onClick={() => act('handle_id')} />
            </LabeledList.Item>
            <LabeledList.Item label="Point Goal">
              <NumberInput
                value={goal}
                width="48px"
                minValue={1}
                maxValue={1000}
                onChange={(e, value) => act('set_goal', { value })} />
            </LabeledList.Item>
            <LabeledList.Item label="Occupant">
              {prisoner.name || 'No Occupant'}
            </LabeledList.Item>
            <LabeledList.Item label="Criminal Status">
              {prisoner.crimstat || 'No Status'}
            </LabeledList.Item>
          </LabeledList>
        </Section>
        <Button
          fluid
          content="Process Prisoner"
          disabled={!can_teleport}
          textAlign="center"
          color="bad"
          onClick={() => act('teleport')} />
      </Window.Content>
    </Window>
  );
};