code:
import {
  Box,
  Stack,
  Section,
  Button,
  Input,
  Dropdown,
  Icon,
} from '../../components';
import { Component } from 'inferno';
import { shallowDiffers } from 'common/react';

export class VariableMenu extends Component {
  constructor() {
    super();
    this.state = {
      variable_name: "",
      variable_type: "any",
    };
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (shallowDiffers(this.state, nextState)) {
      return true;
    }

    const { variables } = this.props;
    if (variables.length !== nextProps.variables.length) {
      return true;
    }
    for (let i = 0; i < variables.length; i++) {
      if (shallowDiffers(variables[i], nextProps.variables[i])) {
        return true;
      }
    }
    return false;
  }

  render() {
    const {
      variables,
      onAddVariable,
      onRemoveVariable,
      onClose,
      handleMouseDownSetter,
      handleMouseDownGetter,
      types,
      ...rest
    } = this.props;
    const {
      variable_name,
      variable_type,
    } = this.state;

    return (
      <Section
        title="Variable Options"
        {...rest}
        fill
        buttons={
          <Button
            icon="times"
            color="transparent"
            mr={2}
            onClick={onClose}
          />
        }
      >
        <Stack height="100%">
          <Stack.Item grow={1} mr={2}>
            <Section fill scrollable>
              <Stack vertical fill>
                {variables.map(val => (
                  <Stack.Item key={val.name}>
                    <Box backgroundColor="transparent" px="1px" py="1px" height="100%">
                      <Stack>
                        <Stack.Item basis="50%" grow>
                          <Box width="100%" overflow="hidden">
                            {val.name}
                          </Box>
                        </Stack.Item>
                        <Stack.Item minWidth="80px">
                          <Button
                            textAlign="center"
                            fluid
                            color={val.color}
                          >
                            {val.datatype}
                          </Button>
                        </Stack.Item>
                        <Stack.Item>
                          <Button
                            fluid
                            onMouseDown={(e) =>
                              handleMouseDownSetter(e, val.name)}
                            color={val.color}
                            tooltip={
                              multiline`
                            Drag me onto the circuit's grid
                            to make a setter for this variable`
                            }
                            icon="pen"
                          />
                        </Stack.Item>
                        <Stack.Item>
                          <Button
                            fluid
                            tooltip={
                              multiline`
                            Drag me onto the circuit's grid
                            to make a getter for this variable`
                            }
                            color={val.color}
                            onMouseDown={(e) =>
                              handleMouseDownGetter(e, val.name)}
                            icon="book-open"
                          />
                        </Stack.Item>
                        <Stack.Item>
                          <Button
                            icon="times"
                            color="bad"
                            onClick={(e) => onRemoveVariable(val.name, e)}
                          />
                        </Stack.Item>
                      </Stack>
                    </Box>
                  </Stack.Item>
                ))}
              </Stack>
            </Section>
          </Stack.Item>
          <Stack.Item height="100%" width="25%">
            <Box height="100%">
              <Stack vertical fill>
                <Stack.Item>
                  <Input
                    placeholder="Name"
                    fluid
                    onChange={(e, nameVal) => this.setState({
                      variable_name: nameVal,
                    })}
                  />
                </Stack.Item>
                <Stack.Item>
                  <Stack fill>
                    <Stack.Item grow>
                      <Dropdown
                        options={types}
                        displayText={variable_type}
                        className="Datatype__Option"
                        color="black"
                        width="100%"
                        over
                        onSelected={(selectedVal) => this.setState({
                          variable_type: selectedVal,
                        })}
                      />
                    </Stack.Item>
                    <Stack.Item>
                      <Button
                        height="100%"
                        color="green"
                        onClick={(e) =>
                          onAddVariable(variable_name, variable_type, false, e)}
                        fluid
                      >
                        <IconButton icon="plus" />
                      </Button>
                    </Stack.Item>
                    <Stack.Item>
                      <Button
                        height="100%"
                        color="green"
                        onClick={(e) =>
                          onAddVariable(variable_name, variable_type, true, e)}
                        fluid
                      >
                        <IconButton icon="list" />
                      </Button>
                    </Stack.Item>
                  </Stack>
                </Stack.Item>
              </Stack>
            </Box>
          </Stack.Item>
        </Stack>
      </Section>
    );
  }
}

const IconButton = (props, context) => {
  return (
    <Stack fill align="center">
      <Stack.Item grow basis="content">
        <Icon
          name={props.icon}
          size={1}
          width="100%"
          m="0em"
        />
      </Stack.Item>
    </Stack>
  );
};


code:
export const NULL_REF = '[0x0]';
export const ABSOLUTE_Y_OFFSET = -32;
export const SVG_CURVE_INTENSITY = 64;

export const MOUSE_BUTTON_LEFT = 0;
export const OPTION_DROPDOWN_LARGE_CHAR_AMOUNT = 12;
// in milliseconds
export const TIME_UNTIL_PORT_RELEASE_WORKS = 100;

repo_name: AnalWerewolf/sundown

license: mit


code:
import { useBackend } from '../../backend';
import {
  Input,
  InfinitePlane,
  Stack,
  Box,
  Button,
} from '../../components';
import { Component } from 'inferno';
import { Window } from '../../layouts';
import { resolveAsset } from '../../assets';
import { CircuitInfo } from './CircuitInfo';
import { ABSOLUTE_Y_OFFSET, MOUSE_BUTTON_LEFT, TIME_UNTIL_PORT_RELEASE_WORKS } from './constants';
import { Connections } from './Connections';
import { ObjectComponent } from './ObjectComponent';
import { VariableMenu } from './VariableMenu';

export class IntegratedCircuit extends Component {
  constructor() {
    super();
    this.state = {
      locations: {},
      selectedPort: null,
      mouseX: null,
      mouseY: null,
      zoom: 1,
      backgroundX: 0,
      backgroundY: 0,
      menuOpen: false,
    };
    this.handlePortLocation = this.handlePortLocation.bind(this);
    this.handleMouseDown = this.handleMouseDown.bind(this);
    this.handleMouseUp = this.handleMouseUp.bind(this);
    this.handlePortClick = this.handlePortClick.bind(this);
    this.handlePortRightClick = this.handlePortRightClick.bind(this);
    this.handlePortUp = this.handlePortUp.bind(this);

    this.handlePortDrag = this.handlePortDrag.bind(this);
    this.handlePortRelease = this.handlePortRelease.bind(this);
    this.handleZoomChange = this.handleZoomChange.bind(this);
    this.handleBackgroundMoved = this.handleBackgroundMoved.bind(this);

    this.onVarClickedSetter = this.onVarClickedSetter.bind(this);
    this.onVarClickedGetter = this.onVarClickedGetter.bind(this);
    this.handleVarDropped = this.handleVarDropped.bind(this);
  }

  // Helper function to get an element's exact position
  getPosition(el) {
    let xPos = 0;
    let yPos = 0;

    while (el) {
      xPos += el.offsetLeft;
      yPos += el.offsetTop;
      el = el.offsetParent;
    }
    return {
      x: xPos,
      y: yPos + ABSOLUTE_Y_OFFSET,
    };
  }

  handlePortLocation(port, dom) {
    const { locations } = this.state;

    if (!dom) {
      return;
    }

    const lastPosition = locations[port.ref];
    const position = this.getPosition(dom);
    position.color = port.color;

    if (
      isNaN(position.x)
      || isNaN(position.y)
      || (lastPosition
        && lastPosition.x === position.x
        && lastPosition.y === position.y)
    ) {
      return;
    }
    locations[port.ref] = position;
    this.setState({ locations: locations });
  }

  handlePortClick(portIndex, componentId, port, isOutput, event) {
    if (this.state.selectedPort) {
      this.handlePortUp(portIndex, componentId, port, isOutput, event);
      return;
    }

    if (event.button !== MOUSE_BUTTON_LEFT) {
      return;
    }

    event.stopPropagation();
    this.setState({
      selectedPort: {
        index: portIndex,
        component_id: componentId,
        is_output: isOutput,
        ref: port.ref,
      },
    });

    this.handlePortDrag(event);

    this.timeUntilPortReleaseTimesOut
      = Date.now() + TIME_UNTIL_PORT_RELEASE_WORKS;

    window.addEventListener('mousemove', this.handlePortDrag);
    window.addEventListener('mouseup', this.handlePortRelease);
  }

  // mouse up called whilst over a port. This means we can check if selectedPort
  // exists and do perform some actions if it does.
  handlePortUp(portIndex, componentId, port, isOutput, event) {
    const { act, data: uiData } = useBackend(this.context);
    const {
      selectedPort,
    } = this.state;
    if (!selectedPort) {
      return;
    }
    if (selectedPort.is_output === isOutput) {
      return;
    }
    this.setState({
      selectedPort: null,
    });
    let data;
    if (isOutput) {
      data = {
        input_port_id: selectedPort.index,
        output_port_id: portIndex,
        input_component_id: selectedPort.component_id,
        output_component_id: componentId,
      };
    } else {
      data = {
        input_port_id: portIndex,
        output_port_id: selectedPort.index,
        input_component_id: componentId,
        output_component_id: selectedPort.component_id,
      };
    }
    act("add_connection", data);

    const { components } = uiData;
    const {
      input_component_id,
      input_port_id,
      output_component_id,
      output_port_id,
    } = data;

    const input_comp = components[input_component_id-1];
    const input_port = input_comp.input_ports[input_port_id-1];
    const output_comp = components[output_component_id-1];
    const output_port = output_comp.output_ports[output_port_id-1];
    // Do not predict ports that do not match because there is no guarantee
    // that they will properly match.
    // TODO: Implement proper prediction for this
    if (!input_port || input_port.type !== output_port.type) {
      return;
    }
    input_port.connected_to.push(isOutput? port.ref : selectedPort.ref);
  }

