code:
# Converting old tgui interfaces to tgui-next

This guide is going to assume you already know roughly how tgui-next works, how to make new uis, etc. It's mostly aimed at helping translate concepts between tgui and tgui-next, and clarify some confusing parts of the transition.

## Backend

Backend in almost every case does not require any changes. In particularly heavy ui cases, something to be aware of is the new `ui_static_data()` proc. This proc allows you to split some data sent to the interface off into data that will only be sent on ui initialize and when manually updated by elsewhere in the code. Useful for things like cargo where you have a very large set of mostly identical code.

Keep in mind that for uis where *all* data doesn't need to be live updating, you can just toggle off autoupdate for the ui instead of messing with static data.

## Frontend

The very first thing to note is the name of the `ract` file containing the old interface. Whatever the name is (minus the extension) is going to be what the route key is going to be.

One thing I like to do before starting work on a conversion is screenshot what the old interface looks like so I have something to reference to make sure that the styling can line up as well.

## General syntax changes

Ractive has a fairly different templating syntax from React.

### `data`

You likely already know that React data inserts look like this

```jsx
{data.example_data}
```

Ractive looks very similar, the only real difference is that React uses one paranthesis instead of two.

```ractive
{{data.example_data}}
```

However, you may occasionally come across data inserts that instead of referencing the `data` var or things contained within it instead reference `adata`. `adata` was short for animated data, and was used for smooth number animations in interfaces. instead of having a seperate data structure for this. tgui-next instead uses a component, which is `AnimatedNumber`.

`AnimatedNumber` is used like this

```jsx
<AnimatedNumber value={data.example_data}/>
```

Make sure you don't forget to import it.

### Conditionals

Ractive conditionals look very different from React conditionals.

A ractive `if` (only render if result of expression is true) looks like this

```ractive
{{#if data.condition}}
  <span>Example Render</span>
{{/if}}
```

The equivalent React would be

```jsx
{!!data.condition && (
  <Fragment>Example Render</Fragment>
)}
```

This might look a bit intimidating compared to the reactive part but it's not as complicated as it seems:

1. A new jsx context is opened with `{}`
2. jsx contexts like this always render whatever the return value is, so we can use `&&` to return a value we want. `&&` returns the last true value (or not "falsey" because this is js).
3. jsx tags are never "falsey", so a conditioned paired with a jsx tag will mean the condition being true will continue on and return the tag. `()` is just used to contain the tag
4. The `!!` is not a special operator, it is a literal double negation. This is because most `false` values coming from byond are going to actually be `0`, which would be rendered if the condition is false. Negating `0` returns `true`, negating `true` returns `false`, which isn't rendered.
5. `Fragment` is actually a true "dead tag". It's similar to `span` in that it just contains things without providing functionality, but it's unwrapped before the final render and children of it are injected into its parent. In a case where you only need to render text without any styling, it's probably better to just return a string literal (`"Example Render"`), but this was just to illustrate that you can put any tag in this expression.

You don't really need to know all this to understand how to use it, but I find it helps with understanding when things go wrong.

Ractive conditionals can have an `else` as well
```ractive
{{#if data.condition}}
  value
{{else}}
  other value
{{/if}}
```

Similarly to the previous example, just add a `||` operator to handle the
"falsy" condition:

```jsx
{!!data.condition && (
  <Fragment>
    value
  </Fragment>
) || (
  <Fragment>
    other value
  </Fragment>
)}
```

There's also our good old friend - the ternary:

```jsx
{data.condition ? 'value' : 'other value'}
```

Keep in mind you can also use tags here like the conditional example,
and you can mix string literals, values, and tags as well.

```jsx
{data.is_robot ? (
  <Button content="Robot Button"/>
) : 'Not a robot'}
```

### Loops

Ractive has loops for iterating over data and inserting something for each
member of an array or object

```
{{#each data.list_of_foo}}
  foo {{number}} is here.
{{/each}}
```

This didn't care whether the data was an array or an object, and members of each entry of the loop were "unwrapped" so to say. `{{number}}` in that example is referring to the `{{number}}` value on the entry of the list for that iterate.

The React equivalent to this is going to be `map`.

_AN IMPORTANT DISTINCTION HERE IS THAT NOW WE CARE WHETHER THIS IS AN OBJECT OR AN ARRAY BEING ACTED ON._

Objects are represented by `{}`, arrays by `[]`

"How can I tell?" you may ask. It's fairly simple, associated lists on the byond side are going to be turned into objects when they get json converted, normal lists are going to be turned into arrays.

`list("bla", "blo")` would become `["bla", "blo"]` and `list("foo" = 1, "bar" = 2)` would become `{"foo": 1, "bar": 2}`

First things first, above the `return` of the function you're making the interface in, you're going to want to add something like this
```jsx
const things = data.things || [];
```

This ensures that you'll never be reading a null entry by mistake. Substitute `{}` for objects as appropriate.

If it's an array, you'll want to do this in the template
```jsx
{things.map(thing => (
  <Fragment>
    Thing {thing.number} is here!
  </Fragment>
))}
```

`map` is a function that calls a passed function (a lambda) on each entry, and returns the value. You should already know that returned tags and values (except `false`) get rendered, so that's how it's rendering each time.

A lambda is what's known as an anonymous function, it's a function that doesn't have a name that's only used for a specific usage. `map` wants a function that has one parameter, so we define one parameter then use `=>` to say the parameter has to do with the following block.

`parameter => ()` is just a shorthand for `parameter => {return();}`

This is quite a bit higher concept than ractive's each statements, so feel free to look around and ~~copy paste~~ learn from how other interfaces use this.

Now for objects, there's a genuinely pretty gross syntax here. We apoligize, it's related to ie8 compatibility nonsense.

```jsx
{map((value, key) => (
  <Fragment>
    Key is {key}, value is {value}
  </Fragment>
))(fooObject)}
```

Again, sorry for this syntax. `fooObject` would be the object being iterated on, value would be the value of the iterated entry on the list, and key would be the key. the naming of value and key isn't important here, but knowing that it goes `value`, `key` in that order is important.

It is sometimes better to preemptively convert an object to array before
the big return statement, like this:

```jsx
const fooArray = map((value, key) => {
  return { key, value };
})(fooObject);
```

Or if you just want to discard all keys, this will also work nicely:

```jsx
const fooArray = toArray(fooObject);
```

Also occasionally you'd see an else:

```
{{#each data.potentially_empty_list}}
  Thing "{{name}}" is in this list!
{{else}}
  None found!
{{/each}}
```

This would iterate using the first contents each time, or display the second option if the list was empty.

To do a similar thing in JSX, just check if array is empty like this:

```jsx
{fooArray.length === 0 && 'fooArray is empty.'}
{fooArray.map(foo => (
  <Fragment>
    Foo is {foo}
  </Fragment>
))}
```

### Extra Stuff

I'll put some extra stuff here when I think of it.

## Components

This will be a reference of tgui components and the tgui-next equivalent.

### `ui-display`

Equivalent of `<ui-display>` is `<Section>`

```
<ui-display title="Status">
  Contents
</ui-display>
```

becomes

```jsx
<Section title="Status">
  Contents
</Section>
```

A feature sometimes used is if `ui-display` has the `button` property, it will contain a `partial` command. This becomes the `buttons` property on `Section`:

```
<ui-display title="Status" button>
  {{#partial button}}
    <ui-button /> // lots more button bullshit here
  {{/partial}}
  Contents
</ui-display>
```

becomes

```jsx
<Section
  title="Status"
  buttons={(
    <Button />
  )}>
  Contents
</Section>
```

### `ui-section`

Very important to note `ui-section` is NOT the equivalent of `Section`

`<ui-section>` does not have a direct equivalent, but the closest equivalent is `<LabeledList>`

```
<ui-section label="power">
  No Power
</ui-section>
<ui-section label="connection">
  No Connection
</ui-section>
```

becomes

```jsx
<LabeledList>
  <LabeledList.Item label="power">
    No Power
  </LabeledList.Item>
  <LabeledList.Item label="connection">
    No Connection
  </LabeledList.Item>
</LabeledList>
```

Important to note that `LabeledList.Item` has `buttons` as well.

Also good to know that if you need the contents of a `LabeledList.Item` to be colored, you can just set the `color` prop on it instead of putting a `span` inside it.

### `ui-notice`

`<ui-notice>` has a direct equivalent in `<NoticeBox>`

```
<ui-notice>
  Notice stuff!
</ui-notice>
```

becomes

```jsx
<NoticeBox>
  Notice stuff!
</NoticeBox>
```

### `ui-button`

The equivalent of `ui-button` is `Button` but it works quite a bit differently.

```
<ui-button
  state='{{data.condition ? "disabled" : null}}'
  action="ui_action"
  params={param: value}>
  Click
</ui-button>
```

becomes

```jsx
<Button
  content="Click"
  disabled={data.condition}
  onClick={() => act('ui_action', {
    param: value,
  })}/>
```


code:
# tgui Migration Guide to v4 from v3

## The Easy Part

- Copy and replace the following files in bulk:
  - `code/__DEFINES/tgui.dm`
  - `code/controllers/subsystem/tgui.dm`
  - `code/modules/tgui/**`
  - `tgui/**`
    - Except: `tgui/packages/tgui/interfaces`
    - Manually resolve conflicts for files that were touched outside the
      `interfaces` folder.
