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

export const AuxBaseConsole = (props, context) => {
  const { data } = useBackend(context);
  const [tab, setTab] = useSharedState(context, 'tab', 1);
  const {
    type,
    blind_drop,
    turrets = [],
  } = data;
  return (
    <Window
      width={turrets.length ? 620 : 350}
      height={turrets.length ? 310 : 260}>
      <Window.Content scrollable={!!turrets.length}>
        <Tabs>
          <Tabs.Tab
            icon="list"
            lineHeight="23px"
            selected={tab === 1}
            onClick={() => setTab(1)}>
            {type === "shuttle" ? "Shuttle Launch" : "Base Launch"}
          </Tabs.Tab>
          <Tabs.Tab
            icon="list"
            lineHeight="23px"
            selected={tab === 2}
            onClick={() => setTab(2)}>
            Turrets ({turrets.length})
          </Tabs.Tab>
        </Tabs>
        {tab === 1 && (
          <ShuttleConsoleContent
            type={type}
            blind_drop={blind_drop} />
        )}
        {tab === 2 && (
          <AuxBaseConsoleContent />
        )}
      </Window.Content>
    </Window>
  );
};

const STATUS_COLOR_KEYS = {
  "ERROR": "bad",
  "Disabled": "bad",
  "Firing": "average",
  "All Clear": "good",
};

export const AuxBaseConsoleContent = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    turrets = [],
  } = data;
  return (
    <Section
      title={"Turret Control"}
      buttons={(
        !!turrets.length && (
          <Button
            icon="power-off"
            content={"Toggle Power"}
            onClick={() => act('turrets_power')} />
        ))}>
      {!turrets.length ? (
        <NoticeBox>
          No connected turrets
        </NoticeBox>
      ) : (
        <Table cellpadding="3" textAlign="center">
          <Table.Row header>
            <Table.Cell>Unit</Table.Cell>
            <Table.Cell>Condition</Table.Cell>
            <Table.Cell>Status</Table.Cell>
            <Table.Cell>Direction</Table.Cell>
            <Table.Cell>Distance</Table.Cell>
            <Table.Cell>Power</Table.Cell>
          </Table.Row>
          {turrets.map(turret => (
            <Table.Row key={turret.key}>
              <Table.Cell bold>{turret.name}</Table.Cell>
              <Table.Cell>{turret.integrity}%</Table.Cell>
              <Table.Cell
                color={STATUS_COLOR_KEYS[turret.status] || "bad"}>
                {turret.status}
              </Table.Cell>
              <Table.Cell>{turret.direction}</Table.Cell>
              <Table.Cell>{turret.distance}m</Table.Cell>
              <Table.Cell>
                <Button
                  icon="power-off"
                  content="Toggle"
                  onClick={() => act('single_turret_power', {
                    single_turret_power: turret.ref,
                  })} />
              </Table.Cell>
            </Table.Row>
          ))}
        </Table>
      )}
    </Section>
  );
};


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

export const BankMachine = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    current_balance,
    siphoning,
    station_name,
  } = data;
  return (
    <Window
      width={350}
      height={155}>
      <Window.Content>
        <NoticeBox danger>
          Authorized personnel only
        </NoticeBox>
        <Section title={station_name + ' Vault'}>
          <LabeledList>
            <LabeledList.Item
              label="Current Balance"
              buttons={(
                <Button
                  icon={siphoning ? 'times' : 'sync'}
                  content={siphoning ? 'Stop Siphoning' : 'Siphon Credits'}
                  selected={siphoning}
                  onClick={() => act(siphoning ? 'halt' : 'siphon')} />
              )}>
              <AnimatedNumber
                value={current_balance}
                format={value => formatMoney(value)} />
              {' cr'}
            </LabeledList.Item>
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};

repo_name: AnalWerewolf/sundown

license: mit


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

export const Bepis = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    amount,
  } = data;
  return (
    <Window
      width={500}
      height={480}>
      <Window.Content>

          <Section
            title="Information"
            backgroundColor="#450F44"
            buttons={(
              <Button
                icon="power-off"
                content={data.manual_power ? 'Off' : 'On'}
                selected={!data.manual_power}
                onClick={() => act('toggle_power')} />
            )}>
            All you need to know about the B.E.P.I.S. and you!
            The B.E.P.I.S. performs hundreds of tests a second
            using electrical and financial resources to invent
            new products, or discover new technologies otherwise
            overlooked for being too risky or too niche to produce!

          <Section
            title="Payer's Account"
            buttons={(
              <Button
                icon="redo-alt"
                content="Reset Account"
                onClick={() => act('account_reset')} />
            )}>
            Console is currently being operated
            by {data.account_owner ? data.account_owner : 'no one'}.

          <Grid>
            <Grid.Column size={1.5}>

                <LabeledList>
                  <LabeledList.Item label="Available Credits">
                    {data.stored_cash}
                  </LabeledList.Item>
                  <LabeledList.Item label="Investment Variability">
                    {data.accuracy_percentage}%
                  </LabeledList.Item>
                  <LabeledList.Item label="Innovation Bonus">
                    {data.positive_cash_offset}
                  </LabeledList.Item>
                  <LabeledList.Item label="Risk Offset"
                    color="bad">
                    {data.negative_cash_offset}
                  </LabeledList.Item>
                  <LabeledList.Item label="Deposit Amount">
                    <NumberInput
                      value={amount}
                      unit="Credits"
                      minValue={100}
                      maxValue={30000}
                      step={100}
                      stepPixelSize={2}
                      onChange={(e, value) => act('amount', {
                        amount: value,
                      })} />
                  </LabeledList.Item>
                </LabeledList>

              <Box>
                <Button
                  icon="donate"
                  content="Deposit Credits and Start"
                  disabled={data.manual_power === 1
                    || data.silicon_check === 1}
                  onClick={() => act('begin_experiment')}
                />
              </Box>
            </Grid.Column>
            <Grid.Column>

                <Box>
                  Average technology cost: {data.mean_value}
                </Box>
                <Box>
                  Current chance of Success: Est. {data.success_estimate}%
                </Box>
                {data.error_name && (
                  <Box color="bad">
                    Previous Failure Reason: Deposited cash value too low.
                    Please insert more money for future success.
                  </Box>
                )}

            </Grid.Column>
          </Grid>

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


code:
import { classes } from 'common/react';
import { createSearch } from 'common/string';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Dimmer, Flex, Icon, Input, NoticeBox, NumberInput, Section, Table, Tabs } from '../components';
import { formatMoney } from '../format';
import { Window } from '../layouts';

const MAX_SEARCH_RESULTS = 25;

export const Biogenerator = (props, context) => {
  const { data } = useBackend(context);
  const {
    beaker,
    processing,
  } = data;
  return (
    <Window
      width={550}
      height={420}>
      {!!processing && (
        <Dimmer fontSize="32px">
          <Icon name="cog" spin={1} />
          {' Processing...'}
        </Dimmer>
      )}
      <Window.Content scrollable>
        {!beaker && (
          <NoticeBox>No Container</NoticeBox>
        )}
        {!!beaker && (
          <BiogeneratorContent />
        )}
      </Window.Content>
    </Window>
  );
};

export const BiogeneratorContent = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    biomass,
    can_process,
    categories = [],
  } = data;
  const [
    searchText,
    setSearchText,
  ] = useLocalState(context, 'searchText', '');
  const [
    selectedCategory,
    setSelectedCategory,
  ] = useLocalState(context, 'category', categories[0]?.name);
  const testSearch = createSearch(searchText, item => {
    return item.name;
  });
  const items = searchText.length > 0
    // Flatten all categories and apply search to it
    && categories
      .flatMap(category => category.items || [])
      .filter(testSearch)
      .filter((item, i) => i < MAX_SEARCH_RESULTS)
    // Select a category and show all items in it
    || categories
      .find(category => category.name === selectedCategory)
      ?.items
    // If none of that results in a list, return an empty list
    || [];
  return (
    <Section
      title={(
        <Box
          inline
          color={biomass > 0 ? 'good' : 'bad'}>
          {formatMoney(biomass)} Biomass
        </Box>
      )}
      buttons={(
        <>
          Search
          <Input
            autoFocus
            value={searchText}
            onInput={(e, value) => setSearchText(value)}
            mx={1} />
          <Button
            icon="eject"
            content="Eject"
            onClick={() => act('eject')} />
          <Button
            icon="cog"
            content="Activate"
            disabled={!can_process}
            onClick={() => act('activate')} />
        </>
      )}>
      <Flex>
        {searchText.length === 0 && (
          <Flex.Item>
            <Tabs vertical>
              {categories.map(category => (
                <Tabs.Tab
                  key={category.name}
                  selected={category.name === selectedCategory}
                  onClick={() => setSelectedCategory(category.name)}>
                  {category.name} ({category.items?.length || 0})
                </Tabs.Tab>
              ))}
            </Tabs>
          </Flex.Item>
        )}
        <Flex.Item grow={1} basis={0}>
          {items.length === 0 && (
            <NoticeBox>
              {searchText.length === 0
                ? 'No items in this category.'
                : 'No results found.'}
            </NoticeBox>
          )}

            <ItemList
              biomass={biomass}
              items={items} />

        </Flex.Item>
      </Flex>

  );
};

