code:
import { toFixed } from 'common/math';
import { Fragment } from 'inferno';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
import { Scrubber, Vent } from './common/AtmosControls';
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';

export const AirAlarm = (props, context) => {
  const { act, data } = useBackend(context);
  const locked = data.locked && !data.siliconUser;
  return (
    <Window
      width={440}
      height={650}>
      <Window.Content scrollable>
        <InterfaceLockNoticeBox />
        <AirAlarmStatus />
        {!locked && (
          <AirAlarmControl />
        )}
      </Window.Content>
    </Window>
  );
};

const AirAlarmStatus = (props, context) => {
  const { data } = useBackend(context);
  const entries = (data.environment_data || [])
    .filter(entry => entry.value >= 0.01);
  const dangerMap = {
    0: {
      color: 'good',
      localStatusText: 'Optimal',
    },
    1: {
      color: 'average',
      localStatusText: 'Caution',
    },
    2: {
      color: 'bad',
      localStatusText: 'Danger (Internals Required)',
    },
  };
  const localStatus = dangerMap[data.danger_level] || dangerMap[0];
  return (

      <LabeledList>
        {entries.length > 0 && (
          <>
            {entries.map(entry => {
              const status = dangerMap[entry.danger_level] || dangerMap[0];
              return (
                <LabeledList.Item
                  key={entry.name}
                  label={entry.name}
                  color={status.color}>
                  {toFixed(entry.value, 2)}{entry.unit}
                </LabeledList.Item>
              );
            })}
            <LabeledList.Item
              label="Local status"
              color={localStatus.color}>
              {localStatus.localStatusText}
            </LabeledList.Item>
            <LabeledList.Item
              label="Area status"
              color={data.atmos_alarm || data.fire_alarm ? 'bad' : 'good'}>
              {data.atmos_alarm && 'Atmosphere Alarm'
                || data.fire_alarm && 'Fire Alarm'
                || 'Nominal'}
            </LabeledList.Item>
          </>
        ) || (
          <LabeledList.Item
            label="Warning"
            color="bad">
            Cannot obtain air sample for analysis.
          </LabeledList.Item>
        )}
        {!!data.emagged && (
          <LabeledList.Item
            label="Warning"
            color="bad">
            Safety measures offline. Device may exhibit abnormal behavior.
          </LabeledList.Item>
        )}
      </LabeledList>

  );
};

const AIR_ALARM_ROUTES = {
  home: {
    title: 'Air Controls',
    component: () => AirAlarmControlHome,
  },
  vents: {
    title: 'Vent Controls',
    component: () => AirAlarmControlVents,
  },
  scrubbers: {
    title: 'Scrubber Controls',
    component: () => AirAlarmControlScrubbers,
  },
  modes: {
    title: 'Operating Mode',
    component: () => AirAlarmControlModes,
  },
  thresholds: {
    title: 'Alarm Thresholds',
    component: () => AirAlarmControlThresholds,
  },
};

const AirAlarmControl = (props, context) => {
  const [screen, setScreen] = useLocalState(context, 'screen');
  const route = AIR_ALARM_ROUTES[screen] || AIR_ALARM_ROUTES.home;
  const Component = route.component();
  return (
    <Section
      title={route.title}
      buttons={screen && (
        <Button
          icon="arrow-left"
          content="Back"
          onClick={() => setScreen()} />
      )}>
      <Component />

  );
};

//  Home screen
// --------------------------------------------------------

const AirAlarmControlHome = (props, context) => {
  const { act, data } = useBackend(context);
  const [screen, setScreen] = useLocalState(context, 'screen');
  const {
    mode,
    atmos_alarm,
  } = data;
  return (
    <>
      <Button
        icon={atmos_alarm
          ? 'exclamation-triangle'
          : 'exclamation'}
        color={atmos_alarm && 'caution'}
        content="Area Atmosphere Alarm"
        onClick={() => act(atmos_alarm ? 'reset' : 'alarm')} />
      <Box mt={1} />
      <Button
        icon={mode === 3
          ? 'exclamation-triangle'
          : 'exclamation'}
        color={mode === 3 && 'danger'}
        content="Panic Siphon"
        onClick={() => act('mode', {
          mode: mode === 3 ? 1 : 3,
        })} />
      <Box mt={2} />
      <Button
        icon="sign-out-alt"
        content="Vent Controls"
        onClick={() => setScreen('vents')} />
      <Box mt={1} />
      <Button
        icon="filter"
        content="Scrubber Controls"
        onClick={() => setScreen('scrubbers')} />
      <Box mt={1} />
      <Button
        icon="cog"
        content="Operating Mode"
        onClick={() => setScreen('modes')} />
      <Box mt={1} />
      <Button
        icon="chart-bar"
        content="Alarm Thresholds"
        onClick={() => setScreen('thresholds')} />
    </>
  );
};

//  Vents
// --------------------------------------------------------

const AirAlarmControlVents = (props, context) => {
  const { data } = useBackend(context);
  const { vents } = data;
  if (!vents || vents.length === 0) {
    return 'Nothing to show';
  }
  return vents.map(vent => (
    <Vent
      key={vent.id_tag}
      vent={vent} />
  ));
};

//  Scrubbers
// --------------------------------------------------------

const AirAlarmControlScrubbers = (props, context) => {
  const { data } = useBackend(context);
  const { scrubbers } = data;
  if (!scrubbers || scrubbers.length === 0) {
    return 'Nothing to show';
  }
  return scrubbers.map(scrubber => (
    <Scrubber
      key={scrubber.id_tag}
      scrubber={scrubber} />
  ));
};

//  Modes
// --------------------------------------------------------

const AirAlarmControlModes = (props, context) => {
  const { act, data } = useBackend(context);
  const { modes } = data;
  if (!modes || modes.length === 0) {
    return 'Nothing to show';
  }
  return modes.map(mode => (
    <Fragment key={mode.mode}>
      <Button
        icon={mode.selected ? 'check-square-o' : 'square-o'}
        selected={mode.selected}
        color={mode.selected && mode.danger && 'danger'}
        content={mode.name}
        onClick={() => act('mode', { mode: mode.mode })} />
      <Box mt={1} />
    </Fragment>
  ));
};

//  Thresholds
// --------------------------------------------------------

const AirAlarmControlThresholds = (props, context) => {
  const { act, data } = useBackend(context);
  const { thresholds } = data;
  return (

          hazard_min
          warning_min
          warning_max
          hazard_max

        {thresholds.map(threshold => (

            {threshold.name}
            {threshold.settings.map(setting => (

                <Button
                  content={toFixed(setting.selected, 2)}
                  onClick={() => act('threshold', {
                    env: setting.env,
                    var: setting.val,
                  })} />

            ))}

        ))}

  );
};


code:
import { useBackend, useLocalState } from '../backend';
import { Button, Flex, Grid, Input, LabeledList, Section, Tabs } from '../components';
import { Window } from '../layouts';
import { sortBy } from 'common/collections';

export const AirlockElectronics = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    oneAccess,
    unres_direction,
    passedName,
    passedCycleId,
  } = data;
  const regions = data.regions || [];
  const accesses = data.accesses || [];
  return (
    <Window
      width={420}
      height={485}>
      <Window.Content>
        <Section title="Main">
          <LabeledList>
            <LabeledList.Item
              label="Access Required">
              <Button
                icon={oneAccess ? 'unlock' : 'lock'}
                content={oneAccess ? 'One' : 'All'}
                onClick={() => act('one_access')} />
            </LabeledList.Item>
            <LabeledList.Item
              label="Unrestricted Access">
              <Button
                icon={unres_direction & 1 ? 'check-square-o' : 'square-o'}
                content="North"
                selected={unres_direction & 1}
                onClick={() => act('direc_set', {
                  unres_direction: '1',
                })} />
              <Button
                icon={unres_direction & 2 ? 'check-square-o' : 'square-o'}
                content="South"
                selected={unres_direction & 2}
                onClick={() => act('direc_set', {
                  unres_direction: '2',
                })} />
              <Button
                icon={unres_direction & 4 ? 'check-square-o' : 'square-o'}
                content="East"
                selected={unres_direction & 4}
                onClick={() => act('direc_set', {
                  unres_direction: '4',
                })} />
              <Button
                icon={unres_direction & 8 ? 'check-square-o' : 'square-o'}
                content="West"
                selected={unres_direction & 8}
                onClick={() => act('direc_set', {
                  unres_direction: '8',
                })} />
            </LabeledList.Item>
            <LabeledList.Item
              label="Airlock Name">
              <Input fluid
                maxLength={30}
                value={passedName}
                onChange={(e, value) => act('passedName', {
                  passedName: value,
                })} />
            </LabeledList.Item>
            <LabeledList.Item
              label="Cycling Id">
              <Input fluid
                maxLength={30}
                value={passedCycleId}
                onChange={(e, value) => act('passedCycleId', {
                  passedCycleId: value,
                })} />
            </LabeledList.Item>
          </LabeledList>
        </Section>
        <AirlockAccessList
          accesses={regions}
          selectedList={accesses}
          accessMod={ref => act('set', {
            access: ref,
          })}
          grantAll={() => act('grant_all')}
          denyAll={() => act('clear_all')}
          grantDep={ref => act('grant_region', {
            region: ref,
          })}
          denyDep={ref => act('deny_region', {
            region: ref,
          })} />
      </Window.Content>
    </Window>
  );
};

const diffMap = {
  0: {
    icon: 'times-circle',
    color: 'bad',
  },
  1: {
    icon: 'stop-circle',
    color: null,
  },
  2: {
    icon: 'check-circle',
    color: 'good',
  },
};