- Copy the updated `log_tgui` proc from:
  - `code/__HELPERS/_logging.dm`

If you have a dual nano/tgui setup, then make sure to rename all ui procs
on `/datum`, such as `ui_interact` to `tgui_interact`, to avoid namespace
clashing. Usual stuff.

## Update `ui_interact` proc signatures

First of all, tgui states need to move from `ui_interact` to `ui_state`.

One way of doing it, is to just cherry pick those procs from upstream.

If you want to search and replace manually, search for `state = GLOB`, and
extract those things into `ui_state` procs like so:

```dm
.../ui_state(mob/user)
	return GLOB.default_state
```

Then reduce `ui_interact` until you finish with something like this:

```dm
.../ui_interact(mob/user, datum/tgui/ui)
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		ui = new(user, src, "FooBar", "Foo Bar UI", 600, 400)
		ui.open()
```

## Update asset delivery code

Remove all asset code that injects stylesheets by modifying tgui's `basehtml`.
You no longer need to do that.

Find all occurences of `asset.send(user)` in `ui_interact`, and refactor those
snippets to the following proc:

```dm
.../ui_assets(mob/user)
	return list(
    get_asset_datum(/datum/asset/simple/foobar),
  )
```

## Check `ui_act` for new bugs

Code behind `ui_act` was recently refactored to use JSON-encoded payloads
instead of just strings. Since it can now carry numbers and other complex
types, you should check that the code is type agnostic and does not break
due to an assumption that every parameter is a string.

One of such offenders is the personal crafting interface, where it needlessly
compares parameters to `""` and `"0"`. You can now replace this code with
simple assignments, because an empty category will now be properly `null`
instead of an empty string.

## Backend data changes

Interfaces that relied on `config.window.id`, must now use
`window.__windowId__`, which is a global constant unique for the page
the script is running on (so you can be sure it never changes).

In case of `ByondUi`, this parameter can be completely omitted, because
parent will always default to the current window id.

Affected interfaces:

- `CameraConsole`
- Any interface that uses the `ByondUi` component

---

That's all folks!

There is a lot of stuff that was refactored under the hood, but normal UI
stuff wouldn't and shouldn't touch it, so you should be good with just
going through the checklist above.

repo_name: AnalWerewolf/sundown

license: mit


code:
# Tutorial and Examples

## Main concepts

Basic tgui backend code consists of the following vars and procs:

```
ui_interact(mob/user, datum/tgui/ui)
ui_data(mob/user)
ui_act(action, params)
ui_state()
```

- `src_object` - The atom, which UI corresponds to in the game world.
- `ui_interact` - The proc where you will handle a request to open an
interface. Typically, you would update an existing UI (if it exists),
or set up a new instance of UI by calling the `SStgui` subsystem.
- `ui_data` - In this proc you munges whatever complex data your `src_object`
has into an associative list, which will then be sent to UI as a JSON string.
- `ui_act` - This proc receives user actions and reacts to them by changing
the state of the game.
- `ui_state` - This proc dictates under what conditions a UI may be interacted
with. This may be the standard checks that check if you are in range and
conscious, or more.

Once backend is complete, you create an new interface component on the
frontend, which will receive this JSON data and render it on screen.

States are easy to write and extend, and what make tgui interactions so
powerful. Because states can be overridden from other procs, you can build
powerful interactions for embedded objects or remote access.

## Using It

### Backend

Let's start with a very basic hello world.

```dm
/obj/machinery/my_machine/ui_interact(mob/user, datum/tgui/ui)
  ui = SStgui.try_update_ui(user, src, ui)
  if(!ui)
    ui = new(user, src, "MyMachine")
    ui.open()
```

This is the proc that defines our interface. There's a bit going on here, so
let's break it down. First, we override the ui_interact proc on our object. This
will be called by `interact` for you, which is in turn called by `attack_hand`
(or `attack_self` for items). `ui_interact` is also called to update a UI (hence
the `try_update_ui`), so we accept an existing UI to update.

Inside the `if(!ui)` block (which means we are creating a new UI), we choose our
template, title, and size; we can also set various options like `style` (for
themes), or autoupdate. These options will be elaborated on later (as will
`ui_state`s).

After `ui_interact`, we need to define `ui_data`. This just returns a list of
data for our object to use. Let's imagine our object has a few vars:

```dm
/obj/machinery/my_machine/ui_data(mob/user)
  var/list/data = list()
  data["health"] = health
  data["color"] = color

  return data
```

The `ui_data` proc is what people often find the hardest about tgui, but its
really quite simple! You just need to represent your object as numbers, strings,
and lists, instead of atoms and datums.

Finally, the `ui_act` proc is called by the interface whenever the user used an
input. The input's `action` and `params` are passed to the proc.

```dm
/obj/machinery/my_machine/ui_act(action, params)
  . = ..()
  if(.)
    return
  if(action == "change_color")
    var/new_color = params["color"]
    if(!(color in allowed_coors))
      return FALSE
    color = new_color
    . = TRUE
  update_icon()
```

The `..()` (parent call) is very important here, as it is how we check that the
user is allowed to use this interface (to avoid so-called href exploits). When
any event has been handled `..()` will return `TRUE`. It is important to clamp
and sanitize all input here. Always assume the user is attempting to exploit the
game.

When `..()` has returned `TRUE` your interface can safely assume that the user's
action has been handled already by some parent proc and you should not continue
to handle this, instead preserving and returning the parent proc's return value.

Also note the use of `. = TRUE` (or `FALSE`), which is used to notify the UI
that this input has been handled. When `ui_act` eventually returns, a value of
`TRUE` indicates that the input has been handled and that the UI should update.
This is important for UIs that do not auto-update, as otherwise the user will
not be able to see the interface update based on thier actions.

### Frontend