const ItemList = (props, context) => {
  const { act } = useBackend(context);
  const [
    hoveredItem,
    setHoveredItem,
  ] = useLocalState(context, 'hoveredItem', {});
  const hoveredCost = hoveredItem.cost || 0;
  // Append extra hover data to items
  const items = props.items.map(item => {
    const [
      amount,
      setAmount,
    ] = useLocalState(context, "amount" + item.name, 1);
    const notSameItem = hoveredItem.name !== item.name;
    const notEnoughHovered = props.biomass - hoveredCost
      * hoveredItem.amount < item.cost * amount;
    const disabledDueToHovered = notSameItem && notEnoughHovered;
    const disabled = props.biomass < item.cost * amount || disabledDueToHovered;
    return {
      ...item,
      disabled,
      amount,
      setAmount,
    };
  });
  return items.map(item => (

        {' '}{item.name}

        <NumberInput
          value={Math.round(item.amount)}
          width="35px"
          minValue={1}
          maxValue={10}
          onChange={(e, value) => item.setAmount(value)} />

        <Button
          style={{
            'text-align': 'right',
          }}
          fluid
          content={item.cost * item.amount + ' ' + "BIO"}
          disabled={item.disabled}
          onmouseover={() => setHoveredItem(item)}
          onmouseout={() => setHoveredItem({})}
          onClick={() => act('create', {
            id: item.id,
            amount: item.amount,
          })} />

  ));
};


code:
import { useBackend } from '../backend';
import { AnimatedNumber, Box, Button, Flex, Modal, Section, Stack, Tabs } from '../components';
import { formatMoney } from '../format';
import { Window } from '../layouts';

export const BlackMarketUplink = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    categories = [],
    markets = [],
    items = [],
    money,
    viewing_market,
    viewing_category,
  } = data;
  return (
    <Window
      width={600}
      height={480}
      theme="hackerman">
      <ShipmentSelector />
      <Window.Content scrollable>
        <Section
          title="Black Market Uplink"
          buttons={(
            <Box inline bold>
              <AnimatedNumber
                value={money}
                format={value => formatMoney(value) + ' cr'} />
            </Box>
          )} />
        <Tabs>
          {markets.map(market => (
            <Tabs.Tab
              key={market.id}
              selected={market.id === viewing_market}
              onClick={() => act('set_market', {
                market: market.id,
              })}>
              {market.name}
            </Tabs.Tab>
          ))}
        </Tabs>
        <Flex>
          <Flex.Item>
            <Tabs vertical>
              {categories.map(category => (
                <Tabs.Tab
                  key={category}
                  mt={0.5}
                  selected={viewing_category === category}
                  onClick={() => act('set_category', {
                    category: category,
                  })}>
                  {category}
                </Tabs.Tab>
              ))}
            </Tabs>
          </Flex.Item>
          <Flex.Item grow={1} basis={0}>
            {items.map(item => (
              <Box
                key={item.name}
                className="candystripe"
                p={1}
                pb={2}>
                <Stack align="baseline">
                  <Stack.Item grow bold>
                    {item.name}
                  </Stack.Item>
                  <Stack.Item color="label">
                    {item.amount
                      ? item.amount + " in stock"
                      : "Out of stock"}
                  </Stack.Item>
                  <Stack.Item>
                    {formatMoney(item.cost) + ' cr'}
                  </Stack.Item>
                  <Stack.Item>
                    <Button
                      content="Buy"
                      disabled={!item.amount || item.cost > money}
                      onClick={() => act('select', {
                        item: item.id,
                      })} />
                  </Stack.Item>
                </Stack>
                {item.desc}
              </Box>
            ))}
          </Flex.Item>
        </Flex>
      </Window.Content>
    </Window>
  );
};

const ShipmentSelector = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    buying,
    ltsrbt_built,
    money,
  } = data;
  if (!buying) {
    return null;
  }
  const deliveryMethods = data.delivery_methods.map(method => {
    const description = data.delivery_method_description[method.name];
    return {
      ...method,
      description,
    };
  });
  return (
    <Modal textAlign="center">
      <Flex mb={1}>
        {deliveryMethods.map(method => {
          if (method.name === 'LTSRBT' && !ltsrbt_built) {
            return null;
          }
          return (
            <Flex.Item
              key={method.name}
              mx={1}
              width="250px">
              <Box fontSize="30px">
                {method.name}
              </Box>
              <Box mt={1}>
                {method.description}
              </Box>
              <Button
                mt={2}
                content={formatMoney(method.price) + ' cr'}
                disabled={money < method.price}
                onClick={() => act('buy', {
                  method: method.name,
                })} />
            </Flex.Item>
          );
        })}
      </Flex>
      <Button
        content="Cancel"
        color="bad"
        onClick={() => act('cancel')} />
    </Modal>
  );
};


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