export const AirlockAccessList = (props, context) => {
  const {
    accesses = [],
    selectedList = [],
    accessMod,
    grantAll,
    denyAll,
    grantDep,
    denyDep,
  } = props;
  const [
    selectedAccessName,
    setSelectedAccessName,
  ] = useLocalState(context, 'accessName', accesses[0]?.name);
  const selectedAccess = accesses
    .find(access => access.name === selectedAccessName);
  const selectedAccessEntries = sortBy(
    entry => entry.desc,
  )(selectedAccess?.accesses || []);

  const checkAccessIcon = accesses => {
    let oneAccess = false;
    let oneInaccess = false;
    for (let element of accesses) {
      if (selectedList.includes(element.ref)) {
        oneAccess = true;
      }
      else {
        oneInaccess = true;
      }
    }
    if (!oneAccess && oneInaccess) {
      return 0;
    }
    else if (oneAccess && oneInaccess) {
      return 1;
    }
    else {
      return 2;
    }
  };

  return (
    <Section
      title="Access"
      buttons={(
        <>
          <Button
            icon="check-double"
            content="Grant All"
            color="good"
            onClick={() => grantAll()} />
          <Button
            icon="undo"
            content="Deny All"
            color="bad"
            onClick={() => denyAll()} />
        </>
      )}>
      <Flex>
        <Flex.Item>
          <Tabs vertical>
            {accesses.map(access => {
              const entries = access.accesses || [];
              const icon = diffMap[checkAccessIcon(entries)].icon;
              const color = diffMap[checkAccessIcon(entries)].color;
              return (
                <Tabs.Tab
                  key={access.name}
                  altSelection
                  color={color}
                  icon={icon}
                  selected={access.name === selectedAccessName}
                  onClick={() => setSelectedAccessName(access.name)}>
                  {access.name}
                </Tabs.Tab>
              );
            })}
          </Tabs>
        </Flex.Item>
        <Flex.Item grow={1} ml={1.5}>
          <Grid>
            <Grid.Column mr={0}>
              <Button
                fluid
                icon="check"
                content="Grant Region"
                color="good"
                onClick={() => grantDep(selectedAccess.name)} />
            </Grid.Column>
            <Grid.Column ml={0}>
              <Button
                fluid
                icon="times"
                content="Deny Region"
                color="bad"
                onClick={() => denyDep(selectedAccess.name)} />
            </Grid.Column>
          </Grid>
          {selectedAccessEntries.map(entry => (
            <Button.Checkbox
              fluid
              key={entry.desc}
              content={entry.desc}
              checked={selectedList.includes(entry.ref)}
              onClick={() => accessMod(entry.ref)} />
          ))}
        </Flex.Item>
      </Flex>
    </Section>
  );
};

repo_name: AnalWerewolf/sundown

license: mit


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

import { clamp01 } from 'common/math';
import { useBackend } from '../backend';
import { Component, createRef } from 'inferno';
import { Box, Flex, Section } from '../components';
import { Window } from '../layouts';
import {
  KEY_ENTER,
  KEY_LEFT,
  KEY_RIGHT,
  KEY_SPACE,
  KEY_TAB,
} from 'common/keycodes';

export class AlertModal extends Component {
  constructor() {
    super();

    this.buttonRefs = [createRef()];
    this.state = { current: 0 };
  }

  componentDidMount() {
    const { data } = useBackend(this.context);
    const { buttons, autofocus } = data;
    const { current } = this.state;
    const button = this.buttonRefs[current].current;

    // Fill ref array with refs for other buttons
    for (let i = 1; i < buttons.length; i++) {
      this.buttonRefs.push(createRef());
    }

    if (autofocus) {
      setTimeout(() => button.focus(), 1);
    }
  }

  setCurrent(current, isArrowKey) {
    const { data } = useBackend(this.context);
    const { buttons } = data;

    // Mimic alert() behavior for tabs and arrow keys
    if (current >= buttons.length) {
      current = isArrowKey ? current - 1 : 0;
    } else if (current < 0) {
      current = isArrowKey ? 0 : buttons.length - 1;
    }

    const button = this.buttonRefs[current].current;

    // Prevents an error from occurring on close
    if (button) {
      setTimeout(() => button.focus(), 1);
    }
    this.setState({ current });
  }

  render() {
    const { act, data } = useBackend(this.context);
    const { title, message, buttons, timeout } = data;
    const { current } = this.state;
    const focusCurrentButton = () => this.setCurrent(current, false);

    return (
      <Window
        title={title}
        width={350}
        height={150}>
        {timeout && <Loader value={timeout} />}
        <Window.Content
          onFocus={focusCurrentButton}
          onClick={focusCurrentButton}>
          <Section fill>
            <Flex direction="column" height="100%">
              <Flex.Item grow={1}>
                <Flex
                  direction="column"
                  className="AlertModal__Message"
                  height="100%">
                  <Flex.Item>
                    <Box m={1}>
                      {message}
                    </Box>
                  </Flex.Item>
                </Flex>
              </Flex.Item>
              <Flex.Item my={2}>
                <Flex className="AlertModal__Buttons">
                  {buttons.map((button, buttonIndex) => (
                    <Flex.Item key={buttonIndex} mx={1}>
                      <div
                        ref={this.buttonRefs[buttonIndex]}
                        className="Button Button--color--default"
                        px={3}
                        onClick={() => act("choose", { choice: button })}
                        onKeyDown={e => {
                          const keyCode = window.event ? e.which : e.keyCode;

                          /**
                           * Simulate a click when pressing space or enter,
                           * allow keyboard navigation, override tab behavior
                           */
                          if (keyCode === KEY_SPACE || keyCode === KEY_ENTER) {
                            act("choose", { choice: button });
                          } else if (
                            keyCode === KEY_LEFT
                            || (e.shiftKey && keyCode === KEY_TAB)
                          ) {
                            this.setCurrent(current - 1, keyCode === KEY_LEFT);
                          } else if (
                            keyCode === KEY_RIGHT || keyCode === KEY_TAB
                          ) {
                            this.setCurrent(current + 1, keyCode === KEY_RIGHT);
                          }
                        }}>
                        {button}
                      </div>
                    </Flex.Item>
                  ))}
                </Flex>
              </Flex.Item>
            </Flex>
          </Section>
        </Window.Content>
      </Window>
    );
  }

}

export const Loader = props => {
  const { value } = props;

  return (
    <div className="AlertModal__Loader">
      <Box
        className="AlertModal__LoaderProgress"
        style={{ width: clamp01(value) * 100 + '%' }} />
    </div>
  );
};


code:
import { useBackend } from '../backend';
import { Icon, Section, Stack } from '../components';
import { BooleanLike } from 'common/react';
import { Window } from '../layouts';

type Objective = {
  count: number;
  name: string;
  explanation: string;
  complete: BooleanLike;
  was_uncompleted: BooleanLike;
  reward: number;
}

type Info = {
  objectives: Objective[];
};

export const AntagInfoBrainwashed = (props, context) => {
  return (
    <Window
      width={400}
      height={400}
      theme="abductor">
      <Window.Content
        backgroundColor="#722e7d">
        <Icon
          size={16}
          name="flushed"
          color="#3f1945"
          position="absolute"
          top="42%"
          left="26%" />
        <Section fill>
          <Stack align="baseline" vertical fill>
            <Stack.Item fontFamily="Wingdings">
              Hey, no! Stop translating this!
            </Stack.Item>
            <Stack.Item mt={-0.25} fontSize="20px">
              Your mind reels...
            </Stack.Item>
            <Stack.Item mt={-0.25} fontSize="20px">
              It is focusing on a single purpose...
            </Stack.Item>
            <Stack.Item mt={1.5} grow >
              <ObjectivePrintout />
            </Stack.Item>
            <Stack.Item fontSize="20px">
              Follow the Directives, at any cost!
            </Stack.Item>
            <Stack.Item fontFamily="Wingdings">
              You ruined my cool font effect.
            </Stack.Item>
          </Stack>
        </Section>
      </Window.Content>
    </Window>
  );
};

const ObjectivePrintout = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    objectives,
  } = data;
  return (
    <Stack vertical>
      <Stack.Item bold>
        Your current objectives:
      </Stack.Item>
      <Stack.Item>
        {!objectives && "None!"
        || objectives.map(objective => (
          <>
            <Stack.Item key={objective.count}>
              #{objective.count}: {objective.explanation}
            </Stack.Item>
            <Stack.Item textColor="red">
              This Directive must be followed.
            </Stack.Item>
          </>
        )) }
      </Stack.Item>
    </Stack>
  );
};


code:
import { multiline } from 'common/string';
import { useBackend, useSharedState } from '../backend';
import { Box, Button, Dimmer, Dropdown, Section, Stack } from '../components';
import { Window } from '../layouts';

const hivestyle = {
  fontWeight: 'bold',
  color: 'yellow',
};

const absorbstyle = {
  color: 'red',
  fontWeight: 'bold',
};

const revivestyle = {
  color: 'lightblue',
  fontWeight: 'bold',
};

const transformstyle = {
  color: 'orange',
  fontWeight: 'bold',
};

const storestyle = {
  color: 'lightgreen',
  fontWeight: 'bold',
};

type Objective = {
  count: number;
  name: string;
  explanation: string;
}

type Memory = {
  name: string;
  story: string;
}

type Info = {
  hive_name: string;
  stolen_antag_info: string;
  memories: Memory[];
  objectives: Objective[];
};

const ObjectivePrintout = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    objectives,
  } = data;
  return (
    <Stack vertical>
      <Stack.Item bold>
        Your current objectives:
      </Stack.Item>
      <Stack.Item>
        {!objectives && "None!"
        || objectives.map(objective => (
          <Stack.Item key={objective.count}>
            #{objective.count}: {objective.explanation}
          </Stack.Item>
        )) }
      </Stack.Item>
    </Stack>
  );
};

const IntroductionSection = (props, context) => {
  const { act, data } = useBackend<Info>(context);
  const {
    hive_name,
    objectives,
  } = data;
  return (

 4
      }>
      <Stack vertical fill>
        <Stack.Item fontSize="25px">
          You are the Changeling from the

             {hive_name}
          .
        </Stack.Item>
        <Stack.Item>
          <ObjectivePrintout />
        </Stack.Item>
      </Stack>

  );
};