Finally, let's make a React Component for your interface. This is also
a source of confusion for new developers. If you got some basic javascript
and HTML knowledge, that should ease the learning process, although we
recommend getting yourself introduced to
[React and JSX](https://reactjs.org/docs/introducing-jsx.html).

A React component is not a regular HTML template. A component is a
javascript function, which accepts a `props` object (that contains
properties passed to a component) and a `context` object (which is
necessary to access UI data) as arguments, and outputs an HTML-like
structure.

So let's create our first React Component. Create a file with a name
`SampleInterface.js` (or any other name you want), and copy this code
snippet (make sure component name matches the file name):

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

export const SampleInterface = (props, context) => {
  const { act, data } = useBackend(context);
  // Extract `health` and `color` variables from the `data` object.
  const {
    health,
    color,
  } = data;
  return (
    <Window resizable>
      <Window.Content scrollable>
        <Section title="Health status">
          <LabeledList>
            <LabeledList.Item label="Health">
              {health}
            </LabeledList.Item>
            <LabeledList.Item label="Color">
              {color}
            </LabeledList.Item>
            <LabeledList.Item label="Button">
              <Button
                content="Dispatch a 'test' action"
                onClick={() => act('test')} />
            </LabeledList.Item>
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};
```

Here are the key variables you get from a `useBackend(context)` function:

- `config` is part of core tgui. It contains meta-information about the
interface and who uses it, BYOND refs to various objects, and so forth.
You are rarely going to use it, but sometimes it can be used to your
advantage when doing complex UIs.
- `data` is the data returned from `ui_data` and `ui_static_data` procs in
your DM code. Pretty straight forward.
  - Note, that javascript doesn't have associative arrays, so when you
  return an associative list from DM, it will be available in `data` as a
  javascript object instead of an array. You can use it normally
  like so: `object.key`, so it's not a problem if it's representing a
  data structure, but common `Array` methods, such as `array.map(item => ...)`,
  are not available on it. Always prefer returning clean arrays from your
  code, since arrays are easier to work with in javascript!
- `act(name, params)` is a function, which you can call to dispatch an action
to your DM code. It will be processed in `ui_act` proc. Action name will be
available in `params["action"]`, mixed together with the rest of parameters
you have passed in `params` object.

**Let's talk about the syntax.**

The syntax you're seeing here is called JSX - a very simple extension of the
core javascript language. It's basically a pre-processor, that takes
expressions that look like html, and turns them into function calls.

Take a look at this example:

```jsx
<div className={'color-' + status}>
  You are in {status} condition!
</div>
```

After compiling the code above, this is what it becomes:

```js
createElement('div',
  { className: 'color-' + status },
  'You are in ', status, ' condition!');
```

It is very important to remember, that JSX is just a javascript expression
made out of `createElement` function calls. Naturally, this allows doing
all sorts of stuff on these expressions, just like you would with anything
else in javascript.

Take a look at these examples:

**Render an element inside of another element if `showProgress` is true.**

This example uses the `&&` operator (the logical AND). It returns
the first operand if it evaluates to `false`, and returns the second operand
if it evaluates to `true`.

If `showProgress` is `true`, the whole expression evaluates
to a `<ProgressBar />` element. If `showProgress` is `false`, the whole
expression evaluates to `false`, and `false` is not rendered by React.

```jsx
<Box>
  {showProgress && (
    <ProgressBar value={progress} />
  )}
</Box>
```

You can also use the `||` operator (the logical OR), which works the same way,
except it will return the second operand on `false` instead of `true`.

**Loop over the array to map every item to a corresponding React element.**

`Array.map()` is a method, that calls a function on every item in the array,
and builds a new array based on what was returned by that function.

```jsx
<LabeledList>
  {items.map(item => (
    <LabeledList.Item
      key={item.id}
      label={item.label}>
      {item.content}
    </LabeledList.Item>
  ))}
</LabeledList>
```

If you need more examples of what you can do with React, see the
[interface conversion guide](docs/converting-old-tgui-interfaces.md).

#### Splitting UIs into smaller, modular components

You interface will eventually get really, really big. The easiest thing
you can do in this situation, is divide and conquer. Grab a chunk of your
JSX code, and wrap it into a second, smaller React component:

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

export const SampleInterface = (props, context) => {
  return (
    <Window resizable>
      <Window.Content scrollable>
        <HealthStatus user="Jerry" />
      </Window.Content>
    </Window>
  );
};

const HealthStatus = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    user,
  } = props;
  const {
    health,
    color,
  } = data;
  return (
    <Section title={"Health status of: " + user}>
      <LabeledList>
        <LabeledList.Item label="Health">
          {health}
        </LabeledList.Item>
        <LabeledList.Item label="Color">
          {color}
        </LabeledList.Item>
      </LabeledList>
    </Section>
  );
};
```

## Copypasta

We all do it, even the best of us. If you just want to make a tgui **fast**,
here's what you need (note that you'll probably be forced to clean your shit up
upon code review):

```dm
/obj/copypasta/ui_interact(mob/user, datum/tgui/ui)
  ui = SStgui.try_update_ui(user, src, ui)
  if(!ui)
    ui = new(user, src, "copypasta")
    ui.open()

/obj/copypasta/ui_data(mob/user)
  var/list/data = list()
  data["var"] = var
  return data

/obj/copypasta/ui_act(action, params)
  if(..())
    return
  switch(action)
    if("copypasta")
      var/newvar = params["var"]
      // A demo of proper input sanitation.
      var = CLAMP(newvar, min_val, max_val)
      . = TRUE
  update_icon() // Not applicable to all objects.
```

And the template:

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

export const SampleInterface = (props, context) => {
  const { act, data } = useBackend(context);
  // Extract `health` and `color` variables from the `data` object.
  const {
    health,
    color,
  } = data;
  return (
    <Window resizable>
      <Window.Content scrollable>
        <Section title="Health status">
          <LabeledList>
            <LabeledList.Item label="Health">
              {health}
            </LabeledList.Item>
            <LabeledList.Item label="Color">
              {color}
            </LabeledList.Item>
            <LabeledList.Item label="Button">
              <Button
                content="Dispatch a 'test' action"
                onClick={() => act('test')} />
            </LabeledList.Item>
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};
```


code:
## Jest

You can now write and run unit tests in tgui.

It's quite simple: create a file ending in `.test.ts` or `.spec.ts` (usually with the same filename as the file you're testing), and create a test case:

```js
test('something', () => {
  expect('a').toBe('a');
});
```

To run the tests, type the following into the terminal:

```
bin/tgui --test
```

There is an example test in `packages/common/react.spec.ts`.

You can read more about Jest here: https://jestjs.io/docs/en/getting-started

Note, that there is still no real solution to test UIs for now, even though a lot of the support is here (jest + jsdom). That will come later.


code:
/**
 * @file
 * @copyright 2021 Aleksej Komarov
 * @license MIT
 */

// Webpack asset modules.
// Should match extensions used in webpack config.
declare module '*.png' {
  const content: string;
  export default content;
}

declare module '*.jpg' {
  const content: string;
  export default content;
}

declare module '*.svg' {
  const content: string;
  export default content;
}

type ByondType = {
  /**
   * True if javascript is running in BYOND.
   */
  IS_BYOND: boolean;

  /**
   * True if browser is IE8 or lower.
   */
  IS_LTE_IE8: boolean;

  /**
   * True if browser is IE9 or lower.
   */
  IS_LTE_IE9: boolean;

  /**
   * True if browser is IE10 or lower.
   */
  IS_LTE_IE10: boolean;

  /**
   * True if browser is IE11 or lower.
   */
  IS_LTE_IE11: boolean;

  /**
   * Makes a BYOND call.
   *
   * If path is empty, this will trigger a Topic call.
   * You can reference a specific object by setting the "src" parameter.
   *
   * See: https://secure.byond.com/docs/ref/skinparams.html
   */
  call(path: string, params: object): void;

  /**
   * Makes an asynchronous BYOND call. Returns a promise.
   */
  callAsync(path: string, params: object): Promise<any>;

  /**
   * Makes a Topic call.
   *
   * You can reference a specific object by setting the "src" parameter.
   */
  topic(params: object): void;

  /**
   * Runs a command or a verb.
   */
  command(command: string): void;

  /**
   * Retrieves all properties of the BYOND skin element.
   *
   * Returns a promise with a key-value object containing all properties.
   */
  winget(id: string): Promise<object>;

  /**
   * Retrieves all properties of the BYOND skin element.
   *
   * Returns a promise with a key-value object containing all properties.
   */
  winget(id: string, propName: '*'): Promise<object>;

  /**
   * Retrieves an exactly one property of the BYOND skin element,
   * as defined in `propName`.
   *
   * Returns a promise with the value of that property.
   */
  winget(id: string, propName: string): Promise<any>;

  /**
   * Retrieves multiple properties of the BYOND skin element,
   * as defined in the `propNames` array.
   *
   * Returns a promise with a key-value object containing listed properties.
   */
  winget(id: string, propNames: string[]): Promise<object>;

  /**
   * Assigns properties to BYOND skin elements.
   */
  winset(props: object): void;

  /**
   * Assigns properties to the BYOND skin element.
   */
  winset(id: string, props: object): void;

  /**
   * Sets a property on the BYOND skin element to a certain value.
   */
  winset(id: string, propName: string, propValue: any): void;

  /**
   * Parses BYOND JSON.
   *
   * Uses a special encoding to preverse Infinity and NaN.
   */
  parseJson(text: string): any;

  /**
   * Loads a stylesheet into the document.
   */
  loadCss(url: string): void;

  /**
   * Loads a script into the document.
   */
  loadJs(url: string): void;
};

/**
 * Object that provides access to Byond Skin API and is available in
 * any tgui application.
 */
const Byond: ByondType;

interface Window {
  /**
   * ID of the Byond window this script is running on.
   * Should be used as a parameter to winget/winset.
   */
  __windowId__: string;
  __updateQueue__: unknown[];
  update: (msg: unknown) => unknown;
  Byond: ByondType;
}


code:
module.exports = {
  roots: ['<rootDir>/packages'],
  testMatch: [
    '<rootDir>/packages/**/__tests__/*.{js,ts,tsx}',
    '<rootDir>/packages/**/*.{spec,test}.{js,ts,tsx}',
  ],
  testPathIgnorePatterns: [
    '<rootDir>/packages/tgui-bench',
  ],
  testEnvironment: 'jsdom',
  testRunner: require.resolve('jest-circus/runner'),
  transform: {
    '^.+\\.(js|cjs|ts|tsx)$': require.resolve('babel-jest'),
  },
  moduleFileExtensions: ['js', 'cjs', 'ts', 'tsx', 'json'],
  resetMocks: true,
};


tgui-workspace

tgui:build: webpack

tgui:analyze: webpack --analyze

tgui:lint: eslint packages --ext .js,.cjs,.ts,.tsx

tgui:sonar: eslint packages --ext .js,.cjs,.ts,.tsx -c .eslintrc-sonar.yml

tgui:tsc: tsc

tgui:test: jest --watch

tgui:test-simple: CI=true jest --color

tgui:test-ci: CI=true jest --color --collect-coverage

tgui:bench: webpack --env TGUI_BENCH=1 && node packages/tgui-bench/index.js


code:
import { range, zip } from "./collections";

// Type assertions, these will lint if the types are wrong.
const _zip1: [string, number] = zip(["a"], [1])[0];

describe("range", () => {
  test("range(0, 5)", () => {
    expect(range(0, 5)).toEqual([0, 1, 2, 3, 4]);
  });
});

describe("zip", () => {
  test("zip(['a', 'b', 'c'], [1, 2, 3, 4])", () => {
    expect(zip(["a", "b", "c"], [1, 2, 3, 4])).toEqual([
      ["a", 1],
      ["b", 2],
      ["c", 3],
    ]);
  });
});


code:
/**
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

/**
 * Iterates over elements of collection, returning an array of all elements
 * iteratee returns truthy for. The predicate is invoked with three
 * arguments: (value, index|key, collection).
 *
 * If collection is 'null' or 'undefined', it will be returned "as is"
 * without emitting any errors (which can be useful in some cases).
 *
 * @returns {any[]}
 */
export const filter = <T>(iterateeFn: (
  input: T,
  index: number,
  collection: T[],
) => boolean) =>
    (collection: T[]): T[] => {
      if (collection === null || collection === undefined) {
        return collection;
      }
      if (Array.isArray(collection)) {
        const result: T[] = [];
        for (let i = 0; i < collection.length; i++) {
          const item = collection[i];
          if (iterateeFn(item, i, collection)) {
            result.push(item);
          }
        }
        return result;
      }
      throw new Error(`filter() can't iterate on type ${typeof collection}`);
    };

type MapFunction = {
  <T, U>(iterateeFn: (
    value: T,
    index: number,
    collection: T[],
  ) => U): (collection: T[]) => U[];

  <T, U, K extends string | number>(iterateeFn: (
    value: T,
    index: K,
    collection: Record<K, T>,
  ) => U): (collection: Record<K, T>) => U[];
};

/**
 * Creates an array of values by running each element in collection
 * thru an iteratee function. The iteratee is invoked with three
 * arguments: (value, index|key, collection).
 *
 * If collection is 'null' or 'undefined', it will be returned "as is"
 * without emitting any errors (which can be useful in some cases).
 */
export const map: MapFunction = <T, U>(iterateeFn) =>
  (collection: T[]): U[] => {
    if (collection === null || collection === undefined) {
      return collection;
    }

    if (Array.isArray(collection)) {
      return collection.map(iterateeFn);
    }

    if (typeof collection === 'object') {
      return Object.entries(collection).map(([key, value]) => {
        return iterateeFn(value, key, collection);
      });
    }

    throw new Error(`map() can't iterate on type ${typeof collection}`);
  };

/**
 * Given a collection, will run each element through an iteratee function.
 * Will then filter out undefined values.
 */
export const filterMap = <T, U>(collection: T[], iterateeFn: (
  value: T
) => U | undefined): U[] => {
  const finalCollection: U[] = [];

  for (const value of collection) {
    const output = iterateeFn(value);
    if (output !== undefined) {
      finalCollection.push(output);
    }
  }

  return finalCollection;
};

const COMPARATOR = (objA, objB) => {
  const criteriaA = objA.criteria;
  const criteriaB = objB.criteria;
  const length = criteriaA.length;
  for (let i = 0; i < length; i++) {
    const a = criteriaA[i];
    const b = criteriaB[i];
    if (a < b) {
      return -1;
    }
    if (a > b) {
      return 1;
    }
  }
  return 0;
};

/**
 * Creates an array of elements, sorted in ascending order by the results
 * of running each element in a collection thru each iteratee.
 *
 * Iteratees are called with one argument (value).
 */
export const sortBy = <T>(
  ...iterateeFns: ((input: T) => unknown)[]
) => (array: T[]): T[] => {
    if (!Array.isArray(array)) {
      return array;
    }
    let length = array.length;
    // Iterate over the array to collect criteria to sort it by
    let mappedArray: {
      criteria: unknown[],
      value: T,
    }[] = [];
    for (let i = 0; i < length; i++) {
      const value = array[i];
      mappedArray.push({
        criteria: iterateeFns.map(fn => fn(value)),
        value,
      });
    }
    // Sort criteria using the base comparator
    mappedArray.sort(COMPARATOR);

    // Unwrap values
    const values: T[] = [];
    while (length--) {
      values[length] = mappedArray[length].value;
    }
    return values;
  };

export const sort = sortBy();

export const sortStrings = sortBy<string>();

/**
 * Returns a range of numbers from start to end, exclusively.
 * For example, range(0, 5) will return [0, 1, 2, 3, 4].
 */
export const range = (start: number, end: number): number[] =>
  new Array(end - start).fill(null).map((_, index) => index + start);

/**
 * A fast implementation of reduce.
 */
export const reduce = (reducerFn, initialValue) => array => {
  const length = array.length;
  let i;
  let result;
  if (initialValue === undefined) {
    i = 1;
    result = array[0];
  }
  else {
    i = 0;
    result = initialValue;
  }
  for (; i < length; i++) {
    result = reducerFn(result, array[i], i, array);
  }
  return result;
};

/**
 * Creates a duplicate-free version of an array, using SameValueZero for
 * equality comparisons, in which only the first occurrence of each element
 * is kept. The order of result values is determined by the order they occur
 * in the array.
 *
 * It accepts iteratee which is invoked for each element in array to generate
 * the criterion by which uniqueness is computed. The order of result values
 * is determined by the order they occur in the array. The iteratee is
 * invoked with one argument: value.
 */
export const uniqBy = <T extends unknown>(
  iterateeFn?: (value: T) => unknown
) => (array: T[]): T[] => {
    const { length } = array;
    const result: T[] = [];
    const seen: unknown[] = iterateeFn ? [] : result;
    let index = -1;
    outer:
    while (++index < length) {
      let value: T | 0 = array[index];
      const computed = iterateeFn ? iterateeFn(value) : value;
      if (computed === computed) {
        let seenIndex = seen.length;
        while (seenIndex--) {
          if (seen[seenIndex] === computed) {
            continue outer;
          }
        }
        if (iterateeFn) {
          seen.push(computed);
        }
        result.push(value);
      }
      else if (!seen.includes(computed)) {
        if (seen !== result) {
          seen.push(computed);
        }
        result.push(value);
      }
    }
    return result;
  };
/* eslint-enable indent */

export const uniq = uniqBy();

type Zip<T extends unknown[][]> = {
  [I in keyof T]: T[I] extends (infer U)[] ? U : never;
}[];

/**
 * Creates an array of grouped elements, the first of which contains
 * the first elements of the given arrays, the second of which contains
 * the second elements of the given arrays, and so on.
 */
export const zip = <T extends unknown[][]>(...arrays: T): Zip<T> => {
  if (arrays.length === 0) {
    return [];
  }
  const numArrays = arrays.length;
  const numValues = arrays[0].length;
  const result: Zip<T> = [];
  for (let valueIndex = 0; valueIndex < numValues; valueIndex++) {
    const entry: unknown[] = [];
    for (let arrayIndex = 0; arrayIndex < numArrays; arrayIndex++) {
      entry.push(arrays[arrayIndex][valueIndex]);
    }

    // I tried everything to remove this any, and have no idea how to do it.
    result.push(entry as any);
  }
  return result;
};

/**
 * This method is like "zip" except that it accepts iteratee to
 * specify how grouped values should be combined. The iteratee is
 * invoked with the elements of each group.
 */
export const zipWith = <T, U>(iterateeFn: (...values: T[]) => U) =>
  (...arrays: T[][]): U[] => {
    return map((values: T[]) => iterateeFn(...values))(zip(...arrays));
  };

const binarySearch = <T, U = unknown>(
  getKey: (value: T) => U,
  collection: readonly T[],
  inserting: T,
): number => {
  if (collection.length === 0) {
    return 0;
  }

  const insertingKey = getKey(inserting);

  let [low, high] = [0, collection.length];

  // Because we have checked if the collection is empty, it's impossible
  // for this to be used before assignment.
  let compare: U = undefined as unknown as U;
  let middle = 0;

  while (low < high) {
    middle = (low + high) >> 1;

    compare = getKey(collection[middle]);

    if (compare < insertingKey) {
      low = middle + 1;
    } else if (compare === insertingKey) {
      return middle;
    } else {
      high = middle;
    }
  }

  return compare > insertingKey ? middle : middle + 1;
};

export const binaryInsertWith = <T, U = unknown>(getKey: (value: T) => U):
  ((collection: readonly T[], value: T) => T[]) =>
{
  return (collection, value) => {
    const copy = [...collection];
    copy.splice(binarySearch(getKey, collection, value), 0, value);
    return copy;
  };
};


code:
/**
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

const EPSILON = 0.0001;

export class Color {
  constructor(r = 0, g = 0, b = 0, a = 1) {
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = a;
  }

  toString() {
    // Alpha component needs to permit fractional values, so cannot use |
    let alpha = parseFloat(this.a);
    if (isNaN(alpha)) {
      alpha = 1;
    }
    return `rgba(${this.r | 0}, ${this.g | 0}, ${this.b | 0}, ${alpha})`;
  }
}

/**
 * Creates a color from the CSS hex color notation.
 */
Color.fromHex = hex => (
  new Color(
    parseInt(hex.substr(1, 2), 16),
    parseInt(hex.substr(3, 2), 16),
    parseInt(hex.substr(5, 2), 16))
);

/**
 * Linear interpolation of two colors.
 */
Color.lerp = (c1, c2, n) => (
  new Color(
    (c2.r - c1.r) * n + c1.r,
    (c2.g - c1.g) * n + c1.g,
    (c2.b - c1.b) * n + c1.b,
    (c2.a - c1.a) * n + c1.a)
);

/**
 * Loops up the color in the provided list of colors
 * with linear interpolation.
 */
Color.lookup = (value, colors = []) => {
  const len = colors.length;
  if (len < 2) {
    throw new Error('Needs at least two colors!');
  }
  const scaled = value * (len - 1);
  if (value < EPSILON) {
    return colors[0];
  }
  if (value >= 1 - EPSILON) {
    return colors[len - 1];
  }
  const ratio = scaled % 1;
  const index = scaled | 0;
  return Color.lerp(colors[index], colors[index + 1], ratio);
};


code:
/**
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

export class EventEmitter {
  constructor() {
    this.listeners = {};
  }

  on(name, listener) {
    this.listeners[name] = this.listeners[name] || [];
    this.listeners[name].push(listener);
  }

  off(name, listener) {
    const listeners = this.listeners[name];
    if (!listeners) {
      throw new Error(`There is no listeners for "${name}"`);
    }
    this.listeners[name] = listeners
      .filter(existingListener => {
        return existingListener !== listener;
      });
  }

  emit(name, ...params) {
    const listeners = this.listeners[name];
    if (!listeners) {
      return;
    }
    for (let i = 0, len = listeners.length; i < len; i += 1) {
      const listener = listeners[i];
      listener(...params);
    }
  }

  clear() {
    this.listeners = {};
  }
}


code:
/**
 * Throws an error such that a non-exhaustive check will error at compile time
 * when using TypeScript, rather than at runtime.
 *
 * For example:
 * enum Color { Red, Green, Blue }
 * switch (color) {
 *  case Color.Red:
 *    return "red";
 *  case Color.Green:
 *    return "green";
 *  default:
 *    // This will error at compile time that we forgot blue.
 *    exhaustiveCheck(color);
 * }
 */
export const exhaustiveCheck = (input: never) => {
  throw new Error(`Unhandled case: ${input}`);
};


code:
/**
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

/**
 * Creates a function that returns the result of invoking the given
 * functions, where each successive invocation is supplied the return
 * value of the previous.
 */
export const flow = (...funcs) => (input, ...rest) => {
  let output = input;
  for (let func of funcs) {
    // Recurse into the array of functions
    if (Array.isArray(func)) {
      output = flow(...func)(output, ...rest);
    }
    else if (func) {
      output = func(output, ...rest);
    }
  }
  return output;
};

/**
 * Composes single-argument functions from right to left.
 *
 * All functions might accept a context in form of additional arguments.
 * If the resulting function is called with more than 1 argument, rest of
 * the arguments are passed to all functions unchanged.
 *
 * @param {...Function} funcs The functions to compose
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (input, ...rest) => f(g(h(input, ...rest), ...rest), ...rest)
 */
export const compose = (...funcs) => {
  if (funcs.length === 0) {
    return arg => arg;
  }
  if (funcs.length === 1) {
    return funcs[0];
  }
  return funcs.reduce((a, b) => (value, ...rest) =>
    a(b(value, ...rest), ...rest));
};


code:
/**
 * All possible browser keycodes, in one file.
 *
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

export const KEY_BACKSPACE = 8;
export const KEY_TAB = 9;
export const KEY_ENTER = 13;
export const KEY_SHIFT = 16;
export const KEY_CTRL = 17;
export const KEY_ALT = 18;
export const KEY_PAUSE = 19;
export const KEY_CAPSLOCK = 20;
export const KEY_ESCAPE = 27;
export const KEY_SPACE = 32;
export const KEY_PAGEUP = 33;
export const KEY_PAGEDOWN = 34;
export const KEY_END = 35;
export const KEY_HOME = 36;
export const KEY_LEFT = 37;
export const KEY_UP = 38;
export const KEY_RIGHT = 39;
export const KEY_DOWN = 40;
export const KEY_INSERT = 45;
export const KEY_DELETE = 46;
export const KEY_0 = 48;
export const KEY_1 = 49;
export const KEY_2 = 50;
export const KEY_3 = 51;
export const KEY_4 = 52;
export const KEY_5 = 53;
export const KEY_6 = 54;
export const KEY_7 = 55;
export const KEY_8 = 56;
export const KEY_9 = 57;
export const KEY_A = 65;
export const KEY_B = 66;
export const KEY_C = 67;
export const KEY_D = 68;
export const KEY_E = 69;
export const KEY_F = 70;
export const KEY_G = 71;
export const KEY_H = 72;
export const KEY_I = 73;
export const KEY_J = 74;
export const KEY_K = 75;
export const KEY_L = 76;
export const KEY_M = 77;
export const KEY_N = 78;
export const KEY_O = 79;
export const KEY_P = 80;
export const KEY_Q = 81;
export const KEY_R = 82;
export const KEY_S = 83;
export const KEY_T = 84;
export const KEY_U = 85;
export const KEY_V = 86;
export const KEY_W = 87;
export const KEY_X = 88;
export const KEY_Y = 89;
export const KEY_Z = 90;
export const KEY_F1 = 112;
export const KEY_F2 = 113;
export const KEY_F3 = 114;
export const KEY_F4 = 115;
export const KEY_F5 = 116;
export const KEY_F6 = 117;
export const KEY_F7 = 118;
export const KEY_F8 = 119;
export const KEY_F9 = 120;
export const KEY_F10 = 121;
export const KEY_F11 = 122;
export const KEY_F12 = 123;
export const KEY_SEMICOLON = 186;
export const KEY_EQUAL = 187;
export const KEY_COMMA = 188;
export const KEY_MINUS = 189;
export const KEY_PERIOD = 190;
export const KEY_SLASH = 191;
export const KEY_LEFT_BRACKET = 219;
export const KEY_BACKSLASH = 220;
export const KEY_RIGHT_BRACKET = 221;
export const KEY_QUOTE = 222;


code:
/**
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

/**
 * Limits a number to the range between 'min' and 'max'.
 */
export const clamp = (value, min, max) => {
  return value < min ? min : value > max ? max : value;
};

/**
 * Limits a number between 0 and 1.
 */
export const clamp01 = value => {
  return value < 0 ? 0 : value > 1 ? 1 : value;
};

/**
 * Scales a number to fit into the range between min and max.
 */
export const scale = (value, min, max) => {
  return (value - min) / (max - min);
};

/**
 * Robust number rounding.
 *
 * Adapted from Locutus, see: http://locutus.io/php/math/round/
 *
 * @param  {number} value
 * @param  {number} precision
 * @return {number}
 */
export const round = (value, precision) => {
  if (!value || isNaN(value)) {
    return value;
  }
  // helper variables
  let m, f, isHalf, sgn;
  // making sure precision is integer
  precision |= 0;
  m = Math.pow(10, precision);
  value *= m;
  // sign of the number
  sgn = +(value > 0) | -(value < 0);
  // isHalf = value % 1 === 0.5 * sgn;
  isHalf = Math.abs(value % 1) >= 0.4999999999854481;
  f = Math.floor(value);
  if (isHalf) {
    // rounds .5 away from zero
    value = f + (sgn > 0);
  }
  return (isHalf ? value : Math.round(value)) / m;
};

/**
 * Returns a string representing a number in fixed point notation.
 */
export const toFixed = (value, fractionDigits = 0) => {
  return Number(value).toFixed(Math.max(fractionDigits, 0));
};

/**
 * Checks whether a value is within the provided range.
 *
 * Range is an array of two numbers, for example: [0, 15].
 */
export const inRange = (value, range) => {
  return range
    && value >= range[0]
    && value <= range[1];
};

/**
 * Walks over the object with ranges, comparing value against every range,
 * and returns the key of the first matching range.
 *
 * Range is an array of two numbers, for example: [0, 15].
 */
export const keyOfMatchingRange = (value, ranges) => {
  for (let rangeName of Object.keys(ranges)) {
    const range = ranges[rangeName];
    if (inRange(value, range)) {
      return rangeName;
    }
  }
};

/**
 * Get number of digits following the decimal point in a number
 */
export const numberOfDecimalDigits = value => {
  if (Math.floor(value) !== value) {
    return value.toString().split('.')[1].length || 0;
  }
  return 0;
};


code:
/**
 * Ghetto performance measurement tools.
 *
 * Uses NODE_ENV to remove itself from production builds.
 *
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

const FPS = 60;
const FRAME_DURATION = 1000 / FPS;

// True if Performance API is supported
const supportsPerf = !!window.performance?.now;
// High precision markers
let hpMarkersByName = {};
// Low precision markers
let lpMarkersByName = {};

/**
 * Marks a certain spot in the code for later measurements.
 */
const mark = (name, timestamp) => {
  if (process.env.NODE_ENV !== 'production') {
    if (supportsPerf && !timestamp) {
      hpMarkersByName[name] = performance.now();
    }
    lpMarkersByName[name] = timestamp || Date.now();
  }
};

/**
 * Calculates and returns the difference between two markers as a string.
 *
 * Use logger.log() to print the measurement.
 */
const measure = (markerNameA, markerNameB) => {
  if (process.env.NODE_ENV !== 'production') {
    let markerA = hpMarkersByName[markerNameA];
    let markerB = hpMarkersByName[markerNameB];
    if (!markerA || !markerB) {
      markerA = lpMarkersByName[markerNameA];
      markerB = lpMarkersByName[markerNameB];
    }
    const duration = Math.abs(markerB - markerA);
    return formatDuration(duration);
  }
};

const formatDuration = duration => {
  const durationInFrames = duration / FRAME_DURATION;
  return duration.toFixed(duration < 10 ? 1 : 0) + 'ms '
    + '(' + durationInFrames.toFixed(2) + ' frames)';
};

export const perf = {
  mark,
  measure,
};


code:
/**
 * @file
 * @copyright 2021 Aleksej Komarov
 * @license MIT
 */

import { classes } from './react';

describe('classes', () => {
  test('empty', () => {
    expect(classes([])).toBe('');
  });

  test('result contains inputs', () => {
    const output = classes(['foo', 'bar', false, true, 0, 1, 'baz']);
    expect(output).toContain('foo');
    expect(output).toContain('bar');
    expect(output).toContain('baz');
  });
});


code:
/**
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

/**
 * Helper for conditionally adding/removing classes in React
 */
export const classes = (classNames: (string | BooleanLike)[]) => {
  let className = '';
  for (let i = 0; i < classNames.length; i++) {
    const part = classNames[i];
    if (typeof part === 'string') {
      className += part + ' ';
    }
  }
  return className;
};

/**
 * Normalizes children prop, so that it is always an array of VDom
 * elements.
 */
export const normalizeChildren = <T>(children: T | T[]) => {
  if (Array.isArray(children)) {
    return children.flat().filter(value => value) as T[];
  }
  if (typeof children === 'object') {
    return [children];
  }
  return [];
};

/**
 * Shallowly checks if two objects are different.
 * Credit: https://github.com/developit/preact-compat
 */
export const shallowDiffers = (a: object, b: object) => {
  let i;
  for (i in a) {
    if (!(i in b)) {
      return true;
    }
  }
  for (i in b) {
    if (a[i] !== b[i]) {
      return true;
    }
  }
  return false;
};

/**
 * Default inferno hooks for pure components.
 */
export const pureComponentHooks = {
  onComponentShouldUpdate: (lastProps, nextProps) => {
    return shallowDiffers(lastProps, nextProps);
  },
};

/**
 * A helper to determine whether the object is renderable by React.
 */
export const canRender = (value: unknown) => {
  return value !== undefined
    && value !== null
    && typeof value !== 'boolean';
};

/**
 * A common case in tgui, when you pass a value conditionally, these are
 * the types that can fall through the condition.
 */
export type BooleanLike = number | boolean | null | undefined;


code:
/**
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

import { compose } from './fp';

/**
 * Creates a Redux store.
 */
export const createStore = (reducer, enhancer) => {
  // Apply a store enhancer (applyMiddleware is one of them).
  if (enhancer) {
    return enhancer(createStore)(reducer);
  }

  let currentState;
  let listeners = [];

  const getState = () => currentState;

  const subscribe = listener => {
    listeners.push(listener);
  };

  const dispatch = action => {
    currentState = reducer(currentState, action);
    for (let i = 0; i < listeners.length; i++) {
      listeners[i]();
    }
  };

  // This creates the initial store by causing each reducer to be called
  // with an undefined state
  dispatch({
    type: '@@INIT',
  });

  return {
    dispatch,
    subscribe,
    getState,
  };
};

/**
 * Creates a store enhancer which applies middleware to all dispatched
 * actions.
 */
export const applyMiddleware = (...middlewares) => {
  return createStore => (reducer, ...args) => {
    const store = createStore(reducer, ...args);

    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed.');
    };

    const storeApi = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args),
    };

    const chain = middlewares.map(middleware => middleware(storeApi));
    dispatch = compose(...chain)(store.dispatch);

    return {
      ...store,
      dispatch,
    };
  };
};