  handlePortDrag(event) {
    const { data } = useBackend(this.context);
    const { screen_x, screen_y } = data;
    this.setState((state) => ({
      mouseX: event.clientX - (state.backgroundX || screen_x),
      mouseY: event.clientY - (state.backgroundY || screen_y),
    }));
  }

  handlePortRelease(event) {
    window.removeEventListener('mouseup', this.handlePortRelease);

    // This will let players release their mouse when dragging
    // to stop connecting the port, whilst letting players
    // click on the port to click and connect.
    if (this.timeUntilPortReleaseTimesOut > Date.now()) {
      return;
    }

    this.setState({
      selectedPort: null,
    });

    window.removeEventListener('mousemove', this.handlePortDrag);
  }

  handlePortRightClick(portIndex, componentId, port, isOutput, event) {
    const { act } = useBackend(this.context);

    event.preventDefault();
    act('remove_connection', {
      component_id: componentId,
      is_input: !isOutput,
      port_id: portIndex,
    });
  }

  handleZoomChange(newZoom) {
    this.setState({
      zoom: newZoom,
    });
  }

  handleBackgroundMoved(newX, newY) {
    this.setState({
      backgroundX: newX,
      backgroundY: newY,
    });
  }

  componentDidMount() {
    window.addEventListener('mousedown', this.handleMouseDown);
    window.addEventListener('mouseup', this.handleMouseUp);
  }

  componentWillUnmount() {
    window.removeEventListener('mousedown', this.handleMouseDown);
    window.removeEventListener('mouseup', this.handleMouseUp);
  }

  handleMouseDown(event) {
    const { act, data } = useBackend(this.context);
    const { examined_name } = data;
    if (examined_name) {
      act('remove_examined_component');
    }

    if (this.state.selectedPort) {
      this.handlePortRelease(event);
    }
  }

  handleMouseUp(event) {
    const { act } = useBackend(this.context);
    const { backgroundX, backgroundY } = this.state;
    if (backgroundX && backgroundY) {
      act("move_screen", {
        screen_x: backgroundX,
        screen_y: backgroundY,
      });
    }
  }

  onVarClickedSetter(event, variable) {
    this.handleVarClicked(event, variable, true);
  }

  onVarClickedGetter(event, variable) {
    this.handleVarClicked(event, variable, false);
  }

  handleVarClicked(event, variable, is_setter) {
    this.setState({
      draggingVariable: variable,
      variableIsSetter: is_setter,
    });

    window.addEventListener('mouseup', this.handleVarDropped);
  }

  handleVarDropped(event) {
    const { data, act } = useBackend(this.context);
    const {
      draggingVariable,
      variableIsSetter,
      backgroundX,
      backgroundY,
      zoom,
    } = this.state;
    const {
      screen_x,
      screen_y,
    } = data;

    const xPos = (event.clientX - (backgroundX || screen_x));
    const yPos = (event.clientY - (backgroundY || screen_y));

    act("add_setter_or_getter", {
      variable: draggingVariable,
      is_setter: variableIsSetter,
      rel_x: xPos*Math.pow(zoom, -1),
      rel_y: (yPos + ABSOLUTE_Y_OFFSET)*Math.pow(zoom, -1),
    });

    this.setState({
      draggingVariable: null,
      variableIsSetter: null,
    });

    window.removeEventListener('mouseup', this.handleVarDropped);
  }

  render() {
    const { act, data } = useBackend(this.context);
    const {
      components,
      display_name,
      examined_name,
      examined_desc,
      examined_notices,
      examined_rel_x,
      examined_rel_y,
      screen_x,
      screen_y,
      is_admin,
      variables,
      global_basic_types,
    } = data;
    const { locations, selectedPort, menuOpen } = this.state;
    const connections = [];

    for (const comp of components) {
      if (comp === null) {
        continue;
      }

      for (const input of comp.input_ports) {
        for (const output of input.connected_to) {
          const output_port = locations[output];
          connections.push({
            color: (output_port && output_port.color) || 'blue',
            from: output_port,
            to: locations[input.ref],
          });
        }
      }
    }

    if (selectedPort) {
      const { mouseX, mouseY, zoom } = this.state;
      const isOutput = selectedPort.is_output;
      const portLocation = locations[selectedPort.ref];
      const mouseCoords = {
        x: (mouseX)*Math.pow(zoom, -1),
        y: (mouseY + ABSOLUTE_Y_OFFSET)*Math.pow(zoom, -1),
      };
      connections.push({
        color: (portLocation && portLocation.color) || 'blue',
        from: isOutput? portLocation : mouseCoords,
        to: isOutput? mouseCoords : portLocation,
      });
    }

    return (
      <Window
        width={1200}
        height={800}
        buttons={(
          <Box
            width="160px"
            position="absolute"
            top="5px"
            height="22px"
          >
            <Stack>
              <Stack.Item grow>
                <Input
                  fluid
                  placeholder="Circuit Name"
                  value={display_name}
                  onChange={(e, value) => act("set_display_name", { display_name: value })}
                />
              </Stack.Item>
              <Stack.Item basis="24px">
                <Button
                  position="absolute"
                  top={0}
                  color="transparent"
                  icon="cog"
                  selected={menuOpen}
                  onClick={() => this.setState((state) => ({
                    menuOpen: !state.menuOpen,
                  }))}
                />
              </Stack.Item>
              {!!is_admin && (
                <Stack.Item>
                  <Button
                    position="absolute"
                    top={0}
                    color="transparent"
                    onClick={() => act("save_circuit")}
                    icon="save"
                  />
                </Stack.Item>
              )}
            </Stack>
          </Box>
        )}
      >
        <Window.Content
          style={{
            'background-image': 'none',
          }}>
          <InfinitePlane
            width="100%"
            height="100%"
            backgroundImage={resolveAsset('grid_background.png')}
            imageWidth={900}
            onZoomChange={this.handleZoomChange}
            onBackgroundMoved={this.handleBackgroundMoved}
            initialLeft={screen_x}
            initialTop={screen_y}
          >
            {components.map(
              (comp, index) =>
                comp && (
                  <ObjectComponent
                    key={index}
                    {...comp}
                    index={index + 1}
                    onPortUpdated={this.handlePortLocation}
                    onPortLoaded={this.handlePortLocation}
                    onPortMouseDown={this.handlePortClick}
                    onPortRightClick={this.handlePortRightClick}
                    onPortMouseUp={this.handlePortUp}
                  />
                )
            )}
            <Connections connections={connections} />
          </InfinitePlane>
          {!!examined_name && (
            <CircuitInfo
              position="absolute"
              className="CircuitInfo__Examined"
              top={`${examined_rel_y}px`}
              left={`${examined_rel_x}px`}
              name={examined_name}
              desc={examined_desc}
              notices={examined_notices}
            />
          )}
          {!!menuOpen && (
            <Box
              position="absolute"
              left={0}
              bottom={0}
              height="20%"
              minHeight="175px"
              minWidth="600px"
              width="50%"
              style={{
                "border-radius": "0px 32px 0px 0px",
                "background-color": "rgba(0, 0, 0, 0.3)",
                "-ms-user-select": "none",
              }}
              unselectable="on"
            >
              <VariableMenu
                variables={variables}
                types={global_basic_types}
                onClose={(event) => this.setState({ menuOpen: false })}
                onAddVariable={(name, type, asList, event) => act("add_variable", {
                  variable_name: name,
                  variable_datatype: type,
                  is_list: asList,
                })}
                onRemoveVariable={(name, event) => act("remove_variable", {
                  variable_name: name,
                })}
                handleMouseDownSetter={this.onVarClickedSetter}
                handleMouseDownGetter={this.onVarClickedGetter}
                style={{
                  "border-radius": "0px 32px 0px 0px",
                }}
              />
            </Box>
          )}
        </Window.Content>
      </Window>
    );
  }
}


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

export const Intellicard = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    name,
    isDead,
    isBraindead,
    health,
    wireless,
    radio,
    wiping,
    laws = [],
  } = data;
  const offline = isDead || isBraindead;
  return (
    <Window
      width={500}
      height={500}>
      <Window.Content scrollable>
        <Section
          title={name || "Empty Card"}
          buttons={!!name && (
            <Button
              icon="trash"
              content={wiping ? 'Stop Wiping' : 'Wipe'}
              disabled={isDead}
              onClick={() => act('wipe')} />
          )}>
          {!!name && (
            <LabeledList>
              <LabeledList.Item
                label="Status"
                color={offline ? 'bad' : 'good'}>
                {offline ? 'Offline' : 'Operation'}
              </LabeledList.Item>
              <LabeledList.Item label="Software Integrity">
                <ProgressBar
                  value={health}
                  minValue={0}
                  maxValue={100}
                  ranges={{
                    good: [70, Infinity],
                    average: [50, 70],
                    bad: [-Infinity, 50],
                  }}
                />
              </LabeledList.Item>
              <LabeledList.Item label="Settings">
                <Button
                  icon="signal"
                  content="Wireless Activity"
                  selected={wireless}
                  onClick={() => act('wireless')} />
                <Button
                  icon="microphone"
                  content="Subspace Radio"
                  selected={radio}
                  onClick={() => act('radio')} />
              </LabeledList.Item>
              <LabeledList.Item label="Laws">
                {laws.map(law => (
                  <BlockQuote key={law}>
                    {law}
                  </BlockQuote>
                ))}
              </LabeledList.Item>
            </LabeledList>
          )}
        </Section>
      </Window.Content>
    </Window>
  );
};


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