const AbilitiesSection = (props, context) => {
  const { data } = useBackend<Info>(context);
  return (

      <Stack fill>
        <Stack.Item basis={0} grow>
          <Stack fill vertical>
            <Stack.Item basis={0} textColor="label" grow>
              Your

                 Absorb DNA
               ability
              allows you to steal the DNA and memories of a victim.
              Your

                 Extract DNA Sting
               ability
              also steals the DNA of a victim, and is undetectable, but
              does not grant you their memories or speech patterns.
            </Stack.Item>
            <Stack.Divider />
            <Stack.Item basis={0} textColor="label" grow>
              Your

                 Reviving Stasis
               ability
              allows you to revive. It means nothing short of a complete body
              destruction can stop you! Obviously, this is loud and
              so should not be done in front of people you are not
              planning on silencing.
            </Stack.Item>
          </Stack>
        </Stack.Item>
        <Stack.Divider />
        <Stack.Item basis={0} grow>
          <Stack fill vertical>
            <Stack.Item basis={0} textColor="label" grow>
              Your

                 Transform
               ability
              allows you to change into the form of those you have
              collected DNA from, lethally and nonlethally. It will
              also mimic (NOT REAL CLOTHING) the clothing they were wearing
              for every slot you have open.
            </Stack.Item>
            <Stack.Divider />
            <Stack.Item basis={0} textColor="label" grow>
              The

                 Cellular Emporium
               is
              where you purchase more abilities beyond your starting kit.
              You have 10 genetic points to spend on abilities and you are
              able to readapt after absorbing a body, refunding your points
              for different kits.
            </Stack.Item>
          </Stack>
        </Stack.Item>
      </Stack>

  );
};

const MemoriesSection = (props, context) => {
  const { act, data } = useBackend<Info>(context);
  const {
    memories,
    stolen_antag_info,
  } = data;
  const [
    selectedMemory,
    setSelectedMemory,
  ] = useSharedState(context, "memory", !!memories && memories[0] || null);
  const memoryMap = {};
  for (const index in memories) {
    const memory = memories[index];
    memoryMap[memory.name] = memory;
  }
  return (
    <Section
      fill
      scrollable={!!memories}
      title="Stolen Memories"
      buttons={
        <Button
          icon="info"
          tooltipPosition="left"
          tooltip={multiline`
            Absorbing targets allows
            you to collect their memories. They should
            help you impersonate your target!
          `} />
      }>
      {!memories && (
        <Box>
          {!stolen_antag_info && (
            <Dimmer mr="-100%" bold>
              You need to absorb a victim first!
            </Dimmer>
          )}
        </Box>
      ) || (
        <Stack vertical>
          <Stack.Item>
            <Dropdown
              width="100%"
              selected={selectedMemory?.name}
              options={
                memories.map(memory => {
                  return memory.name;
                })
              }
              onSelected={selected => setSelectedMemory(memoryMap[selected])} />
          </Stack.Item>
          <Stack.Item>
            {!!selectedMemory && selectedMemory.story}
          </Stack.Item>
        </Stack>
      )}

  );
};

const VictimPatternsSection = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    stolen_antag_info,
  } = data;
  return (

      {!!stolen_antag_info && (
        stolen_antag_info
      )}

  );
};

export const AntagInfoChangeling = (props, context) => {
  const { data } = useBackend<Info>(context);
  return (
    <Window
      width={620}
      height={580}>
      <Window.Content
        style={{
          "backgroundImage": "none",
        }}>
        <Stack vertical fill>
          <Stack.Item maxHeight={13.2}>
            <IntroductionSection />
          </Stack.Item>
          <Stack.Item grow={4}>
            <AbilitiesSection />
          </Stack.Item>
          <Stack.Item grow={3}>
            <Stack fill>
              <Stack.Item grow basis={0}>
                <MemoriesSection />
              </Stack.Item>
              <Stack.Item grow ml={0} basis={0}>
                <VictimPatternsSection />
              </Stack.Item>
            </Stack>
          </Stack.Item>
        </Stack>
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend } from '../backend';
import { Box, Section, Stack } from '../components';
import { BooleanLike } from 'common/react';
import { Window } from '../layouts';

const jauntstyle = {
  color: 'lightblue',
};

const injurestyle = {
  color: 'yellow',
};

type Objective = {
  count: number;
  name: string;
  explanation: string;
  complete: BooleanLike;
  was_uncompleted: BooleanLike;
  reward: number;
}

type Info = {
  fluff: string;
  explain_attack: BooleanLike;
  objectives: Objective[];
};

export const AntagInfoDemon = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    fluff,
    objectives,
    explain_attack,
  } = data;
  return (
    <Window
      width={620}
      height={356}
      theme="syndicate">
      <Window.Content
        style={{ 'background-image': 'none' }}>
        <Stack fill>
          <Stack.Item>
            <DemonRunes />
          </Stack.Item>
          <Stack.Item grow>
            <Stack vertical width="544px" fill>
              <Stack.Item grow>

 2}>
                  <Stack vertical>
                    <Stack.Item textAlign="center" textColor="red" fontSize="20px">
                      {fluff}
                    </Stack.Item>
                    <Stack.Item>
                      <ObjectivePrintout />
                    </Stack.Item>
                  </Stack>

              </Stack.Item>
              {!!explain_attack && (
                <Stack.Item>

                    <Stack vertical>
                      <Stack.Item>
                        Blood Jaunt: You
                        can dive in and out of blood to travel anywhere
                        you need to be. You will gain a speed boost upon
                        leaving the jaunt for surprise attacks. You can
                        drag victims you have disabled through the blood,
                        consuming them and restoring health.
                      </Stack.Item>
                      <Stack.Divider />
                      <Stack.Item>
                        Monstrous strike: You
                        can launch a devastating slam attack by right-clicking,
                        capable of smashing bones in one strike. Great for
                        preventing the escape of your victims, as their wounds
                        will slow them.
                      </Stack.Item>
                    </Stack>

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

const ObjectivePrintout = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    objectives,
  } = data;
  return (
    <Stack vertical>
      <Stack.Item bold>
        It is in your nature to accomplish these goals:
      </Stack.Item>
      <Stack.Item>
        {!objectives && "None!"
        || objectives.map(objective => (
          <Stack.Item fontSize="20px" key={objective.count}>
            #{objective.count}: {objective.explanation}
          </Stack.Item>
        )) }
      </Stack.Item>
    </Stack>
  );
};

const DemonRunes = (props, context) => {
  return (

      {/*
      shoutout to my boy Yuktopus from Crash Bandicoot: Crash of the Titans.
      Damn, that was such a good game.
      */}
      <Box className="HellishRunes__demonrune" >
        Y
U
K
T
O
P
U
S

        Y
U
K
T
O
P
U
S

        Y
U
K
T
O
P
U
S

        Y
U
K
T
O
P
U
S
      </Box>

  );
};


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

type Info = {
  antag_name: string;
  gang_name: string;
  gang_objective: string;
  gang_clothes: string[];
};

export const AntagInfoGangmember = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    gang_name,
    antag_name,
  } = data;
  return (
    <Window
      width={620}
      height={500}>
      <Window.Content
        style={{
          'background-image': 'none',
        }}>

          <Stack vertical>
            <Stack.Item textColor="red" fontSize="20px">
              {gang_name} for life! You are a {antag_name}!
            </Stack.Item>
            <Stack.Item fontSize="18px">
              As a gang member, support your family above all!
              Tag turf with a spraycan, wear your family's clothes,
              induct new members with induction packages,
              and accomplish your family objective.
            </Stack.Item>
            <Stack.Divider />
            <Stack.Item>
              <Stack fill>
                <Stack.Item grow basis={0}>
                  <GangClothesPrintout />
                </Stack.Item>
                <Stack.Item grow basis={0}>
                  <GangPhonePrintout />
                </Stack.Item>
              </Stack>
            </Stack.Item>
            <Stack.Divider />
            <Stack.Item grow basis={0}>
              <GangObjectivePrintout />
            </Stack.Item>
          </Stack>

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

const GangClothesPrintout = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    gang_name,
    gang_clothes,
  } = data;
  return (
    <Stack vertical>
      <Stack.Item>
        <Stack>
          <Stack.Item mt={0.5} mb={1}>
            <Icon
              size={2}
              name="tshirt" />
          </Stack.Item>
          <Stack.Item bold>
            Wear the following to represent the {gang_name}:
          </Stack.Item>
        </Stack>
      </Stack.Item>

        {gang_clothes && gang_clothes.length ? (
          gang_clothes.map(clothes_item => (
            <Stack.Item key={clothes_item}>
              - {clothes_item}
            </Stack.Item>
          ))
        ) : (
          "- Anything!"
        )}

    </Stack>
  );
};

const GangPhonePrintout = () => {
  return (
    <Stack vertical>
      <Stack.Item>
        <Stack>
          <Stack.Item mt={0.5}>
            <Icon
              size={2}
              name="phone" />
          </Stack.Item>
          <Stack.Item bold>
            You were given a cell phone with your induction package!
          </Stack.Item>
        </Stack>
      </Stack.Item>
      <Stack.Item>

          Use it in hand to activate it, then speak into it to talk with
          your other family members.

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

const GangObjectivePrintout = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    gang_objective,
  } = data;
  return (
    <Stack vertical>
      <Stack.Item bold fontSize="16px">
        Your family's goal:
      </Stack.Item>
      <Stack.Item>
        {gang_objective
          || "No objective set! This is a problem!"}
      </Stack.Item>
    </Stack>
  );
};


code:
import { useBackend } from '../backend';
import { Section, Stack } from '../components';
import { BooleanLike } from 'common/react';
import { Window } from '../layouts';

type Objective = {
  count: number;
  name: string;
  explanation: string;
  complete: BooleanLike;
  was_uncompleted: BooleanLike;
  reward: number;
}

type Info = {
  antag_name: string;
  objectives: Objective[];
};