/**
 * Combines reducers by running them in their own object namespaces as
 * defined in reducersObj paramter.
 *
 * Main difference from redux/combineReducers is that it preserves keys
 * in the state that are not present in the reducers object. This function
 * is also more flexible than the redux counterpart.
 */
export const combineReducers = reducersObj => {
  const keys = Object.keys(reducersObj);
  let hasChanged = false;
  return (prevState = {}, action) => {
    const nextState = { ...prevState };
    for (let key of keys) {
      const reducer = reducersObj[key];
      const prevDomainState = prevState[key];
      const nextDomainState = reducer(prevDomainState, action);
      if (prevDomainState !== nextDomainState) {
        hasChanged = true;
        nextState[key] = nextDomainState;
      }
    }
    return hasChanged
      ? nextState
      : prevState;
  };
};

/**
 * A utility function to create an action creator for the given action
 * type string. The action creator accepts a single argument, which will
 * be included in the action object as a field called payload. The action
 * creator function will also have its toString() overriden so that it
 * returns the action type, allowing it to be used in reducer logic that
 * is looking for that action type.
 *
 * @param {string} type The action type to use for created actions.
 * @param {any} prepare (optional) a method that takes any number of arguments
 * and returns { payload } or { payload, meta }. If this is given, the
 * resulting action creator will pass it's arguments to this method to
 * calculate payload & meta.
 *
 * @public
 */