export const InteractionMenu = (props, context) => {
  const { act, data } = useBackend(context);
  const { categories, target_name, block_interact } = data;

  return (
    <Window
      width={400}
      height={600}
      title={"Interacting With " + target_name}
      theme="cutesy">
      <Window.Content>
        <Stack
          fill
          vertical>
          <Stack.Item>
            {block_interact && (
              <NoticeBox>
                Unable to interact
              </NoticeBox>
            ) || (
              <NoticeBox>
                Able to interact
              </NoticeBox>
            )}
          </Stack.Item>
          <Stack.Item
            grow>
            <Section
              fill
              scrollable>
              {categories.length !== 0 ? (
                <Flex.Item>
                  {categories.map(category => (
                    <Section
                      title={category.name}
                      key={category.name}>
                      {category.interactions.map(interaction => (
                        <Tooltip
                          key={interaction.name}
                          content={interaction.desc}>
                          <Button
                            mt={0.5}
                            mx={0.5}
                            color={"default"}
                            icon={interaction.button_icon}
                            disabled={interaction.block_interact}
                            onClick={() => act('interact', { interaction: interaction.name })}>
                            {interaction.name}
                          </Button>
                        </Tooltip>
                      ))}
                    </Section>
                  ))}
                </Flex.Item>
              ) : (
                <NoticeBox
                  danger>
                  No valid interactions!
                </NoticeBox>
              )}
            </Section>
          </Stack.Item>
        </Stack>
      </Window.Content>
    </Window>
  );
};


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

export const Interview = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    welcome_message,
    questions,
    read_only,
    queue_pos,
    is_admin,
    status,
    connected,
  } = data;

  const rendered_status = (status) => {
    switch (status) {
      case 'interview_approved':
        return <NoticeBox success>This interview was approved.</NoticeBox>;
      case 'interview_denied':
        return <NoticeBox danger>This interview was denied.</NoticeBox>;
      default:
        return (
          <NoticeBox info>
            Your answers have been submitted. You are position {queue_pos} in
            queue.
          </NoticeBox>
        );
    }
  };

  // Matches a complete markdown-style link, capturing the whole [...](...)
  const link_regex = /(\[[^[]+\]\([^)]+\))/;
  // Decomposes a markdown-style link into the link and display text
  const link_decompose_regex = /\[([^[]+)\]\(([^)]+)\)/;

  // Renders any markdown-style links within a provided body of text
  const linkify_text = (text) => {
    let parts = text.split(link_regex);
    for (let i = 1; i < parts.length; i += 2) {
      const match = link_decompose_regex.exec(parts[i]);
      parts[i] = (

          {match[1]}

      );
    }
    return parts;
  };

  return (
    <Window width={500} height={600} canClose={is_admin}>
      <Window.Content scrollable>
        {(!read_only && (

{linkify_text(welcome_message)}

        ))
          || rendered_status(status)}
        <Section
          title="Questionnaire"
          buttons={

              <Button
                content={read_only ? 'Submitted' : 'Submit'}
                onClick={() => act('submit')}
                disabled={read_only}
              />
              {!!is_admin && status === 'interview_pending' && (

                  <Button
                    content="Admin PM"
                    enabled={connected}
                    onClick={() => act('adminpm')}
                  />
                  <Button
                    content="Approve"
                    color="good"
                    onClick={() => act('approve')}
                  />
                  <Button
                    content="Deny"
                    color="bad"
                    onClick={() => act('deny')}
                  />

              )}

          }>
          {!read_only && (

              Please answer the following questions, and press submit when you
              are satisfied with your answers.

              You will not be able to edit your answers after submitting.

          )}
          {questions.map(({ qidx, question, response }) => (
            <Section key={qidx} title={`Question ${qidx}`}>

{linkify_text(question)}

              {((read_only || is_admin) && (

{response || 'No response.'}

              )) || (
                <TextArea
                  value={response}
                  fluid
                  height={10}
                  maxLength={500}
                  placeholder="Write your response here, max of 500 characters."
                  onChange={(e, input) =>
                    input !== response
                    && act('update_answer', {
                      qidx: qidx,
                      answer: input,
                    })}
                />
              )}

          ))}

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


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

export const InterviewManager = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    open_interviews,
    closed_interviews,
  } = data;

  const colorMap = status => {
    switch (status) {
      case "interview_approved":
        return "good";
      case "interview_denied":
        return "bad";
      case "interview_pending":
        return "average";
    }
  };

  return (
    <Window
      width={500}
      height={600}
      theme="admin">
      <Window.Content scrollable>

          {open_interviews.map(({ id, ckey, status, queued, disconnected }) => (
            <Button
              key={id}
              content={ckey + (disconnected ? " (DC)" : "")}
              color={queued ? "default" : colorMap(status)}
              onClick={() => act('open', { "id": id })} />
          ))}

          {closed_interviews.map(({ id, ckey, status, disconnected }) => (
            <Button
              key={id}
              content={ckey + (disconnected ? " (DC)" : "")}
              color={colorMap(status)}
              onClick={() => act('open', { "id": id })} />
          ))}

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


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

export const Jukebox = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    active,
    track_selected,
    track_length,
    track_beat,
    volume,
  } = data;
  const songs = flow([
    sortBy(
      song => song.name),
  ])(data.songs || []);
  return (
    <Window
      width={370}
      height={313}>
      <Window.Content>
        <Section
          title="Song Player"
          buttons={(
            <Button
              icon={active ? 'pause' : 'play'}
              content={active ? 'Stop' : 'Play'}
              selected={active}
              onClick={() => act('toggle')} />
          )}>
          <LabeledList>
            <LabeledList.Item label="Track Selected">
              <Dropdown
                overflow-y="scroll"
                width="240px"
                options={songs.map(song => song.name)}
                disabled={active}
                selected={track_selected || "Select a Track"}
                onSelected={value => act('select_track', {
                  track: value,
                })} />
            </LabeledList.Item>
            <LabeledList.Item label="Track Length">
              {track_selected ? track_length : "No Track Selected"}
            </LabeledList.Item>
            <LabeledList.Item label="Track Beat">
              {track_selected ? track_beat : "No Track Selected"}
              {track_beat === 1 ? " beat" : " beats"}
            </LabeledList.Item>
          </LabeledList>
        </Section>
        <Section title="Machine Settings">
          <LabeledControls justify="center">
            <LabeledControls.Item label="Volume">
              <Box position="relative">
                <Knob
                  size={3.2}
                  color={volume >= 25 ? 'red' : 'green'}
                  value={volume}
                  unit="%"
                  minValue={0}
                  maxValue={50}
                  step={1}
                  stepPixelSize={1}
                  disabled={active}
                  onDrag={(e, value) => act('set_volume', {
                    volume: value,
                  })} />
                <Button
                  fluid
                  position="absolute"
                  top="-2px"
                  right="-22px"
                  color="transparent"
                  icon="fast-backward"
                  onClick={() => act('set_volume', {
                    volume: "min",
                  })} />
                <Button
                  fluid
                  position="absolute"
                  top="16px"
                  right="-22px"
                  color="transparent"
                  icon="fast-forward"
                  onClick={() => act('set_volume', {
                    volume: "max",
                  })} />
                <Button
                  fluid
                  position="absolute"
                  top="34px"
                  right="-22px"
                  color="transparent"
                  icon="undo"
                  onClick={() => act('set_volume', {
                    volume: "reset",
                  })} />
              </Box>
            </LabeledControls.Item>
          </LabeledControls>
        </Section>
      </Window.Content>
    </Window>
  );
};


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

export const KeycardAuth = (props, context) => {
  const { act, data } = useBackend(context);
  return (
    <Window
      width={375}
      height={125}>
      <Window.Content>

          <Box>
            {data.waiting === 1 && (
              Waiting for another device to confirm your request...
            )}
          </Box>
          <Box>
            {data.waiting === 0 && (
              <>
                {!!data.auth_required && (
                  <Button
                    icon="check-square"
                    color="red"
                    textAlign="center"
                    lineHeight="60px"
                    fluid
                    onClick={() => act('auth_swipe')}
                    content="Authorize" />
                )}
                {data.auth_required === 0 && (
                  <>
                    <Button
                      icon="exclamation-triangle"
                      fluid
                      onClick={() => {
                        return act('red_alert');
                      }}
                      content="Red Alert" />
                    <Button
                      icon="wrench"
                      fluid
                      onClick={() => act('emergency_maint')}
                      content="Emergency Maintenance Access" />
                    <Button
                      icon="meteor"
                      fluid
                      onClick={() => act('bsa_unlock')}
                      content="Bluespace Artillery Unlock" />
                  </>
                )}
              </>
            )}
          </Box>

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


code:
import { toTitleCase } from 'common/string';
import { useBackend } from '../backend';
import { Box, Button, LabeledList, Section, Table } from '../components';
import { Window } from '../layouts';

export const LaborClaimConsole = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    can_go_home,
    id_points,
    ores,
    status_info,
    unclaimed_points,
  } = data;
  return (
    <Window
      width={315}
      height={440}>
      <Window.Content>

          <LabeledList>
            <LabeledList.Item label="Status">
              {status_info}
            </LabeledList.Item>
            <LabeledList.Item label="Shuttle controls">
              <Button
                content="Move shuttle"
                disabled={!can_go_home}
                onClick={() => act('move_shuttle')} />
            </LabeledList.Item>
            <LabeledList.Item label="Points">
              {id_points}
            </LabeledList.Item>
            <LabeledList.Item
              label="Unclaimed points"
              buttons={(
                <Button
                  content="Claim points"
                  disabled={!unclaimed_points}
                  onClick={() => act('claim_points')} />
              )}>
              {unclaimed_points}
            </LabeledList.Item>
          </LabeledList>

                Material

                Value

            {ores.map(ore => (

                  {toTitleCase(ore.ore)}

                  <Box color="label" inline>
                    {ore.value}
                  </Box>

            ))}

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


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

export const LanguageMenu = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    admin_mode,
    is_living,
    omnitongue,
    languages = [],
    unknown_languages = [],
  } = data;
  return (
    <Window
      title="Language Menu"
      width={700}
      height={600}>
      <Window.Content scrollable>
        <Section title="Known Languages">
          <LabeledList>
            {languages.map(language => (
              <LabeledList.Item
                key={language.name}
                label={language.name}
                buttons={(
                  <>
                    {!!is_living && (
                      <Button
                        content={language.is_default
                          ? 'Default Language'
                          : 'Select as Default'}
                        disabled={!language.can_speak}
                        selected={language.is_default}
                        onClick={() => act('select_default', {
                          language_name: language.name,
                        })} />
                    )}
                    {!!admin_mode && (
                      <>
                        <Button
                          content="Grant"
                          onClick={() => act('grant_language', {
                            language_name: language.name,
                          })} />
                        <Button
                          content="Remove"
                          onClick={() => act('remove_language', {
                            language_name: language.name,
                          })} />
                      </>
                    )}
                  </>
                )}>
                {language.desc}
                {' '}
                Key: ,{language.key}
                {' '}
                {language.can_understand
                  ? 'Can understand.'
                  : 'Cannot understand.'}
                {' '}
                {language.can_speak
                  ? 'Can speak.'
                  : 'Cannot speak.'}
              </LabeledList.Item>
            ))}
          </LabeledList>
        </Section>
        {!!admin_mode && (
          <Section
            title="Unknown Languages"
            buttons={(
              <Button
                content={'Omnitongue '
                  + (omnitongue ? 'Enabled' : 'Disabled')}
                selected={omnitongue}
                onClick={() => act('toggle_omnitongue')} />
            )}>
            <LabeledList>
              {unknown_languages.map(language => (
                <LabeledList.Item
                  key={language.name}
                  label={language.name}
                  buttons={(
                    <Button
                      content="Grant"
                      onClick={() => act('grant_language', {
                        language_name: language.name,
                      })} />
                  )}>
                  {language.desc}
                  {' '}
                  Key: ,{language.key}
                  {' '}
                  {!!language.shadow && '(gained from mob)'}
                  {' '}
                  {language.can_speak
                    ? 'Can speak.'
                    : 'Cannot speak.' }
                </LabeledList.Item>
              ))}
            </LabeledList>
          </Section>
        )}
      </Window.Content>
    </Window>
  );
};


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