export const AntagInfoGeneric = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    antag_name,
  } = data;
  return (
    <Window
      width={620}
      height={250}>
      <Window.Content>
        <Section scrollable fill>
          <Stack vertical>
            <Stack.Item textColor="red" fontSize="20px">
              You are the {antag_name}!
            </Stack.Item>
            <Stack.Item>
              <ObjectivePrintout />
            </Stack.Item>
          </Stack>
        </Section>
      </Window.Content>
    </Window>
  );
};

const ObjectivePrintout = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    objectives,
  } = data;
  return (
    <Stack vertical>
      <Stack.Item bold>
        Your objectives:
      </Stack.Item>
      <Stack.Item>
        {!objectives && "None!"
        || objectives.map(objective => (
          <Stack.Item key={objective.count}>
            #{objective.count}: {objective.explanation}
          </Stack.Item>
        )) }
      </Stack.Item>
    </Stack>
  );
};


code:
import { useBackend } from '../backend';
import { Section, Stack, Box } from '../components';
import { Window } from '../layouts';
import { BooleanLike } from 'common/react';

const hereticstyle = {
  fontWeight: 'bold',
  color: 'yellow',
};

type Objective = {
  count: number;
  name: string;
  explanation: string;
}

type Info = {
  total_sacrifices: number;
  ascended: BooleanLike;
  objectives: Objective[];
};

const ObjectivePrintout = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    objectives,
  } = data;
  return (
    <Stack vertical>
      <Stack.Item bold>
        The old ones gave you these tasks to fulfill:
      </Stack.Item>
      <Stack.Item>
        {!objectives && "None!"
        || objectives.map(objective => (
          <Stack.Item key={objective.count}>
            {objective.count}: {objective.explanation}
          </Stack.Item>
        )) }
      </Stack.Item>
    </Stack>
  );
};

const InformationSection = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    total_sacrifices,
    ascended,
  } = data;
  return (
    <Stack vertical fill>
      {!!ascended && (
        <Stack.Item>
          <Stack width="100%" justify="space-evenly" align="center" inline>
            <Stack.Item>
              You have
            </Stack.Item>
            <Stack.Item>
              <Box inline fontSize="24px" color="red">ASCENDED</Box>
            </Stack.Item>
          </Stack>
        </Stack.Item>
      )}
      <Stack.Item grow>
        You have made a total of {total_sacrifices || 0} sacrifices.
      </Stack.Item>
    </Stack>
  );
};

const IntroductionSection = (props, context) => {
  return (
    <Stack
      align="center"
      justify="space-evenly"
      height="100%"
      width="100%"
    >
      <Stack.Item width="100%">
        <Section title="You are the Heretic!" fill>
          <Stack vertical fill>
            <Stack.Item>
              Another day at a meaningless job.
              You feel a <Box inline color="red">shimmer</Box> around you,
              as a realization of something <Box inline color="blue">strange</Box> in your backpack unfolds.
              You look at it, unknowingly opening a new chapter in your life.
              The <Box inline color="red">Gates of Mansus</Box> open up to your mind.
              Their hand is at your throat, yet you see Them not.
            </Stack.Item>
            <Stack.Item>
              <InformationSection />
            </Stack.Item>
            <Stack.Divider />
            <Stack.Item grow>
              <ObjectivePrintout />
            </Stack.Item>
          </Stack>
        </Section>
      </Stack.Item>
    </Stack>
  );
};

export const AntagInfoHeretic = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    ascended,
  } = data;
  return (
    <Window
      width={620}
      height={320}
    >
      <Window.Content
        style={{
          "background-image": "none",
          "background": ascended
            ? "radial-gradient(circle, rgba(24,9,9,1) 54%, rgba(31,10,10,1) 60%, rgba(46,11,11,1) 80%, rgba(47,14,14,1) 100%);"
            : "radial-gradient(circle, rgba(9,9,24,1) 54%, rgba(10,10,31,1) 60%, rgba(21,11,46,1) 80%, rgba(24,14,47,1) 100%);",
        }}
      >
        <IntroductionSection />
      </Window.Content>
    </Window>
  );
};


code:
import { BlockQuote, Stack } from '../components';
import { Window } from '../layouts';

const goodstyle = {
  color: 'lightgreen',
};

const badstyle = {
  color: 'red',
};

const noticestyle = {
  color: 'lightblue',
};

export const AntagInfoMorph = (props, context) => {
  return (
    <Window
      width={620}
      height={170}
      theme="abductor">
      <Window.Content>
        <Stack vertical fill>
          <Stack.Item fontSize="25px">
            You are a morph...
          </Stack.Item>
          <Stack.Item>

              ...a shapeshifting abomination that can eat almost anything.
              You may take the form of anything you can
              see by shift-clicking it.

                 This process will alert any nearby observers.
               While morphed,
              you move faster, but are unable to
              attack creatures or eat anything.
              In addition,

                 anyone within three tiles will note an uncanny
                wrongness if examining you.
               You can attack any item or dead
              creature to consume it -

                 creatures will restore your health.
               Finally, you can restore yourself
              to your original form while morphed
              by shift-clicking yourself.

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


code:
import { BlockQuote, LabeledList, Section, Stack } from '../components';
import { Window } from '../layouts';

const tipstyle = {
  color: 'white',
};

const noticestyle = {
  color: 'lightblue',
};

export const AntagInfoNightmare = (props, context) => {
  return (
    <Window
      width={620}
      height={340}>
      <Window.Content backgroundColor="#0d0d0d">
        <Stack fill>
          <Stack.Item width="46.2%">

              <Stack vertical fill>
                <Stack.Item fontSize="25px">
                  You are a Nightmare.
                </Stack.Item>
                <Stack.Item>

                    You are a creature from beyond the stars that
                    has incredibly strong powers in the darkness,
                    becoming nigh unbeatable. Unfortunately, you
                    wither and burn away in the light. You must use your
                     light eater to
                    dim the station, making hunting easier.

                </Stack.Item>
                <Stack.Divider />
                <Stack.Item textColor="label">

                    Tip #1:

                  Move often. The station will be hunting you
                  after you are discovered, so don't
                  stay in one area for long.

                    Tip #2:

                  Pick unfair fights. You are incredibly strong
                  in one versus one situations, use it. The more
                  you fight, the harder it will be to keep it dark.

                    Tip #3:

                  Fully destroy APCs when possible. Instead of
                  hunting lights that can be fixed, hunt the APCs
                  which are harder to repair.
                </Stack.Item>
              </Stack>

          </Stack.Item>
          <Stack.Item width="53%">

              <LabeledList>
                <LabeledList.Item label="Shadow Dance">
                  Whilst in the shadows, you are immune to all
                  ranged attacks, whilst also rapidly regenerating
                  health.
                </LabeledList.Item>
                <LabeledList.Item label="Shadow Walk">
                  You are allowed unlimited, unrestricted
                  movement in the dark. Light will pull you out of this.
                </LabeledList.Item>
                <LabeledList.Item label="Heart of Darkness">
                  Your heart invites the shadows. If you
                  die in the darkness, you will eventually
                  revive if left alone.
                </LabeledList.Item>
                <LabeledList.Item label="Light Eater">
                  Your twisted appendage. It will consume
                  the light of what it touches,
                  be it victim or object.
                </LabeledList.Item>
              </LabeledList>

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


code:
import { useBackend } from '../backend';
import { multiline } from 'common/string';
import { BlockQuote, Button, Dimmer, Section, Stack } from '../components';
import { BooleanLike } from 'common/react';
import { Window } from '../layouts';

const allystyle = {
  fontWeight: 'bold',
  color: 'yellow',
};

const badstyle = {
  color: 'red',
  fontWeight: 'bold',
};

const goalstyle = {
  color: 'lightblue',
  fontWeight: 'bold',
};

type Objective = {
  count: number;
  name: string;
  explanation: string;
}

type Info = {
  has_codewords: BooleanLike;
  phrases: string;
  responses: string;
  theme: string;
  allies: string;
  goal: string;
  intro: string;
  code: string;
  failsafe_code: string;
  has_uplink: BooleanLike;
  uplink_intro: string;
  uplink_unlock_info: string;
  objectives: Objective[];
};

const ObjectivePrintout = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    objectives,
  } = data;
  return (
    <Stack vertical>
      <Stack.Item bold>
        Your current objectives:
      </Stack.Item>
      <Stack.Item>
        {!objectives && "None!"
        || objectives.map(objective => (
          <Stack.Item key={objective.count}>
            #{objective.count}: {objective.explanation}
          </Stack.Item>
        )) }
      </Stack.Item>
    </Stack>
  );
};

const IntroductionSection = (props, context) => {
  const { act, data } = useBackend<Info>(context);
  const {
    intro,
  } = data;
  return (

      <Stack vertical fill>
        <Stack.Item fontSize="25px">
          {intro}
        </Stack.Item>
        <Stack.Item grow>
          <ObjectivePrintout />
        </Stack.Item>
      </Stack>

  );
};

const EmployerSection = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    allies,
    goal,
  } = data;
  return (
    <Section
      fill
      title="Employer"
      scrollable
      buttons={
        <Button
          icon="hammer"
          tooltip={multiline`
            This is a gameplay suggestion for bored traitors.
            You don't have to follow it, unless you want some
            ideas for how to spend the round.`}
          tooltipPosition="bottom-start">
          Policy
        </Button>
      }>
      <Stack vertical fill>
        <Stack.Item grow>
          <Stack vertical>
            <Stack.Item>

                Your allegiances:

                {allies}

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

                Employer thoughts:

                {goal}

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

  );
};