export const BluespaceArtillery = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    notice,
    connected,
    unlocked,
    target,
  } = data;
  return (
    <Window
      width={400}
      height={220}>
      <Window.Content>
        {!!notice && (
          <NoticeBox>
            {notice}
          </NoticeBox>
        )}
        {connected ? (
          <>
            <Section
              title="Target"
              buttons={(
                <Button
                  icon="crosshairs"
                  disabled={!unlocked}
                  onClick={() => act('recalibrate')} />
              )}>
              <Box
                color={target ? 'average' : 'bad'}
                fontSize="25px">
                {target || 'No Target Set'}
              </Box>

              {unlocked ? (
                <Box style={{ margin: 'auto' }}>
                  <Button
                    fluid
                    content="FIRE"
                    color="bad"
                    disabled={!target}
                    fontSize="30px"
                    textAlign="center"
                    lineHeight="46px"
                    onClick={() => act('fire')} />
                </Box>
              ) : (
                <>
                  <Box
                    color="bad"
                    fontSize="18px">
                    Bluespace artillery is currently locked.
                  </Box>
                  <Box mt={1}>
                    Awaiting authorization via keycard reader from at minimum
                    two station heads.
                  </Box>
                </>
              )}

          </>
        ) : (

            <LabeledList>
              <LabeledList.Item label="Maintenance">
                <Button
                  icon="wrench"
                  content="Complete Deployment"
                  onClick={() => act('build')} />
              </LabeledList.Item>
            </LabeledList>

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


code:
import { useBackend, useSharedState } from '../backend';
import { Icon, ProgressBar, Tabs } from '../components';
import { Window } from '../layouts';

const directionToIcon = {
  north: 0,
  northeast: 45,
  east: 90,
  southeast: 135,
  south: 180,
  southwest: 225,
  west: 270,
  northwest: 315,
};

export const BluespaceLocator = (props, context) => {
  const [tab, setTab] = useSharedState(context, "tab", "implant");
  return (
    <Window
      width={300}
      height={300}>
      <Window.Content scrollable>
        <Tabs>
          <Tabs.Tab
            selected={tab === "implant"}
            onClick={() => setTab("implant")}>
            Implants
          </Tabs.Tab>
          <Tabs.Tab
            selected={tab === "beacon"}
            onClick={() => setTab("beacon")}>
            Teleporter Beacons
          </Tabs.Tab>
        </Tabs>
        {tab === "beacon" && (
          <TeleporterBeacons />
        )
        || tab === "implant" && (
          <TrackingImplants />
        )}
      </Window.Content>
    </Window>
  );
};

const TeleporterBeacons = (props, context) => {
  const { data } = useBackend(context);
  const { telebeacons } = data;
  return (
    telebeacons.map(beacon => (
      <SignalLocator
        key={beacon.name}
        name={beacon.name}
        distance={beacon.distance}
        direction={directionToIcon[beacon.direction]} />
    ))
  );
};

const TrackingImplants = (props, context) => {
  const { data } = useBackend(context);
  const { trackimplants } = data;
  return (
    trackimplants.map(implant => (
      <SignalLocator
        key={implant.name}
        name={implant.name}
        distance={implant.distance}
        direction={directionToIcon[implant.direction]} />
    ))
  );
};

const SignalLocator = (props, context) => {
  const { data } = useBackend(context);
  const { trackingrange } = data;
  const {
    name,
    direction,
    distance,
  } = props;
  return (
    <ProgressBar
      mb={1}
      value={trackingrange - distance}
      minValue={0}
      maxValue={trackingrange}
      ranges={{
        red: [0, trackingrange/3],
        yellow: [trackingrange/3, 2 * (trackingrange/3)],
        green: [2 * (trackingrange/3), trackingrange],
      }}>
      {name}
      <Icon
        ml={2}
        name="arrow-up"
        rotation={direction} />
    </ProgressBar>
  );
};


code:
import { filter, sortBy } from 'common/collections';
import { flow } from 'common/fp';
import { toFixed } from 'common/math';
import { useBackend } from '../backend';
import { Button, Divider, LabeledList, NumberInput, ProgressBar, Section, Stack, Box } from '../components';
import { getGasColor, getGasLabel } from '../constants';
import { Window } from '../layouts';

const mappedTopMargin = "2%";

export const BluespaceSender = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    on,
    gas_transfer_rate,
    price_multiplier,
  } = data;
  const bluespace_network_gases = flow([
    filter(gas => gas.amount >= 0.01),
    sortBy(gas => -gas.amount),
  ])(data.bluespace_network_gases || []);
  const gasMax = Math.max(1, ...bluespace_network_gases.map(gas => gas.amount));
  return (
    <Window
      title="Bluespace Sender"
      width={500}
      height={600}>
      <Window.Content>
        <Section
          scrollable
          fill
          title="Bluespace Network Gases"
          buttons={(
            <>
              <Button
                mr={0.5}
                color="transparent"
                icon="info"
                tooltipPosition="bottom-start"
                tooltip={multiline`
                Any gas you pipe into here will be added to the Bluespace
                Network! That means any connected Bluespace Vendor (multitool)
                will hook up to all the gas stored in this, and charge
                this machine's price to buy it.
              `} />
              <NumberInput
                animated
                value={gas_transfer_rate}
                step={0.01}
                width="63px"
                unit="moles/S"
                minValue={0}
                maxValue={1}
                onDrag={(e, value) => act('rate', {
                  rate: value,
                })} />
              <Button
                ml={0.5}
                icon={data.on ? 'power-off' : 'times'}
                content={data.on ? 'On' : 'Off'}
                selected={data.on}
                tooltipPosition="bottom-start"
                tooltip="Will only take in gases while on."
                onClick={() => act('power')} />
              <Button
                ml={0.5}
                content="Retrieve gases"
                tooltipPosition="bottom-start"
                tooltip="Will transfer any gases inside to the pipe."
                onClick={() => act('retrieve')} />
            </>
          )}>
          <Box>
            {"The vendors have made " + data.credits + " credits so far."}
          </Box>
          <Divider />
          <LabeledList>
            {bluespace_network_gases.map(gas => (
              <>
                <Stack key={gas.name}>
                  <Stack.Item color="label" basis={10} ml={1}>
                    {getGasLabel(gas.name) + " prices: "}
                    <br />
                    <Box mt={0.25}>
                      <NumberInput
                        animated
                        value={gas.price}
                        width="63px"
                        unit="per mole"
                        minValue={0}
                        maxValue={100}
                        onDrag={(e, value) => act('price', {
                          gas_price: value,
                          gas_type: gas.id,
                        })} />
                    </Box>
                  </Stack.Item>
                  <Stack.Item grow mt={mappedTopMargin} mr={1}>
                    <ProgressBar
                      color={getGasColor(gas.name)}
                      value={gas.amount}
                      minValue={0}
                      maxValue={gasMax}>
                      {toFixed(gas.amount, 2) + ' moles'}
                    </ProgressBar>
                  </Stack.Item>
                </Stack>
                <Divider />
              </>
            ))}
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};


code:
import { filter, sortBy } from 'common/collections';
import { flow } from 'common/fp';
import { toFixed } from 'common/math';
import { multiline } from 'common/string';
import { useBackend } from '../backend';
import { Button, Divider, LabeledList, NumberInput, ProgressBar, Section, Stack } from '../components';
import { getGasColor, getGasLabel } from '../constants';
import { Window } from '../layouts';

export const BluespaceVendor = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    on,
    tank_filling_amount,
    price_multiplier,
    pumping,
    selected_gas,
  } = data;
  const bluespace_network_gases = flow([
    filter(gas => gas.amount >= 0.01),
    sortBy(gas => -gas.amount),
  ])(data.bluespace_network_gases || []);
  const gasMax = Math.max(1, ...bluespace_network_gases.map(gas => gas.amount));
  return (
    <Window
      title="Bluespace Vendor"
      width={500}
      height={600}>
      <Window.Content>
        <Stack vertical fill>
          <Stack.Item>
            <Section
              title="Controls"
              buttons={(
                <>
                  <Button
                    ml={1}
                    icon="plus"
                    content="Prepare Tank"
                    disabled={
                      data.pumping || data.inserted_tank || !data.tank_amount
                    }
                    onClick={() => act('tank_prepare')} />
                  <Button
                    ml={1}
                    icon="minus"
                    content="Remove Tank"
                    disabled={data.pumping || !data.inserted_tank}
                    onClick={() => act('tank_expel')} />
                </>
              )}>
              <Stack>
                <Stack.Item>
                  <NumberInput
                    animated
                    value={tank_filling_amount}
                    width="63px"
                    unit="% tank filling goal"
                    minValue={0}
                    maxValue={100}
                    onDrag={(e, value) => act('pumping_rate', {
                      rate: value,
                    })} />
                </Stack.Item>
                <Stack.Item grow>
                  {
                    <ProgressBar
                      value={data.tank_full / 1010}
                      ranges={{
                        good: [0.67, 1],
                        average: [0.34, 0.66],
                        bad: [0, 0.33],
                      }} />
                  }
                </Stack.Item>
              </Stack>

            </Section>
          </Stack.Item>
          <Stack.Item grow>
            <Section
              scrollable
              fill
              title="Bluespace Network Gases"
              buttons={(
                <Button
                  color="transparent"
                  icon="info"
                  tooltipPosition="bottom-start"
                  tooltip={multiline`
                  Quick guide for machine use: prepare a tank to create a
                  new one in the machine, pick how much you want it filled,
                  and finally press start on the gas of your choice!
                `} />
              )}>
              <LabeledList>
                {bluespace_network_gases.map(gas => (
                  <>
                    <Stack key={gas.name}>
                      <Stack.Item color="label" basis={8} ml={1}>
                        {getGasLabel(gas.name) + " is " + gas.price + " credits per mole"}
                      </Stack.Item>
                      <Stack.Item grow mt={1}>
                        <ProgressBar
                          color={getGasColor(gas.name)}
                          value={gas.amount}
                          minValue={0}
                          maxValue={gasMax}>
                          {toFixed(gas.amount, 2) + ' moles'}
                        </ProgressBar>
                      </Stack.Item>
                      <Stack.Item ml={-0.1} mr={1} mt={1}>
                        {!data.pumping && data.selected_gas !== gas.id && (
                          <Button
                            ml={1}
                            icon="play"
                            tooltipPosition="left"
                            tooltip={"Start adding " + gas.name + "."}
                            disabled={!data.inserted_tank}
                            onClick={() => act('start_pumping', {
                              gas_id: gas.id,
                            })} />
                        ) || (
                          <Button
                            ml={1}
                            disabled={data.selected_gas !== gas.id}
                            icon="minus"
                            tooltipPosition="left"
                            tooltip={"Stop adding " + gas.name + "."}
                            onClick={() => act('stop_pumping', {
                              gas_id: gas.id,
                            })} />
                        )}
                      </Stack.Item>
                    </Stack>
                    <Divider />
                  </>
                ))}
              </LabeledList>
            </Section>
          </Stack.Item>
        </Stack>
      </Window.Content>
    </Window>
  );
};


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