export const createAction = (type, prepare = null) => {
  const actionCreator = (...args) => {
    if (!prepare) {
      return { type, payload: args[0] };
    }
    const prepared = prepare(...args);
    if (!prepared) {
      throw new Error('prepare function did not return an object');
    }
    const action = { type };
    if ('payload' in prepared) {
      action.payload = prepared.payload;
    }
    if ('meta' in prepared) {
      action.meta = prepared.meta;
    }
    return action;
  };
  actionCreator.toString = () => '' + type;
  actionCreator.type = type;
  actionCreator.match = action => action.type === type;
  return actionCreator;
};

// Implementation specific
// --------------------------------------------------------

export const useDispatch = context => {
  return context.store.dispatch;
};

export const useSelector = (context, selector) => {
  return selector(context.store.getState());
};


code:
/**
 * Browser-agnostic abstraction of key-value web storage.
 *
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

export const IMPL_MEMORY = 0;
export const IMPL_LOCAL_STORAGE = 1;
export const IMPL_INDEXED_DB = 2;

const INDEXED_DB_VERSION = 1;
const INDEXED_DB_NAME = 'tgui';
const INDEXED_DB_STORE_NAME = 'storage-v1';

const READ_ONLY = 'readonly';
const READ_WRITE = 'readwrite';

const testGeneric = testFn => () => {
  try {
    return Boolean(testFn());
  }
  catch {
    return false;
  }
};

// Localstorage can sometimes throw an error, even if DOM storage is not
// disabled in IE11 settings.
// See: https://superuser.com/questions/1080011
const testLocalStorage = testGeneric(() => (
  window.localStorage && window.localStorage.getItem
));

const testIndexedDb = testGeneric(() => (
  (window.indexedDB || window.msIndexedDB)
  && (window.IDBTransaction || window.msIDBTransaction)
));

class MemoryBackend {
  constructor() {
    this.impl = IMPL_MEMORY;
    this.store = {};
  }

  get(key) {
    return this.store[key];
  }

  set(key, value) {
    this.store[key] = value;
  }

  remove(key) {
    this.store[key] = undefined;
  }

  clear() {
    this.store = {};
  }
}

class LocalStorageBackend {
  constructor() {
    this.impl = IMPL_LOCAL_STORAGE;
  }

  get(key) {
    const value = localStorage.getItem(key);
    if (typeof value === 'string') {
      return JSON.parse(value);
    }
  }

  set(key, value) {
    localStorage.setItem(key, JSON.stringify(value));
  }

  remove(key) {
    localStorage.removeItem(key);
  }

  clear() {
    localStorage.clear();
  }
}

class IndexedDbBackend {
  constructor() {
    this.impl = IMPL_INDEXED_DB;
    /** @type {Promise<IDBDatabase>} */
    this.dbPromise = new Promise((resolve, reject) => {
      const indexedDB = window.indexedDB || window.msIndexedDB;
      const req = indexedDB.open(INDEXED_DB_NAME, INDEXED_DB_VERSION);
      req.onupgradeneeded = () => {
        try {
          req.result.createObjectStore(INDEXED_DB_STORE_NAME);
        }
        catch (err) {
          reject(new Error('Failed to upgrade IDB: ' + req.error));
        }
      };
      req.onsuccess = () => resolve(req.result);
      req.onerror = () => {
        reject(new Error('Failed to open IDB: ' + req.error));
      };
    });
  }

  getStore(mode) {
    return this.dbPromise.then(db => db
      .transaction(INDEXED_DB_STORE_NAME, mode)
      .objectStore(INDEXED_DB_STORE_NAME));
  }

  async get(key) {
    const store = await this.getStore(READ_ONLY);
    return new Promise((resolve, reject) => {
      const req = store.get(key);
      req.onsuccess = () => resolve(req.result);
      req.onerror = () => reject(req.error);
    });
  }

  async set(key, value) {
    // The reason we don't _save_ null is because IE 10 does
    // not support saving the `null` type in IndexedDB. How
    // ironic, given the bug below!
    // See: https://github.com/mozilla/localForage/issues/161
    if (value === null) {
      value = undefined;
    }
    // NOTE: We deliberately make this operation transactionless
    const store = await this.getStore(READ_WRITE);
    store.put(value, key);
  }

  async remove(key) {
    // NOTE: We deliberately make this operation transactionless
    const store = await this.getStore(READ_WRITE);
    store.delete(key);
  }

  async clear() {
    // NOTE: We deliberately make this operation transactionless
    const store = await this.getStore(READ_WRITE);
    store.clear();
  }
}