const LaunchpadButtonPad = (props, context) => {
  const { act } = useBackend(context);
  return (
    <Grid width="1px">
      <Grid.Column>
        <Button
          fluid
          icon="arrow-left"
          iconRotation={45}
          mb={1}
          onClick={() => act('move_pos', {
            x: -1,
            y: 1,
          })} />
        <Button
          fluid
          icon="arrow-left"
          mb={1}
          onClick={() => act('move_pos', {
            x: -1,
          })} />
        <Button
          fluid
          icon="arrow-down"
          iconRotation={45}
          mb={1}
          onClick={() => act('move_pos', {
            x: -1,
            y: -1,
          })} />
      </Grid.Column>
      <Grid.Column>
        <Button
          fluid
          icon="arrow-up"
          mb={1}
          onClick={() => act('move_pos', {
            y: 1,
          })} />
        <Button
          fluid
          content="R"
          mb={1}
          onClick={() => act('set_pos', {
            x: 0,
            y: 0,
          })} />
        <Button
          fluid
          icon="arrow-down"
          mb={1}
          onClick={() => act('move_pos', {
            y: -1,
          })} />
      </Grid.Column>
      <Grid.Column>
        <Button
          fluid
          icon="arrow-up"
          iconRotation={45}
          mb={1}
          onClick={() => act('move_pos', {
            x: 1,
            y: 1,
          })} />
        <Button
          fluid
          icon="arrow-right"
          mb={1}
          onClick={() => act('move_pos', {
            x: 1,
          })} />
        <Button
          fluid
          icon="arrow-right"
          iconRotation={45}
          mb={1}
          onClick={() => act('move_pos', {
            x: 1,
            y: -1,
          })} />
      </Grid.Column>
    </Grid>
  );
};

export const LaunchpadControl = (props, context) => {
  const { topLevel } = props;
  const { act, data } = useBackend(context);
  const {
    x,
    y,
    pad_name,
    range,
  } = data;
  return (
    <Section
      title={(
        <Input
          value={pad_name}
          width="170px"
          onChange={(e, value) => act('rename', {
            name: value,
          })} />
      )}
      level={topLevel ? 1 : 2}
      buttons={(
        <Button
          icon="times"
          content="Remove"
          color="bad"
          onClick={() => act('remove')} />
      )}>
      <Grid>
        <Grid.Column>

            <LaunchpadButtonPad />

        </Grid.Column>
        <Grid.Column>

            <Box fontSize="26px">
              <Box mb={1}>
                <Box
                  inline
                  bold
                  mr={1}>
                  X:
                </Box>
                <NumberInput
                  value={x}
                  minValue={-range}
                  maxValue={range}
                  lineHeight="30px"
                  fontSize="26px"
                  width="90px"
                  height="30px"
                  stepPixelSize={10}
                  onChange={(e, value) => act('set_pos', {
                    x: value,
                  })} />
              </Box>
              <Box>
                <Box
                  inline
                  bold
                  mr={1}>
                  Y:
                </Box>
                <NumberInput
                  value={y}
                  minValue={-range}
                  maxValue={range}
                  stepPixelSize={10}
                  lineHeight="30px"
                  fontSize="26px"
                  width="90px"
                  height="30px"
                  onChange={(e, value) => act('set_pos', {
                    y: value,
                  })} />
              </Box>
            </Box>

        </Grid.Column>
      </Grid>
      <Grid>
        <Grid.Column>
          <Button
            fluid
            icon="upload"
            content="Launch"
            textAlign="center"
            onClick={() => act('launch')} />
        </Grid.Column>
        <Grid.Column>
          <Button
            fluid
            icon="download"
            content="Pull"
            textAlign="center"
            onClick={() => act('pull')} />
        </Grid.Column>
      </Grid>

  );
};