const UplinkSection = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    has_uplink,
    uplink_intro,
    uplink_unlock_info,
    code,
    failsafe_code,
  } = data;
  return (

      <Stack fill>
        {!has_uplink && (
          <Dimmer>
            <Stack.Item fontSize="18px">
              You were not supplied with an uplink.
            </Stack.Item>
          </Dimmer>
        ) || (
          <>
            <Stack.Item bold>
              {uplink_intro}

              Code: {code}

              Failsafe: {failsafe_code}
            </Stack.Item>
            <Stack.Divider />
            <Stack.Item mt="1%">

                {uplink_unlock_info}

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

  );
};

const CodewordsSection = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    has_codewords,
    phrases,
    responses,
  } = data;
  return (

      <Stack fill>
        {!has_codewords && (

            You have not been supplied the Syndicate codewords.
            You will have to use alternative methods to find potential allies.
            Proceed with caution, however, as everyone is a potential foe.

        ) || (
          <>
            <Stack.Item grow basis={0}>

                The Syndicate have provided you with the following
                codewords to identify fellow agents. Use the codewords
                during regular conversation to identify other agents.
                Proceed with caution, however, as everyone is a
                potential foe.

                   You have memorized the codewords, allowing you
                  to recognise them when heard.

            </Stack.Item>
            <Stack.Divider mr={1} />
            <Stack.Item grow basis={0}>
              <Stack vertical>
                <Stack.Item>
                  Code Phrases:
                </Stack.Item>
                <Stack.Item bold textColor="blue">
                  {phrases}
                </Stack.Item>
                <Stack.Item>
                  Code Responses:
                </Stack.Item>
                <Stack.Item bold textColor="red">
                  {responses}
                </Stack.Item>
              </Stack>
            </Stack.Item>
          </>
        )}
      </Stack>

  );
};

export const AntagInfoTraitor = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    theme,
  } = data;
  return (
    <Window
      width={620}
      height={580}
      theme={theme}>
      <Window.Content>
        <Stack vertical fill>
          <Stack.Item grow>
            <Stack fill>
              <Stack.Item width="70%">
                <IntroductionSection />
              </Stack.Item>
              <Stack.Item width="30%">
                <EmployerSection />
              </Stack.Item>
            </Stack>
          </Stack.Item>
          <Stack.Item>
            <UplinkSection />
          </Stack.Item>
          <Stack.Item>
            <CodewordsSection />
          </Stack.Item>
        </Stack>
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend } from '../backend';
import { Section, Stack } from '../components';
import { BooleanLike } from 'common/react';
import { Window } from '../layouts';

const teleportstyle = {
  color: 'yellow',
};

const robestyle = {
  color: 'lightblue',
};

const destructionstyle = {
  color: 'red',
};

const defensestyle = {
  color: 'orange',
};

const transportstyle = {
  color: 'yellow',
};

const summonstyle = {
  color: 'cyan',
};

const ritualstyle = {
  color: 'violet',
};

type Objective = {
  count: number;
  name: string;
  explanation: string;
  complete: BooleanLike;
  was_uncompleted: BooleanLike;
  reward: number;
}

type Info = {
  objectives: Objective[];
};

export const AntagInfoWizard = (props, context) => {
  return (
    <Window
      width={620}
      height={580}
      theme="wizard">
      <Window.Content>
        <Stack vertical fill>
          <Stack.Item grow>

              <Stack vertical>
                <Stack.Item textColor="red" fontSize="20px">
                  You are the Space Wizard!
                </Stack.Item>
                <Stack.Item>
                  <ObjectivePrintout />
                </Stack.Item>
              </Stack>

          </Stack.Item>
          <Stack.Item>

              <Stack vertical fill>
                <Stack.Item>
                  You have a spellbook which is bound to you. You can use it
                  to choose a magical arsenal.

                    The deadly page has the offensive spells, to destroy your
                    enemies.

                    The defensive page has defensive spells, to keep yourself
                    alive. Remember, you may be powerful, but you are still
                    only human.

                    The transport page has mobility spells, very important
                    aspect of staying alive and getting things done.

                    The summoning page has summoning and other helpful spells
                    for not fighting alone. Careful, not every summon is on your
                    side.

                    The rituals page has powerful global effects, that will pit
                    the station against itself. Do mind that these are either
                    expensive, or just for panache.

                </Stack.Item>
                <Stack.Item textColor="lightgreen">
                  (If you are unsure what to get or are new to the Federation,
                  go to the "Wizard Approved Loadouts" section.
                  There you will find some kits that work fairly well for new
                  wizards.)
                </Stack.Item>
              </Stack>

          </Stack.Item>
          <Stack.Item>

              <Stack>
                <Stack.Item>
                  Teleport scroll: 4
                  uses to teleport wherever you want.
                  You will not be able to come back to the den, so
                  be sure you have everything ready before departing.

                  Wizard robes: Used
                  to cast most spells. Your spellbook will let
                  you know which spells cannot be cast without a garb.
                </Stack.Item>
              </Stack>

          </Stack.Item>
          <Stack.Item>

              Remember: Do not forget to prepare your spells.

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

const ObjectivePrintout = (props, context) => {
  const { data } = useBackend<Info>(context);
  const {
    objectives,
  } = data;
  return (
    <Stack vertical>
      <Stack.Item bold>
        The Space Wizards Federation has given you the following tasks:
      </Stack.Item>
      <Stack.Item>
        {!objectives && "None!"
        || objectives.map(objective => (
          <Stack.Item key={objective.count}>
            #{objective.count}: {objective.explanation}
          </Stack.Item>
        )) }
      </Stack.Item>
    </Stack>
  );
};


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

export const Apc = (props, context) => {
  return (
    <Window
      width={450}
      height={445}>
      <Window.Content scrollable>
        <ApcContent />
      </Window.Content>
    </Window>
  );
};

const powerStatusMap = {
  2: {
    color: 'good',
    externalPowerText: 'External Power',
    chargingText: 'Fully Charged',
  },
  1: {
    color: 'average',
    externalPowerText: 'Low External Power',
    chargingText: 'Charging',
  },
  0: {
    color: 'bad',
    externalPowerText: 'No External Power',
    chargingText: 'Not Charging',
  },
};

const malfMap = {
  1: {
    icon: 'terminal',
    content: 'Override Programming',
    action: 'hack',
  },
  2: {
    icon: 'caret-square-down',
    content: 'Shunt Core Process',
    action: 'occupy',
  },
  3: {
    icon: 'caret-square-left',
    content: 'Return to Main Core',
    action: 'deoccupy',
  },
  4: {
    icon: 'caret-square-down',
    content: 'Shunt Core Process',
    action: 'occupy',
  },
};

const ApcContent = (props, context) => {
  const { act, data } = useBackend(context);
  const locked = data.locked && !data.siliconUser;
  const externalPowerStatus = powerStatusMap[data.externalPower]
    || powerStatusMap[0];
  const chargingStatus = powerStatusMap[data.chargingStatus]
    || powerStatusMap[0];
  const channelArray = data.powerChannels || [];
  const malfStatus = malfMap[data.malfStatus] || malfMap[0];
  const adjustedCellChange = data.powerCellStatus / 100;
  if (data.failTime > 0) {
    return (
      <NoticeBox>

SYSTEM FAILURE

          I/O regulators malfunction detected!
          Waiting for system reboot...

        Automatic reboot in {data.failTime} seconds...
        <Button
          icon="sync"
          content="Reboot Now"
          onClick={() => act('reboot')} />
      </NoticeBox>
    );
  }
  return (
    <>
      <InterfaceLockNoticeBox />

        <LabeledList>
          <LabeledList.Item
            label="Main Breaker"
            color={externalPowerStatus.color}
            buttons={(
              <Button
                icon={data.isOperating ? 'power-off' : 'times'}
                content={data.isOperating ? 'On' : 'Off'}
                selected={data.isOperating && !locked}
                disabled={locked}
                onClick={() => act('breaker')} />
            )}>
            [ {externalPowerStatus.externalPowerText} ]
          </LabeledList.Item>
          <LabeledList.Item label="Power Cell">
            <ProgressBar
              color="good"
              value={adjustedCellChange} />
          </LabeledList.Item>
          <LabeledList.Item
            label="Charge Mode"
            color={chargingStatus.color}
            buttons={(
              <Button
                icon={data.chargeMode ? 'sync' : 'times'}
                content={data.chargeMode ? 'Auto' : 'Off'}
                disabled={locked}
                onClick={() => act('charge')} />
            )}>
            [ {chargingStatus.chargingText} ]
          </LabeledList.Item>
        </LabeledList>

        <LabeledList>
          {channelArray.map(channel => {
            const { topicParams } = channel;
            return (
              <LabeledList.Item
                key={channel.title}
                label={channel.title}
                buttons={(
                  <>
                    <Box inline mx={2}
                      color={channel.status >= 2 ? 'good' : 'bad'}>
                      {channel.status >= 2 ? 'On' : 'Off'}
                    </Box>
                    <Button
                      icon="sync"
                      content="Auto"
                      selected={!locked && (
                        channel.status === 1 || channel.status === 3
                      )}
                      disabled={locked}
                      onClick={() => act('channel', topicParams.auto)} />
                    <Button
                      icon="power-off"
                      content="On"
                      selected={!locked && channel.status === 2}
                      disabled={locked}
                      onClick={() => act('channel', topicParams.on)} />
                    <Button
                      icon="times"
                      content="Off"
                      selected={!locked && channel.status === 0}
                      disabled={locked}
                      onClick={() => act('channel', topicParams.off)} />
                  </>
                )}>
                {channel.powerLoad}
              </LabeledList.Item>
            );
          })}
          <LabeledList.Item label="Total Load">
            {data.totalLoad}
          </LabeledList.Item>
        </LabeledList>

      <Section
        title="Misc"
        buttons={!!data.siliconUser && (
          <>
            {!!data.malfStatus && (
              <Button
                icon={malfStatus.icon}
                content={malfStatus.content}
                color="bad"
                onClick={() => act(malfStatus.action)} />
            )}
            <Button
              icon="lightbulb-o"
              content="Overload"
              onClick={() => act('overload')} />
          </>
        )}>
        <LabeledList>
          <LabeledList.Item
            label="Cover Lock"
            buttons={(
              <Button
                icon={data.coverLocked ? 'lock' : 'unlock'}
                content={data.coverLocked ? 'Engaged' : 'Disengaged'}
                disabled={locked}
                onClick={() => act('cover')} />
            )} />
          <LabeledList.Item
            label="Emergency Lighting"
            buttons={(
              <Button
                icon="lightbulb-o"
                content={data.emergencyLights ? 'Enabled' : 'Disabled'}
                disabled={locked}
                onClick={() => act('emergency_lighting')} />
            )} />
          <LabeledList.Item
            label="Night Shift Lighting"
            buttons={(
              <Button
                icon="lightbulb-o"
                content={data.nightshiftLights ? 'Enabled' : 'Disabled'}
                onClick={() => act('toggle_nightshift')} />
            )} />
        </LabeledList>

    </>
  );
};


code:
import { map, sortBy } from 'common/collections';
import { flow } from 'common/fp';
import { pureComponentHooks } from 'common/react';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Dimmer, Flex, Icon, Table, Tabs } from '../components';
import { Window } from '../layouts';
import { AreaCharge, powerRank } from './PowerMonitor';

export const ApcControl = (props, context) => {
  const { data } = useBackend(context);
  return (
    <Window
      title="APC Controller"
      width={550}
      height={500}>
      {data.authenticated === 1 && (
        <ApcLoggedIn />
      )}
      {data.authenticated === 0 && (
        <ApcLoggedOut />
      )}
    </Window>
  );
};

const ApcLoggedOut = (props, context) => {
  const { act, data } = useBackend(context);
  const { emagged } = data;
  const text = emagged === 1 ? 'Open' : 'Log In';
  return (
    <Window.Content>
      <Button
        fluid
        color={emagged === 1 ? '' : 'good'}
        content={text}
        onClick={() => act('log-in')} />
    </Window.Content>
  );
};

const ApcLoggedIn = (props, context) => {
  const { act, data } = useBackend(context);
  const { restoring } = data;
  const [
    tabIndex,
    setTabIndex,
  ] = useLocalState(context, 'tab-index', 1);
  return (
    <>
      <Tabs>
        <Tabs.Tab
          selected={tabIndex === 1}
          onClick={() => {
            setTabIndex(1);
            act('check-apcs');
          }}>
          APC Control Panel
        </Tabs.Tab>
        <Tabs.Tab
          selected={tabIndex === 2}
          onClick={() => {
            setTabIndex(2);
            act('check-logs');
          }}>
          Log View Panel
        </Tabs.Tab>
      </Tabs>
      {restoring === 1 && (
        <Dimmer fontSize="32px">
          <Icon name="cog" spin />
          {' Resetting...'}
        </Dimmer>
      )}
      {tabIndex === 1 && (
        <>
          <ControlPanel />
          <Box fillPositionedParent top="53px">
            <Window.Content scrollable>
              <ApcControlScene />
            </Window.Content>
          </Box>
        </>
      )}
      {tabIndex === 2 && (
        <Box fillPositionedParent top="20px">
          <Window.Content scrollable>
            <LogPanel />
          </Window.Content>
        </Box>
      )}
    </>
  );
};

const ControlPanel = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    emagged,
    logging,
  } = data;
  const [
    sortByField,
    setSortByField,
  ] = useLocalState(context, 'sortByField', null);
  return (
    <Flex>
      <Flex.Item>
        <Box inline mr={2} color="label">
          Sort by:
        </Box>
        <Button.Checkbox
          checked={sortByField === 'name'}
          content="Name"
          onClick={() => setSortByField(sortByField !== 'name' && 'name')} />
        <Button.Checkbox
          checked={sortByField === 'charge'}
          content="Charge"
          onClick={() => setSortByField(
            sortByField !== 'charge' && 'charge'
          )} />
        <Button.Checkbox
          checked={sortByField === 'draw'}
          content="Draw"
          onClick={() => setSortByField(sortByField !== 'draw' && 'draw')} />
      </Flex.Item>
      <Flex.Item grow={1} />
      <Flex.Item>
        {emagged === 1 && (
          <>
            <Button
              color={logging === 1 ? 'bad' : 'good'}
              content={logging === 1 ? 'Stop Logging' : 'Restore Logging'}
              onClick={() => act('toggle-logs')}
            />
            <Button
              content="Reset Console"
              onClick={() => act('restore-console')}
            />
          </>
        )}
        <Button
          color="bad"
          content="Log Out"
          onClick={() => act('log-out')}
        />
      </Flex.Item>
    </Flex>
  );
};

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

  const [
    sortByField,
  ] = useLocalState(context, 'sortByField', null);

  const apcs = flow([
    map((apc, i) => ({
      ...apc,
      // Generate a unique id
      id: apc.name + i,
    })),
    sortByField === 'name' && sortBy(apc => apc.name),
    sortByField === 'charge' && sortBy(apc => -apc.charge),
    sortByField === 'draw' && sortBy(
      apc => -powerRank(apc.load),
      apc => -parseFloat(apc.load)),
  ])(data.apcs);
  return (

          On/Off

          Area

          Charge

          Draw

          Eqp

          Lgt

          Env

      {apcs.map((apc, i) => (

            <Button
              icon={apc.operating ? 'power-off' : 'times'}
              color={apc.operating ? 'good' : 'bad'}
              onClick={() => act('breaker', {
                ref: apc.ref,
              })}
            />

            <Button
              onClick={() => act('access-apc', {
                ref: apc.ref,
              })}>
              {apc.name}
            </Button>

            <AreaCharge
              charging={apc.charging}
              charge={apc.charge}
            />

            {apc.load}

            <AreaStatusColorButton
              target="equipment"
              status={apc.eqp}
              apc={apc}
              act={act}
            />

            <AreaStatusColorButton
              target="lighting"
              status={apc.lgt}
              apc={apc}
              act={act}
            />

            <AreaStatusColorButton
              target="environ"
              status={apc.env}
              apc={apc}
              act={act}
            />

      ))}

  );
};

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

  const logs = flow([
    map((line, i) => ({
      ...line,
      // Generate a unique id
      id: line.entry + i,
    })),
    logs => logs.reverse(),
  ])(data.logs);
  return (
    <Box m={-0.5}>
      {logs.map(line => (
        <Box
          p={0.5}
          key={line.id}
          className="candystripe"
          bold>
          {line.entry}
        </Box>
      ))}
    </Box>
  );
};