/**
 * Web Storage Proxy object, which selects the best backend available
 * depending on the environment.
 */
class StorageProxy {
  constructor() {
    this.backendPromise = (async () => {
      if (testIndexedDb()) {
        try {
          const backend = new IndexedDbBackend();
          await backend.dbPromise;
          return backend;
        }
        catch {}
      }
      if (testLocalStorage()) {
        return new LocalStorageBackend();
      }
      return new MemoryBackend();
    })();
  }

  async get(key) {
    const backend = await this.backendPromise;
    return backend.get(key);
  }

  async set(key, value) {
    const backend = await this.backendPromise;
    return backend.set(key, value);
  }

  async remove(key) {
    const backend = await this.backendPromise;
    return backend.remove(key);
  }

  async clear() {
    const backend = await this.backendPromise;
    return backend.clear();
  }
}

export const storage = new StorageProxy();


code:
/**
 * This plugin saves overall about 10KB on the final bundle size, so it's
 * sort of worth it.
 *
 * We are using a .cjs extension because:
 *
 * 1. Webpack CLI only supports CommonJS modules;
 * 2. tgui-dev-server supports both, but we still need to signal NodeJS
 * to import it as a CommonJS module, hence .cjs extension.
 *
 * We need to copy-paste the whole "multiline" function because we can't
 * synchronously import an ES module from a CommonJS module.
 *
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

/**
 * Removes excess whitespace and indentation from the string.
 */