export const LaunchpadConsole = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    launchpads = [],
    selected_id,
  } = data;
  return (
    <Window
      width={475}
      height={260}>
      <Window.Content scrollable>
        {launchpads.length === 0 && (
          <NoticeBox>
            No Pads Connected
          </NoticeBox>
        ) || (

            <Flex minHeight="190px">
              <Flex.Item width="140px" minHeight="190px">
                {launchpads.map(launchpad => (
                  <Button
                    fluid
                    ellipsis
                    key={launchpad.name}
                    content={launchpad.name}
                    selected={selected_id === launchpad.id}
                    color="transparent"
                    onClick={() => act('select_pad', {
                      id: launchpad.id,
                    })} />
                ))}
              </Flex.Item>
              <Flex.Item minHeight="100%">
                <Divider vertical />
              </Flex.Item>
              <Flex.Item grow={1} basis={0} minHeight="100%">
                {selected_id && (
                  <LaunchpadControl />
                ) || (
                  <Box>
                    Please select a pad
                  </Box>
                )}
              </Flex.Item>
            </Flex>

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


code:
import { useBackend } from '../backend';
import { NoticeBox } from '../components';
import { Window } from '../layouts';
import { LaunchpadControl } from './LaunchpadConsole';

export const LaunchpadRemote = (props, context) => {
  const { data } = useBackend(context);
  const {
    has_pad,
    pad_closed,
  } = data;
  return (
    <Window
      title="Briefcase Launchpad Remote"
      width={300}
      height={240}
      theme="syndicate">
      <Window.Content>
        {!has_pad && (
          <NoticeBox>
            No Launchpad Connected
          </NoticeBox>
        ) || pad_closed && (
          <NoticeBox>
            Launchpad Closed
          </NoticeBox>
        ) || (
          <LaunchpadControl topLevel />
        )}
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend, useSharedState } from '../backend';
import { Box, Button, Dimmer, Icon, LabeledList, Section, Tabs } from '../components';
import { Window } from '../layouts';

export const Limbgrower = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    reagents = [],
    total_reagents,
    max_reagents,
    categories = [],
    busy,
  } = data;
  const [tab, setTab] = useSharedState(
    context, 'category', categories[0]?.name);
  const designList = categories
    .find(category => category.name === tab)
    ?.designs || [];

  return (
    <Window
      title="Limb Grower"
      width={400}
      height={550}>
      {!!busy && (
        <Dimmer fontSize="32px">
          <Icon name="cog" spin={1} />
          {' Building...'}
        </Dimmer>
      )}
      <Window.Content scrollable>

          <Box mb={1}>
            {total_reagents} / {max_reagents} reagent capacity used.
          </Box>
          <LabeledList>
            {reagents.map(reagent => (
              <LabeledList.Item
                key={reagent.reagent_name}
                label={reagent.reagent_name}
                buttons={(
                  <Button.Confirm
                    textAlign="center"
                    width="120px"
                    content="Remove Reagent"
                    color="bad"
                    onClick={() => act('empty_reagent', {
                      reagent_type: reagent.reagent_type,
                    })} />
                )}>
                {reagent.reagent_amount}u
              </LabeledList.Item>
            ))}
          </LabeledList>

          <Tabs>
            {categories.map(category => (
              <Tabs.Tab
                fluid
                key={category.name}
                selected={tab === category.name}
                onClick={() => setTab(category.name)}>
                {category.name}
              </Tabs.Tab>
            ))}
          </Tabs>
          <LabeledList>
            {designList.map(design => (
              <LabeledList.Item
                key={design.name}
                label={design.name}
                buttons={(
                  <Button
                    content="Make"
                    color="good"
                    onClick={() => act('make_limb', {
                      design_id: design.id,
                      active_tab: design.parent_category,
                    })} />
                )}>
                {design.needed_reagents.map(reagent => (
                  <Box key={reagent.name}>
                    {reagent.name}: {reagent.amount}u
                  </Box>
                ))}
              </LabeledList.Item>
            ))}
          </LabeledList>

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


code:
/**
 * @file
 * @copyright 2020 watermelon914 (https://github.com/watermelon914)
 * @license MIT
 */

import { clamp01 } from 'common/math';
import { useBackend } from '../backend';
import { Box, Button, Section, Stack, Input } from '../components';
import { KEY_DOWN, KEY_UP, KEY_ENTER } from 'common/keycodes';
import { Window } from '../layouts';
import { Component, createRef } from 'inferno';
import { globalEvents } from "../events";

export class ListInput extends Component {
  constructor() {
    super();
    this.inputRef = createRef();

    this.state = {
      showSearchBar: true,
      displayedArray: null,
      selectedButton: null,
    };

    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.handleKey = this.handleKey.bind(this);
  }

  handleKeyDown(e) {
    const { act, data } = useBackend(this.context);
    const {
      buttons,
    } = data;
    const {
      selectedButton,
    } = this.state;

    e.preventDefault();
    if (e.keyCode === KEY_UP || e.keyCode === KEY_DOWN) {
      let direction = 1;
      if (e.keyCode === KEY_UP) {
        direction = -1;
      }

      let index = 0;
      for (index; index < buttons.length; index++) {
        if (buttons[index] === selectedButton) {
          break;
        }
      }
      index += direction;
      if (index < 0) {
        index = buttons.length - 1;
      } else if (index >= buttons.length) {
        index = 0;
      }
      this.setState({
        selectedButton: buttons[index],
      });
      document.getElementById(buttons[index]).focus();
      return;
    }

    if (e.keyCode === KEY_ENTER) {
      act("choose", { choice: selectedButton });
      return;
    }

    const input = this.inputRef.current?.inputRef?.current;
    if (input) {
      input.value = e.key;
      input.selectionStart = input.selectionEnd = input.value.length;
      input.focus();
    }
  }

  handleKey(key) {
    key.event.preventDefault();
  }

  componentDidMount() {
    globalEvents.on("keydown", this.handleKey);
    setTimeout(() => this.inputRef.current?.inputRef?.current.focus(), 1);
  }

  componentWillUnmount() {
    globalEvents.off("keydown", this.handleKey);
  }

  render() {
    const { act, data } = useBackend(this.context);
    const {
      title,
      message,
      buttons,
      timeout,
    } = data;
    const {
      showSearchBar,
      displayedArray,
      selectedButton,
    } = this.state;

    if (!displayedArray) {
      this.setState({
        displayedArray: buttons,
        selectedButton: buttons[0],
      });
      return;
    }

    return (
      <Window
        title={title}
        width={325}
        height={325}>
        {timeout !== undefined && <Loader value={timeout} />}
        <Window.Content>
          <Stack fill vertical>
            <Stack.Item grow>
              <Section
                fill
                scrollable
                className="ListInput__Section"
                title={message}
                tabIndex={0}
                onKeyDown={this.handleKeyDown}
                buttons={(
                  <Button
                    compact
                    icon="search"
                    color="transparent"
                    selected={showSearchBar}
                    tooltip="Search Bar"
                    tooltipPosition="left"
                    onClick={() => {
                      this.setState({
                        showSearchBar: !showSearchBar,
                        displayedArray: buttons,
                      });
                    }}
                  />
                )}>
                {displayedArray.map(button => (
                  <Button
                    style={{
                      "animation": "none",
                      "transition": "none",
                    }}
                    key={button}
                    color="transparent"
                    fluid
                    id={button}
                    selected={selectedButton === button}
                    onClick={() => {
                      if (selectedButton === button) {
                        act("choose", { choice: button });
                      }
                      else {
                        this.setState({
                          selectedButton: button,
                        });
                      }
                    }}>
                    {button}
                  </Button>
                ))}
              </Section>
            </Stack.Item>
            {showSearchBar && (
              <Stack.Item>
                <Input
                  fluid
                  ref={this.inputRef}
                  onEnter={(e, value) => {
                    act("choose", { choice: selectedButton });
                  }}
                  onInput={(e, value) => {
                    const filteredArray = buttons.filter(val => (
                      val.toLowerCase().search(value.toLowerCase()) !== -1
                    ));
                    this.setState({
                      displayedArray: filteredArray,
                      selectedButton: filteredArray[0],
                    });
                  }}
                />
              </Stack.Item>
            )}
            <Stack.Item>
              <Stack textAlign="center">
                <Stack.Item grow basis={0}>
                  <Button
                    fluid
                    color="good"
                    lineHeight={2}
                    content="Confirm"
                    disabled={selectedButton === null}
                    onClick={() => act("choose", { choice: selectedButton })}
                  />
                </Stack.Item>
                <Stack.Item grow basis={0}>
                  <Button
                    fluid
                    color="bad"
                    lineHeight={2}
                    content="Cancel"
                    onClick={() => act("cancel")}
                  />
                </Stack.Item>
              </Stack>
            </Stack.Item>
          </Stack>
        </Window.Content>
      </Window>
    );
  }
}

export const Loader = props => {
  const { value } = props;
  return (
    <div className="ListInput__Loader">
      <Box
        className="ListInput__LoaderProgress"
        style={{
          width: clamp01(value) * 100 + '%',
        }} />
    </div>
  );
};


code:
import { classes } from 'common/react';
import { multiline } from 'common/string';
import { useBackend } from '../backend';
import { Box, Button, Collapsible, Flex, NoticeBox, Section, Stack, TimeDisplay } from '../components';
import { Window } from '../layouts';

export const MafiaPanel = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    actions,
    phase,
    roleinfo,
    role_theme,
    admin_controls,
  } = data;
  return (
    <Window
      title="Mafia"
      theme={role_theme}
      width={650}
      height={580}>
      <Window.Content>
        <Stack fill vertical>
          {!roleinfo && (
            <Stack.Item grow>
              <MafiaLobby />
            </Stack.Item>
          )}
          {!!roleinfo && (
            <Stack.Item>
              <MafiaRole />
            </Stack.Item>
          )}
          {actions?.map(action => (
            <Stack.Item key={action}>
              <Button
                onClick={() => act('mf_action', {
                  atype: action,
                })}>
                {action}
              </Button>
            </Stack.Item>
          ))}
          {!!roleinfo && (
            <Stack.Item>
              <MafiaJudgement />
            </Stack.Item>
          )}
          {phase !== 'No Game' && (
            <Stack.Item grow>
              <Stack fill>
                <Stack.Item grow={1.34} basis={0}>
                  <MafiaPlayers />
                </Stack.Item>
                <Stack.Item grow={1} basis={0}>
                  <Stack fill vertical>
                    <Stack.Item grow>
                      <MafiaListOfRoles />
                    </Stack.Item>
                    {!!roleinfo && (
                      <Stack.Item height="80px">

                          {roleinfo.action_log?.map(line => (
                            <Box key={line}>{line}</Box>
                          ))}

                      </Stack.Item>
                    )}
                  </Stack>
                </Stack.Item>
              </Stack>
            </Stack.Item>
          )}
          {!!admin_controls && (
            <Stack.Item>
              <MafiaAdmin />
            </Stack.Item>
          )}
        </Stack>
      </Window.Content>
    </Window>
  );
};

const MafiaLobby = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    lobbydata,
    phase,
    timeleft,
  } = data;
  const readyGhosts = lobbydata ? lobbydata.filter(
    player => player.status === "Ready") : null;
  return (
    <Section
      fill
      scrollable
      title="Lobby"
      buttons={(
        <>
          Phase = {phase}
          {' | '}
          <TimeDisplay auto="down" value={timeleft} />
          {' '}
          <Button
            icon="clipboard-check"
            tooltipPosition="bottom-start"
            tooltip={multiline`
              Signs you up for the next game. If there
              is an ongoing one, you will be signed up
              for the next.
            `}
            content="Sign Up"
            onClick={() => act('mf_signup')} />
          <Button
            icon="eye"
            tooltipPosition="bottom-start"
            tooltip={multiline`
              Spectates games until you turn it off.
              Automatically enabled when you die in game,
              because I assumed you would want to see the
              conclusion. You won't get messages if you
              rejoin SS13.
            `}
            content="Spectate"
            onClick={() => act('mf_spectate')} />
        </>
      )}>
      <NoticeBox info>
        The lobby currently has {readyGhosts
          ? readyGhosts.length : "0"}/12 valid players signed up.
      </NoticeBox>
      {lobbydata?.map(lobbyist => (
        <Stack
          key={lobbyist}
          className="candystripe"
          p={1}
          align="baseline">
          <Stack.Item grow>
            {lobbyist.name}
          </Stack.Item>
          <Stack.Item>
            Status:
          </Stack.Item>
          <Stack.Item
            color={lobbyist.status === 'Ready' ? 'green' : 'red'}>
            {lobbyist.status} {lobbyist.spectating}
          </Stack.Item>
        </Stack>
      ))}

  );
};

const MafiaRole = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    phase,
    roleinfo,
    timeleft,
  } = data;
  return (
    <Section
      title={phase}
      minHeight="100px"
      maxHeight="50px"
      buttons={(
        <Box
          style={{
            'font-family': 'Consolas, monospace',
            'font-size': '14px',
            'line-height': 1.5,
            'font-weight': 'bold',
          }}>
          <TimeDisplay auto="down" value={timeleft} />
        </Box>
      )}>
      <Stack align="center">
        <Stack.Item grow>
          <Box bold>
            You are the {roleinfo.role}
          </Box>
          <Box italic>
            {roleinfo.desc}
          </Box>
        </Stack.Item>
        <Stack.Item>
          <Box
            className={classes([
              'mafia32x32',
              roleinfo.revealed_icon,
            ])}
            style={{
              'transform': 'scale(2) translate(0px, 10%)',
              'vertical-align': 'middle',
            }} />
          <Box
            className={classes([
              'mafia32x32',
              roleinfo.hud_icon,
            ])}
            style={{
              'transform': 'scale(2) translate(-5px, -5px)',
              'vertical-align': 'middle',
            }} />
        </Stack.Item>
      </Stack>

  );
};