const AreaStatusColorButton = props => {
  const { target, status, apc, act } = props;
  const power = Boolean(status & 2);
  const mode = Boolean(status & 1);
  return (
    <Button
      icon={mode ? 'sync' : 'power-off'}
      color={power ? 'good' : 'bad'}
      onClick={() => act('toggle-minor', {
        type: target,
        value: statusChange(status),
        ref: apc.ref,
      })}
    />
  );
};

const statusChange = status => {
  // mode flip power flip both flip
  // 0, 2, 3
  return status === 0 ? 2 : status === 2 ? 3 : 0;
};

AreaStatusColorButton.defaultHooks = pureComponentHooks;


code:
import { multiline } from 'common/string';
import { resolveAsset } from '../assets';
import { useBackend } from '../backend';
import { BlockQuote, Box, Button, Icon, Section, Stack } from '../components';
import { Window } from '../layouts';

export const ApprenticeContract = (props, context) => {
  return (
    <Window
      width={620}
      height={600}
      theme="wizard">
      <Window.Content>
        <Stack vertical fill>
          <Stack.Item>

              If you cannot reach any of your apprentices today,
              you can feed the contract back into your spellbook to refund it.

          </Stack.Item>
          <Stack.Item grow>
            <ApprenticeSelection
              iconName="fire"
              fluffName="Apprentice of Destruction"
              schoolTitle="destruction"
              assetName="destruction.png"
              blurb={multiline`
                Your apprentice is skilled in offensive magic.
                They know Magic Missile and Fireball.
              `} />
            <ApprenticeSelection
              iconName="route"
              fluffName="Student of Translocation"
              schoolTitle="bluespace"
              assetName="bluespace.png"
              blurb={multiline`
              Your apprentice is able to defy physics, melting through
              solid objects and travelling great distances in the
              blink of an eye. They know Teleport and Ethereal Jaunt.
              `} />
            <ApprenticeSelection
              iconName="medkit"
              fluffName="Neophyte of Restoration"
              schoolTitle="healing"
              assetName="healing.png"
              blurb={multiline`
              Your apprentice is training to cast spells that will
              aid your survival. They know Forcewall and Charge and
              come with a Staff of Healing.
              `} />
            <ApprenticeSelection
              iconName="user-secret"
              fluffName="Robeless Pupil"
              schoolTitle="robeless"
              assetName="robeless.png"
              blurb={multiline`
              Your apprentice is training to cast spells without
              their robes. They know Knock and Mindswap.
              `} />
          </Stack.Item>
        </Stack>
      </Window.Content>
    </Window>
  );
};

const ApprenticeSelection = (props, context) => {
  const { act } = useBackend(context);
  const {
    iconName,
    fluffName,
    schoolTitle,
    assetName,
    blurb,
  } = props;
  return (

      <Stack align="middle" fill>
        <Stack.Item>
          <Stack vertical>
            <Stack.Item>

            </Stack.Item>
            <Stack.Item>
              <Button
                textAlign="center"
                fluid
                onClick={() => act('buy', {
                  school: schoolTitle,
                })}>
                Select
              </Button>
            </Stack.Item>
          </Stack>
        </Stack.Item>
        <Stack.Item grow>
          <Box fontSize="20px" height="30%">
            <Icon name={iconName} /> {fluffName}
          </Box>

            {blurb}

        </Stack.Item>
      </Stack>

  );
};


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