const multiline = str => {
  const lines = str.split('\n');
  // Determine base indentation
  let minIndent;
  for (let line of lines) {
    for (let indent = 0; indent < line.length; indent++) {
      const char = line[indent];
      if (char !== ' ') {
        if (minIndent === undefined || indent < minIndent) {
          minIndent = indent;
        }
        break;
      }
    }
  }
  if (!minIndent) {
    minIndent = 0;
  }
  // Remove this base indentation and trim the resulting string
  // from both ends.
  return lines
    .map(line => line.substr(minIndent).trimRight())
    .join('\n')
    .trim();
};

const StringPlugin = ref => {
  return {
    visitor: {
      TaggedTemplateExpression: path => {
        if (path.node.tag.name === 'multiline') {
          const { quasi } = path.node;
          if (quasi.expressions.length > 0) {
            throw new Error('Multiline tag does not support expressions!');
          }
          if (quasi.quasis.length > 1) {
            throw new Error('Quasis is longer than 1');
          }
          const { value } = quasi.quasis[0];
          value.raw = multiline(value.raw);
          value.cooked = multiline(value.cooked);
          path.replaceWith(quasi);
        }
      },
    },
  };
};

module.exports = {
  __esModule: true,
  default: StringPlugin,
};


code:
/**
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

/**
 * Removes excess whitespace and indentation from the string.
 */
export const multiline = str => {
  if (Array.isArray(str)) {
    // Small stub to allow usage as a template tag
    return multiline(str.join(''));
  }
  const lines = str.split('\n');
  // Determine base indentation
  let minIndent;
  for (let line of lines) {
    for (let indent = 0; indent < line.length; indent++) {
      const char = line[indent];
      if (char !== ' ') {
        if (minIndent === undefined || indent < minIndent) {
          minIndent = indent;
        }
        break;
      }
    }
  }
  if (!minIndent) {
    minIndent = 0;
  }
  // Remove this base indentation and trim the resulting string
  // from both ends.
  return lines
    .map(line => line.substr(minIndent).trimRight())
    .join('\n')
    .trim();
};

/**
 * Creates a glob pattern matcher.
 *
 * Matches strings with wildcards.
 *
 * Example: createGlobPattern('*@domain')('user@domain') === true
 */
export const createGlobPattern = pattern => {
  const escapeString = str => str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
  const regex = new RegExp('^'
    + pattern.split(/\*+/).map(escapeString).join('.*')
    + '$');
  return str => regex.test(str);
};

/**
 * Creates a search terms matcher.
 *
 * Returns true if given string matches the search text.
 *
 * @template T
 * @param {string} searchText
 * @param {(obj: T) => string} stringifier
 * @returns {(obj: T) => boolean}
 */
export const createSearch = (searchText, stringifier) => {
  const preparedSearchText = searchText.toLowerCase().trim();
  return obj => {
    if (!preparedSearchText) {
      return true;
    }
    const str = stringifier ? stringifier(obj) : obj;
    if (!str) {
      return false;
    }
    return str
      .toLowerCase()
      .includes(preparedSearchText);
  };
};

export const capitalize = str => {
  // Handle array
  if (Array.isArray(str)) {
    return str.map(capitalize);
  }
  // Handle string
  return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
};

export const toTitleCase = str => {
  // Handle array
  if (Array.isArray(str)) {
    return str.map(toTitleCase);
  }
  // Pass non-string
  if (typeof str !== 'string') {
    return str;
  }
  // Handle string
  const WORDS_UPPER = ['Id', 'Tv'];
  const WORDS_LOWER = [
    'A', 'An', 'And', 'As', 'At', 'But', 'By', 'For', 'For', 'From', 'In',
    'Into', 'Near', 'Nor', 'Of', 'On', 'Onto', 'Or', 'The', 'To', 'With',
  ];
  let currentStr = str.replace(/([^\W_]+[^\s-]*) */g, str => {
    return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
  });
  for (let word of WORDS_LOWER) {
    const regex = new RegExp('\\s' + word + '\\s', 'g');
    currentStr = currentStr.replace(regex, str => str.toLowerCase());
  }
  for (let word of WORDS_UPPER) {
    const regex = new RegExp('\\b' + word + '\\b', 'g');
    currentStr = currentStr.replace(regex, str => str.toLowerCase());
  }
  return currentStr;
};

/**
 * Decodes HTML entities, and removes unnecessary HTML tags.
 *
 * @param  {String} str Encoded HTML string
 * @return {String} Decoded HTML string
 */
export const decodeHtmlEntities = str => {
  if (!str) {
    return str;
  }
  const translate_re = /&(nbsp|amp|quot|lt|gt|apos);/g;
  const translate = {
    nbsp: ' ',
    amp: '&',
    quot: '"',
    lt: '<',
    gt: '>',
    apos: '\'',
  };
  return str
    // Newline tags
    .replace(/<br>/gi, '\n')
    .replace(/<\/?[a-z0-9-_]+[^>]*>/gi, '')
    // Basic entities
    .replace(translate_re, (match, entity) => translate[entity])
    // Decimal entities
    .replace(/&#?([0-9]+);/gi, (match, numStr) => {
      const num = parseInt(numStr, 10);
      return String.fromCharCode(num);
    })
    // Hex entities
    .replace(/&#x?([0-9a-f]+);/gi, (match, numStr) => {
      const num = parseInt(numStr, 16);
      return String.fromCharCode(num);
    });
};

/**
 * Converts an object into a query string,
 */
export const buildQueryString = obj => Object.keys(obj)
  .map(key => encodeURIComponent(key)
    + '=' + encodeURIComponent(obj[key]))
  .join('&');


code:
/**
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

/**
 * Returns a function, that, as long as it continues to be invoked, will
 * not be triggered. The function will be called after it stops being
 * called for N milliseconds. If `immediate` is passed, trigger the
 * function on the leading edge, instead of the trailing.
 */
export const debounce = (fn, time, immediate = false) => {
  let timeout;
  return (...args) => {
    const later = () => {
      timeout = null;
      if (!immediate) {
        fn(...args);
      }
    };
    const callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, time);
    if (callNow) {
      fn(...args);
    }
  };
};

/**
 * Suspends an asynchronous function for N milliseconds.
 *
 * @param {number} time
 */
export const sleep = time => (
  new Promise(resolve => setTimeout(resolve, time))
);


code:
/**
 * Returns the arguments of a function F as an array.
 */
export type ArgumentsOf<F extends Function>
  = F extends (...args: infer A) => unknown ? A : never;


code:
/**
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

/**
 * Creates a UUID v4 string
 *
 * @return {string}
 */
export const createUuid = () => {
  let d = new Date().getTime();
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = (d + Math.random() * 16) % 16 | 0;
    d = Math.floor(d / 16);
    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
  });
};


code:
/**
 * N-dimensional vector manipulation functions.
 *
 * Vectors are plain number arrays, i.e. [x, y, z].
 *
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

import { map, reduce, zipWith } from './collections';

const ADD = (a, b) => a + b;
const SUB = (a, b) => a - b;
const MUL = (a, b) => a * b;
const DIV = (a, b) => a / b;

export const vecAdd = (...vecs) => {
  return reduce((a, b) => zipWith(ADD)(a, b))(vecs);
};

export const vecSubtract = (...vecs) => {
  return reduce((a, b) => zipWith(SUB)(a, b))(vecs);
};

export const vecMultiply = (...vecs) => {
  return reduce((a, b) => zipWith(MUL)(a, b))(vecs);
};

export const vecDivide = (...vecs) => {
  return reduce((a, b) => zipWith(DIV)(a, b))(vecs);
};

export const vecScale = (vec, n) => {
  return map(x => x * n)(vec);
};

export const vecInverse = vec => {
  return map(x => -x)(vec);
};

export const vecLength = vec => {
  return Math.sqrt(reduce(ADD)(zipWith(MUL)(vec, vec)));
};

export const vecNormalize = vec => {
  return vecDivide(vec, vecLength(vec));
};


code:
/**
 * @file
 * @copyright 2021 AnturK https://github.com/AnturK
 * @license MIT
 */

module.exports = {
  name: 'tgfont',
  inputDir: './icons',
  outputDir: './dist',
  fontTypes: ['woff2', 'eot'],
  assetTypes: ['css'],
  prefix: 'tg',
};


code:
bad-touch.svg contains:
- hug by Phạm Thanh Lộc from the Noun Project
- Fight by Rudez Studio from the Noun Project

prosthetic-leg.svg contains:
- prosthetic leg by Gan Khoon Lay from the Noun Project


code:
/**
 * @file
 * @copyright 2021 AnturK https://github.com/AnturK
 * @license MIT
 */

// Change working directory to project root
process.chdir(__dirname);

// Silently make a dist folder
try {
  require('fs').mkdirSync('dist');
}
catch (err) {}


tgfont

tgfont:build: node mkdist.cjs && fantasticon --config config.cjs


code:
/**
 * @file
 * @copyright 2021 Aleksej Komarov
 * @license MIT
 */

import { setupGlobalEvents } from 'tgui/events';
import 'tgui/styles/main.scss';
import Benchmark from './lib/benchmark';

const sendMessage = (obj: any) => {
  const req = new XMLHttpRequest();
  req.open('POST', `/message`, false);
  req.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
  req.timeout = 250;
  req.send(JSON.stringify(obj));
};