const MafiaListOfRoles = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    all_roles,
  } = data;
  return (
    <Section
      fill
      scrollable
      title="Roles and Notes"
      minHeight="120px"
      buttons={
        <>
          <Button
            color="transparent"
            icon="address-book"
            tooltipPosition="bottom-start"
            tooltip={multiline`
              The top section is the roles in the game. You can
              press the question mark to get a quick blurb
              about the role itself.`}
          />
          <Button
            color="transparent"
            icon="edit"
            tooltipPosition="bottom-start"
            tooltip={multiline`
              The bottom section are your notes. on some roles this
              will just be an empty box, but on others it records the
              actions of your abilities (so for example, your
              detective work revealing a changeling).`}
          />
        </>
      }>
      <Flex direction="column">
        {all_roles?.map(r => (
          <Flex.Item
            key={r}
            height="30px"
            className="Section__title candystripe">
            <Flex
              height="18px"
              align="center"
              justify="space-between">
              <Flex.Item>
                {r}
              </Flex.Item>
              <Flex.Item
                textAlign="right">
                <Button
                  color="transparent"
                  icon="question"
                  onClick={() => act('mf_lookup', {
                    atype: r.slice(0, -3),
                  })}
                />
              </Flex.Item>
            </Flex>
          </Flex.Item>
        ))}
      </Flex>

  );
};

const MafiaJudgement = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    judgement_phase,
  } = data;
  return (
    <Section
      title="Judgement"
      buttons={
        <Button
          color="transparent"
          icon="info"
          tooltipPosition="left"
          tooltip={multiline`
            When someone is on trial, you are in charge of their fate.
            Innocent winning means the person on trial can live to see
            another day... and in losing they do not. You can go back
            to abstaining with the middle button if you reconsider.
          `}
        />
      }>
      <Flex justify="space-around">
        <Button
          icon="smile-beam"
          content="INNOCENT!"
          color="good"
          disabled={!judgement_phase}
          onClick={() => act('vote_innocent')} />
        {!judgement_phase && (
          <Box>
            There is nobody on trial at the moment.
          </Box>
        )}
        {!!judgement_phase && (
          <Box>
            It is now time to vote, vote the accused innocent or guilty!
          </Box>
        )}
        <Button
          icon="angry"
          color="bad"
          disabled={!judgement_phase}
          onClick={() => act('vote_guilty')}>
          GUILTY!
        </Button>
      </Flex>
      <Flex justify="center">
        <Button
          icon="meh"
          color="white"
          disabled={!judgement_phase}
          onClick={() => act('vote_abstain')}>
          Abstain
        </Button>
      </Flex>

  );
};

const MafiaPlayers = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    players,
  } = data;
  return (

      <Flex direction="column">
        {players?.map(player => (
          <Flex.Item
            height="30px"
            className="Section__title candystripe"
            key={player.ref}>
            <Stack height="18px" align="center">
              <Stack.Item grow color={!player.alive && 'red'}>
                {player.name} {!player.alive && '(DEAD)'}
              </Stack.Item>
              <Stack.Item shrink={0}>
                {player.votes !== undefined
                  && !!player.alive
                  && `Votes: ${player.votes}`}
              </Stack.Item>
              <Stack.Item shrink={0} minWidth="42px" textAlign="center">
                {player.actions?.map(action => (
                  <Button
                    key={action}
                    fluid
                    onClick={() => act('mf_targ_action', {
                      atype: action,
                      target: player.ref,
                    })}>
                    {action}
                  </Button>
                ))}
              </Stack.Item>
            </Stack>
          </Flex.Item>
        ))}
      </Flex>

  );
};

const MafiaAdmin = (props, context) => {
  const { act, data } = useBackend(context);
  return (
    <Collapsible
      title="ADMIN CONTROLS"
      color="red">

        <Collapsible
          title="A kind, coder warning"
          color="transparent">
          Almost all of these are all built to help me debug
          the game (ow, debugging a 12 player game!) So they are
          rudamentary and prone to breaking at the drop of a hat.
          Make sure you know what you're doing when you press one.
          Also because an admin did it: do not gib/delete/dust
          anyone! It will runtime the game to death
        </Collapsible>
        <Button
          icon="arrow-right"
          onClick={() => act('next_phase')}>
          Next Phase
        </Button>
        <Button
          icon="home"
          onClick={() => act('players_home')}>
          Send All Players Home
        </Button>
        <Button
          icon="sync-alt"
          onClick={() => act('new_game')}>
          New Game
        </Button>
        <Button
          icon="skull"
          onClick={() => act('nuke')}>
          Nuke
        </Button>

        <Button
          icon="paint-brush"
          onClick={() => act('debug_setup')}>
          Create Custom Setup
        </Button>
        <Button
          icon="paint-roller"
          onClick={() => act('cancel_setup')}>
          Reset Custom Setup
        </Button>

    </Collapsible>
  );
};


code:
import { useBackend } from '../backend';
import { Window } from '../layouts';
import { GenericUplink } from './Uplink';

export const MalfunctionModulePicker = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    processingTime,
  } = data;
  return (
    <Window
      width={620}
      height={525}
      theme="malfunction">
      <Window.Content scrollable>
        <GenericUplink
          currencyAmount={processingTime}
          currencySymbol="PT" />
      </Window.Content>
    </Window>
  );
};


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

export const MassDriverControl = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    connected,
    minutes,
    seconds,
    timing,
    power,
    poddoor,
  } = data;
  return (
    <Window
      width={300}
      height={connected ? 215 : 107}>
      <Window.Content>
        {!!connected && (
          <Section
            title="Auto Launch"
            buttons={(
              <Button
                icon={"clock-o"}
                content={timing ? 'Stop' : 'Start'}
                selected={timing}
                onClick={() => act('time')} />
            )}>
            <Button
              icon="fast-backward"
              disabled={timing}
              onClick={() => act('input', { adjust: -30 })} />
            <Button
              icon="backward"
              disabled={timing}
              onClick={() => act('input', { adjust: -1 })} />
            {' '}
            {String(minutes).padStart(2, '0')}:
            {String(seconds).padStart(2, '0')}
            {' '}
            <Button
              icon="forward"
              disabled={timing}
              onClick={() => act('input', { adjust: 1 })} />
            <Button
              icon="fast-forward"
              disabled={timing}
              onClick={() => act('input', { adjust: 30 })} />
          </Section>
        )}
        <Section
          title="Controls"
          buttons={(
            <Button
              icon={"toggle-on"}
              content="Toggle Outer Door"
              disabled={timing || !poddoor}
              onClick={() => act('door')} />
          )}>
          {!!connected && (
            <>
              <LabeledList>
                <LabeledList.Item
                  label="Power Level"
                  buttons={
                    <Button
                      icon={"bomb"}
                      content="Test Fire"
                      disabled={timing}
                      onClick={() => act('driver_test')} />
                  }>
                  <NumberInput
                    value={power}
                    width="40px"
                    minValue={0.25}
                    maxValue={16}
                    onChange={(e, value) => {
                      return act('set_power', {
                        power: value,
                      });
                    }} />
                </LabeledList.Item>
              </LabeledList>
              <Button
                fluid
                content="Launch"
                disabled={timing}
                mt={1.5}
                icon="arrow-up"
                textAlign="center"
                onClick={() => act('launch')} />
            </>
          ) || (
            <Box color="bad">
              No connected mass driver
            </Box>
          )}
        </Section>
      </Window.Content>
    </Window>
  );
};


code:
import { round } from 'common/math';
import { useBackend } from '../backend';
import { Box, Button, Dimmer, Icon, Section, Slider, Table } from '../components';
import { Window } from '../layouts';

export const MassSpec = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    processing,
    lowerRange,
    upperRange,
    graphUpperRange,
    graphLowerRange,
    eta,
    beaker1CurrentVolume,
    beaker2CurrentVolume,
    beaker1MaxVolume,
    beaker2MaxVolume,
    peakHeight,
    beaker1,
    beaker2,
    beaker1Contents = [],
    beaker2Contents = [],
  } = data;

  const centerValue = (lowerRange + upperRange) / 2;

  return (
    <Window
      width={490}
      height={650}>
      <Window.Content scrollable>
        {!!processing && (
          <Dimmer fontSize="32px">
            <Icon name="cog" spin={1} />
            {' Purifying... '+round(eta)+"s"}
          </Dimmer>
        )}
        <Section
          title="Mass Spectroscopy"
          buttons={
            <Button
              icon="power-off"
              content="Start"
              disabled={!!processing || !beaker1Contents.length || !beaker2}
              tooltip={!beaker1Contents.length ? "Missing input reagents!" : !beaker2 ? "Missing an output beaker!" : "Begin purifying"}
              tooltipPosition="left"
              onClick={() => act('activate')} />
          }>
          {beaker1Contents.length && (
            <MassSpectroscopy
              lowerRange={lowerRange}
              centerValue={centerValue}
              upperRange={upperRange}
              graphLowerRange={graphLowerRange}
              graphUpperRange={graphUpperRange}
              maxAbsorbance={peakHeight}
              reagentPeaks={beaker1Contents} />
          ) || (
            <Box>
              Please insert an input beaker with reagents!
            </Box>
          )}
        </Section>

        <Section
          title="Input beaker"
          buttons={!!beaker1Contents && (
            <>
              {!!beaker1MaxVolume && (
                <Box inline color="label" mr={2}>
                  {beaker1CurrentVolume} / {beaker1MaxVolume} units
                </Box>
              )}
              <Button
                icon="eject"
                content="Eject"
                disabled={!beaker1}
                onClick={() => act('eject1')} />
            </>
          )}>
          <BeakerMassProfile
            loaded={!!beaker1}
            beaker={beaker1Contents} />
          {!!beaker1Contents.length && (
            <Box>
              {"Eta of selection: " + round(eta) + " seconds"}
            </Box>
          )}
        </Section>
        <Section
          title="Output beaker"
          buttons={!!beaker2Contents && (
            <>
              {!!beaker2MaxVolume && (
                <Box inline color="label" mr={2}>
                  {beaker2CurrentVolume} / {beaker2MaxVolume} units
                </Box>
              )}
              <Button
                icon="eject"
                content="Eject"
                disabled={!beaker2}
                onClick={() => act('eject2')} />
            </>
          )}>
          <BeakerMassProfile
            loaded={!!beaker2}
            beaker={beaker2Contents}
            details />
        </Section>
      </Window.Content>
    </Window>
  );
};