export const BorgPanel = (props, context) => {
  const { act, data } = useBackend(context);
  const borg = data.borg || {};
  const cell = data.cell || {};
  const cellPercent = cell.charge / cell.maxcharge;
  const channels = data.channels || [];
  const modules = data.modules || [];
  const upgrades = data.upgrades || [];
  const ais = data.ais || [];
  const laws = data.laws || [];
  return (
    <Window
      title="Borg Panel"
      width={700}
      height={700}>
      <Window.Content scrollable>
        <Section
          title={borg.name}
          buttons={(
            <Button
              icon="pencil-alt"
              content="Rename"
              onClick={() => act('rename')} />
          )}>
          <LabeledList>
            <LabeledList.Item label="Status">
              <Button
                icon={borg.emagged ? 'check-square-o' : 'square-o'}
                content="Emagged"
                selected={borg.emagged}
                onClick={() => act('toggle_emagged')} />
              <Button
                icon={borg.lockdown ? 'check-square-o' : 'square-o'}
                content="Locked Down"
                selected={borg.lockdown}
                onClick={() => act('toggle_lockdown')} />
              <Button
                icon={borg.scrambledcodes ? 'check-square-o' : 'square-o'}
                content="Scrambled Codes"
                selected={borg.scrambledcodes}
                onClick={() => act('toggle_scrambledcodes')} />
            </LabeledList.Item>
            <LabeledList.Item label="Charge">
              {!cell.missing ? (
                <ProgressBar
                  value={cellPercent}>
                  {cell.charge + ' / ' + cell.maxcharge}
                </ProgressBar>
              ) : (

                  No cell installed

              )}

              <Button
                icon="pencil-alt"
                content="Set"
                onClick={() => act('set_charge')} />
              <Button
                icon="eject"
                content="Change"
                onClick={() => act('change_cell')} />
              <Button
                icon="trash"
                content="Remove"
                color="bad"
                onClick={() => act('remove_cell')} />
            </LabeledList.Item>
            <LabeledList.Item label="Radio Channels">
              {channels.map(channel => (
                <Button
                  key={channel.name}
                  icon={channel.installed ? 'check-square-o' : 'square-o'}
                  content={channel.name}
                  selected={channel.installed}
                  onClick={() => act('toggle_radio', {
                    channel: channel.name,
                  })} />
              ))}
            </LabeledList.Item>
            <LabeledList.Item label="Model">
              {modules.map(module => (
                <Button
                  key={module.type}
                  icon={borg.active_module === module.type
                    ? 'check-square-o'
                    : 'square-o'}
                  content={module.name}
                  selected={borg.active_module === module.type}
                  onClick={() => act('setmodule', {
                    module: module.type,
                  })} />
              ))}
            </LabeledList.Item>
            <LabeledList.Item label="Upgrades">
              {upgrades.map(upgrade => (
                <Button
                  key={upgrade.type}
                  icon={upgrade.installed ? 'check-square-o' : 'square-o'}
                  content={upgrade.name}
                  selected={upgrade.installed}
                  onClick={() => act('toggle_upgrade', {
                    upgrade: upgrade.type,
                  })} />
              ))}
            </LabeledList.Item>
            <LabeledList.Item label="Master AI">
              {ais.map(ai => (
                <Button
                  key={ai.ref}
                  icon={ai.connected ? 'check-square-o' : 'square-o'}
                  content={ai.name}
                  selected={ai.connected}
                  onClick={() => act('slavetoai', {
                    slavetoai: ai.ref,
                  })} />
              ))}
            </LabeledList.Item>
          </LabeledList>

        <Section
          title="Laws"
          buttons={(
            <Button
              icon={borg.lawupdate ? 'check-square-o' : 'square-o'}
              content="Lawsync"
              selected={borg.lawupdate}
              onClick={() => act('toggle_lawupdate')} />
          )}>
          {laws.map(law => (
            <Box key={law}>
              {law}
            </Box>
          ))}

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


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

export const BrigTimer = (props, context) => {
  const { act, data } = useBackend(context);
  return (
    <Window
      width={300}
      height={138}>
      <Window.Content scrollable>
        <Section
          title="Cell Timer"
          buttons={(
            <>
              <Button
                icon="clock-o"
                content={data.timing ? 'Stop' : 'Start'}
                selected={data.timing}
                onClick={() => act(data.timing ? 'stop' : 'start')} />
              <Button
                icon="lightbulb-o"
                content={data.flash_charging ? 'Recharging' : 'Flash'}
                disabled={data.flash_charging}
                onClick={() => act('flash')} />
            </>
          )}>
          <Button
            icon="fast-backward"
            onClick={() => act('time', { adjust: -600 })} />
          <Button
            icon="backward"
            onClick={() => act('time', { adjust: -100 })} />
          {' '}
          {String(data.minutes).padStart(2, '0')}:
          {String(data.seconds).padStart(2, '0')}
          {' '}
          <Button
            icon="forward"
            onClick={() => act('time', { adjust: 100 })} />
          <Button
            icon="fast-forward"
            onClick={() => act('time', { adjust: 600 })} />
          <br />
          <Button
            icon="hourglass-start"
            content="Short"
            onClick={() => act('preset', { preset: 'short' })} />
          <Button
            icon="hourglass-start"
            content="Medium"
            onClick={() => act('preset', { preset: 'medium' })} />
          <Button
            icon="hourglass-start"
            content="Long"
            onClick={() => act('preset', { preset: 'long' })} />
        </Section>
      </Window.Content>
    </Window>
  );
};


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

export const CTFPanel = (props, context) => {
  const { act, data } = useBackend(context);
  const teams = data.teams || [];
  const enabled = data.enabled || [];
  return (
    <Window
      title="CTF Panel"
      width={700}
      height={600}>
      <Window.Content scrollable>
        <Box textAlign="center" fontSize="18px">
          {enabled}
        </Box>

        <Divider />

        <Flex align="center" wrap="wrap" textAlign="center" m={-0.5}>
          {teams.map(team => (
            <Flex.Item key={team.name} width="49%" m={0.5} mb={8}>
              <Section
                key={team.name}
                title={`${team.color} Team`}
              >
                <Stack fill mb={1}>
                  <Stack.Item grow>
                    <Box>
                      {team.team_size} member
                      {team.team_size === 1 ? "" : "s"}
                    </Box>
                  </Stack.Item>

                  <Stack.Item grow>
                    <Box>
                      {team.score} point
                      {team.score === 1 ? "" : "s"}
                    </Box>
                  </Stack.Item>
                </Stack>

                <Button
                  content="Jump"
                  fontSize="18px"
                  fluid={1}
                  color={team.color.toLowerCase()}
                  onClick={() => act('jump', {
                    refs: team.refs,
                  })} />

                <Button
                  content="Join"
                  fontSize="18px"
                  fluid={1}
                  color={team.color.toLowerCase()}
                  onClick={() => act('join', {
                    refs: team.refs,
                  })} />

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


code:
import { filter, sortBy } from 'common/collections';
import { flow } from 'common/fp';
import { classes } from 'common/react';
import { createSearch } from 'common/string';
import { useBackend, useLocalState } from '../backend';
import { Button, ByondUi, Flex, Input, Section } from '../components';
import { Window } from '../layouts';

/**
 * Returns previous and next camera names relative to the currently
 * active camera.
 */
export const prevNextCamera = (cameras, activeCamera) => {
  if (!activeCamera) {
    return [];
  }
  const index = cameras.findIndex(camera => (
    camera.name === activeCamera.name
  ));
  return [
    cameras[index - 1]?.name,
    cameras[index + 1]?.name,
  ];
};

/**
 * Camera selector.
 *
 * Filters cameras, applies search terms and sorts the alphabetically.
 */
export const selectCameras = (cameras, searchText = '') => {
  const testSearch = createSearch(searchText, camera => camera.name);
  return flow([
    // Null camera filter
    filter(camera => camera?.name),
    // Optional search term
    searchText && filter(testSearch),
    // Slightly expensive, but way better than sorting in BYOND
    sortBy(camera => camera.name),
  ])(cameras);
};

export const CameraConsole = (props, context) => {
  const { act, data } = useBackend(context);
  const { mapRef, activeCamera } = data;
  const cameras = selectCameras(data.cameras);
  const [
    prevCameraName,
    nextCameraName,
  ] = prevNextCamera(cameras, activeCamera);
  return (
    <Window
      width={870}
      height={708}>

        <Window.Content scrollable>
          <CameraConsoleContent />
        </Window.Content>

          Camera:
          {activeCamera
            && activeCamera.name
            || '—'}

          <Button
            icon="chevron-left"
            disabled={!prevCameraName}
            onClick={() => act('switch_camera', {
              name: prevCameraName,
            })} />
          <Button
            icon="chevron-right"
            disabled={!nextCameraName}
            onClick={() => act('switch_camera', {
              name: nextCameraName,
            })} />

        <ByondUi
          className="CameraConsole__map"
          params={{
            id: mapRef,
            type: 'map',
          }} />

    </Window>
  );
};

export const CameraConsoleContent = (props, context) => {
  const { act, data } = useBackend(context);
  const [
    searchText,
    setSearchText,
  ] = useLocalState(context, 'searchText', '');
  const { activeCamera } = data;
  const cameras = selectCameras(data.cameras, searchText);
  return (
    <Flex
      direction={"column"}
      height="100%">
      <Flex.Item>
        <Input
          autoFocus
          fluid
          mt={1}
          placeholder="Search for a camera"
          onInput={(e, value) => setSearchText(value)} />
      </Flex.Item>
      <Flex.Item
        height="100%">

          {cameras.map(camera => (
          // We're not using the component here because performance
          // would be absolutely abysmal (50+ ms for each re-render).

 act('switch_camera', {
                name: camera.name,
              })}>
              {camera.name}

          ))}

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


code:
import { toFixed } from 'common/math';
import { useBackend } from '../backend';
import { Box, Button, Flex, Icon, Knob, LabeledControls, LabeledList, RoundGauge, Section, Tooltip } from '../components';
import { formatSiUnit } from '../format';
import { Window } from '../layouts';

const formatPressure = value => {
  if (value < 10000) {
    return toFixed(value) + ' kPa';
  }
  return formatSiUnit(value * 1000, 1, 'Pa');
};

export const Canister = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    portConnected,
    tankPressure,
    releasePressure,
    defaultReleasePressure,
    minReleasePressure,
    maxReleasePressure,
    pressureLimit,
    valveOpen,
    isPrototype,
    hasHoldingTank,
    holdingTank,
    holdingTankLeakPressure,
    holdingTankFragPressure,
    restricted,
  } = data;
  return (
    <Window
      width={300}
      height={275}>
      <Window.Content>
        <Flex direction="column" height="100%">
          <Flex.Item mb={1}>
            <Section
              title="Canister"
              buttons={(
                <>
                  {!!isPrototype && (
                    <Button
                      mr={1}
                      icon={restricted ? 'lock' : 'unlock'}
                      color="caution"
                      content={restricted
                        ? 'Engineering'
                        : 'Public'}
                      onClick={() => act('restricted')} />
                  )}
                  <Button
                    icon="pencil-alt"
                    content="Relabel"
                    onClick={() => act('relabel')} />
                </>
              )}>
              <LabeledControls>
                <LabeledControls.Item
                  minWidth="66px"
                  label="Pressure">
                  <RoundGauge
                    size={1.75}
                    value={tankPressure}
                    minValue={0}
                    maxValue={pressureLimit}
                    alertAfter={pressureLimit * 0.70}
                    ranges={{
                      "good": [0, pressureLimit * 0.70],
                      "average": [pressureLimit * 0.70, pressureLimit * 0.85],
                      "bad": [pressureLimit * 0.85, pressureLimit],
                    }}
                    format={formatPressure} />
                </LabeledControls.Item>
                <LabeledControls.Item label="Regulator">
                  <Box
                    position="relative"
                    left="-8px">
                    <Knob
                      size={1.25}
                      color={!!valveOpen && 'yellow'}
                      value={releasePressure}
                      unit="kPa"
                      minValue={minReleasePressure}
                      maxValue={maxReleasePressure}
                      step={5}
                      stepPixelSize={1}
                      onDrag={(e, value) => act('pressure', {
                        pressure: value,
                      })} />
                    <Button
                      fluid
                      position="absolute"
                      top="-2px"
                      right="-20px"
                      color="transparent"
                      icon="fast-forward"
                      onClick={() => act('pressure', {
                        pressure: maxReleasePressure,
                      })} />
                    <Button
                      fluid
                      position="absolute"
                      top="16px"
                      right="-20px"
                      color="transparent"
                      icon="undo"
                      onClick={() => act('pressure', {
                        pressure: defaultReleasePressure,
                      })} />
                  </Box>
                </LabeledControls.Item>
                <LabeledControls.Item label="Valve">
                  <Button
                    my={0.5}
                    width="50px"
                    lineHeight={2}
                    fontSize="11px"
                    color={valveOpen
                      ? (hasHoldingTank ? 'caution' : 'danger')
                      : null}
                    content={valveOpen ? 'Open' : 'Closed'}
                    onClick={() => act('valve')} />
                </LabeledControls.Item>
                <LabeledControls.Item
                  mr={1}
                  label="Port">
                  <Tooltip
                    content={portConnected
                      ? 'Connected'
                      : 'Disconnected'}
                    position="top"
                  >
                    <Box position="relative">
                      <Icon
                        size={1.25}
                        name={portConnected ? 'plug' : 'times'}
                        color={portConnected ? 'good' : 'bad'} />
                    </Box>
                  </Tooltip>
                </LabeledControls.Item>
              </LabeledControls>
            </Section>
          </Flex.Item>
          <Flex.Item grow={1}>
            <Section
              height="100%"
              title="Holding Tank"
              buttons={!!hasHoldingTank && (
                <Button
                  icon="eject"
                  color={valveOpen && 'danger'}
                  content="Eject"
                  onClick={() => act('eject')} />
              )}>
              {!!hasHoldingTank && (
                <LabeledList>
                  <LabeledList.Item label="Label">
                    {holdingTank.name}
                  </LabeledList.Item>
                  <LabeledList.Item label="Pressure">
                    <RoundGauge
                      value={holdingTank.tankPressure}
                      minValue={0}
                      maxValue={holdingTankFragPressure * 1.15}
                      alertAfter={holdingTankLeakPressure}
                      ranges={{
                        "good": [0, holdingTankLeakPressure],
                        "average": [holdingTankLeakPressure, holdingTankFragPressure],
                        "bad": [holdingTankFragPressure, holdingTankFragPressure * 1.15],
                      }}
                      format={formatPressure}
                      size={1.75} />
                  </LabeledList.Item>
                </LabeledList>
              )}
              {!hasHoldingTank && (
                <Box color="average">
                  No Holding Tank
                </Box>
              )}
            </Section>
          </Flex.Item>
        </Flex>
      </Window.Content>
    </Window>
  );
};


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

const PX_PER_UNIT = 24;

class PaintCanvas extends Component {
  constructor(props) {
    super(props);
    this.canvasRef = createRef();
    this.onCVClick = props.onCanvasClick;
  }

  componentDidMount() {
    this.drawCanvas(this.props);
  }

  componentDidUpdate() {
    this.drawCanvas(this.props);
  }

  drawCanvas(propSource) {
    const ctx = this.canvasRef.current.getContext("2d");
    const grid = propSource.value;
    const x_size = grid.length;
    if (!x_size) {
      return;
    }
    const y_size = grid[0].length;
    const x_scale = Math.round(this.canvasRef.current.width / x_size);
    const y_scale = Math.round(this.canvasRef.current.height / y_size);
    ctx.save();
    ctx.scale(x_scale, y_scale);
    for (let x = 0; x < grid.length; x++) {
      const element = grid[x];
      for (let y = 0; y < element.length; y++) {
        const color = element[y];
        ctx.fillStyle = color;
        ctx.fillRect(x, y, 1, 1);
      }
    }
    ctx.restore();
  }

  clickwrapper(event) {
    const x_size = this.props.value.length;
    if (!x_size) {
      return;
    }
    const y_size = this.props.value[0].length;
    const x_scale = this.canvasRef.current.width / x_size;
    const y_scale = this.canvasRef.current.height / y_size;
    const x = Math.floor(event.offsetX / x_scale)+1;
    const y = Math.floor(event.offsetY / y_scale)+1;
    this.onCVClick(x, y);
  }

  render() {
    const {
      res = 1,
      value,
      dotsize = PX_PER_UNIT,
      ...rest
    } = this.props;
    const [width, height] = getImageSize(value);
    return (
      <canvas
        ref={this.canvasRef}
        width={(width * dotsize) || 300}
        height={(height * dotsize) || 300}
        {...rest}
        onClick={e => this.clickwrapper(e)}>
        Canvas failed to render.
      </canvas>
    );
  }
}

const getImageSize = value => {
  const width = value.length;
  const height = width !== 0 ? value[0].length : 0;
  return [width, height];
};

export const Canvas = (props, context) => {
  const { act, data } = useBackend(context);
  const dotsize = PX_PER_UNIT;
  const [width, height] = getImageSize(data.grid);
  return (
    <Window
      width={Math.min(700, width * dotsize + 72)}
      height={Math.min(700, height * dotsize + 72)}>
      <Window.Content>
        <Box textAlign="center">
          <PaintCanvas
            value={data.grid}
            dotsize={dotsize}
            onCanvasClick={(x, y) => act("paint", { x, y })} />
          <Box>
            {!data.finalized && (
              <Button.Confirm
                onClick={() => act("finalize")}
                content="Finalize" />
            )}
            {data.name}
          </Box>
        </Box>
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend, useSharedState } from '../backend';
import { AnimatedNumber, Box, Button, Flex, LabeledList, Section, Table, Tabs } from '../components';
import { formatMoney } from '../format';
import { Window } from '../layouts';

export const Cargo = (props, context) => {
  return (
    <Window
      width={780}
      height={750}>
      <Window.Content scrollable>
        <CargoContent />
      </Window.Content>
    </Window>
  );
};

export const CargoContent = (props, context) => {
  const { act, data } = useBackend(context);
  const [tab, setTab] = useSharedState(context, 'tab', 'catalog');
  const {
    requestonly,
  } = data;
  const cart = data.cart || [];
  const requests = data.requests || [];
  return (
    <Box>
      <CargoStatus />

        <Tabs>
          <Tabs.Tab
            icon="list"
            selected={tab === 'catalog'}
            onClick={() => setTab('catalog')}>
            Catalog
          </Tabs.Tab>
          <Tabs.Tab
            icon="envelope"
            textColor={tab !== 'requests'
              && requests.length > 0
              && 'yellow'}
            selected={tab === 'requests'}
            onClick={() => setTab('requests')}>
            Requests ({requests.length})
          </Tabs.Tab>
          {!requestonly && (
            <Tabs.Tab
              icon="shopping-cart"
              textColor={tab !== 'cart'
                && cart.length > 0
                && 'yellow'}
              selected={tab === 'cart'}
              onClick={() => setTab('cart')}>
              Checkout ({cart.length})
            </Tabs.Tab>
          )}
        </Tabs>

      {tab === 'catalog' && (
        <CargoCatalog />
      )}
      {tab === 'requests' && (
        <CargoRequests />
      )}
      {tab === 'cart' && (
        <CargoCart />
      )}
    </Box>
  );
};

const CargoStatus = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    grocery,
    away,
    docked,
    loan,
    loan_dispatched,
    location,
    message,
    points,
    requestonly,
    can_send,
  } = data;
  return (
    <Section
      title="Cargo"
      buttons={(
        <Box inline bold>
          <AnimatedNumber
            value={points}
            format={value => formatMoney(value)} />
          {' credits'}
        </Box>
      )}>
      <LabeledList>
        <LabeledList.Item label="Shuttle">
          {docked && !requestonly && can_send &&(
            <Button
              color={grocery && "orange" || "green"}
              content={location}
              tooltip={grocery && "The chef is waiting on their grocery supplies." || ""}
              tooltipPosition="right"
              onClick={() => act('send')} />
          ) || location}
        </LabeledList.Item>
        <LabeledList.Item label="CentCom Message">
          {message}
        </LabeledList.Item>
        {!!loan && !requestonly && (
          <LabeledList.Item label="Loan">
            {!loan_dispatched && (
              <Button
                content="Loan Shuttle"
                disabled={!(away && docked)}
                onClick={() => act('loan')} />
            ) || (
              <Box color="bad">
                Loaned to Centcom
              </Box>
            )}
          </LabeledList.Item>
        )}
      </LabeledList>

  );
};

export const CargoCatalog = (props, context) => {
  const { express } = props;
  const { act, data } = useBackend(context);
  const {
    self_paid,
    app_cost,
  } = data;
  const supplies = Object.values(data.supplies);
  const [
    activeSupplyName,
    setActiveSupplyName,
  ] = useSharedState(context, 'supply', supplies[0]?.name);
  const activeSupply = supplies.find(supply => {
    return supply.name === activeSupplyName;
  });
  return (
    <Section
      title="Catalog"
      buttons={!express && (
        <>
          <CargoCartButtons />
          <Button.Checkbox
            ml={2}
            content="Buy Privately"
            checked={self_paid}
            onClick={() => act('toggleprivate')} />
        </>
      )}>
      <Flex>
        <Flex.Item ml={-1} mr={1}>
          <Tabs vertical>
            {supplies.map(supply => (
              <Tabs.Tab
                key={supply.name}
                selected={supply.name === activeSupplyName}
                onClick={() => setActiveSupplyName(supply.name)}>
                {supply.name} ({supply.packs.length})
              </Tabs.Tab>
            ))}
          </Tabs>
        </Flex.Item>
        <Flex.Item grow={1} basis={0}>

            {activeSupply?.packs.map(pack => {
              const tags = [];
              if (pack.small_item) {
                tags.push('Small');
              }
              if (pack.access) {
                tags.push('Restricted');
              }
              return (

                    {pack.name}

                    {tags.join(', ')}

                    <Button
                      fluid
                      tooltip={pack.desc}
                      tooltipPosition="left"
                      onClick={() => act('add', {
                        id: pack.id,
                      })}>
                      {formatMoney((self_paid && !pack.goody) || app_cost
                        ? Math.round(pack.cost * 1.1)
                        : pack.cost)}
                      {' cr'}
                    </Button>

              );
            })}

        </Flex.Item>
      </Flex>

  );
};

const CargoRequests = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    requestonly,
    can_send,
    can_approve_requests,
  } = data;
  const requests = data.requests || [];
  // Labeled list reimplementation to squeeze extra columns out of it
  return (
    <Section
      title="Active Requests"
      buttons={!requestonly && (
        <Button
          icon="times"
          content="Clear"
          color="transparent"
          onClick={() => act('denyall')} />
      )}>
      {requests.length === 0 && (
        <Box color="good">
          No Requests
        </Box>
      )}
      {requests.length > 0 && (

          {requests.map(request => (

                #{request.id}

                {request.object}

                {request.orderer}

                {request.reason}

                {formatMoney(request.cost)} cr

              {(!requestonly || can_send)&& can_approve_requests &&(

                  <Button
                    icon="check"
                    color="good"
                    onClick={() => act('approve', {
                      id: request.id,
                    })} />
                  <Button
                    icon="times"
                    color="bad"
                    onClick={() => act('deny', {
                      id: request.id,
                    })} />

              )}

          ))}

      )}

  );
};

const CargoCartButtons = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    requestonly,
    can_send,
    can_approve_requests,
  } = data;
  const cart = data.cart || [];
  const total = cart.reduce((total, entry) => total + entry.cost, 0);
  if (requestonly || !can_send || !can_approve_requests) {
    return null;
  }
  return (
    <>
      <Box inline mx={1}>
        {cart.length === 0 && 'Cart is empty'}
        {cart.length === 1 && '1 item'}
        {cart.length >= 2 && cart.length + ' items'}
        {' '}
        {total > 0 && `(${formatMoney(total)} cr)`}
      </Box>
      <Button
        icon="times"
        color="transparent"
        content="Clear"
        onClick={() => act('clear')} />
    </>
  );
};

const CargoCart = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    requestonly,
    away,
    docked,
    location,
    can_send,
  } = data;
  const cart = data.cart || [];
  return (
    <Section
      title="Current Cart"
      buttons={(
        <CargoCartButtons />
      )}>
      {cart.length === 0 && (
        <Box color="label">
          Nothing in cart
        </Box>
      )}
      {cart.length > 0 && (

          {cart.map(entry => (

                #{entry.id}

                {entry.object}

                {!!entry.paid && (
                  [Paid Privately]
                )}

                {formatMoney(entry.cost)} cr

                {can_send &&(
                  <Button
                    icon="minus"
                    onClick={() => act('remove', {
                      id: entry.id,
                    })} />
                )}

          ))}

      )}
      {cart.length > 0 && !requestonly && (
        <Box mt={2}>
          {away === 1 && docked === 1 && (
            <Button
              color="green"
              style={{
                'line-height': '28px',
                'padding': '0 12px',
              }}
              content="Confirm the order"
              onClick={() => act('send')} />
          ) || (
            <Box opacity={0.5}>
              Shuttle in {location}.
            </Box>
          )}
        </Box>
      )}

  );
};


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