const setupApp = async () => {
  // Delay setup
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', setupApp);
    return;
  }

  setupGlobalEvents({
    ignoreWindowFocus: true,
  });

  const requireTest = require.context('./tests', false, /\.test\./);

  for (const file of requireTest.keys()) {
    sendMessage({ type: 'suite-start', file });
    try {
      const tests = requireTest(file);
      await new Promise<void>((resolve) => {
        const suite = new Benchmark.Suite(file, {
          onCycle(e) {
            sendMessage({
              type: 'suite-cycle',
              message: String(e.target),
            });
          },
          onComplete() {
            // This message is somewhat useless, but leaving it here in case
            // someone has an idea how to show more useful data.
            // sendMessage({
            //   type: 'suite-complete',
            //   message: 'Fastest is ' + this.filter('fastest').map('name'),
            // });
            resolve();
          },
          onError(e) {
            sendMessage({ type: 'error', e });
            resolve();
          },
        });
        for (const [name, fn] of Object.entries(tests)) {
          if (typeof fn === 'function') {
            suite.add(name, fn);
          }
        }
        suite.run();
      });
    }
    catch (error) {
      sendMessage({ type: 'error', error });
    }
  }
  sendMessage({ type: 'finished' });
};

setupApp();


code:
/**
 * @file
 * @copyright 2021 Aleksej Komarov
 * @license MIT
 */

const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');
const { fastify } = require('fastify');

process.chdir(__dirname);

const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time));

const IE_TIMEOUT_SECONDS = 60;

const setup = async () => {
  const server = fastify();

  let hasResponded = false;

  let assets = '';
  assets += `<script>\n`;
  assets += `Byond.loadJs('tgui-bench.bundle.js');\n`;
  assets += `Byond.loadCss('tgui-bench.bundle.css');\n`;
  assets += `</script>\n`;

  const publicDir = path.resolve(__dirname, '../../public');
  const page = fs.readFileSync(path.join(publicDir, 'tgui.html'), 'utf-8')
    .replace('<!-- tgui:assets -->\n', assets);

  server.register(require('fastify-static'), {
    root: publicDir,
  });

  server.get('/', async (req, res) => {
    return res.type('text/html').send(page);
  });

  server.post('/message', async (req, res) => {
    if (!hasResponded) {
      process.stdout.write('\n');
      hasResponded = true;
    }
    const { type, ...rest } = req.body;
    if (type === 'suite-start') {
      console.log(`=> Test '${rest.file}'`);
      return res.send();
    }
    if (type === 'suite-cycle') {
      console.log(rest.message);
      return res.send();
    }
    if (type === 'suite-complete') {
      console.log(rest.message);
      return res.send();
    }
    if (type === 'finished') {
      await res.send();
      process.exit(0);
    }
    // Unhandled message
    console.log(req.body);
    return res.send();
  });

  try {
    await server.listen(3002, '0.0.0.0');
  }
  catch (err) {
    console.error(err);
    process.exit(1);
  }

  if (process.platform === 'win32') {
    exec(`start "" "iexplore" "http://127.0.0.1:3002"`);
  }

  console.log('Waiting for Internet Explorer to respond.');
  for (let i = 0; i < IE_TIMEOUT_SECONDS; i++) {
    await sleep(1000);
    if (hasResponded) {
      return;
    }
    process.stdout.write('.');
  }
  process.stdout.write('\n');
  console.error('Did not receive a response, exiting.');
  process.exit(1);
};

setup();


code:
/* eslint-disable */
// Type definitions for Benchmark v2.1.4
// Project: https://benchmarkjs.com
// Definitions by: Asana <https://asana.com>
//                 Charlie Fish <https://github.com/fishcharlie>
//                 Blair Zajac <https://github.com/blair>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped

declare class Benchmark {
  static filter<T>(arr: T[], callback: (value: T) => any, thisArg?: any): T[];
  static filter<T>(arr: T[], filter: string, thisArg?: any): T[];
  static formatNumber(num: number): string;
  static join(obj: Object, separator1?: string, separator2?: string): string;
  static invoke(
    benches: Benchmark[],
    name: string | Object,
    ...args: any[]
  ): any[];
  static runInContext(context: Object): Function;

  static each(obj: Object | any[], callback: Function, thisArg?: any): void;
  static forEach<T>(arr: T[], callback: (value: T) => any, thisArg?: any): void;
  static forOwn(obj: Object, callback: Function, thisArg?: any): void;
  static has(obj: Object, path: any[] | string): boolean;
  static indexOf<T>(arr: T[], value: T, fromIndex?: number): number;
  static map<T, K>(arr: T[], callback: (value: T) => K, thisArg?: any): K[];
  static reduce<T, K>(
    arr: T[],
    callback: (accumulator: K, value: T) => K,
    thisArg?: any
  ): K;

  static options: Benchmark.Options;
  static platform: Benchmark.Platform;
  static support: Benchmark.Support;
  static version: string;

  constructor(fn: Function | string, options?: Benchmark.Options);
  constructor(name: string, fn: Function | string, options?: Benchmark.Options);
  constructor(name: string, options?: Benchmark.Options);
  constructor(options: Benchmark.Options);

  id: number;
  name: string;
  count: number;
  cycles: number;
  hz: number;
  compiled: Function | string;
  error: Error;
  fn: Function | string;
  aborted: boolean;
  running: boolean;
  setup: Function | string;
  teardown: Function | string;

  stats: Benchmark.Stats;
  times: Benchmark.Times;

  abort(): Benchmark;
  clone(options: Benchmark.Options): Benchmark;
  compare(benchmark: Benchmark): number;
  emit(type: string | Object): any;
  listeners(type: string): Function[];
  off(type?: string, listener?: Function): Benchmark;
  off(types: string[]): Benchmark;
  on(type?: string, listener?: Function): Benchmark;
  on(types: string[]): Benchmark;
  reset(): Benchmark;
  run(options?: Benchmark.Options): Benchmark;
  toString(): string;
}

declare namespace Benchmark {
  export interface Options {
    async?: boolean | undefined;
    defer?: boolean | undefined;
    delay?: number | undefined;
    id?: string | undefined;
    initCount?: number | undefined;
    maxTime?: number | undefined;
    minSamples?: number | undefined;
    minTime?: number | undefined;
    name?: string | undefined;
    onAbort?: Function | undefined;
    onComplete?: Function | undefined;
    onCycle?: Function | undefined;
    onError?: Function | undefined;
    onReset?: Function | undefined;
    onStart?: Function | undefined;
    setup?: Function | string | undefined;
    teardown?: Function | string | undefined;
    fn?: Function | string | undefined;
    queued?: boolean | undefined;
  }

  export interface Platform {
    description: string;
    layout: string;
    product: string;
    name: string;
    manufacturer: string;
    os: string;
    prerelease: string;
    version: string;
    toString(): string;
  }

  export interface Support {
    browser: boolean;
    timeout: boolean;
    decompilation: boolean;
  }

  export interface Stats {
    moe: number;
    rme: number;
    sem: number;
    deviation: number;
    mean: number;
    sample: any[];
    variance: number;
  }

  export interface Times {
    cycle: number;
    elapsed: number;
    period: number;
    timeStamp: number;
  }

  export class Deferred {
    constructor(clone: Benchmark);

    benchmark: Benchmark;
    cycles: number;
    elapsed: number;
    timeStamp: number;

    resolve(): void;
  }

  export interface Target {
    options: Options;
    async?: boolean | undefined;
    defer?: boolean | undefined;
    delay?: number | undefined;
    initCount?: number | undefined;
    maxTime?: number | undefined;
    minSamples?: number | undefined;
    minTime?: number | undefined;
    name?: string | undefined;
    fn?: Function | undefined;
    id: number;
    stats?: Stats | undefined;
    times?: Times | undefined;
    running: boolean;
    count?: number | undefined;
    compiled?: Function | undefined;
    cycles?: number | undefined;
    hz?: number | undefined;
  }

  export class Event {
    constructor(type: string | Object);

    aborted: boolean;
    cancelled: boolean;
    currentTarget: Object;
    result: any;
    target: Target;
    timeStamp: number;
    type: string;
  }

  export class Suite {
    static options: { name: string };

    constructor(name?: string, options?: Options);

    length: number;
    aborted: boolean;
    running: boolean;

    abort(): Suite;
    add(name: string, fn: Function | string, options?: Options): Suite;
    add(fn: Function | string, options?: Options): Suite;
    add(name: string, options?: Options): Suite;
    add(options: Options): Suite;
    clone(options: Options): Suite;
    emit(type: string | Object): any;
    filter(callback: Function | string): Suite;
    join(separator?: string): string;
    listeners(type: string): Function[];
    off(type?: string, callback?: Function): Suite;
    off(types: string[]): Suite;
    on(type?: string, callback?: Function): Suite;
    on(types: string[]): Suite;
    push(benchmark: Benchmark): number;
    reset(): Suite;
    run(options?: Options): Suite;
    reverse(): any[];
    sort(compareFn: (a: any, b: any) => number): any[];
    splice(start: number, deleteCount?: number): any[];
    unshift(benchmark: Benchmark): number;

    each(callback: Function): Suite;
    forEach(callback: Function): Suite;
    indexOf(value: any): number;
    map(callback: Function | string): any[];
    reduce<T>(callback: Function, accumulator: T): T;

    pop(): Function;
    shift(): Benchmark;
    slice(start: number, end: number): any[];
    slice(start: number, deleteCount: number, ...values: any[]): any[];
  }
}

export = Benchmark;