const BeakerMassProfile = props => {
  const {
    loaded,
    details,
    beaker = [],
  } = props;

  return (

    <Box>
      {!loaded && (
        <Box color="label">
          No beaker loaded.
        </Box>
      ) || beaker.length === 0 && (
        <Box color="label">
          Beaker is empty.
        </Box>
      ) || (
        <Table className="candystripe">
          <Table.Row>
            <Table.Cell bold collapsing color="label">
              Reagent
            </Table.Cell>
            <Table.Cell bold collapsing color="label">
              Volume
            </Table.Cell>
            <Table.Cell bold collapsing color="label">
              Mass
            </Table.Cell>
            <Table.Cell bold collapsing color="label">
              Type
            </Table.Cell>
            {!!details && (
              <Table.Cell bold collapsing color="label">
                Results
              </Table.Cell>
            )}
          </Table.Row>
          {beaker.map(reagent => (
            <Table.Row key={reagent.name}>
              <Table.Cell collapsing color={reagent.selected ? "green" : "default"}>
                {reagent.name}
              </Table.Cell>
              <Table.Cell collapsing color={reagent.selected ? "green" : "default"}>
                {reagent.volume}
              </Table.Cell>
              <Table.Cell collapsing color={reagent.selected ? "green" : "default"}>
                {reagent.mass}
              </Table.Cell>
              <Table.Cell collapsing color={reagent.color}>
                ▮{reagent.type}
              </Table.Cell>
              {!!details && (
                <Table.Cell>
                  {reagent.log}
                </Table.Cell>
              )}
            </Table.Row>
          ))}
        </Table>
      )}
    </Box>
  );
};

const MassSpectroscopy = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    lowerRange,
    centerValue,
    upperRange,
    graphUpperRange,
    graphLowerRange,
    maxAbsorbance,
    reagentPeaks = [],
  } = props;

  const deltaRange = graphUpperRange - graphLowerRange;

  const graphIncrement = deltaRange * 0.2;

  return (
    <>
      <Box position="absolute" x="200" transform="translate(30,30)">
        <svg background-size="200px" width="200" height="400">
          <text x="0" y="250" text-anchor="middle" fill="white" font-size="16" transform="translate(0,0) scale(0.8 0.8)">
            {/* x axis*/}
            <tspan x="250" y="318" font-weight="bold" font-size="1.4em">Mass (g)</tspan>
            <tspan x="0" y="283">{graphLowerRange}</tspan>
            <tspan x="100" y="283">{round(graphLowerRange + (graphIncrement), 1)}</tspan>
            <tspan x="200" y="283">{round(graphLowerRange + (graphIncrement * 2), 1)}</tspan>
            <tspan x="300" y="283">{round(graphLowerRange + (graphIncrement * 3), 1)}</tspan>
            <tspan x="400" y="283">{round(graphLowerRange + (graphIncrement * 4), 1)}</tspan>
            <tspan x="500" y="283">{graphUpperRange}</tspan>
            {/* y axis*/}
            <tspan x="520" y="0" dy="6">{round(maxAbsorbance, 1)}</tspan>
            <tspan x="520" y="50" dy="6">{round(maxAbsorbance * 0.8, 1)}</tspan>
            <tspan x="520" y="100" dy="6">{round(maxAbsorbance * 0.6, 1)}</tspan>
            <tspan x="520" y="150" dy="6">{round(maxAbsorbance * 0.4, 1)}</tspan>
            <tspan x="520" y="200" dy="6">{round(maxAbsorbance * 0.2, 1)}</tspan>
            <tspan x="520" y="250" dy="6">0</tspan>
          </text>
          <text text-anchor="middle" transform="translate(430,100) rotate(90) scale(0.8 0.8)" fill="white" font-size="16">
            <tspan font-weight="bold" font-size="1.4em">Absorbance (AU)</tspan>
          </text>
          <g transform="translate(0, 0) scale(0.8 0.8)">
            {reagentPeaks.map(peak => (
              // Triangle peak
              <polygon key={peak.name} points={`${((peak.mass - 10) / graphUpperRange) * 500},265 ${((peak.mass) / graphUpperRange) * 500},${250 - ((peak.volume / maxAbsorbance) * 250)} ${((peak.mass + 10) / graphUpperRange) * 500},265 `} opacity="0.6" style={`fill:${peak.color}`} />
            ))}
            <polygon points={`${(lowerRange/deltaRange)*500},265 ${(lowerRange/deltaRange)*500},0 ${(upperRange/deltaRange)*500},0 ${(upperRange/deltaRange)*500},265`} opacity="0.2" style={`fill:blue`} />
            <line x1={0} y1={265} x2={502} y2={264} stroke={"white"} stroke-width={3} />
            <line x1={501} y1={264} x2={501} y2={0} stroke={"white"} stroke-width={3} />
          </g>
        </svg>
      </Box>
      <Box>
        <Slider
          name={"Left slider"}
          position="relative"
          step={graphUpperRange/400}
          height={17.2}
          format={value => round(value)}
          width={(centerValue/graphUpperRange)*400+"px"}
          value={lowerRange}
          minValue={graphLowerRange}
          maxValue={centerValue}
          color={"invisible"}
          onDrag={(e, value) => act('leftSlider', {
            value: value,
          })} >
          {" "}
        </Slider>
        <Slider
          name={"Right slider"}
          position="absolute"
          height={17.2}
          format={value => round(value)}
          step={graphUpperRange/400}
          width={400-((centerValue/graphUpperRange)*400)+"px"}
          value={upperRange}
          minValue={centerValue}
          maxValue={graphUpperRange}
          color={"invisible"}
          onDrag={(e, value) => act('rightSlider', {
            value: value,
          })} >
          {" "}
        </Slider>
        <Box>
          <Slider
            name={"Center slider"}
            position="relative"
            step={graphUpperRange/400}
            mt={0.3}
            mb={5}
            value={centerValue}
            height={1.9}
            format={value => round(value)}
            width={400+"px"}
            minValue={graphLowerRange + 1}
            maxValue={graphUpperRange - 1}
            color={"invisible"}
            onDrag={(e, value) => act('centerSlider', {
              value: value,
            })} >
            {" "}
          </Slider>
        </Box>
      </Box>
    </>
  );
};


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

export const MechBayPowerConsole = (props, context) => {
  const { act, data } = useBackend(context);
  const { recharge_port } = data;
  const mech = recharge_port && recharge_port.mech;
  const cell = mech && mech.cell;
  return (
    <Window
      width={400}
      height={200}>
      <Window.Content>
        <Section
          title="Mech status"
          textAlign="center"
          buttons={(
            <Button
              icon="sync"
              content="Sync"
              onClick={() => act('reconnect')} />
          )}>
          <LabeledList>
            <LabeledList.Item label="Integrity">
              {!recharge_port && (
                <NoticeBox>
                  No power port detected. Please re-sync.
                </NoticeBox>
              ) || !mech && (
                <NoticeBox>
                  No mech detected.
                </NoticeBox>
              ) || (
                <ProgressBar
                  value={mech.health / mech.maxhealth}
                  ranges={{
                    good: [0.7, Infinity],
                    average: [0.3, 0.7],
                    bad: [-Infinity, 0.3],
                  }} />
              )}
            </LabeledList.Item>
            <LabeledList.Item label="Power">
              {!recharge_port && (
                <NoticeBox>
                  No power port detected. Please re-sync.
                </NoticeBox>
              ) || !mech && (
                <NoticeBox>
                  No mech detected.
                </NoticeBox>
              ) || !cell && (
                <NoticeBox>
                  No cell is installed.
                </NoticeBox>
              ) || (
                <ProgressBar
                  value={cell.charge / cell.maxcharge}
                  ranges={{
                    good: [0.7, Infinity],
                    average: [0.3, 0.7],
                    bad: [-Infinity, 0.3],
                  }}>
                  <AnimatedNumber value={cell.charge} />
                  {' / ' + cell.maxcharge}
                </ProgressBar>
              )}
            </LabeledList.Item>
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};


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

export const MechpadControl = (props, context) => {
  const { topLevel } = props;
  const { act, data } = useBackend(context);
  const {
    pad_name,
    connected_mechpad,
  } = data;
  return (
    <Section
      title={(
        <Input
          value={pad_name}
          width="170px"
          onChange={(e, value) => act('rename', {
            name: value,
          })} />
      )}
      level={topLevel ? 1 : 2}
      buttons={(
        <Button
          icon="times"
          content="Remove"
          color="bad"
          onClick={() => act('remove')} />
      )}>
      {!connected_mechpad && (
        <Box color="bad" textAlign="center">
          No Pad Connected.
        </Box>
      ) || (
        <Button
          fluid
          icon="upload"
          content="Launch"
          textAlign="center"
          onClick={() => act('launch')} />
      )}
    </Section>
  );
};

export const MechpadConsole = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    mechpads = [],
    selected_id,
  } = data;
  return (
    <Window
      width={475}
      height={130}>
      <Window.Content>
        {mechpads.length === 0 && (
          <NoticeBox>
            No Pads Connected
          </NoticeBox>
        ) || (
          <Section>
            <Flex minHeight="70px">
              <Flex.Item width="140px" minHeight="70px">
                {mechpads.map(mechpad => (
                  <Button
                    fluid
                    ellipsis
                    key={mechpad.name}
                    content={mechpad.name}
                    selected={selected_id === mechpad.id}
                    color="transparent"
                    onClick={() => act('select_pad', {
                      id: mechpad.id,
                    })} />
                ))}
              </Flex.Item>
              <Flex.Item minHeight="100%">
                <Divider vertical />
              </Flex.Item>
              <Flex.Item grow={1} basis={0} minHeight="100%">
                {selected_id && (
                  <MechpadControl />
                ) || (
                  <Box>
                    Please select a pad
                  </Box>
                )}
              </Flex.Item>
            </Flex>
          </Section>
        )}
      </Window.Content>
    </Window>
  );
};