export const CargoExpress = (props, context) => {
  const { act, data } = useBackend(context);
  return (
    <Window
      width={600}
      height={700}>
      <Window.Content scrollable>
        <InterfaceLockNoticeBox
          accessText="a QM-level ID card" />
        {!data.locked && (
          <CargoExpressContent />
        )}
      </Window.Content>
    </Window>
  );
};

const CargoExpressContent = (props, context) => {
  const { act, data } = useBackend(context);
  return (
    <>
      <Section
        title="Cargo Express"
        buttons={(
          <Box inline bold>
            <AnimatedNumber
              value={Math.round(data.points)} />
            {' credits'}
          </Box>
        )}>
        <LabeledList>
          <LabeledList.Item label="Landing Location">
            <Button
              content="Cargo Bay"
              selected={!data.usingBeacon}
              onClick={() => act('LZCargo')} />
            <Button
              selected={data.usingBeacon}
              disabled={!data.hasBeacon}
              onClick={() => act('LZBeacon')}>
              {data.beaconzone} ({data.beaconName})
            </Button>
            <Button
              content={data.printMsg}
              disabled={!data.canBuyBeacon}
              onClick={() => act('printBeacon')} />
          </LabeledList.Item>
          <LabeledList.Item label="Notice">
            {data.message}
          </LabeledList.Item>
        </LabeledList>
      </Section>
      <CargoCatalog express />
    </>
  );
};


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