export const Aquarium = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    temperature,
    fluid_type,
    minTemperature,
    maxTemperature,
    fluidTypes,
    contents,
    allow_breeding,
  } = data;
  return (
    <Window
      width={500}
      height={400}>
      <Window.Content>

          <LabeledControls>
            <LabeledControls.Item label="Temperature">
              <Knob
                size={1.25}
                mb={1}
                value={temperature}
                unit="K"
                minValue={minTemperature}
                maxValue={maxTemperature}
                step={1}
                stepPixelSize={1}
                onDrag={(e, value) => act('temperature', {
                  temperature: value,
                })} />
            </LabeledControls.Item>
            <LabeledControls.Item label="Fluid">
              <Flex direction="column" mb={1}>
                {fluidTypes.map(f => (
                  <Flex.Item key={f}>
                    <Button
                      fluid
                      content={f}
                      selected={fluid_type === f}
                      onClick={() => act('fluid', { fluid: f })} />
                  </Flex.Item>
                ))}
              </Flex>
            </LabeledControls.Item>
            <LabeledControls.Item label="Reproduction Prevention System">
              <Button
                content={allow_breeding ? "Offline" : "Online"}
                selected={!allow_breeding}
                onClick={() => act('allow_breeding')} />
            </LabeledControls.Item>
          </LabeledControls>

          {contents.map(movable => (
            <Button
              key={movable.ref}
              content={movable.name}
              onClick={() => act('remove', { ref: movable.ref })} />
          ))}

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


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

export const AtmosAlertConsole = (props, context) => {
  const { act, data } = useBackend(context);
  const priorityAlerts = data.priority || [];
  const minorAlerts = data.minor || [];
  return (
    <Window
      width={350}
      height={300}>
      <Window.Content scrollable>

            {priorityAlerts.length === 0 && (

                No Priority Alerts

            )}
            {priorityAlerts.map(alert => (

                <Button
                  icon="times"
                  content={alert}
                  color="bad"
                  onClick={() => act('clear', { zone: alert })} />

            ))}
            {minorAlerts.length === 0 && (

                No Minor Alerts

            )}
            {minorAlerts.map(alert => (

                <Button
                  icon="times"
                  content={alert}
                  color="average"
                  onClick={() => act('clear', { zone: alert })} />

            ))}

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


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

export const AtmosControlConsole = (props, context) => {
  const { act, data } = useBackend(context);
  const sensors = data.sensors || [];
  return (
    <Window
      width={500}
      height={315}>
      <Window.Content scrollable>
        <Section
          title={!!data.tank && sensors[0]?.long_name}>
          {sensors.map(sensor => {
            const gases = sensor.gases || {};
            return (
              <Section
                key={sensor.id_tag}
                title={!data.tank && sensor.long_name}
                level={2}>
                <LabeledList>
                  <LabeledList.Item label="Pressure">
                    {toFixed(sensor.pressure, 2) + ' kPa'}
                  </LabeledList.Item>
                  {!!sensor.temperature && (
                    <LabeledList.Item label="Temperature">
                      {toFixed(sensor.temperature, 2) + ' K'}
                    </LabeledList.Item>
                  )}
                  {map((gasPercent, gasId) => (
                    <LabeledList.Item label={gasId}>
                      {toFixed(gasPercent, 2) + '%'}
                    </LabeledList.Item>
                  ))(gases)}
                </LabeledList>
              </Section>
            );
          })}
        </Section>
        {data.tank && (
          <Section
            title="Controls"
            buttons={(
              <Button
                icon="undo"
                content="Reconnect"
                onClick={() => act('reconnect')} />
            )}>
            <LabeledList>
              <LabeledList.Item label="Input Injector">
                <Button
                  icon={data.inputting ? 'power-off' : 'times'}
                  content={data.inputting ? 'Injecting' : 'Off'}
                  selected={data.inputting}
                  onClick={() => act('input')} />
              </LabeledList.Item>
              <LabeledList.Item label="Input Rate">
                <NumberInput
                  value={data.inputRate}
                  unit="L/s"
                  width="63px"
                  minValue={0}
                  maxValue={data.maxInputRate}
                  // This takes an exceptionally long time to update
                  // due to being an async signal
                  suppressFlicker={2000}
                  onChange={(e, value) => act('rate', {
                    rate: value,
                  })} />
              </LabeledList.Item>
              <LabeledList.Item label="Output Regulator">
                <Button
                  icon={data.outputting ? 'power-off' : 'times'}
                  content={data.outputting ? 'Open' : 'Closed'}
                  selected={data.outputting}
                  onClick={() => act('output')} />
              </LabeledList.Item>
              <LabeledList.Item label="Output Pressure">
                <NumberInput
                  value={parseFloat(data.outputPressure)}
                  unit="kPa"
                  width="75px"
                  minValue={0}
                  maxValue={data.maxOutputPressure}
                  step={10}
                  // This takes an exceptionally long time to update
                  // due to being an async signal
                  suppressFlicker={2000}
                  onChange={(e, value) => act('pressure', {
                    pressure: value,
                  })} />
              </LabeledList.Item>
            </LabeledList>
          </Section>
        )}
      </Window.Content>
    </Window>
  );
};


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

export const AtmosControlPanel = (props, context) => {
  const { act, data } = useBackend(context);
  const groups = flow([
    map((group, i) => ({
      ...group,
      // Generate a unique id
      id: group.area + i,
    })),
    sortBy(group => group.id),
  ])(data.excited_groups);
  return (
    <Window
      title="SSAir Control Panel"
      width={900}
      height={500}>

        <Flex
          justify="space-between"
          align="baseline">
          <Flex.Item>
            <Button
              onClick={() => act('toggle-freeze')}
              color={data.frozen === 1 ? 'good' : 'bad'}>
              {data.frozen === 1
                ? 'Freeze Subsystem'
                : 'Unfreeze Subsystem'}
            </Button>
          </Flex.Item>
          <Flex.Item>
            Fire Cnt: {data.fire_count}
          </Flex.Item>
          <Flex.Item>
            Active Turfs: {data.active_size}
          </Flex.Item>
          <Flex.Item>
            Excited Groups: {data.excited_size}
          </Flex.Item>
          <Flex.Item>
            Hotspots: {data.hotspots_size}
          </Flex.Item>
          <Flex.Item>
            Superconductors: {data.conducting_size}
          </Flex.Item>
          <Flex.Item>
            <Button.Checkbox
              checked={data.showing_user}
              onClick={() => act('toggle_user_display')}>
              Personal View
            </Button.Checkbox>
          </Flex.Item>
          <Flex.Item>
            <Button.Checkbox
              checked={data.show_all}
              onClick={() => act('toggle_show_all')}>
              Display all
            </Button.Checkbox>
          </Flex.Item>
        </Flex>

      <Box fillPositionedParent top="45px">
        <Window.Content scrollable>

                  Area Name

                  Breakdown

                  Dismantle

                  Turfs

                  {data.display_max === 1 && "Max Share"}

                  Display

              {groups.map(group => (

                    <Button
                      content={group.area}
                      onClick={() => act('move-to-target', {
                        spot: group.jump_to,
                      })} />

                    {group.breakdown}

                    {group.dismantle}

                    {group.size}

                    {data.display_max === 1 && group.max_share}

                    <Button.Checkbox
                      checked={group.should_show}
                      onClick={() => act('toggle_show_group', {
                        group: group.group,
                      })} />

              ))}

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


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

export const AtmosFilter = (props, context) => {
  const { act, data } = useBackend(context);
  const filter_types = data.filter_types || [];
  return (
    <Window
      width={420}
      height={221}>
      <Window.Content>
        <Section>
          <LabeledList>
            <LabeledList.Item label="Power">
              <Button
                icon={data.on ? 'power-off' : 'times'}
                content={data.on ? 'On' : 'Off'}
                selected={data.on}
                onClick={() => act('power')} />
            </LabeledList.Item>
            <LabeledList.Item label="Transfer Rate">
              <NumberInput
                animated
                value={parseFloat(data.rate)}
                width="63px"
                unit="L/s"
                minValue={0}
                maxValue={data.max_rate}
                onDrag={(e, value) => act('rate', {
                  rate: value,
                })} />
              <Button
                ml={1}
                icon="plus"
                content="Max"
                disabled={data.rate === data.max_rate}
                onClick={() => act('rate', {
                  rate: 'max',
                })} />
            </LabeledList.Item>
            <LabeledList.Item label="Filters">
              {filter_types.map(filter => (
                <Button
                  key={filter.id}
                  icon={filter.enabled ? 'check-square-o' : 'square-o'}
                  content={getGasLabel(filter.gas_id, filter.gas_name)}
                  selected={filter.enabled}
                  onClick={() => act('toggle_filter', {
                    val: filter.gas_id,
                  })} />
              ))}
            </LabeledList.Item>
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};


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

export const AtmosMixer = (props, context) => {
  const { act, data } = useBackend(context);
  return (
    <Window
      width={370}
      height={165}>
      <Window.Content>
        <Section>
          <LabeledList>
            <LabeledList.Item label="Power">
              <Button
                icon={data.on ? 'power-off' : 'times'}
                content={data.on ? 'On' : 'Off'}
                selected={data.on}
                onClick={() => act('power')} />
            </LabeledList.Item>
            <LabeledList.Item label="Output Pressure">
              <NumberInput
                animated
                value={parseFloat(data.set_pressure)}
                unit="kPa"
                width="75px"
                minValue={0}
                maxValue={data.max_pressure}
                step={10}
                onChange={(e, value) => act('pressure', {
                  pressure: value,
                })} />
              <Button
                ml={1}
                icon="plus"
                content="Max"
                disabled={data.set_pressure === data.max_pressure}
                onClick={() => act('pressure', {
                  pressure: 'max',
                })} />
            </LabeledList.Item>
            <LabeledList.Item label="Node 1">
              <NumberInput
                animated
                value={data.node1_concentration}
                unit="%"
                width="60px"
                minValue={0}
                maxValue={100}
                stepPixelSize={2}
                onDrag={(e, value) => act('node1', {
                  concentration: value,
                })} />
            </LabeledList.Item>
            <LabeledList.Item label="Node 2">
              <NumberInput
                animated
                value={data.node2_concentration}
                unit="%"
                width="60px"
                minValue={0}
                maxValue={100}
                stepPixelSize={2}
                onDrag={(e, value) => act('node2', {
                  concentration: value,
                })} />
            </LabeledList.Item>
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};


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

export const AtmosPump = (props, context) => {
  const { act, data } = useBackend(context);
  return (
    <Window
      width={335}
      height={115}>
      <Window.Content>
        <Section>
          <LabeledList>
            <LabeledList.Item label="Power">
              <Button
                icon={data.on ? 'power-off' : 'times'}
                content={data.on ? 'On' : 'Off'}
                selected={data.on}
                onClick={() => act('power')} />
            </LabeledList.Item>
            {data.max_rate ? (
              <LabeledList.Item label="Transfer Rate">
                <NumberInput
                  animated
                  value={parseFloat(data.rate)}
                  width="63px"
                  unit="L/s"
                  minValue={0}
                  maxValue={data.max_rate}
                  onChange={(e, value) => act('rate', {
                    rate: value,
                  })} />
                <Button
                  ml={1}
                  icon="plus"
                  content="Max"
                  disabled={data.rate === data.max_rate}
                  onClick={() => act('rate', {
                    rate: 'max',
                  })} />
              </LabeledList.Item>
            ) : (
              <LabeledList.Item label="Output Pressure">
                <NumberInput
                  animated
                  value={parseFloat(data.pressure)}
                  unit="kPa"
                  width="75px"
                  minValue={0}
                  maxValue={data.max_pressure}
                  step={10}
                  onChange={(e, value) => act('pressure', {
                    pressure: value,
                  })} />
                <Button
                  ml={1}
                  icon="plus"
                  content="Max"
                  disabled={data.pressure === data.max_pressure}
                  onClick={() => act('pressure', {
                    pressure: 'max',
                  })} />
              </LabeledList.Item>
            )}
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};


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

export const AtmosTempGate = (props, context) => {
  const { act, data } = useBackend(context);
  return (
    <Window
      width={335}
      height={115}>
      <Window.Content>
        <Section>
          <LabeledList>
            <LabeledList.Item label="Power">
              <Button
                icon={data.on ? 'power-off' : 'times'}
                content={data.on ? 'On' : 'Off'}
                selected={data.on}
                onClick={() => act('power')} />
            </LabeledList.Item>
            <LabeledList.Item label="Heat settings">
              <NumberInput
                animated
                value={parseFloat(data.temperature)}
                unit="K"
                width="75px"
                minValue={data.min_temperature}
                maxValue={data.max_temperature}
                step={1}
                onChange={(e, value) => act('temperature', {
                  temperature: value,
                })} />
              <Button
                ml={1}
                icon="plus"
                content="Max"
                disabled={data.temperature === data.max_temperature}
                onClick={() => act('temperature', {
                  temperature: 'max',
                })} />
            </LabeledList.Item>
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};


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

export const AtmosTempPump = (props, context) => {
  const { act, data } = useBackend(context);
  return (
    <Window
      width={335}
      height={115}>
      <Window.Content>
        <Section>
          <LabeledList>
            <LabeledList.Item label="Power">
              <Button
                icon={data.on ? 'power-off' : 'times'}
                content={data.on ? 'On' : 'Off'}
                selected={data.on}
                onClick={() => act('power')} />
            </LabeledList.Item>
            <LabeledList.Item label="Heat transfer rate">
              <NumberInput
                animated
                value={parseFloat(data.rate)}
                unit="%"
                width="75px"
                minValue={0}
                maxValue={data.max_heat_transfer_rate}
                step={1}
                onChange={(e, value) => act('rate', {
                  rate: value,
                })} />
              <Button
                ml={1}
                icon="plus"
                content="Max"
                disabled={data.rate === data.max_heat_transfer_rate}
                onClick={() => act('rate', {
                  rate: 'max',
                })} />
            </LabeledList.Item>
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend, useLocalState } from '../backend';
import { Button, LabeledList, Section, ProgressBar, Flex, Box, Table, Collapsible, Input, Dimmer, Icon } from '../components';
import { Window } from '../layouts';
import { capitalize } from "common/string";

export const Autolathe = (props, context) => {
  const { act, data } = useBackend(context);
  // Extract `health` and `color` variables from the `data` object.
  const {
    materialtotal,
    materialsmax,
    materials = [],
    categories = [],
    designs = [],
    active,
  } = data;
  const [
    current_category,
    setCategory,
  ] = useLocalState(context, 'current_category', "None");
  const filteredmaterials = materials.filter(material =>
    material.mineral_amount > 0);
  return (
    <Window
      title="Autolathe"
      width={600}
      height={600}>
      <Window.Content scrollable>

          <LabeledList>
            <LabeledList.Item
              label="Total Materials">
              <ProgressBar
                value={materialtotal}
                minValue={0}
                maxValue={materialsmax}
                ranges={{
                  "good": [materialsmax * 0.85, materialsmax],
                  "average": [materialsmax * 0.25, materialsmax * 0.85],
                  "bad": [0, materialsmax * 0.25],
                }}>
                {materialtotal + '/' + materialsmax + ' cm³'}
              </ProgressBar>
            </LabeledList.Item>
            <LabeledList.Item>
              {filteredmaterials.length > 0 && (
                <Collapsible title="Materials">
                  <LabeledList>
                    {filteredmaterials.map(filteredmaterial => (
                      <LabeledList.Item
                        key={filteredmaterial.id}
                        label={capitalize(filteredmaterial.name)}>
                        <ProgressBar
                          style={{
                            transform: 'scaleX(-1) scaleY(1)',
                          }}
                          value={materialsmax - filteredmaterial.mineral_amount}
                          maxValue={materialsmax}
                          color="black"
                          backgroundColor={filteredmaterial.matcolour}>

{filteredmaterial.mineral_amount + ' cm³'}

                        </ProgressBar>
                      </LabeledList.Item>
                    ))}
                  </LabeledList>
                </Collapsible>)}
            </LabeledList.Item>
          </LabeledList>

          <Input fluid
            placeholder="Search Recipes..."
            selfClear
            onChange={(e, value) => {
              if (value.length) {
                act('search', {
                  to_search: value,
                });
                setCategory('results for "' + value + '"');
              }
            }} />

          <Box>
            {categories.map(category => (
              // eslint-disable-next-line react/jsx-key
              <Button
                selected={current_category === category}
                content={category}
                onClick={() => {
                  act('category', {
                    selectedCategory: category,
                  });
                  setCategory(category);
                }} />
            ))}
          </Box>

        {current_category.toString() !== "None" && (
          <Section
            title={'Displaying ' + current_category.toString()}
            buttons={(
              <Button
                icon="times"
                content="Close Category"
                onClick={() => {
                  act('menu');
                  setCategory("None");
                }} />
            )}>
            {active === 1 && (
              <Dimmer fontSize="32px">
                <Icon name="cog" spin />
                {'Building items...'}
              </Dimmer>
            )}
            <Flex direction="row" wrap="nowrap">

                {designs.length
                  && (designs.map(design => (

                      <Flex.Item>
                        <Button
                          content={design.name}
                          disabled={design.buildable}
                          onClick={() => act('make', {
                            id: design.id,
                            multiplier: '1',
                          })} />
                      </Flex.Item>
                      {design.sheet ? (

                          <Flex.Item grow={1}>
                            <Button
                              icon="hammer"
                              content="10"
                              disabled={!design.mult10}
                              onClick={() => act('make', {
                                id: design.id,
                                multiplier: '10',
                              })} />
                            <Button
                              icon="hammer"
                              content="25"
                              disabled={!design.mult25}
                              onClick={() => act('make', {
                                id: design.id,
                                multiplier: '25',
                              })} />
                          </Flex.Item>

                      ) : (

                          <Flex.Item grow={3}>
                            <Button
                              icon="hammer"
                              content="5"
                              disabled={!design.mult5}
                              onClick={() => act('make', {
                                id: design.id,
                                multiplier: '5',
                              })} />
                            <Button
                              icon="hammer"
                              content="10"
                              disabled={!design.mult10}
                              onClick={() => act('make', {
                                id: design.id,
                                multiplier: '10',
                              })} />
                          </Flex.Item>

                      )}

                        <Button.Input
                          content={"[Max:" + design.maxmult + ']'}
                          maxValue={design.maxmult}
                          disabled={design.buildable}
                          backgroundColor={design.buildable ? '#999999' : 'default'}
                          onCommit={(e, value) => act('make', {
                            id: design.id,
                            multiplier: value,
                          })} />

                      {design.cost}

                  ))) || (

                      {"No designs found."}

                )}

            </Flex>

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


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

const TOOLTIP_TEXT = multiline`
  %PERSON will be replaced with their name.
  %RANK with their job.
`;

export const AutomatedAnnouncement = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    arrivalToggle,
    arrival,
    newheadToggle,
    newhead,
  } = data;
  return (
    <Window
      title="Automated Announcement System"
      width={500}
      height={225}>
      <Window.Content>
        <Section
          title="Arrival Announcement"
          buttons={(
            <Button
              icon={arrivalToggle ? 'power-off' : 'times'}
              selected={arrivalToggle}
              content={arrivalToggle ? 'On' : 'Off'}
              onClick={() => act('ArrivalToggle')} />
          )}>
          <LabeledList>
            <LabeledList.Item
              label="Message"
              buttons={(
                <Button
                  icon="info"
                  tooltip={TOOLTIP_TEXT}
                  tooltipPosition="left" />
              )}>
              <Input
                fluid
                value={arrival}
                onChange={(e, value) => act('ArrivalText', {
                  newText: value,
                })} />
            </LabeledList.Item>
          </LabeledList>
        </Section>
        <Section
          title="Departmental Head Announcement"
          buttons={(
            <Button
              icon={newheadToggle ? 'power-off' : 'times'}
              selected={newheadToggle}
              content={newheadToggle ? 'On' : 'Off'}
              onClick={() => act('NewheadToggle')} />
          )}>
          <LabeledList>
            <LabeledList.Item
              label="Message"
              buttons={(
                <Button
                  icon="info"
                  tooltip={TOOLTIP_TEXT}
                  tooltipPosition="left" />
              )}>
              <Input
                fluid
                value={newhead}
                onChange={(e, value) => act('NewheadText', {
                  newText: value,
                })} />
            </LabeledList.Item>
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};