code:
import { multiline } from 'common/string';
import { useBackend, useSharedState } from '../backend';
import { AnimatedNumber, Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Stack } from '../components';
import { Window } from '../layouts';

export const MedicalKiosk = (props, context) => {
  const { act, data } = useBackend(context);
  const [scanIndex] = useSharedState(context, 'scanIndex');
  const {
    active_status_1,
    active_status_2,
    active_status_3,
    active_status_4,
  } = data;
  return (
    <Window
      width={575}
      height={420}>
      <Window.Content scrollable>
        <Flex mb={1}>
          <Flex.Item mr={1}>

              <MedicalKioskScanButton
                index={1}
                icon="procedures"
                name="General Health Scan"
                description={multiline`
                  Reads back exact values of your general health scan.
                `} />
              <MedicalKioskScanButton
                index={2}
                icon="heartbeat"
                name="Symptom Based Checkup"
                description={multiline`
                  Provides information based on various non-obvious symptoms,
                  like blood levels or disease status.
                `} />
              <MedicalKioskScanButton
                index={3}
                icon="radiation-alt"
                name="Neurological/Radiological Scan"
                description={multiline`
                  Provides information about brain trauma and radiation.
                `} />
              <MedicalKioskScanButton
                index={4}
                icon="mortar-pestle"
                name="Chemical and Psychoactive Scan"
                description={multiline`
                  Provides a list of consumed chemicals, as well as potential
                  side effects.
                `} />

          </Flex.Item>
          <Flex.Item grow={1} basis={0}>
            <MedicalKioskInstructions />
          </Flex.Item>
        </Flex>
        {!!active_status_1 && scanIndex === 1 && (
          <MedicalKioskScanResults1 />
        )}
        {!!active_status_2 && scanIndex === 2 && (
          <MedicalKioskScanResults2 />
        )}
        {!!active_status_3 && scanIndex === 3 && (
          <MedicalKioskScanResults3 />
        )}
        {!!active_status_4 && scanIndex === 4 && (
          <MedicalKioskScanResults4 />
        )}
      </Window.Content>
    </Window>
  );
};

const MedicalKioskScanButton = (props, context) => {
  const {
    index,
    name,
    description,
    icon,
  } = props;
  const { act, data } = useBackend(context);
  const [scanIndex, setScanIndex] = useSharedState(context, 'scanIndex');
  const paid = data[`active_status_${index}`];
  return (
    <Stack align="baseline">
      <Stack.Item width="16px" textAlign="center">
        <Icon
          name={paid ? 'check' : 'dollar-sign'}
          color={paid ? 'green' : 'grey'} />
      </Stack.Item>
      <Stack.Item grow basis="content">
        <Button
          fluid
          icon={icon}
          selected={paid && scanIndex === index}
          tooltip={description}
          tooltipPosition="right"
          content={name}
          onClick={() => {
            if (!paid) {
              act(`beginScan_${index}`);
            }
            setScanIndex(index);
          }} />
      </Stack.Item>
    </Stack>
  );
};

const MedicalKioskInstructions = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    kiosk_cost,
    patient_name,
  } = data;
  return (

      <Box italic>
        Greetings Valued Employee! Please select a desired automatic
        health check procedure. Diagnosis costs {kiosk_cost} credits.
      </Box>
      <Box mt={1}>
        <Box inline color="label" mr={1}>
          Patient:
        </Box>
        {patient_name}
      </Box>
      <Button
        mt={1}
        tooltip={multiline`
          Resets the current scanning target, cancelling current scans.
        `}
        icon="sync"
        color="average"
        onClick={() => act('clearTarget')}
        content="Reset Scanner" />

  );
};

const MedicalKioskScanResults1 = (props, context) => {
  const { data } = useBackend(context);
  const {
    patient_health,
    brute_health,
    burn_health,
    suffocation_health,
    toxin_health,
  } = data;
  return (

      <LabeledList>
        <LabeledList.Item
          label="Total Health">
          <ProgressBar
            value={patient_health / 100}>
            <AnimatedNumber value={patient_health} />%
          </ProgressBar>
        </LabeledList.Item>
        <LabeledList.Divider />
        <LabeledList.Item
          label="Brute Damage">
          <ProgressBar
            value={brute_health / 100}
            color="bad">
            <AnimatedNumber value={brute_health} />
          </ProgressBar>
        </LabeledList.Item>
        <LabeledList.Item
          label="Burn Damage">
          <ProgressBar
            value={burn_health / 100}
            color="bad">
            <AnimatedNumber value={burn_health} />
          </ProgressBar>
        </LabeledList.Item>
        <LabeledList.Item
          label="Oxygen Damage">
          <ProgressBar
            value={suffocation_health / 100}
            color="bad">
            <AnimatedNumber value={suffocation_health} />
          </ProgressBar>
        </LabeledList.Item>
        <LabeledList.Item
          label="Toxin Damage">
          <ProgressBar
            value={toxin_health / 100}
            color="bad">
            <AnimatedNumber value={toxin_health} />
          </ProgressBar>
        </LabeledList.Item>
      </LabeledList>

  );
};

const MedicalKioskScanResults2 = (props, context) => {
  const { data } = useBackend(context);
  const {
    patient_status,
    patient_illness,
    illness_info,
    bleed_status,
    blood_levels,
    blood_status,
  } = data;
  return (

      <LabeledList>
        <LabeledList.Item
          label="Patient Status"
          color="good">
          {patient_status}
        </LabeledList.Item>
        <LabeledList.Divider />
        <LabeledList.Item
          label="Disease Status">
          {patient_illness}
        </LabeledList.Item>
        <LabeledList.Item
          label="Disease information">
          {illness_info}
        </LabeledList.Item>
        <LabeledList.Divider />
        <LabeledList.Item
          label="Blood Levels">
          <ProgressBar
            value={blood_levels / 100}
            color="bad">
            <AnimatedNumber value={blood_levels} />
          </ProgressBar>
          <Box mt={1} color="label">
            {bleed_status}
          </Box>
        </LabeledList.Item>
        <LabeledList.Item
          label="Blood Information">
          {blood_status}
        </LabeledList.Item>
      </LabeledList>

  );
};

const MedicalKioskScanResults3 = (props, context) => {
  const { data } = useBackend(context);
  const {
    clone_health,
    brain_damage,
    brain_health,
    rad_contamination_status,
    rad_contamination_value,
    rad_sickness_status,
    rad_sickness_value,
    trauma_status,
  } = data;
  return (

      <LabeledList>
        <LabeledList.Item
          label="Cellular Damage">
          <ProgressBar
            value={clone_health / 100}
            color="good">
            <AnimatedNumber value={clone_health} />
          </ProgressBar>
        </LabeledList.Item>
        <LabeledList.Divider />
        <LabeledList.Item
          label="Brain Damage">
          <ProgressBar
            value={brain_damage / 100}
            color="good">
            <AnimatedNumber value={brain_damage} />
          </ProgressBar>
        </LabeledList.Item>
        <LabeledList.Item
          label="Brain Status"
          color="health-0">
          {brain_health}
        </LabeledList.Item>
        <LabeledList.Item
          label="Brain Trauma Status">
          {trauma_status}
        </LabeledList.Item>
        <LabeledList.Divider />
        <LabeledList.Item
          label="Radiation Sickness Status">
          {rad_sickness_status}
        </LabeledList.Item>
        <LabeledList.Item
          label="Radiation Sickness Percentage">
          {rad_sickness_value}%
        </LabeledList.Item>
        <LabeledList.Item
          label="Radiation Contamination Status">
          {rad_contamination_status}
        </LabeledList.Item>
        <LabeledList.Item
          label="Radiation Contamination Percentage">
          {rad_contamination_value}%
        </LabeledList.Item>
      </LabeledList>

  );
};

const MedicalKioskScanResults4 = (props, context) => {
  const { data } = useBackend(context);
  const {
    chemical_list = [],
    overdose_list = [],
    addict_list = [],
    hallucinating_status,
  } = data;
  return (

      <LabeledList>
        <LabeledList.Item
          label="Chemical Contents">
          {chemical_list.length === 0 && (
            <Box color="average">
              No reagents detected.
            </Box>
          )}
          {chemical_list.map(chem => (
            <Box
              key={chem.id}
              color="good">
              {chem.volume} units of {chem.name}
            </Box>
          ))}
        </LabeledList.Item>
        <LabeledList.Item
          label="Overdose Status"
          color="bad">
          {overdose_list.length === 0 && (
            <Box color="good">
              Patient is not overdosing.
            </Box>
          )}
          {overdose_list.map(chem => (
            <Box key={chem.id}>
              Overdosing on {chem.name}
            </Box>
          ))}
        </LabeledList.Item>
        <LabeledList.Item
          label="Addiction Status"
          color="bad">
          {addict_list.length === 0 && (
            <Box color="good">
              Patient has no addictions.
            </Box>
          )}
          {addict_list.map(chem => (
            <Box key={chem.id}>
              Addicted to {chem.name}
            </Box>
          ))}
        </LabeledList.Item>
        <LabeledList.Item
          label="Psychoactive Status">
          {hallucinating_status}
        </LabeledList.Item>
      </LabeledList>

  );
};