export const CargoHoldTerminal = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    points,
    pad,
    sending,
    status_report,
  } = data;
  return (
    <Window
      width={600}
      height={230}>
      <Window.Content scrollable>
        <Section>
          <LabeledList>
            <LabeledList.Item label="Current Cargo Value">
              <Box inline bold>
                <AnimatedNumber value={Math.round(points)} /> credits
              </Box>
            </LabeledList.Item>
          </LabeledList>
        </Section>
        <Section
          title="Cargo Pad"
          buttons={(
            <>
              <Button
                icon={"sync"}
                content={"Recalculate Value"}
                disabled={!pad}
                onClick={() => act('recalc')} />
              <Button
                icon={sending ? 'times' : 'arrow-up'}
                content={sending ? "Stop Sending" : "Send Goods"}
                selected={sending}
                disabled={!pad}
                onClick={() => act(sending ? 'stop' : 'send')} />
            </>
          )}>
          <LabeledList>
            <LabeledList.Item
              label="Status"
              color={pad ? "good" : "bad"}>
              {pad ? "Online" : "Not Found"}
            </LabeledList.Item>
            <LabeledList.Item label="Cargo Report">
              {status_report}
            </LabeledList.Item>
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};


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

export const CellularEmporium = (props, context) => {
  const { act, data } = useBackend(context);
  const { abilities } = data;
  return (
    <Window
      width={900}
      height={480}>
      <Window.Content scrollable>

          <LabeledList>
            <LabeledList.Item
              label="Genetic Points"
              buttons={(
                <Button
                  icon="undo"
                  content="Readapt"
                  disabled={!data.can_readapt}
                  onClick={() => act('readapt')} />
              )}>
              {data.genetic_points_remaining}
            </LabeledList.Item>
          </LabeledList>

          <LabeledList>
            {abilities.map(ability => (
              <LabeledList.Item
                key={ability.name}
                className="candystripe"
                label={ability.name}
                buttons={(
                  <>
                    {ability.dna_cost}
                    {' '}
                    <Button
                      content={ability.owned ? 'Evolved' : 'Evolve'}
                      selected={ability.owned}
                      onClick={() => act('evolve', {
                        name: ability.name,
                      })} />
                  </>
                )}>
                {ability.desc}
                <Box color="good">
                  {ability.helptext}
                </Box>
              </LabeledList.Item>
            ))}
          </LabeledList>

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


code:
import { toFixed } from 'common/math';
import { classes } from 'common/react';
import { storage } from 'common/storage';
import { multiline } from 'common/string';
import { createUuid } from 'common/uuid';
import { Component, Fragment } from 'inferno';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, ByondUi, Divider, Input, Knob, LabeledControls, NumberInput, Section, Stack } from '../components';
import { Window } from '../layouts';

const pod_grey = {
  color: 'grey',
};

const useCompact = context => {
  const [compact, setCompact] = useLocalState(context, 'compact', false);
  const toggleCompact = () => setCompact(!compact);
  return [compact, toggleCompact];
};

export const CentcomPodLauncher = (props, context) => {
  const [compact] = useCompact(context);
  return (
    <Window
      title="Supply Pod Menu (Use against Helen Weinstein)"
      width={compact ? 460 : 730}
      height={compact ? 360 : 440}>
      <CentcomPodLauncherContent />
    </Window>
  );
};

const CentcomPodLauncherContent = (props, context) => {
  const [compact] = useCompact(context);
  return (
    <Window.Content>
      <Stack fill vertical>
        <Stack.Item shrink={0}>
          <PodStatusPage />
        </Stack.Item>
        <Stack.Item grow>
          <Stack fill>
            <Stack.Item grow shrink={0} basis="14.1em">
              <Stack fill vertical>
                <Stack.Item grow>
                  <PresetsPage />
                </Stack.Item>
                <Stack.Item>
                  <ReverseMenu />
                </Stack.Item>
                <Stack.Item>

                    <LaunchPage />

                </Stack.Item>
              </Stack>
            </Stack.Item>
            {!compact && (
              <Stack.Item grow={3}>
                <ViewTabHolder />
              </Stack.Item>
            )}
            <Stack.Item basis="8em">
              <Stack fill vertical>
                <Stack.Item>
                  <Bays />
                </Stack.Item>
                <Stack.Item grow>
                  <Timing />
                </Stack.Item>
                {!compact && (
                  <Stack.Item>
                    <Sounds />
                  </Stack.Item>
                )}
              </Stack>
            </Stack.Item>
            <Stack.Item basis="11em">


code:
import { useBackend } from '../backend';
import { AccessList } from './common/AccessList';
import { Window } from '../layouts';

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

  const {
    accesses,
    selectedList,
    wildcardFlags,
    wildcardSlots,
    trimAccess,
    accessFlags,
    accessFlagNames,
    showBasic,
    ourAccess,
    theftAccess,
    ourTrimAccess,
  } = data;

  const parsedAccess = accesses.flatMap(region => {
    const regionName = region.name;
    const regionAccess = region.accesses;
    const parsedRegion = {
      name: regionName,
      accesses: [],
    };
    parsedRegion.accesses = regionAccess.filter(access => {
      // Snip everything that's part of our trim.
      if (ourTrimAccess.includes(access.ref)) {
        return false;
      }
      // Add anything not part of our trim that's an access (assumed wildcard)
      // Also add any access on the ID card we're stealing from.
      if (ourAccess.includes(access.ref) || theftAccess.includes(access.ref)) {
        return true;
      }
      return false;
    });
    if (parsedRegion.accesses.length) {
      return parsedRegion;
    }
    return [];
  });

  return (
    <Window
      width={500}
      height={620}>
      <Window.Content scrollable>
        <AccessList
          accesses={parsedAccess}
          selectedList={selectedList}
          wildcardFlags={wildcardFlags}
          wildcardSlots={wildcardSlots}
          trimAccess={trimAccess}
          accessFlags={accessFlags}
          accessFlagNames={accessFlagNames}
          showBasic={!!showBasic}
          accessMod={(ref, wildcard) => act('mod_access', {
            access_target: ref,
            access_wildcard: wildcard,
          })} />
      </Window.Content>
    </Window>
  );
};


code:
import { classes } from 'common/react';
import { useBackend } from '../backend';
import { Component, Fragment } from 'inferno';
import {
  Box,
  Button,
  Dropdown,
  Icon,
  Section,
  Stack,
  Table,
} from '../components';
import { Window } from '../layouts';
import { resolveAsset } from '../assets';
import dateformat from 'dateformat';
import yaml from 'js-yaml';

const icons = {
  bugfix: { icon: 'bug', color: 'green' },
  wip: { icon: 'hammer', color: 'orange' },
  qol: { icon: 'hand-holding-heart', color: 'green' },
  soundadd: { icon: 'tg-sound-plus', color: 'green' },
  sounddel: { icon: 'tg-sound-minus', color: 'red' },
  add: { icon: 'check-circle', color: 'green' },
  expansion: { icon: 'check-circle', color: 'green' },
  rscadd: { icon: 'check-circle', color: 'green' },
  rscdel: { icon: 'times-circle', color: 'red' },
  imageadd: { icon: 'tg-image-plus', color: 'green' },
  imagedel: { icon: 'tg-image-minus', color: 'red' },
  spellcheck: { icon: 'spell-check', color: 'green' },
  experiment: { icon: 'radiation', color: 'yellow' },
  balance: { icon: 'balance-scale-right', color: 'yellow' },
  code_imp: { icon: 'code', color: 'green' },
  refactor: { icon: 'tools', color: 'green' },
  config: { icon: 'cogs', color: 'purple' },
  admin: { icon: 'user-shield', color: 'purple' },
  server: { icon: 'server', color: 'purple' },
  tgs: { icon: 'toolbox', color: 'purple' },
  tweak: { icon: 'wrench', color: 'green' },
  unknown: { icon: 'info-circle', color: 'label' },
};

export class Changelog extends Component {
  constructor() {
    super();
    this.state = {
      data: 'Loading changelog data...',
      selectedDate: '',
      selectedIndex: 0,
    };
    this.dateChoices = [];
  }

  setData(data) {
    this.setState({ data });
  }

  setSelectedDate(selectedDate) {
    this.setState({ selectedDate });
  }

  setSelectedIndex(selectedIndex) {
    this.setState({ selectedIndex });
  }

  getData = (date, attemptNumber = 1) => {
    const { act } = useBackend(this.context);
    const self = this;
    const maxAttempts = 6;

    if (attemptNumber > maxAttempts) {
      return this.setData('Failed to load data after ' + maxAttempts + ' attempts');
    }

    act('get_month', { date });

    fetch(resolveAsset(date + '.yml'))
      .then(async (changelogData) => {
        const result = await changelogData.text();
        const errorRegex = /^Cannot find/;

        if (errorRegex.test(result)) {
          const timeout = 50 + attemptNumber * 50;

          self.setData(
            'Loading changelog data' + '.'.repeat(attemptNumber + 3)
          );
          setTimeout(() => {
            self.getData(date, attemptNumber + 1);
          }, timeout);
        } else {
          self.setData(yaml.load(result, { schema: yaml.CORE_SCHEMA }));
        }
      });
  }

  componentDidMount() {
    const { data: { dates = [] } } = useBackend(this.context);

    if (dates) {
      dates.forEach(
        date => this.dateChoices.push(dateformat(date, 'mmmm yyyy', true))
      );
      this.setSelectedDate(this.dateChoices[0]);
      this.getData(dates[0]);
    }
  }

  render() {
    const { data, selectedDate, selectedIndex } = this.state;
    const { data: { dates } } = useBackend(this.context);
    const { dateChoices } = this;

    const dateDropdown = dateChoices.length > 0 && (
      <Stack mb={1}>
        <Stack.Item>
          <Button
            className="Changelog__Button"
            disabled={selectedIndex === 0}
            icon={'chevron-left'}
            onClick={() => {
              const index = selectedIndex - 1;

              this.setData('Loading changelog data...');
              this.setSelectedIndex(index);
              this.setSelectedDate(dateChoices[index]);
              window.scrollTo(
                0,
                document.body.scrollHeight
                || document.documentElement.scrollHeight
              );
              return this.getData(dates[index]);
            }} />
        </Stack.Item>
        <Stack.Item>
          <Dropdown
            displayText={selectedDate}
            options={dateChoices}
            onSelected={value => {
              const index = dateChoices.indexOf(value);

              this.setData('Loading changelog data...');
              this.setSelectedIndex(index);
              this.setSelectedDate(value);
              window.scrollTo(
                0,
                document.body.scrollHeight
                || document.documentElement.scrollHeight
              );
              return this.getData(dates[index]);
            }}
            selected={selectedDate}
            width={'150px'} />
        </Stack.Item>
        <Stack.Item>
          <Button
            className="Changelog__Button"
            disabled={selectedIndex === dateChoices.length - 1}
            icon={'chevron-right'}
            onClick={() => {
              const index = selectedIndex + 1;

              this.setData('Loading changelog data...');
              this.setSelectedIndex(index);
              this.setSelectedDate(dateChoices[index]);
              window.scrollTo(
                0,
                document.body.scrollHeight
                || document.documentElement.scrollHeight
              );
              return this.getData(dates[index]);
            }} />
        </Stack.Item>
      </Stack>
    );

    const header = (

Traditional Games Space Station 13

          Thanks to:
          Baystation 12, /vg/station, NTstation, CDK Station devs,
          FacepunchStation, GoonStation devs, the original Space Station 13
          developers, Invisty for the title image and the countless others who
          have contributed to the game, issue tracker or wiki over the years.

          {'Current project maintainers can be found '}

            here

          {', recent GitHub contributors can be found '}

            here
          .

          {'You can also join our discord '}

            here
          .

        {dateDropdown}

    );

    const footer = (

        {dateDropdown}

GoonStation 13 Development Team

          Coders:
          Stuntwaffle, Showtime, Pantaloons, Nannek, Keelin, Exadv1, hobnob,
          Justicefries, 0staf, sniperchance, AngriestIBM, BrianOBlivion

          Spriters:
          Supernorn, Haruhi, Stuntwaffle, Pantaloons, Rho, SynthOrange,
          I Said No

          Traditional Games Space Station 13 is thankful to the
          GoonStation 13 Development Team for its work on the game up to the
          {' r4407 release. The changelog for changes up to r4407 can be seen '}

            here

          .

          {'Except where otherwise noted, Goon Station 13 is licensed under a '}

            Creative Commons Attribution-Noncommercial-Share Alike 3.0 License

          {'. Rights are currently extended to '}
          SomethingAwful Goons
          {' only.'}

Traditional Games Space Station 13 License

          {'All code after '}

            commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12
            at 4:38 PM PST

          {' is licensed under '}

            GNU AGPL v3

          {'. All code before that commit is licensed under '}

            GNU GPL v3

          {', including tools unless their readme specifies otherwise. See '}

            LICENSE

          {' and '}

            GPLv3.txt

          {' for more details.'}

          The TGS DMAPI API is licensed as a subproject under the MIT license.
          {' See the footer of '}

            code/__DEFINES/tgs.dm

          {' and '}

            code/modules/tgs/LICENSE

          {' for the MIT license.'}

          {'All assets including icons and sound are under a '}

            Creative Commons 3.0 BY-SA license

          {' unless otherwise indicated.'}

    );

    const changes = typeof data === 'object' && Object.keys(data).length > 0 && (
      Object.entries(data).reverse().map(([date, authors]) => (

          <Box ml={3}>
            {Object.entries(authors).map(([name, changes]) => (
              <Fragment key={name}>

{name} changed:

                <Box ml={3}>

                    {changes.map(change => {
                      const changeType = Object.keys(change)[0];
                      return (

                            <Icon
                              color={
                                icons[changeType]
                                  ? icons[changeType].color
                                  : icons['unknown'].color
                              }
                              name={
                                icons[changeType]
                                  ? icons[changeType].icon
                                  : icons['unknown'].icon
                              }
                            />

                            {change[changeType]}

                      );
                    })}

                </Box>
              </Fragment>
            ))}
          </Box>

      ))
    );

    return (
      <Window title="Changelog" width={675} height={650}>
        <Window.Content scrollable>
          {header}
          {changes}
          {typeof data === 'string' &&
{data}
}
          {footer}
        </Window.Content>
      </Window>
    );
  }
}


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

export const ChemAcclimator = (props, context) => {
  const { act, data } = useBackend(context);
  return (
    <Window
      width={320}
      height={271}>
      <Window.Content>
        <Section title="Acclimator">
          <LabeledList>
            <LabeledList.Item label="Current Temperature">
              {data.chem_temp} K
            </LabeledList.Item>
            <LabeledList.Item label="Target Temperature">
              <NumberInput
                value={data.target_temperature}
                unit="K"
                width="59px"
                minValue={0}
                maxValue={1000}
                step={5}
                stepPixelSize={2}
                onChange={(e, value) => act('set_target_temperature', {
                  temperature: value,
                })} />
            </LabeledList.Item>
            <LabeledList.Item label="Acceptable Temp. Difference">
              <NumberInput
                value={data.allowed_temperature_difference}
                unit="K"
                width="59px"
                minValue={1}
                maxValue={data.target_temperature}
                stepPixelSize={2}
                onChange={(e, value) => {
                  act('set_allowed_temperature_difference', {
                    temperature: value,
                  });
                }} />
            </LabeledList.Item>
          </LabeledList>
        </Section>
        <Section
          title="Status"
          buttons={(
            <Button
              icon="power-off"
              content={data.enabled ? "On" : "Off"}
              selected={data.enabled}
              onClick={() => act('toggle_power')} />
          )}>
          <LabeledList>
            <LabeledList.Item label="Volume">
              <NumberInput
                value={data.max_volume}
                unit="u"
                width="50px"
                minValue={data.reagent_volume}
                maxValue={200}
                step={2}
                stepPixelSize={2}
                onChange={(e, value) => act('change_volume', {
                  volume: value,
                })} />
            </LabeledList.Item>
            <LabeledList.Item label="Current Operation">
              {data.acclimate_state}
            </LabeledList.Item>
            <LabeledList.Item label="Current State">
              {data.emptying ? 'Emptying' : 'Filling'}
            </LabeledList.Item>
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};


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

export const ChemDebugSynthesizer = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    amount,
    purity,
    beakerCurrentVolume,
    beakerMaxVolume,
    isBeakerLoaded,
    beakerContents = [],
  } = data;
  return (
    <Window
      width={390}
      height={330}>
      <Window.Content scrollable>
        <Section
          title="Recipient"
          buttons={isBeakerLoaded ? (
            <>
              <Button
                icon="eject"
                content="Eject"
                onClick={() => act('ejectBeaker')} />
              <NumberInput
                value={amount}
                unit="u"
                minValue={1}
                maxValue={beakerMaxVolume}
                step={1}
                stepPixelSize={2}
                onChange={(e, value) => act('amount', {
                  amount: value,
                })} />
              <NumberInput
                value={purity}
                unit="%"
                minValue={0}
                maxValue={120}
                step={1}
                stepPixelSize={2}
                onChange={(e, value) => act('purity', {
                  amount: value,
                })} />
              <Button
                icon="plus"
                content="Input"
                onClick={() => act('input')} />
            </>
          ) : (
            <Button
              icon="plus"
              content="Create Beaker"
              onClick={() => act('makecup')} />
          )}>
          {isBeakerLoaded ? (
            <>
              <Box>
                <AnimatedNumber value={beakerCurrentVolume} />
                {' / ' + beakerMaxVolume + ' u'}
              </Box>
              {beakerContents.length > 0 ? (
                <LabeledList>
                  {beakerContents.map(chem => (
                    <LabeledList.Item
                      key={chem.name}
                      label={chem.name}>
                      {chem.volume} u
                    </LabeledList.Item>
                  ))}
                </LabeledList>
              ) : (
                <Box color="bad">
                  Recipient Empty
                </Box>
              )}
            </>
          ) : (
            <Box color="average">
              No Recipient
            </Box>
          )}
        </Section>
      </Window.Content>
    </Window>
  );
};


code:
import { toFixed } from 'common/math';
import { toTitleCase } from 'common/string';
import { Fragment } from 'inferno';
import { useBackend, useLocalState } from '../backend';
import { AnimatedNumber, Box, Button, Icon, LabeledList, ProgressBar, Section } from '../components';
import { Window } from '../layouts';

export const ChemDispenser = (props, context) => {
  const { act, data } = useBackend(context);
  const recording = !!data.recordingRecipe;
  const { recipeReagents = [] } = data;
  const [hasCol, setHasCol] = useLocalState(
    context, 'has_col', false);
  // TODO: Change how this piece of shit is built on server side
  // It has to be a list, not a fucking OBJECT!
  const recipes = Object.keys(data.recipes)
    .map(name => ({
      name,
      contents: data.recipes[name],
    }));
  const beakerTransferAmounts = data.beakerTransferAmounts || [];
  const beakerContents = recording
    && Object.keys(data.recordingRecipe)
      .map(id => ({
        id,
        name: toTitleCase(id.replace(/_/, ' ')),
        volume: data.recordingRecipe[id],
      }))
    || data.beakerContents
    || [];
  return (
    <Window
      width={565}
      height={620}>
      <Window.Content scrollable>
        <Section
          title="Status"
          buttons={(
            <>
              {recording && (
                <Box inline mx={1} color="red">
                  <Icon name="circle" mr={1} />
                  Recording
                </Box>
              )}
              <Button
                icon="book"
                disabled={!data.isBeakerLoaded}
                content={"Reaction search"}
                tooltip={data.isBeakerLoaded ? "Look up recipes and reagents!" : "Please insert a beaker!"}
                tooltipPosition="bottom-start"
                onClick={() => act('reaction_lookup')} />
              <Button
                icon="cog"
                tooltip="Color code the reagents by pH"
                tooltipPosition="bottom-start"
                selected={hasCol}
                onClick={() => setHasCol(!hasCol)} />
            </>
          )}>
          <LabeledList>
            <LabeledList.Item label="Energy">
              <ProgressBar
                value={data.energy / data.maxEnergy}>
                {toFixed(data.energy) + ' units'}
              </ProgressBar>
            </LabeledList.Item>
          </LabeledList>
        </Section>
        <Section
          title="Recipes"
          buttons={(
            <>
              {!recording && (
                <Box inline mx={1}>
                  <Button
                    color="transparent"
                    content="Clear recipes"
                    onClick={() => act('clear_recipes')} />
                </Box>
              )}
              {!recording && (
                <Button
                  icon="circle"
                  disabled={!data.isBeakerLoaded}
                  content="Record"
                  onClick={() => act('record_recipe')} />
              )}
              {recording && (
                <Button
                  icon="ban"
                  color="transparent"
                  content="Discard"
                  onClick={() => act('cancel_recording')} />
              )}
              {recording && (
                <Button
                  icon="save"
                  color="green"
                  content="Save"
                  onClick={() => act('save_recording')} />
              )}
            </>
          )}>
          <Box mr={-1}>
            {recipes.map(recipe => (
              <Button
                key={recipe.name}
                icon="tint"
                width="129.5px"
                lineHeight={1.75}
                content={recipe.name}
                onClick={() => act('dispense_recipe', {
                  recipe: recipe.name,
                })} />
            ))}
            {recipes.length === 0 && (
              <Box color="light-gray">
                No recipes.
              </Box>
            )}
          </Box>
        </Section>
        <Section
          title="Dispense"
          buttons={(
            beakerTransferAmounts.map(amount => (
              <Button
                key={amount}
                icon="plus"
                selected={amount === data.amount}
                content={amount}
                onClick={() => act('amount', {
                  target: amount,
                })} />
            ))
          )}>
          <Box mr={-1}>
            {data.chemicals.map(chemical => (
              <Button
                key={chemical.id}
                icon="tint"
                width="129.5px"
                lineHeight={1.75}
                content={chemical.title}
                tooltip={"pH: " + chemical.pH}
                backgroundColor={recipeReagents.includes(chemical.id)
                  ? hasCol ? "black" : "green"
                  : hasCol ? chemical.pHCol : "default"}
                onClick={() => act('dispense', {
                  reagent: chemical.id,
                })} />
            ))}
          </Box>
        </Section>
        <Section
          title="Beaker"
          buttons={(
            beakerTransferAmounts.map(amount => (
              <Button
                key={amount}
                icon="minus"
                disabled={recording}
                content={amount}
                onClick={() => act('remove', { amount })} />
            ))
          )}>
          <LabeledList>
            <LabeledList.Item
              label="Beaker"
              buttons={!!data.isBeakerLoaded && (
                <Button
                  icon="eject"
                  content="Eject"
                  disabled={!data.isBeakerLoaded}
                  onClick={() => act('eject')} />
              )}>
              {recording
                && 'Virtual beaker'
                || data.isBeakerLoaded
                  && (
                    <>
                      <AnimatedNumber
                        initial={0}
                        value={data.beakerCurrentVolume} />
                      /{data.beakerMaxVolume} units
                    </>
                  )
                || 'No beaker'}
            </LabeledList.Item>
            <LabeledList.Item
              label="Contents">
              <Box color="label">
                {(!data.isBeakerLoaded && !recording) && 'N/A'
                  || beakerContents.length === 0 && 'Nothing'}
              </Box>
              {beakerContents.map(chemical => (
                <Box
                  key={chemical.name}
                  color="label">
                  <AnimatedNumber
                    initial={0}
                    value={chemical.volume} />
                  {' '}
                  units of {chemical.name}
                </Box>
              ))}
              {((beakerContents.length > 0 && !!data.showpH) && (
                <Box>
                  pH:
                  <AnimatedNumber
                    value={data.beakerCurrentpH} />
                </Box>)
              )}
            </LabeledList.Item>
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};


code:
import { Fragment } from 'inferno';
import { useBackend, useLocalState } from '../backend';
import { Button, Input, Section, Stack } from '../components';
import { Window } from '../layouts';

export const ChemFilterPane = (props, context) => {
  const { act } = useBackend(context);
  const { title, list, reagentName, onReagentInput } = props;
  const titleKey = title.toLowerCase();
  return (
    <Section
      title={title}
      minHeight="240px"
      buttons={(
        <>
          <Input
            placeholder="Reagent"
            width="140px"
            onInput={(e, value) => onReagentInput(value)} />
          <Button
            ml={1}
            icon="plus"
            onClick={() => act('add', {
              which: titleKey,
              name: reagentName,
            })} />
        </>
      )}>
      {list.map(filter => (
        <Fragment key={filter}>
          <Button
            fluid
            icon="minus"
            content={filter}
            onClick={() => act('remove', {
              which: titleKey,
              reagent: filter,
            })} />
        </Fragment>
      ))}
    </Section>
  );
};

export const ChemFilter = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    left = [],
    right = [],
  } = data;
  const [leftName, setLeftName] = useLocalState(context, 'leftName', '');
  const [rightName, setRightName] = useLocalState(context, 'rightName', '');
  return (
    <Window
      width={500}
      height={300}>
      <Window.Content scrollable>
        <Stack>
          <Stack.Item grow>
            <ChemFilterPane
              title="Left"
              list={left}
              reagentName={leftName}
              onReagentInput={value => setLeftName(value)} />
          </Stack.Item>
          <Stack.Item grow>
            <ChemFilterPane
              title="Right"
              list={right}
              reagentName={rightName}
              onReagentInput={value => setRightName(value)} />
          </Stack.Item>
        </Stack>
      </Window.Content>
    </Window>
  );
};