code:
/**
 * @file
 * @copyright 2013 Andrea Giammarchi, WebReflection
 * @license MIT
 */

/* eslint-disable */
(function(window){
  /*! (C) WebReflection Mit Style License */
  if (document.createEvent) return;
  var
    DUNNOABOUTDOMLOADED = true,
    READYEVENTDISPATCHED = false,
    ONREADYSTATECHANGE = 'onreadystatechange',
    DOMCONTENTLOADED = 'DOMContentLoaded',
    SECRET = '__IE8__' + Math.random(),
    // Object = window.Object,
    defineProperty = Object.defineProperty ||
    // just in case ...
    function (object, property, descriptor) {
      object[property] = descriptor.value;
    },
    defineProperties = Object.defineProperties ||
    // IE8 implemented defineProperty but not the plural...
    function (object, descriptors) {
      for(var key in descriptors) {
        if (hasOwnProperty.call(descriptors, key)) {
          try {
            defineProperty(object, key, descriptors[key]);
          } catch(o_O) {
            if (window.console) {
              console.log(key + ' failed on object:', object, o_O.message);
            }
          }
        }
      }
    },
    getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
    hasOwnProperty = Object.prototype.hasOwnProperty,
    // here IE7 will break like a charm
    ElementPrototype = window.Element.prototype,
    TextPrototype = window.Text.prototype,
    // none of above native constructors exist/are exposed
    possiblyNativeEvent = /^[a-z]+$/,
    // ^ actually could probably be just /^[a-z]+$/
    readyStateOK = /loaded|complete/,
    types = {},
    div = document.createElement('div'),
    html = document.documentElement,
    removeAttribute = html.removeAttribute,
    setAttribute = html.setAttribute,
    valueDesc = function (value) {
      return {
        enumerable: true,
        writable: true,
        configurable: true,
        value: value
      };
    }
  ;

  function commonEventLoop(currentTarget, e, $handlers, synthetic) {
    for(var
      handler,
      continuePropagation,
      handlers = $handlers.slice(),
      evt = enrich(e, currentTarget),
      i = 0, length = handlers.length; i < length; i++
    ) {
      handler = handlers[i];
      if (typeof handler === 'object') {
        if (typeof handler.handleEvent === 'function') {
          handler.handleEvent(evt);
        }
      } else {
        handler.call(currentTarget, evt);
      }
      if (evt.stoppedImmediatePropagation) break;
    }
    continuePropagation = !evt.stoppedPropagation;
    /*
    if (continuePropagation && !synthetic && !live(currentTarget)) {
      evt.cancelBubble = true;
    }
    */
    return (
      synthetic &&
      continuePropagation &&
      currentTarget.parentNode
    ) ?
      currentTarget.parentNode.dispatchEvent(evt) :
      !evt.defaultPrevented
    ;
  }

  function commonDescriptor(get, set) {
    return {
      // if you try with enumerable: true
      // IE8 will miserably fail
      configurable: true,
      get: get,
      set: set
    };
  }

  function commonTextContent(protoDest, protoSource, property) {
    var descriptor = getOwnPropertyDescriptor(
      protoSource || protoDest, property
    );
    defineProperty(
      protoDest,
      'textContent',
      commonDescriptor(
        function () {
          return descriptor.get.call(this);
        },
        function (textContent) {
          descriptor.set.call(this, textContent);
        }
      )
    );
  }

  function enrich(e, currentTarget) {
    e.currentTarget = currentTarget;
    e.eventPhase = (
      // AT_TARGET : BUBBLING_PHASE
      e.target === e.currentTarget ? 2 : 3
    );
    return e;
  }

  function find(array, value) {
    var i = array.length;
    while(i-- && array[i] !== value);
    return i;
  }

  function getTextContent() {
    if (this.tagName === 'BR') return '\n';
    var
      textNode = this.firstChild,
      arrayContent = []
    ;
    while(textNode) {
      if (textNode.nodeType !== 8 && textNode.nodeType !== 7) {
        arrayContent.push(textNode.textContent);
      }
      textNode = textNode.nextSibling;
    }
    return arrayContent.join('');
  }

  function live(self) {
    return self.nodeType !== 9 && html.contains(self);
  }

  function onkeyup(e) {
    var evt = document.createEvent('Event');
    evt.initEvent('input', true, true);
    (e.srcElement || e.fromElement || document).dispatchEvent(evt);
  }

  function onReadyState(e) {
    if (!READYEVENTDISPATCHED && readyStateOK.test(
      document.readyState
    )) {
      READYEVENTDISPATCHED = !READYEVENTDISPATCHED;
      document.detachEvent(ONREADYSTATECHANGE, onReadyState);
      e = document.createEvent('Event');
      e.initEvent(DOMCONTENTLOADED, true, true);
      document.dispatchEvent(e);
    }
  }

  function getter(attr) {
    return function() {
      return html[attr] || (document.body && document.body[attr]) || 0;
    };
  }

  function setTextContent(textContent) {
    var node;
    while ((node = this.lastChild)) {
      this.removeChild(node);
    }
    /*jshint eqnull:true */
    if (textContent != null) {
      this.appendChild(document.createTextNode(textContent));
    }
  }

  function verify(self, e) {
    if (!e) {
      e = window.event;
    }
    if (!e.target) {
      e.target = e.srcElement || e.fromElement || document;
    }
    if (!e.timeStamp) {
      e.timeStamp = (new Date()).getTime();
    }
    return e;
  }

  // normalized textContent for:
  //  comment, script, style, text, title
  commonTextContent(
    window.HTMLCommentElement.prototype,
    ElementPrototype,
    'nodeValue'
  );

  commonTextContent(
    window.HTMLScriptElement.prototype,
    null,
    'text'
  );

  commonTextContent(
    TextPrototype,
    null,
    'nodeValue'
  );

  commonTextContent(
    window.HTMLTitleElement.prototype,
    null,
    'text'
  );

  defineProperty(
    window.HTMLStyleElement.prototype,
    'textContent',
    (function(descriptor){
      return commonDescriptor(
        function () {
          return descriptor.get.call(this.styleSheet);
        },
        function (textContent) {
          descriptor.set.call(this.styleSheet, textContent);
        }
      );
    }(getOwnPropertyDescriptor(window.CSSStyleSheet.prototype, 'cssText')))
  );

  var opacityre = /\b\s*alpha\s*\(\s*opacity\s*=\s*(\d+)\s*\)/;
  defineProperty(
    window.CSSStyleDeclaration.prototype,
    'opacity', {
      get: function() {
        var m = this.filter.match(opacityre);
        return m ? (m[1] / 100).toString() : '';
      },
      set: function(value) {
        this.zoom = 1;
        var found = false;
        if (value < 1) {
          value = ' alpha(opacity=' + Math.round(value * 100) + ')';
        }
        else {
          value = '';
        }
        this.filter = this.filter.replace(opacityre,
                        function() { found = true; return value; });
        if (!found && value) {
          this.filter += value;
        }
      }
    }
  );

  defineProperties(
    ElementPrototype,
    {
      // bonus
      textContent: {
        get: getTextContent,
        set: setTextContent
      },
      // http://www.w3.org/TR/ElementTraversal/#interface-elementTraversal
      firstElementChild: {
        get: function () {
          for(var
            childNodes = this.childNodes || [],
            i = 0, length = childNodes.length;
            i < length; i++
          ) {
            if (childNodes[i].nodeType == 1) return childNodes[i];
          }
        }
      },
      lastElementChild: {
        get: function () {
          for(var
            childNodes = this.childNodes || [],
            i = childNodes.length;
            i--;
          ) {
            if (childNodes[i].nodeType == 1) return childNodes[i];
          }
        }
      },
      oninput: {
        get: function () {
          return this._oninput || null;
        },
        set: function (oninput) {
          if (this._oninput) {
            this.removeEventListener('input', this._oninput);
            this._oninput = oninput;
            if (oninput) {
              this.addEventListener('input', oninput);
            }
          }
        }
      },
      previousElementSibling: {
        get: function () {
          var previousElementSibling = this.previousSibling;
          while (previousElementSibling && previousElementSibling.nodeType != 1) {
            previousElementSibling = previousElementSibling.previousSibling;
          }
          return previousElementSibling;
        }
      },
      nextElementSibling: {
        get: function () {
          var nextElementSibling = this.nextSibling;
          while (nextElementSibling && nextElementSibling.nodeType != 1) {
            nextElementSibling = nextElementSibling.nextSibling;
          }
          return nextElementSibling;
        }
      },
      childElementCount: {
        get: function () {
          for(var
            count = 0,
            childNodes = this.childNodes || [],
            i = childNodes.length; i--; count += childNodes[i].nodeType == 1
          );
          return count;
        }
      },
      /*
      // children would be an override
      // IE8 already supports them but with comments too
      // not just nodeType 1
      children: {
        get: function () {
          for(var
            children = [],
            childNodes = this.childNodes || [],
            i = 0, length = childNodes.length;
            i < length; i++
          ) {
            if (childNodes[i].nodeType == 1) {
              children.push(childNodes[i]);
            }
          }
          return children;
        }
      },
      */
      // DOM Level 2 EventTarget methods and events
      addEventListener: valueDesc(function (type, handler, capture) {
        if (typeof handler !== 'function' && typeof handler !== 'object') return;
        var
          self = this,
          ontype = 'on' + type,
          temple =  self[SECRET] ||
                      defineProperty(
                        self, SECRET, {value: {}}
                      )[SECRET],
          currentType = temple[ontype] || (temple[ontype] = {}),
          handlers  = currentType.h || (currentType.h = []),
          e, attr
        ;
        if (!hasOwnProperty.call(currentType, 'w')) {
          currentType.w = function (e) {
            // e[SECRET] is a silent notification needed to avoid
            // fired events during live test
            return e[SECRET] || commonEventLoop(self, verify(self, e), handlers, false);
          };
          // if not detected yet
          if (!hasOwnProperty.call(types, ontype)) {
            // and potentially a native event
            if(possiblyNativeEvent.test(type)) {
              // do this heavy thing
              try {
                // TODO:  should I consider tagName too so that
                //        INPUT[ontype] could be different ?
                e = document.createEventObject();
                // do not clone ever a node
                // specially a document one ...
                // use the secret to ignore them all
                e[SECRET] = true;
                // document a part if a node has never been
                // added to any other node, fireEvent might
                // behave very weirdly (read: trigger unspecified errors)
                if (self.nodeType != 9) {
                  /*jshint eqnull:true */
                  if (self.parentNode == null) {
                    div.appendChild(self);
                  }
                  if ((attr = self.getAttribute(ontype))) {
                    removeAttribute.call(self, ontype);
                  }
                }
                self.fireEvent(ontype, e);
                types[ontype] = true;
              } catch(meh) {
                types[ontype] = false;
                while (div.hasChildNodes()) {
                  div.removeChild(div.firstChild);
                }
              }
              if (attr != null) {
                setAttribute.call(self, ontype, attr);
              }
            } else {
              // no need to bother since
              // 'x-event' ain't native for sure
              types[ontype] = false;
            }
          }
          if ((currentType.n = types[ontype])) {
            self.attachEvent(ontype, currentType.w);
          }
        }
        if (find(handlers, handler) < 0) {
          handlers[capture ? 'unshift' : 'push'](handler);
        }
        if (type === 'input') {
          self.attachEvent('onkeyup', onkeyup);
        }
      }),
      dispatchEvent: valueDesc(function (e) {
        var
          self = this,
          ontype = 'on' + e.type,
          temple =  self[SECRET],
          currentType = temple && temple[ontype],
          valid = !!currentType,
          parentNode
        ;
        if (!e.target) e.target = self;
        return (valid ? (
          currentType.n /* && live(self) */ ?
            self.fireEvent(ontype, e) :
            commonEventLoop(
              self,
              e,
              currentType.h,
              true
            )
        ) : (
          (parentNode = self.parentNode) /* && live(self) */ ?
            parentNode.dispatchEvent(e) :
            true
        )), !e.defaultPrevented;
      }),
      removeEventListener: valueDesc(function (type, handler, capture) {
        if (typeof handler !== 'function' && typeof handler !== 'object') return;
        var
          self = this,
          ontype = 'on' + type,
          temple =  self[SECRET],
          currentType = temple && temple[ontype],
          handlers = currentType && currentType.h,
          i = handlers ? find(handlers, handler) : -1
        ;
        if (-1 < i) handlers.splice(i, 1);
      })
    }
  );

  /* this is not needed in IE8
  defineProperties(window.HTMLSelectElement.prototype, {
    value: {
      get: function () {
        return this.options[this.selectedIndex].value;
      }
    }
  });
  //*/

  // EventTarget methods for Text nodes too
  defineProperties(TextPrototype, {
    addEventListener: valueDesc(ElementPrototype.addEventListener),
    dispatchEvent: valueDesc(ElementPrototype.dispatchEvent),
    removeEventListener: valueDesc(ElementPrototype.removeEventListener)
  });

  defineProperties(
    window.XMLHttpRequest.prototype,
    {
      addEventListener: valueDesc(function (type, handler, capture) {
        var
          self = this,
          ontype = 'on' + type,
          temple =  self[SECRET] ||
                      defineProperty(
                        self, SECRET, {value: {}}
                      )[SECRET],
          currentType = temple[ontype] || (temple[ontype] = {}),
          handlers  = currentType.h || (currentType.h = [])
        ;
        if (find(handlers, handler) < 0) {
          if (!self[ontype]) {
            self[ontype] = function () {
              var e = document.createEvent('Event');
              e.initEvent(type, true, true);
              self.dispatchEvent(e);
            };
          }
          handlers[capture ? 'unshift' : 'push'](handler);
        }
      }),
      dispatchEvent: valueDesc(function (e) {
        var
          self = this,
          ontype = 'on' + e.type,
          temple =  self[SECRET],
          currentType = temple && temple[ontype],
          valid = !!currentType
        ;
        return valid && (
          currentType.n /* && live(self) */ ?
            self.fireEvent(ontype, e) :
            commonEventLoop(
              self,
              e,
              currentType.h,
              true
            )
        );
      }),
      removeEventListener: valueDesc(ElementPrototype.removeEventListener)
    }
  );

  var buttonGetter = getOwnPropertyDescriptor(Event.prototype, 'button').get;
  defineProperties(
    window.Event.prototype,
    {
      bubbles: valueDesc(true),
      cancelable: valueDesc(true),
      preventDefault: valueDesc(function () {
        if (this.cancelable) {
          this.returnValue = false;
        }
      }),
      stopPropagation: valueDesc(function () {
        this.stoppedPropagation = true;
        this.cancelBubble = true;
      }),
      stopImmediatePropagation: valueDesc(function () {
        this.stoppedImmediatePropagation = true;
        this.stopPropagation();
      }),
      initEvent: valueDesc(function(type, bubbles, cancelable){
        this.type = type;
        this.bubbles = !!bubbles;
        this.cancelable = !!cancelable;
        if (!this.bubbles) {
          this.stopPropagation();
        }
      }),
      pageX: {get: function(){ return this._pageX || (this._pageX = this.clientX + window.scrollX - (html.clientLeft || 0)); }},
      pageY: {get: function(){ return this._pageY || (this._pageY = this.clientY + window.scrollY - (html.clientTop || 0)); }},
      which: {get: function(){ return this.keyCode ? this.keyCode : (isNaN(this.button) ? undefined : this.button + 1); }},
      charCode: {get: function(){ return (this.keyCode && this.type == 'keypress') ? this.keyCode : 0; }},
      buttons: {get: function(){ return buttonGetter.call(this); }},
      button: {get: function() {
        var buttons = this.buttons;
        return (buttons & 1 ? 0 : (buttons & 2 ? 2 : (buttons & 4 ? 1 : undefined)));
      }},
      defaultPrevented: {get: function() {
        // if preventDefault() was never called, or returnValue not given a value
        // then returnValue is undefined
        var returnValue = this.returnValue, undef;
        return !(returnValue === undef || returnValue);
      }},
      relatedTarget: {get: function() {
        var type = this.type;
        if (type === 'mouseover') {
          return this.fromElement;
        } else if (type === 'mouseout') {
          return this.toElement;
        } else {
          return null;
        }
      }}
    }
  );

  defineProperties(
    window.HTMLDocument.prototype,
    {
      defaultView: {
        get: function () {
          return this.parentWindow;
        }
      },
      textContent: {
        get: function () {
          return this.nodeType === 11 ? getTextContent.call(this) : null;
        },
        set: function (textContent) {
          if (this.nodeType === 11) {
            setTextContent.call(this, textContent);
          }
        }
      },
      addEventListener: valueDesc(function(type, handler, capture) {
        var self = this;
        ElementPrototype.addEventListener.call(self, type, handler, capture);
        // NOTE:  it won't fire if already loaded, this is NOT a $.ready() shim!
        //        this behaves just like standard browsers
        if (
          DUNNOABOUTDOMLOADED &&
          type === DOMCONTENTLOADED &&
          !readyStateOK.test(
            self.readyState
          )
        ) {
          DUNNOABOUTDOMLOADED = false;
          self.attachEvent(ONREADYSTATECHANGE, onReadyState);
          /* global top */
          if (window == top) {
            (function gonna(e){try{
              self.documentElement.doScroll('left');
              onReadyState();
              }catch(o_O){
              setTimeout(gonna, 50);
            }}());
          }
        }
      }),
      dispatchEvent: valueDesc(ElementPrototype.dispatchEvent),
      removeEventListener: valueDesc(ElementPrototype.removeEventListener),
      createEvent: valueDesc(function(Class){
        var e;
        if (Class !== 'Event') throw new Error('unsupported ' + Class);
        e = document.createEventObject();
        e.timeStamp = (new Date()).getTime();
        return e;
      })
    }
  );

  defineProperties(
    window.Window.prototype,
    {
      getComputedStyle: valueDesc(function(){

        var // partially grabbed from jQuery and Dean's hack
          notpixel = /^(?:[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$/,
          position = /^(top|right|bottom|left)$/,
          re = /\-([a-z])/g,
          place = function (match, $1) {
            return $1.toUpperCase();
          }
        ;

        function ComputedStyle(_) {
          this._ = _;
        }

        ComputedStyle.prototype.getPropertyValue = function (name) {
          var
            el = this._,
            style = el.style,
            currentStyle = el.currentStyle,
            runtimeStyle = el.runtimeStyle,
            result,
            left,
            rtLeft
          ;
          if (name == 'opacity') {
            return style.opacity || '1';
          }
          name = (name === 'float' ? 'style-float' : name).replace(re, place);
          result = currentStyle ? currentStyle[name] : style[name];
          if (notpixel.test(result) && !position.test(name)) {
            left = style.left;
            rtLeft = runtimeStyle && runtimeStyle.left;
            if (rtLeft) {
              runtimeStyle.left = currentStyle.left;
            }
            style.left = name === 'fontSize' ? '1em' : result;
            result = style.pixelLeft + 'px';
            style.left = left;
            if (rtLeft) {
              runtimeStyle.left = rtLeft;
            }
          }
          /*jshint eqnull:true */
          return result == null ?
            result : ((result + '') || 'auto');
        };

        // unsupported
        function PseudoComputedStyle() {}
        PseudoComputedStyle.prototype.getPropertyValue = function () {
          return null;
        };

        return function (el, pseudo) {
          return pseudo ?
            new PseudoComputedStyle(el) :
            new ComputedStyle(el);
        };

      }()),

      addEventListener: valueDesc(function (type, handler, capture) {
        var
          self = window,
          ontype = 'on' + type,
          handlers
        ;
        if (!self[ontype]) {
          self[ontype] = function(e) {
            return commonEventLoop(self, verify(self, e), handlers, false) && undefined;
          };
        }
        handlers = self[ontype][SECRET] || (
          self[ontype][SECRET] = []
        );
        if (find(handlers, handler) < 0) {
          handlers[capture ? 'unshift' : 'push'](handler);
        }
      }),
      dispatchEvent: valueDesc(function (e) {
        var method = window['on' + e.type];
        return method ? method.call(window, e) !== false && !e.defaultPrevented : true;
      }),
      removeEventListener: valueDesc(function (type, handler, capture) {
        var
          ontype = 'on' + type,
          handlers = (window[ontype] || Object)[SECRET],
          i = handlers ? find(handlers, handler) : -1
         ;
        if (-1 < i) handlers.splice(i, 1);
      }),
      pageXOffset: {get: getter('scrollLeft')},
      pageYOffset: {get: getter('scrollTop')},
      scrollX: {get: getter('scrollLeft')},
      scrollY: {get: getter('scrollTop')},
      innerWidth: {get: getter('clientWidth')},
      innerHeight: {get: getter('clientHeight')}
    }
  );

  window.HTMLElement = window.Element;

  (function (styleSheets, HTML5Element, i) {
    for (i = 0; i < HTML5Element.length; i++) document.createElement(HTML5Element[i]);
    if (!styleSheets.length) document.createStyleSheet('');
    styleSheets[0].addRule(HTML5Element.join(','), 'display:block;');
  }(document.styleSheets, ['header', 'nav', 'section', 'article', 'aside', 'footer']));

  (function () {
    if (document.createRange) return;
    document.createRange = function createRange() {
      return new Range();
    };

    function getContents(start, end) {
      var nodes = [start];
      while (start !== end) {
        nodes.push(start = start.nextSibling);
      }
      return nodes;
    }

    function Range() {}
    var proto = Range.prototype;
    proto.cloneContents = function cloneContents() {
      for (var
        fragment = this._start.ownerDocument.createDocumentFragment(),
        nodes = getContents(this._start, this._end),
        i = 0,
        length = nodes.length;
        i < length; i++
      ) {
        fragment.appendChild(nodes[i].cloneNode(true));
      }
      return fragment;
    };
    proto.cloneRange = function cloneRange() {
      var range = new Range();
      range._start = this._start;
      range._end = this._end;
      return range;
    };
    proto.deleteContents = function deleteContents() {
      for (var
        parentNode = this._start.parentNode,
        nodes = getContents(this._start, this._end),
        i = 0,
        length = nodes.length;
        i < length; i++
      ) {
        parentNode.removeChild(nodes[i]);
      }
    };
    proto.extractContents = function extractContents() {
      for (var
        fragment = this._start.ownerDocument.createDocumentFragment(),
        nodes = getContents(this._start, this._end),
        i = 0,
        length = nodes.length;
        i < length; i++
      ) {
        fragment.appendChild(nodes[i]);
      }
      return fragment;
    };
    proto.setEndAfter = function setEndAfter(node) {
      this._end = node;
    };
    proto.setEndBefore = function setEndBefore(node) {
      this._end = node.previousSibling;
    };
    proto.setStartAfter = function setStartAfter(node) {
      this._start = node.nextSibling;
    };
    proto.setStartBefore = function setStartBefore(node) {
      this._start = node;
    };
  }());
}(window));


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

import 'core-js/es';
import 'core-js/web/immediate';
import 'core-js/web/queue-microtask';
import 'core-js/web/timers';
import 'regenerator-runtime/runtime';
import './html5shiv';
import './ie8';
import './dom4';
import './css-om';
import './inferno';
import 'unfetch/polyfill';

repo_name: AnalWerewolf/sundown

license: mit


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

// Inferno needs Int32Array, and it is not covered by core-js.
if (!window.Int32Array) {
  window.Int32Array = Array;
}


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

const EXCLUDED_PATTERNS = [/v4shim/i];
const loadedMappings = {};

export const resolveAsset = name => (
  loadedMappings[name] || name
);

export const assetMiddleware = store => next => action => {
  const { type, payload } = action;
  if (type === 'asset/stylesheet') {
    Byond.loadCss(payload);
    return;
  }
  if (type === 'asset/mappings') {
    for (let name of Object.keys(payload)) {
      // Skip anything that matches excluded patterns
      if (EXCLUDED_PATTERNS.some(regex => regex.test(name))) {
        continue;
      }
      const url = payload[name];
      const ext = name.split('.').pop();
      loadedMappings[name] = url;
      if (ext === 'css') {
        Byond.loadCss(url);
      }
      if (ext === 'js') {
        Byond.loadJs(url);
      }
    }
    return;
  }
  next(action);
};


code:
/**
 * This file provides a clear separation layer between backend updates
 * and what state our React app sees.
 *
 * Sometimes backend can response without a "data" field, but our final
 * state will still contain previous "data" because we are merging
 * the response with already existing state.
 *
 * @file
 * @copyright 2020 Aleksej Komarov
 * @license MIT
 */

import { perf } from 'common/perf';
import { createAction } from 'common/redux';
import { setupDrag } from './drag';
import { focusMap } from './focus';
import { createLogger } from './logging';
import { resumeRenderer, suspendRenderer } from './renderer';

const logger = createLogger('backend');

export const backendUpdate = createAction('backend/update');
export const backendSetSharedState = createAction('backend/setSharedState');
export const backendSuspendStart = createAction('backend/suspendStart');

export const backendSuspendSuccess = () => ({
  type: 'backend/suspendSuccess',
  payload: {
    timestamp: Date.now(),
  },
});

const initialState = {
  config: {},
  data: {},
  shared: {},
  // Start as suspended
  suspended: Date.now(),
  suspending: false,
};

export const backendReducer = (state = initialState, action) => {
  const { type, payload } = action;

  if (type === 'backend/update') {
    // Merge config
    const config = {
      ...state.config,
      ...payload.config,
    };
    // Merge data
    const data = {
      ...state.data,
      ...payload.static_data,
      ...payload.data,
    };
    // Merge shared states
    const shared = { ...state.shared };
    if (payload.shared) {
      for (let key of Object.keys(payload.shared)) {
        const value = payload.shared[key];
        if (value === '') {
          shared[key] = undefined;
        }
        else {
          shared[key] = JSON.parse(value);
        }
      }
    }
    // Return new state
    return {
      ...state,
      config,
      data,
      shared,
      suspended: false,
    };
  }

  if (type === 'backend/setSharedState') {
    const { key, nextState } = payload;
    return {
      ...state,
      shared: {
        ...state.shared,
        [key]: nextState,
      },
    };
  }

  if (type === 'backend/suspendStart') {
    return {
      ...state,
      suspending: true,
    };
  }

  if (type === 'backend/suspendSuccess') {
    const { timestamp } = payload;
    return {
      ...state,
      data: {},
      shared: {},
      config: {
        ...state.config,
        title: '',
        status: 1,
      },
      suspending: false,
      suspended: timestamp,
    };
  }

  return state;
};

export const backendMiddleware = store => {
  let fancyState;
  let suspendInterval;

  return next => action => {
    const { suspended } = selectBackend(store.getState());
    const { type, payload } = action;

    if (type === 'update') {
      store.dispatch(backendUpdate(payload));
      return;
    }

    if (type === 'suspend') {
      store.dispatch(backendSuspendSuccess());
      return;
    }

    if (type === 'ping') {
      sendMessage({
        type: 'pingReply',
      });
      return;
    }

    if (type === 'backend/suspendStart' && !suspendInterval) {
      logger.log(`suspending (${window.__windowId__})`);
      // Keep sending suspend messages until it succeeds.
      // It may fail multiple times due to topic rate limiting.
      const suspendFn = () => sendMessage({
        type: 'suspend',
      });
      suspendFn();
      suspendInterval = setInterval(suspendFn, 2000);
    }

    if (type === 'backend/suspendSuccess') {
      suspendRenderer();
      clearInterval(suspendInterval);
      suspendInterval = undefined;
      Byond.winset(window.__windowId__, {
        'is-visible': false,
      });
      setImmediate(() => focusMap());
    }

    if (type === 'backend/update') {
      const fancy = payload.config?.window?.fancy;
      // Initialize fancy state
      if (fancyState === undefined) {
        fancyState = fancy;
      }
      // React to changes in fancy
      else if (fancyState !== fancy) {
        logger.log('changing fancy mode to', fancy);
        fancyState = fancy;
        Byond.winset(window.__windowId__, {
          titlebar: !fancy,
          'can-resize': !fancy,
        });
      }
    }

    // Resume on incoming update
    if (type === 'backend/update' && suspended) {
      // Show the payload
      logger.log('backend/update', payload);
      // Signal renderer that we have resumed
      resumeRenderer();
      // Setup drag
      setupDrag();
      // We schedule this for the next tick here because resizing and unhiding
      // during the same tick will flash with a white background.
      setImmediate(() => {
        perf.mark('resume/start');
        // Doublecheck if we are not re-suspended.
        const { suspended } = selectBackend(store.getState());
        if (suspended) {
          return;
        }
        Byond.winset(window.__windowId__, {
          'is-visible': true,
        });
        perf.mark('resume/finish');
        if (process.env.NODE_ENV !== 'production') {
          logger.log('visible in',
            perf.measure('render/finish', 'resume/finish'));
        }
      });
    }

    return next(action);
  };
};

/**
 * Sends a message to /datum/tgui_window.
 */
export const sendMessage = (message: any = {}) => {
  const { payload, ...rest } = message;
  const data: any = {
    // Message identifying header
    tgui: 1,
    window_id: window.__windowId__,
    // Message body
    ...rest,
  };
  // JSON-encode the payload
  if (payload !== null && payload !== undefined) {
    data.payload = JSON.stringify(payload);
  }
  Byond.topic(data);
};

/**
 * Sends an action to `ui_act` on `src_object` that this tgui window
 * is associated with.
 */
export const sendAct = (action: string, payload: object = {}) => {
  // Validate that payload is an object
  const isObject = typeof payload === 'object'
    && payload !== null
    && !Array.isArray(payload);
  if (!isObject) {
    logger.error(`Payload for act() must be an object, got this:`, payload);
    return;
  }
  sendMessage({
    type: 'act/' + action,
    payload,
  });
};

type BackendState<TData> = {
  config: {
    title: string,
    status: number,
    interface: string,
    window: {
      key: string,
      size: [number, number],
      fancy: boolean,
      locked: boolean,
    },
    client: {
      ckey: string,
      address: string,
      computer_id: string,
    },
    user: {
      name: string,
      observer: number,
    },
  },
  data: TData,
  shared: Record<string, any>,
  suspending: boolean,
  suspended: boolean,
}

/**
 * Selects a backend-related slice of Redux state
 */
export const selectBackend = <TData>(state: any): BackendState<TData> => (
  state.backend || {}
);

/**
 * A React hook (sort of) for getting tgui state and related functions.
 *
 * This is supposed to be replaced with a real React Hook, which can only
 * be used in functional components.
 *
 * You can make
 */
export const useBackend = <TData>(context: any) => {
  const { store } = context;
  const state = selectBackend<TData>(store.getState());
  return {
    ...state,
    act: sendAct,
  };
};

/**
 * A tuple that contains the state and a setter function for it.
 */
type StateWithSetter<T> = [T, (nextState: T) => void];

/**
 * Allocates state on Redux store without sharing it with other clients.
 *
 * Use it when you want to have a stateful variable in your component
 * that persists between renders, but will be forgotten after you close
 * the UI.
 *
 * It is a lot more performant than `setSharedState`.
 *
 * @param context React context.
 * @param key Key which uniquely identifies this state in Redux store.
 * @param initialState Initializes your global variable with this value.
 */
export const useLocalState = <T>(
  context: any,
  key: string,
  initialState: T,
): StateWithSetter<T> => {
  const { store } = context;
  const state = selectBackend(store.getState());
  const sharedStates = state.shared ?? {};
  const sharedState = (key in sharedStates)
    ? sharedStates[key]
    : initialState;
  return [
    sharedState,
    nextState => {
      store.dispatch(backendSetSharedState({
        key,
        nextState: (
          typeof nextState === 'function'
            ? nextState(sharedState)
            : nextState
        ),
      }));
    },
  ];
};

/**
 * Allocates state on Redux store, and **shares** it with other clients
 * in the game.
 *
 * Use it when you want to have a stateful variable in your component
 * that persists not only between renders, but also gets pushed to other
 * clients that observe this UI.
 *
 * This makes creation of observable s
 *
 * @param context React context.
 * @param key Key which uniquely identifies this state in Redux store.
 * @param initialState Initializes your global variable with this value.
 */
export const useSharedState = <T>(
  context: any,
  key: string,
  initialState: T,
): StateWithSetter<T> => {
  const { store } = context;
  const state = selectBackend(store.getState());
  const sharedStates = state.shared ?? {};
  const sharedState = (key in sharedStates)
    ? sharedStates[key]
    : initialState;
  return [
    sharedState,
    nextState => {
      sendMessage({
        type: 'setSharedState',
        key,
        value: JSON.stringify(
          typeof nextState === 'function'
            ? nextState(sharedState)
            : nextState
        ) || '',
      });
    },
  ];
};


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

import { clamp, toFixed } from 'common/math';
import { Component } from 'inferno';

const FPS = 20;
const Q = 0.5;

const isSafeNumber = value => {
  return typeof value === 'number'
    && Number.isFinite(value)
    && !Number.isNaN(value);
};

export class AnimatedNumber extends Component {
  constructor(props) {
    super(props);
    this.timer = null;
    this.state = {
      value: 0,
    };
    // Use provided initial state
    if (isSafeNumber(props.initial)) {
      this.state.value = props.initial;
    }
    // Set initial state with value provided in props
    else if (isSafeNumber(props.value)) {
      this.state.value = Number(props.value);
    }
  }

  tick() {
    const { props, state } = this;
    const currentValue = Number(state.value);
    const targetValue = Number(props.value);
    // Avoid poisoning our state with infinities and NaN
    if (!isSafeNumber(targetValue)) {
      return;
    }
    // Smooth the value using an exponential moving average
    const value = currentValue * Q + targetValue * (1 - Q);
    this.setState({ value });
  }

  componentDidMount() {
    this.timer = setInterval(() => this.tick(), 1000 / FPS);
  }

  componentWillUnmount() {
    clearTimeout(this.timer);
  }

  render() {
    const { props, state } = this;
    const { format, children } = props;
    const currentValue = state.value;
    const targetValue = props.value;
    // Directly display values which can't be animated
    if (!isSafeNumber(targetValue)) {
      return targetValue || null;
    }
    let formattedValue;
    // Use custom formatter
    if (format) {
      formattedValue = format(currentValue);
    }
    // Fix our animated precision at target value's precision.
    else {
      const fraction = String(targetValue).split('.')[1];
      const precision = fraction ? fraction.length : 0;
      formattedValue = toFixed(currentValue, clamp(precision, 0, 8));
    }
    // Use a custom render function
    if (typeof children === 'function') {
      return children(formattedValue, currentValue);
    }
    return formattedValue;
  }
}


code:
import { Component, createRef } from "inferno";

export class Autofocus extends Component {
  ref = createRef<HTMLDivElement>();

  componentDidMount() {
    setTimeout(() => {
      this.ref.current?.focus();
    }, 1);
  }

  render() {
    return (
      <div ref={this.ref} tabIndex={-1}>
        {this.props.children}
      </div>
    );
  }
}


code:
import { Component } from 'inferno';

const DEFAULT_BLINKING_INTERVAL = 1000;
const DEFAULT_BLINKING_TIME = 1000;

export class Blink extends Component {
  constructor() {
    super();
    this.state = {
      hidden: false,
    };
  }

  createTimer() {
    const {
      interval = DEFAULT_BLINKING_INTERVAL,
      time = DEFAULT_BLINKING_TIME,
    } = this.props;

    clearInterval(this.interval);
    clearTimeout(this.timer);

    this.setState({
      hidden: false,
    });

    this.interval = setInterval(() => {
      this.setState({
        hidden: true,
      });

      this.timer = setTimeout(() => {
        this.setState({
          hidden: false,
        });
      }, time);
    }, interval + time);
  }

  componentDidMount() {
    this.createTimer();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.interval !== this.props.interval
      || prevProps.time !== this.props.time) {
      this.createTimer();
    }
  }

  componentWillUnmount() {
    clearInterval(this.interval);
    clearTimeout(this.timer);
  }

  render(props) {
    return (
      <span style={{
        visibility: this.state.hidden ? "hidden" : "visible",
      }}>
        {props.children}
      </span>
    );
  }
}


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

import { classes } from 'common/react';
import { Box } from './Box';

export const BlockQuote = props => {
  const { className, ...rest } = props;
  return (
    <Box
      className={classes([
        'BlockQuote',
        className,
      ])}
      {...rest} />
  );
};


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

import { BooleanLike, classes, pureComponentHooks } from 'common/react';
import { createVNode, InfernoNode, SFC } from 'inferno';
import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags';
import { CSS_COLORS } from '../constants';

export interface BoxProps {
  [key: string]: any;
  as?: string;
  className?: string | BooleanLike;
  children?: InfernoNode;
  position?: string | BooleanLike;
  overflow?: string | BooleanLike;
  overflowX?: string | BooleanLike;
  overflowY?: string | BooleanLike;
  top?: string | BooleanLike;
  bottom?: string | BooleanLike;
  left?: string | BooleanLike;
  right?: string | BooleanLike;
  width?: string | BooleanLike;
  minWidth?: string | BooleanLike;
  maxWidth?: string | BooleanLike;
  height?: string | BooleanLike;
  minHeight?: string | BooleanLike;
  maxHeight?: string | BooleanLike;
  fontSize?: string | BooleanLike;
  fontFamily?: string;
  lineHeight?: string | BooleanLike;
  opacity?: number;
  textAlign?: string | BooleanLike;
  verticalAlign?: string | BooleanLike;
  inline?: BooleanLike;
  bold?: BooleanLike;
  italic?: BooleanLike;
  nowrap?: BooleanLike;
  preserveWhitespace?: BooleanLike;
  m?: string | BooleanLike;
  mx?: string | BooleanLike;
  my?: string | BooleanLike;
  mt?: string | BooleanLike;
  mb?: string | BooleanLike;
  ml?: string | BooleanLike;
  mr?: string | BooleanLike;
  p?: string | BooleanLike;
  px?: string | BooleanLike;
  py?: string | BooleanLike;
  pt?: string | BooleanLike;
  pb?: string | BooleanLike;
  pl?: string | BooleanLike;
  pr?: string | BooleanLike;
  color?: string | BooleanLike;
  textColor?: string | BooleanLike;
  backgroundColor?: string | BooleanLike;
  fillPositionedParent?: boolean;
}

/**
 * Coverts our rem-like spacing unit into a CSS unit.
 */
export const unit = (value: unknown): string | undefined => {
  if (typeof value === 'string') {
    // Transparently convert pixels into rem units
    if (value.endsWith('px') && !Byond.IS_LTE_IE8) {
      return parseFloat(value) / 12 + 'rem';
    }
    return value;
  }
  if (typeof value === 'number') {
    if (Byond.IS_LTE_IE8) {
      return value * 12 + 'px';
    }
    return value + 'rem';
  }
};

/**
 * Same as `unit`, but half the size for integers numbers.
 */
export const halfUnit = (value: unknown): string | undefined => {
  if (typeof value === 'string') {
    return unit(value);
  }
  if (typeof value === 'number') {
    return unit(value * 0.5);
  }
};

const isColorCode = (str: unknown) => !isColorClass(str);

const isColorClass = (str: unknown): boolean => {
  return typeof str === "string" && CSS_COLORS.includes(str);
};

const mapRawPropTo = attrName => (style, value) => {
  if (typeof value === 'number' || typeof value === 'string') {
    style[attrName] = value;
  }
};

const mapUnitPropTo = (attrName, unit) => (style, value) => {
  if (typeof value === 'number' || typeof value === 'string') {
    style[attrName] = unit(value);
  }
};

const mapBooleanPropTo = (attrName, attrValue) => (style, value) => {
  if (value) {
    style[attrName] = attrValue;
  }
};

const mapDirectionalUnitPropTo = (attrName, unit, dirs) => (style, value) => {
  if (typeof value === 'number' || typeof value === 'string') {
    for (let i = 0; i < dirs.length; i++) {
      style[attrName + '-' + dirs[i]] = unit(value);
    }
  }
};

const mapColorPropTo = attrName => (style, value) => {
  if (isColorCode(value)) {
    style[attrName] = value;
  }
};

const styleMapperByPropName = {
  // Direct mapping
  position: mapRawPropTo('position'),
  overflow: mapRawPropTo('overflow'),
  overflowX: mapRawPropTo('overflow-x'),
  overflowY: mapRawPropTo('overflow-y'),
  top: mapUnitPropTo('top', unit),
  bottom: mapUnitPropTo('bottom', unit),
  left: mapUnitPropTo('left', unit),
  right: mapUnitPropTo('right', unit),
  width: mapUnitPropTo('width', unit),
  minWidth: mapUnitPropTo('min-width', unit),
  maxWidth: mapUnitPropTo('max-width', unit),
  height: mapUnitPropTo('height', unit),
  minHeight: mapUnitPropTo('min-height', unit),
  maxHeight: mapUnitPropTo('max-height', unit),
  fontSize: mapUnitPropTo('font-size', unit),
  fontFamily: mapRawPropTo('font-family'),
  lineHeight: (style, value) => {
    if (typeof value === 'number') {
      style['line-height'] = value;
    }
    else if (typeof value === 'string') {
      style['line-height'] = unit(value);
    }
  },
  opacity: mapRawPropTo('opacity'),
  textAlign: mapRawPropTo('text-align'),
  verticalAlign: mapRawPropTo('vertical-align'),
  // Boolean props
  inline: mapBooleanPropTo('display', 'inline-block'),
  bold: mapBooleanPropTo('font-weight', 'bold'),
  italic: mapBooleanPropTo('font-style', 'italic'),
  nowrap: mapBooleanPropTo('white-space', 'nowrap'),
  preserveWhitespace: mapBooleanPropTo('white-space', 'pre-wrap'),
  // Margins
  m: mapDirectionalUnitPropTo('margin', halfUnit, [
    'top', 'bottom', 'left', 'right',
  ]),
  mx: mapDirectionalUnitPropTo('margin', halfUnit, [
    'left', 'right',
  ]),
  my: mapDirectionalUnitPropTo('margin', halfUnit, [
    'top', 'bottom',
  ]),
  mt: mapUnitPropTo('margin-top', halfUnit),
  mb: mapUnitPropTo('margin-bottom', halfUnit),
  ml: mapUnitPropTo('margin-left', halfUnit),
  mr: mapUnitPropTo('margin-right', halfUnit),
  // Margins
  p: mapDirectionalUnitPropTo('padding', halfUnit, [
    'top', 'bottom', 'left', 'right',
  ]),
  px: mapDirectionalUnitPropTo('padding', halfUnit, [
    'left', 'right',
  ]),
  py: mapDirectionalUnitPropTo('padding', halfUnit, [
    'top', 'bottom',
  ]),
  pt: mapUnitPropTo('padding-top', halfUnit),
  pb: mapUnitPropTo('padding-bottom', halfUnit),
  pl: mapUnitPropTo('padding-left', halfUnit),
  pr: mapUnitPropTo('padding-right', halfUnit),
  // Color props
  color: mapColorPropTo('color'),
  textColor: mapColorPropTo('color'),
  backgroundColor: mapColorPropTo('background-color'),
  // Utility props
  fillPositionedParent: (style, value) => {
    if (value) {
      style['position'] = 'absolute';
      style['top'] = 0;
      style['bottom'] = 0;
      style['left'] = 0;
      style['right'] = 0;
    }
  },
};

export const computeBoxProps = (props: BoxProps) => {
  const computedProps: HTMLAttributes<any> = {};
  const computedStyles = {};
  // Compute props
  for (let propName of Object.keys(props)) {
    if (propName === 'style') {
      continue;
    }
    // IE8: onclick workaround
    if (Byond.IS_LTE_IE8 && propName === 'onClick') {
      computedProps.onclick = props[propName];
      continue;
    }
    const propValue = props[propName];
    const mapPropToStyle = styleMapperByPropName[propName];
    if (mapPropToStyle) {
      mapPropToStyle(computedStyles, propValue);
    }
    else {
      computedProps[propName] = propValue;
    }
  }
  // Concatenate styles
  let style = '';
  for (let attrName of Object.keys(computedStyles)) {
    const attrValue = computedStyles[attrName];
    style += attrName + ':' + attrValue + ';';
  }
  if (props.style) {
    for (let attrName of Object.keys(props.style)) {
      const attrValue = props.style[attrName];
      style += attrName + ':' + attrValue + ';';
    }
  }
  if (style.length > 0) {
    computedProps.style = style;
  }
  return computedProps;
};

export const computeBoxClassName = (props: BoxProps) => {
  const color = props.textColor || props.color;
  const backgroundColor = props.backgroundColor;
  return classes([
    isColorClass(color) && 'color-' + color,
    isColorClass(backgroundColor) && 'color-bg-' + backgroundColor,
  ]);
};

export const Box: SFC<BoxProps> = (props: BoxProps) => {
  const {
    as = 'div',
    className,
    children,
    ...rest
  } = props;
  // Render props
  if (typeof children === 'function') {
    return children(computeBoxProps(props));
  }
  const computedClassName = typeof className === 'string'
    ? className + ' ' + computeBoxClassName(rest)
    : computeBoxClassName(rest);
  const computedProps = computeBoxProps(rest);
  // Render a wrapper element
  return createVNode(
    VNodeFlags.HtmlElement,
    as,
    computedClassName,
    children,
    ChildFlags.UnknownChildren,
    computedProps,
    undefined,
  );
};

Box.defaultHooks = pureComponentHooks;


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

import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from 'common/keycodes';
import { classes, pureComponentHooks } from 'common/react';
import { Component, createRef } from 'inferno';
import { createLogger } from '../logging';
import { Box, computeBoxClassName, computeBoxProps } from './Box';
import { Icon } from './Icon';
import { Tooltip } from './Tooltip';

const logger = createLogger('Button');

export const Button = props => {
  const {
    className,
    fluid,
    icon,
    iconRotation,
    iconSpin,
    iconColor,
    iconPosition,
    color,
    disabled,
    selected,
    tooltip,
    tooltipPosition,
    ellipsis,
    compact,
    circular,
    content,
    children,
    onclick,
    onClick,
    ...rest
  } = props;
  const hasContent = !!(content || children);
  // A warning about the lowercase onclick
  if (onclick) {
    logger.warn(
      `Lowercase 'onclick' is not supported on Button and lowercase`
      + ` prop names are discouraged in general. Please use a camelCase`
      + `'onClick' instead and read: `
      + `https://infernojs.org/docs/guides/event-handling`);
  }
  rest.onClick = e => {
    if (!disabled && onClick) {
      onClick(e);
    }
  };
  // IE8: Use "unselectable" because "user-select" doesn't work.
  if (Byond.IS_LTE_IE8) {
    rest.unselectable = true;
  }
  let buttonContent = (
    <div
      className={classes([
        'Button',
        fluid && 'Button--fluid',
        disabled && 'Button--disabled',
        selected && 'Button--selected',
        hasContent && 'Button--hasContent',
        ellipsis && 'Button--ellipsis',
        circular && 'Button--circular',
        compact && 'Button--compact',
        iconPosition && 'Button--iconPosition--' + iconPosition,
        (color && typeof color === 'string')
          ? 'Button--color--' + color
          : 'Button--color--default',
        className,
        computeBoxClassName(rest),
      ])}
      tabIndex={!disabled && '0'}
      onKeyDown={e => {
        if (props.captureKeys === false) {
          return;
        }
        const keyCode = window.event ? e.which : e.keyCode;
        // Simulate a click when pressing space or enter.
        if (keyCode === KEY_SPACE || keyCode === KEY_ENTER) {
          e.preventDefault();
          if (!disabled && onClick) {
            onClick(e);
          }
          return;
        }
        // Refocus layout on pressing escape.
        if (keyCode === KEY_ESCAPE) {
          e.preventDefault();
          return;
        }
      }}
      {...computeBoxProps(rest)}>
      {(icon && iconPosition !== 'right') && (
        <Icon
          name={icon}
          color={iconColor}
          rotation={iconRotation}
          spin={iconSpin} />
      )}
      {content}
      {children}
      {(icon && iconPosition === 'right') && (
        <Icon
          name={icon}
          color={iconColor}
          rotation={iconRotation}
          spin={iconSpin} />
      )}
    </div>
  );

  if (tooltip) {
    buttonContent = (
      <Tooltip content={tooltip} position={tooltipPosition}>
        {buttonContent}
      </Tooltip>
    );
  }

  return buttonContent;
};

Button.defaultHooks = pureComponentHooks;

export const ButtonCheckbox = props => {
  const { checked, ...rest } = props;
  return (
    <Button
      color="transparent"
      icon={checked ? 'check-square-o' : 'square-o'}
      selected={checked}
      {...rest} />
  );
};

Button.Checkbox = ButtonCheckbox;

export class ButtonConfirm extends Component {
  constructor() {
    super();
    this.state = {
      clickedOnce: false,
    };
    this.handleClick = () => {
      if (this.state.clickedOnce) {
        this.setClickedOnce(false);
      }
    };
  }

  setClickedOnce(clickedOnce) {
    this.setState({
      clickedOnce,
    });
    if (clickedOnce) {
      setTimeout(() => window.addEventListener('click', this.handleClick));
    }
    else {
      window.removeEventListener('click', this.handleClick);
    }
  }

  render() {
    const {
      confirmContent = "Confirm?",
      confirmColor = "bad",
      confirmIcon,
      icon,
      color,
      content,
      onClick,
      ...rest
    } = this.props;
    return (
      <Button
        content={this.state.clickedOnce ? confirmContent : content}
        icon={this.state.clickedOnce ? confirmIcon : icon}
        color={this.state.clickedOnce ? confirmColor : color}
        onClick={() => this.state.clickedOnce
          ? onClick()
          : this.setClickedOnce(true)}
        {...rest}
      />
    );
  }
}

Button.Confirm = ButtonConfirm;

export class ButtonInput extends Component {
  constructor() {
    super();
    this.inputRef = createRef();
    this.state = {
      inInput: false,
    };
  }

  setInInput(inInput) {
    this.setState({
      inInput,
    });
    if (this.inputRef) {
      const input = this.inputRef.current;
      if (inInput) {
        input.value = this.props.currentValue || "";
        try {
          input.focus();
          input.select();
        }
        catch {}
      }
    }
  }

  commitResult(e) {
    if (this.inputRef) {
      const input = this.inputRef.current;
      const hasValue = (input.value !== "");
      if (hasValue) {
        this.props.onCommit(e, input.value);
        return;
      } else {
        if (!this.props.defaultValue) {
          return;
        }
        this.props.onCommit(e, this.props.defaultValue);
      }
    }
  }

  render() {
    const {
      fluid,
      content,
      icon,
      iconRotation,
      iconSpin,
      tooltip,
      tooltipPosition,
      color = 'default',
      placeholder,
      maxLength,
      ...rest
    } = this.props;

    let buttonContent = (
      <Box
        className={classes([
          'Button',
          fluid && 'Button--fluid',
          'Button--color--' + color,
        ])}
        {...rest}
        onClick={() => this.setInInput(true)}>
        {icon && (
          <Icon name={icon} rotation={iconRotation} spin={iconSpin} />
        )}
        <div>
          {content}
        </div>
        <input
          ref={this.inputRef}
          className="NumberInput__input"
          style={{
            'display': !this.state.inInput ? 'none' : undefined,
            'text-align': 'left',
          }}
          onBlur={e => {
            if (!this.state.inInput) {
              return;
            }
            this.setInInput(false);
            this.commitResult(e);
          }}
          onKeyDown={e => {
            if (e.keyCode === KEY_ENTER) {
              this.setInInput(false);
              this.commitResult(e);
              return;
            }
            if (e.keyCode === KEY_ESCAPE) {
              this.setInInput(false);
            }
          }}
        />
      </Box>
    );

    if (tooltip) {
      buttonContent = (
        <Tooltip
          content={tooltip}
          position={tooltipPosition}
        >
          {buttonContent}
        </Tooltip>
      );
    }

    return buttonContent;
  }
}

Button.Input = ButtonInput;


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

import { shallowDiffers } from 'common/react';
import { debounce } from 'common/timer';
import { Component, createRef } from 'inferno';
import { createLogger } from '../logging';
import { computeBoxProps } from './Box';

const logger = createLogger('ByondUi');

// Stack of currently allocated BYOND UI element ids.
const byondUiStack = [];

const createByondUiElement = elementId => {
  // Reserve an index in the stack
  const index = byondUiStack.length;
  byondUiStack.push(null);
  // Get a unique id
  const id = elementId || 'byondui_' + index;
  logger.log(`allocated '${id}'`);
  // Return a control structure
  return {
    render: params => {
      logger.log(`rendering '${id}'`);
      byondUiStack[index] = id;
      Byond.winset(id, params);
    },
    unmount: () => {
      logger.log(`unmounting '${id}'`);
      byondUiStack[index] = null;
      Byond.winset(id, {
        parent: '',
      });
    },
  };
};

window.addEventListener('beforeunload', () => {
  // Cleanly unmount all visible UI elements
  for (let index = 0; index < byondUiStack.length; index++) {
    const id = byondUiStack[index];
    if (typeof id === 'string') {
      logger.log(`unmounting '${id}' (beforeunload)`);
      byondUiStack[index] = null;
      Byond.winset(id, {
        parent: '',
      });
    }
  }
});

/**
 * Get the bounding box of the DOM element.
 */
const getBoundingBox = element => {
  const rect = element.getBoundingClientRect();
  return {
    pos: [
      rect.left,
      rect.top,
    ],
    size: [
      rect.right - rect.left,
      rect.bottom - rect.top,
    ],
  };
};

export class ByondUi extends Component {
  constructor(props) {
    super(props);
    this.containerRef = createRef();
    this.byondUiElement = createByondUiElement(props.params?.id);
    this.handleResize = debounce(() => {
      this.forceUpdate();
    }, 100);
  }

  shouldComponentUpdate(nextProps) {
    const {
      params: prevParams = {},
      ...prevRest
    } = this.props;
    const {
      params: nextParams = {},
      ...nextRest
    } = nextProps;
    return shallowDiffers(prevParams, nextParams)
      || shallowDiffers(prevRest, nextRest);
  }

  componentDidMount() {
    // IE8: It probably works, but fuck you anyway.
    if (Byond.IS_LTE_IE10) {
      return;
    }
    window.addEventListener('resize', this.handleResize);
    this.componentDidUpdate();
    this.handleResize();
  }

  componentDidUpdate() {
    // IE8: It probably works, but fuck you anyway.
    if (Byond.IS_LTE_IE10) {
      return;
    }
    const {
      params = {},
    } = this.props;
    const box = getBoundingBox(this.containerRef.current);
    logger.debug('bounding box', box);
    this.byondUiElement.render({
      parent: window.__windowId__,
      ...params,
      pos: box.pos[0] + ',' + box.pos[1],
      size: box.size[0] + 'x' + box.size[1],
    });
  }

  componentWillUnmount() {
    // IE8: It probably works, but fuck you anyway.
    if (Byond.IS_LTE_IE10) {
      return;
    }
    window.removeEventListener('resize', this.handleResize);
    this.byondUiElement.unmount();
  }

  render() {
    const { params, ...rest } = this.props;
    return (
      <div
        ref={this.containerRef}
        {...computeBoxProps(rest)}>
        {/* Filler */}
        <div style={{ 'min-height': '22px' }} />
      </div>
    );
  }
}


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

import { map, zipWith } from 'common/collections';
import { pureComponentHooks } from 'common/react';
import { Component, createRef } from 'inferno';
import { Box } from './Box';

const normalizeData = (data, scale, rangeX, rangeY) => {
  if (data.length === 0) {
    return [];
  }
  const min = zipWith(Math.min)(...data);
  const max = zipWith(Math.max)(...data);
  if (rangeX !== undefined) {
    min[0] = rangeX[0];
    max[0] = rangeX[1];
  }
  if (rangeY !== undefined) {
    min[1] = rangeY[0];
    max[1] = rangeY[1];
  }
  const normalized = map(point => {
    return zipWith((value, min, max, scale) => {
      return (value - min) / (max - min) * scale;
    })(point, min, max, scale);
  })(data);
  return normalized;
};

const dataToPolylinePoints = data => {
  let points = '';
  for (let i = 0; i < data.length; i++) {
    const point = data[i];
    points += point[0] + ',' + point[1] + ' ';
  }
  return points;
};

class LineChart extends Component {
  constructor(props) {
    super(props);
    this.ref = createRef();
    this.state = {
      // Initial guess
      viewBox: [600, 200],
    };
    this.handleResize = () => {
      const element = this.ref.current;
      this.setState({
        viewBox: [element.offsetWidth, element.offsetHeight],
      });
    };
  }

  componentDidMount() {
    window.addEventListener('resize', this.handleResize);
    this.handleResize();
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize);
  }

  render() {
    const {
      data = [],
      rangeX,
      rangeY,
      fillColor = 'none',
      strokeColor = '#ffffff',
      strokeWidth = 2,
      ...rest
    } = this.props;
    const { viewBox } = this.state;
    const normalized = normalizeData(data, viewBox, rangeX, rangeY);
    // Push data outside viewBox and form a fillable polygon
    if (normalized.length > 0) {
      const first = normalized[0];
      const last = normalized[normalized.length - 1];
      normalized.push([viewBox[0] + strokeWidth, last[1]]);
      normalized.push([viewBox[0] + strokeWidth, -strokeWidth]);
      normalized.push([-strokeWidth, -strokeWidth]);
      normalized.push([-strokeWidth, first[1]]);
    }
    const points = dataToPolylinePoints(normalized);
    return (
      <Box position="relative" {...rest}>
        {props => (
          <div ref={this.ref} {...props}>
            <svg
              viewBox={`0 0 ${viewBox[0]} ${viewBox[1]}`}
              preserveAspectRatio="none"
              style={{
                position: 'absolute',
                top: 0,
                left: 0,
                right: 0,
                bottom: 0,
                overflow: 'hidden',
              }}>
              <polyline
                transform={`scale(1, -1) translate(0, -${viewBox[1]})`}
                fill={fillColor}
                stroke={strokeColor}
                strokeWidth={strokeWidth}
                points={points} />
            </svg>
          </div>
        )}
      </Box>
    );
  }
}

LineChart.defaultHooks = pureComponentHooks;

const Stub = props => null;

// IE8: No inline svg support
export const Chart = {
  Line: Byond.IS_LTE_IE8 ? Stub : LineChart,
};


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

import { Component } from 'inferno';
import { Box } from './Box';
import { Button } from './Button';

export class Collapsible extends Component {
  constructor(props) {
    super(props);
    const { open } = props;
    this.state = {
      open: open || false,
    };
  }

  render() {
    const { props } = this;
    const { open } = this.state;
    const {
      children,
      color = 'default',
      title,
      buttons,
      ...rest
    } = props;
    return (
      <Box mb={1}>

            <Button
              fluid
              color={color}
              icon={open ? 'chevron-down' : 'chevron-right'}
              onClick={() => this.setState({ open: !open })}
              {...rest}>
              {title}
            </Button>

          {buttons && (

              {buttons}

          )}

        {open && (
          <Box mt={1}>
            {children}
          </Box>
        )}
      </Box>
    );
  }
}


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

import { classes, pureComponentHooks } from 'common/react';
import { computeBoxClassName, computeBoxProps } from './Box';

export const ColorBox = props => {
  const {
    content,
    children,
    className,
    color,
    backgroundColor,
    ...rest
  } = props;
  rest.color = content ? null : 'transparent';
  rest.backgroundColor = color || backgroundColor;
  return (
    <div
      className={classes([
        'ColorBox',
        className,
        computeBoxClassName(rest),
      ])}
      {...computeBoxProps(rest)}>
      {content || '.'}
    </div>
  );
};

ColorBox.defaultHooks = pureComponentHooks;


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

import { classes } from 'common/react';
import { Box } from './Box';

export const Dimmer = props => {
  const { className, children, ...rest } = props;
  return (
    <Box
      className={classes([
        'Dimmer',
        ...className,
      ])}
      {...rest}>
      <div className="Dimmer__inner">
        {children}
      </div>
    </Box>
  );
};


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

import { classes } from 'common/react';

export const Divider = props => {
  const {
    vertical,
    hidden,
  } = props;
  return (
    <div
      className={classes([
        'Divider',
        hidden && 'Divider--hidden',
        vertical
          ? 'Divider--vertical'
          : 'Divider--horizontal',
      ])} />
  );
};


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

import { clamp } from 'common/math';
import { pureComponentHooks } from 'common/react';
import { Component, createRef } from 'inferno';
import { AnimatedNumber } from './AnimatedNumber';

const DEFAULT_UPDATE_RATE = 400;

/**
 * Reduces screen offset to a single number based on the matrix provided.
 */
const getScalarScreenOffset = (e, matrix) => {
  return e.screenX * matrix[0] + e.screenY * matrix[1];
};

export class DraggableControl extends Component {
  constructor(props) {
    super(props);
    this.inputRef = createRef();
    this.state = {
      value: props.value,
      dragging: false,
      editing: false,
      internalValue: null,
      origin: null,
      suppressingFlicker: false,
    };

    // Suppresses flickering while the value propagates through the backend
    this.flickerTimer = null;
    this.suppressFlicker = () => {
      const { suppressFlicker } = this.props;
      if (suppressFlicker > 0) {
        this.setState({
          suppressingFlicker: true,
        });
        clearTimeout(this.flickerTimer);
        this.flickerTimer = setTimeout(() => this.setState({
          suppressingFlicker: false,
        }), suppressFlicker);
      }
    };

    this.handleDragStart = e => {
      const {
        value,
        dragMatrix,
      } = this.props;
      const { editing } = this.state;
      if (editing) {
        return;
      }
      document.body.style['pointer-events'] = 'none';
      this.ref = e.target;
      this.setState({
        dragging: false,
        origin: getScalarScreenOffset(e, dragMatrix),
        value,
        internalValue: value,
      });
      this.timer = setTimeout(() => {
        this.setState({
          dragging: true,
        });
      }, 250);
      this.dragInterval = setInterval(() => {
        const { dragging, value } = this.state;
        const { onDrag } = this.props;
        if (dragging && onDrag) {
          onDrag(e, value);
        }
      }, this.props.updateRate || DEFAULT_UPDATE_RATE);
      document.addEventListener('mousemove', this.handleDragMove);
      document.addEventListener('mouseup', this.handleDragEnd);
    };

    this.handleDragMove = e => {
      const {
        minValue,
        maxValue,
        step,
        stepPixelSize,
        dragMatrix,
      } = this.props;
      this.setState(prevState => {
        const state = { ...prevState };
        const offset = getScalarScreenOffset(e, dragMatrix) - state.origin;
        if (prevState.dragging) {
          const stepOffset = Number.isFinite(minValue)
            ? minValue % step
            : 0;
          // Translate mouse movement to value
          // Give it some headroom (by increasing clamp range by 1 step)
          state.internalValue = clamp(
            state.internalValue
              + offset * step / stepPixelSize,
            minValue - step,
            maxValue + step);
          // Clamp the final value
          state.value = clamp(
            state.internalValue
              - state.internalValue % step
              + stepOffset,
            minValue,
            maxValue);
          state.origin = getScalarScreenOffset(e, dragMatrix);
        }
        else if (Math.abs(offset) > 4) {
          state.dragging = true;
        }
        return state;
      });
    };

    this.handleDragEnd = e => {
      const {
        onChange,
        onDrag,
      } = this.props;
      const {
        dragging,
        value,
        internalValue,
      } = this.state;
      document.body.style['pointer-events'] = 'auto';
      clearTimeout(this.timer);
      clearInterval(this.dragInterval);
      this.setState({
        dragging: false,
        editing: !dragging,
        origin: null,
      });
      document.removeEventListener('mousemove', this.handleDragMove);
      document.removeEventListener('mouseup', this.handleDragEnd);
      if (dragging) {
        this.suppressFlicker();
        if (onChange) {
          onChange(e, value);
        }
        if (onDrag) {
          onDrag(e, value);
        }
      }
      else if (this.inputRef) {
        const input = this.inputRef.current;
        input.value = internalValue;
        // IE8: Dies when trying to focus a hidden element
        // (Error: Object does not support this action)
        try {
          input.focus();
          input.select();
        }
        catch {}
      }
    };
  }

  render() {
    const {
      dragging,
      editing,
      value: intermediateValue,
      suppressingFlicker,
    } = this.state;
    const {
      animated,
      value,
      unit,
      minValue,
      maxValue,
      unclamped,
      format,
      onChange,
      onDrag,
      children,
      // Input props
      height,
      lineHeight,
      fontSize,
    } = this.props;
    let displayValue = value;
    if (dragging || suppressingFlicker) {
      displayValue = intermediateValue;
    }
    // Setup a display element
    // Shows a formatted number based on what we are currently doing
    // with the draggable surface.
    const renderDisplayElement = value => (
      value + (unit ? ' ' + unit : '')
    );
    const displayElement = (
      animated && !dragging && !suppressingFlicker && (
        <AnimatedNumber
          value={displayValue}
          format={format}>
          {renderDisplayElement}
        </AnimatedNumber>
      ) || (
        renderDisplayElement(format
          ? format(displayValue)
          : displayValue)
      )
    );
    // Setup an input element
    // Handles direct input via the keyboard
    const inputElement = (
      <input
        ref={this.inputRef}
        className="NumberInput__input"
        style={{
          display: !editing ? 'none' : undefined,
          height: height,
          'line-height': lineHeight,
          'font-size': fontSize,
        }}
        onBlur={e => {
          if (!editing) {
            return;
          }
          let value;
          if (unclamped) {
            value = parseFloat(e.target.value);
          }
          else {
            value = clamp(
              parseFloat(e.target.value),
              minValue,
              maxValue);
          }
          if (Number.isNaN(value)) {
            this.setState({
              editing: false,
            });
            return;
          }
          this.setState({
            editing: false,
            value,
          });
          this.suppressFlicker();
          if (onChange) {
            onChange(e, value);
          }
          if (onDrag) {
            onDrag(e, value);
          }
        }}
        onKeyDown={e => {
          if (e.keyCode === 13) {
            let value;
            if (unclamped) {
              value = parseFloat(e.target.value);
            }
            else {
              value = clamp(
                parseFloat(e.target.value),
                minValue,
                maxValue);
            }
            if (Number.isNaN(value)) {
              this.setState({
                editing: false,
              });
              return;
            }
            this.setState({
              editing: false,
              value,
            });
            this.suppressFlicker();
            if (onChange) {
              onChange(e, value);
            }
            if (onDrag) {
              onDrag(e, value);
            }
            return;
          }
          if (e.keyCode === 27) {
            this.setState({
              editing: false,
            });
            return;
          }
        }} />
    );
    // Return a part of the state for higher-level components to use.
    return children({
      dragging,
      editing,
      value,
      displayValue,
      displayElement,
      inputElement,
      handleDragStart: this.handleDragStart,
    });
  }
}

DraggableControl.defaultHooks = pureComponentHooks;
DraggableControl.defaultProps = {
  minValue: -Infinity,
  maxValue: +Infinity,
  step: 1,
  stepPixelSize: 1,
  suppressFlicker: 50,
  dragMatrix: [1, 0],
};


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

import { classes } from 'common/react';
import { Component } from 'inferno';
import { Box } from './Box';
import { Icon } from './Icon';

export class Dropdown extends Component {
  constructor(props) {
    super(props);
    this.state = {
      selected: props.selected,
      open: false,
    };
    this.handleClick = () => {
      if (this.state.open) {
        this.setOpen(false);
      }
    };
  }

  componentWillUnmount() {
    window.removeEventListener('click', this.handleClick);
  }

  setOpen(open) {
    this.setState({ open: open });
    if (open) {
      setTimeout(() => window.addEventListener('click', this.handleClick));
      this.menuRef.focus();
    }
    else {
      window.removeEventListener('click', this.handleClick);
    }
  }

  setSelected(selected) {
    this.setState({
      selected: selected,
    });
    this.setOpen(false);
    this.props.onSelected(selected);
  }

  buildMenu() {
    const { options = [] } = this.props;
    const ops = options.map(option => {
      let displayText, value;

      if (typeof option === "string") {
        displayText = option;
        value = option;
      } else {
        displayText = option.displayText;
        value = option.value;
      }

      return (
        <Box
          key={value}
          className="Dropdown__menuentry"
          onClick={() => {
            this.setSelected(value);
          }}>
          {displayText}
        </Box>
      );
    });
    return ops.length ? ops : 'No Options Found';
  }

  render() {
    const { props } = this;
    const {
      icon,
      iconRotation,
      iconSpin,
      clipSelectedText = true,
      color = 'default',
      dropdownStyle,
      over,
      noscroll,
      nochevron,
      width,
      openWidth = width,
      onClick,
      onOpen,
      selected,
      disabled,
      displayText,
      ...boxProps
    } = props;
    const {
      className,
      ...rest
    } = boxProps;

    const adjustedOpen = over ? !this.state.open : this.state.open;

    const menu = this.state.open ? (

 { this.menuRef = menu; }}
        tabIndex="-1"
        style={{
          'width': openWidth,
        }}
        className={classes([
          noscroll && 'Dropdown__menu-noscroll' || 'Dropdown__menu',
          over && 'Dropdown__over',
        ])}>
        {this.buildMenu()}

    ) : null;

    return (

        <Box
          width={this.state.open ? openWidth : width}
          className={classes([
            'Dropdown__control',
            'Button',
            'Button--color--' + color,
            disabled && 'Button--disabled',
            className,
          ])}
          {...rest}
          onClick={(event) => {
            if (disabled && !this.state.open) {
              return;
            }
            this.setOpen(!this.state.open);

            if (props.onOpen) {
              props.onOpen(event);
            }
          }}>
          {icon && (
            <Icon
              name={icon}
              rotation={iconRotation}
              spin={iconSpin}
              mr={1} />
          )}

            {displayText ? displayText : this.state.selected}

          {!!nochevron || (

              <Icon name={adjustedOpen ? 'chevron-up' : 'chevron-down'} />

          )}
        </Box>
        {menu}

    );
  }
}


code:
import { Component, createRef, RefObject } from "inferno";

const DEFAULT_ACCEPTABLE_DIFFERENCE = 5;

export class FitText extends Component<{
  acceptableDifference?: number,
  maxWidth: number,
  maxFontSize: number,
  native?: HTMLAttributes<HTMLDivElement>,
}, {
  fontSize: number,
}> {
  ref: RefObject<HTMLDivElement> = createRef();
  state = {
    fontSize: 0,
  }

  constructor() {
    super();

    this.resize = this.resize.bind(this);

    window.addEventListener("resize", this.resize);
  }

  componentDidUpdate(prevProps) {
    if (prevProps.children !== this.props.children) {
      this.resize();
    }
  }

  componentWillUnmount() {
    window.removeEventListener("resize", this.resize);
  }

  resize() {
    const element = this.ref.current;
    if (!element) {
      return;
    }

    const maxWidth = this.props.maxWidth;

    let start = 0;
    let end = this.props.maxFontSize;

    for (let _ = 0; _ < 10; _++) {
      const middle = Math.round((start + end) / 2);
      element.style.fontSize = `${middle}px`;

      const difference = element.offsetWidth - maxWidth;

      if (difference > 0) {
        end = middle;
      } else if (
        difference
          < (this.props.acceptableDifference ?? DEFAULT_ACCEPTABLE_DIFFERENCE)
      ) {
        start = middle;
      } else {
        break;
      }
    }

    this.setState({
      fontSize: Math.round((start + end) / 2),
    });
  }

  componentDidMount() {
    this.resize();
  }

  render() {
    return (
      <span
        ref={this.ref}
        style={{
          "font-size": `${this.state.fontSize}px`,
          ...(
            typeof this.props.native?.style === "object"
            && this.props.native.style
          ),
        }}>
        {this.props.children}
      </span>
    );
  }
}


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

import { BooleanLike, classes, pureComponentHooks } from 'common/react';
import { BoxProps, computeBoxClassName, computeBoxProps, unit } from './Box';

export type FlexProps = BoxProps & {
   direction?: string | BooleanLike;
   wrap?: string | BooleanLike;
   align?: string | BooleanLike;
   justify?: string | BooleanLike;
   inline?: BooleanLike;
 };

export const computeFlexClassName = (props: FlexProps) => {
  return classes([
    'Flex',
    props.inline && 'Flex--inline',
    Byond.IS_LTE_IE10 && 'Flex--iefix',
    Byond.IS_LTE_IE10 && props.direction === 'column' && 'Flex--iefix--column',
    computeBoxClassName(props),
  ]);
};

export const computeFlexProps = (props: FlexProps) => {
  const {
    className,
    direction,
    wrap,
    align,
    justify,
    inline,
    ...rest
  } = props;
  return computeBoxProps({
    style: {
      ...rest.style,
      'flex-direction': direction,
      'flex-wrap': wrap === true ? 'wrap' : wrap,
      'align-items': align,
      'justify-content': justify,
    },
    ...rest,
  });
};

export const Flex = props => {
  const { className, ...rest } = props;
  return (
    <div
      className={classes([
        className,
        computeFlexClassName(rest),
      ])}
      {...computeFlexProps(rest)}
    />
  );
};

Flex.defaultHooks = pureComponentHooks;

export type FlexItemProps = BoxProps & {
   grow?: number;
   order?: number;
   shrink?: number;
   basis?: string | BooleanLike;
   align?: string | BooleanLike;
 };

export const computeFlexItemClassName = (props: FlexItemProps) => {
  return classes([
    'Flex__item',
    Byond.IS_LTE_IE10 && 'Flex__item--iefix',
    computeBoxClassName(props),
  ]);
};

export const computeFlexItemProps = (props: FlexItemProps) => {
  const {
    className,
    style,
    grow,
    order,
    shrink,
    basis,
    align,
    ...rest
  } = props;
  const computedBasis = basis
    // IE11: Set basis to specified width if it's known, which fixes certain
    // bugs when rendering tables inside the flex.
    ?? props.width
    // If grow is used, basis should be set to 0 to be consistent with
    // flex css shorthand `flex: 1`.
    ?? (grow !== undefined ? 0 : undefined);
  return computeBoxProps({
    style: {
      ...style,
      'flex-grow': grow !== undefined && Number(grow),
      'flex-shrink': shrink !== undefined && Number(shrink),
      'flex-basis': unit(computedBasis),
      'order': order,
      'align-self': align,
    },
    ...rest,
  });
};

const FlexItem = props => {
  const { className, ...rest } = props;
  return (
    <div
      className={classes([
        className,
        computeFlexItemClassName(props),
      ])}
      {...computeFlexItemProps(rest)}
    />
  );
};

FlexItem.defaultHooks = pureComponentHooks;

Flex.Item = FlexItem;


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

import { Table } from './Table';
import { pureComponentHooks } from 'common/react';

/** @deprecated */
export const Grid = props => {
  const { children, ...rest } = props;
  return (
    <Table {...rest}>
      <Table.Row>
        {children}
      </Table.Row>
    </Table>
  );
};

Grid.defaultHooks = pureComponentHooks;

/** @deprecated */
export const GridColumn = props => {
  const { size = 1, style, ...rest } = props;
  return (
    <Table.Cell
      style={{
        width: size + '%',
        ...style,
      }}
      {...rest} />
  );
};

Grid.defaultHooks = pureComponentHooks;

Grid.Column = GridColumn;


code:
/**
 * @file
 * @copyright 2020 Aleksej Komarov
 * @author Original Aleksej Komarov
 * @author Changes ThePotato97
 * @license MIT
 */

import { classes, pureComponentHooks } from 'common/react';
import { computeBoxClassName, computeBoxProps } from './Box';

const FA_OUTLINE_REGEX = /-o$/;

export const Icon = props => {
  const {
    name,
    size,
    spin,
    className,
    rotation,
    inverse,
    ...rest
  } = props;

  if (size) {
    if (!rest.style) {
      rest.style = {};
    }
    rest.style['font-size'] = (size * 100) + '%';
  }
  if (typeof rotation === 'number') {
    if (!rest.style) {
      rest.style = {};
    }
    rest.style['transform'] = `rotate(${rotation}deg)`;
  }

  const boxProps = computeBoxProps(rest);

  let iconClass = "";
  if (name.startsWith("tg-")) {
    // tgfont icon
    iconClass = name;
  } else {
    // font awesome icon
    const faRegular = FA_OUTLINE_REGEX.test(name);
    const faName = name.replace(FA_OUTLINE_REGEX, '');
    iconClass = (faRegular ? 'far ' : 'fas ') + 'fa-'+ faName + (spin ? " fa-spin" : "");
  }
  return (
    <i
      className={classes([
        'Icon',
        iconClass,
        className,
        computeBoxClassName(rest),
      ])}
      {...boxProps} />
  );
};

Icon.defaultHooks = pureComponentHooks;

export const IconStack = props => {
  const {
    className,
    children,
    ...rest
  } = props;
  return (
    <span
      class={classes([
        'IconStack',
        className,
        computeBoxClassName(rest),
      ])}
      {...computeBoxProps(rest)}>
      {children}
    </span>
  );
};

Icon.Stack = IconStack;


code:
import { computeBoxProps } from "./Box";
import { Stack } from "./Stack";
import { ProgressBar } from "./ProgressBar";
import { Button } from "./Button";
import { Component } from 'inferno';

const ZOOM_MIN_VAL = 0.5;
const ZOOM_MAX_VAL = 1.5;

const ZOOM_INCREMENT = 0.1;

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

    this.state = {
      mouseDown: false,

      left: 0,
      top: 0,

      lastLeft: 0,
      lastTop: 0,

      zoom: 1,
    };

    this.handleMouseDown = this.handleMouseDown.bind(this);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.handleZoomIncrease = this.handleZoomIncrease.bind(this);
    this.handleZoomDecrease = this.handleZoomDecrease.bind(this);
    this.onMouseUp = this.onMouseUp.bind(this);

    this.doOffsetMouse = this.doOffsetMouse.bind(this);
  }

  componentDidMount() {
    window.addEventListener("mouseup", this.onMouseUp);

    window.addEventListener("mousedown", this.doOffsetMouse);
    window.addEventListener("mousemove", this.doOffsetMouse);
    window.addEventListener("mouseup", this.doOffsetMouse);
  }

  componentWillUnmount() {
    window.removeEventListener("mouseup", this.onMouseUp);

    window.removeEventListener("mousedown", this.doOffsetMouse);
    window.removeEventListener("mousemove", this.doOffsetMouse);
    window.removeEventListener("mouseup", this.doOffsetMouse);
  }

  doOffsetMouse(event) {
    const { zoom } = this.state;
    event.screenZoomX = event.screenX * Math.pow(zoom, -1);
    event.screenZoomY = event.screenY * Math.pow(zoom, -1);
  }

  handleMouseDown(event) {
    this.setState((state) => {
      return {
        mouseDown: true,
        lastLeft: event.clientX - state.left,
        lastTop: event.clientY - state.top,
      };
    });
  }

  onMouseUp() {
    this.setState({
      mouseDown: false,
    });
  }

  handleZoomIncrease(event) {
    const { onZoomChange } = this.props;
    const { zoom } = this.state;
    const newZoomValue = Math.min(zoom+ZOOM_INCREMENT, ZOOM_MAX_VAL);
    this.setState({
      zoom: newZoomValue,
    });
    if (onZoomChange) {
      onZoomChange(newZoomValue);
    }
  }

  handleZoomDecrease(event) {
    const { onZoomChange } = this.props;
    const { zoom } = this.state;
    const newZoomValue = Math.max(zoom-ZOOM_INCREMENT, ZOOM_MIN_VAL);
    this.setState({
      zoom: newZoomValue,
    });

    if (onZoomChange) {
      onZoomChange(newZoomValue);
    }
  }

  handleMouseMove(event) {
    const {
      onBackgroundMoved,
      initialLeft = 0,
      initialTop = 0,
    } = this.props;
    if (this.state.mouseDown) {
      let newX, newY;
      this.setState((state) => {
        newX = event.clientX - state.lastLeft;
        newY = event.clientY - state.lastTop;
        return {
          left: newX,
          top: newY,
        };
      });
      if (onBackgroundMoved) {
        onBackgroundMoved(newX+initialLeft, newY+initialTop);
      }
    }
  }

  render() {
    const {
      children,
      backgroundImage,
      imageWidth,
      initialLeft = 0,
      initialTop = 0,
      ...rest
    } = this.props;
    const {
      left,
      top,
      zoom,
    } = this.state;

    const finalLeft = initialLeft + left;
    const finalTop = initialTop + top;

    return (
      <div
        ref={this.ref}
        {...computeBoxProps({
          ...rest,
          style: {
            ...rest.style,
            overflow: "hidden",
            position: "relative",
          },
        })}
      >
        <div
          onMouseDown={this.handleMouseDown}
          onMouseMove={this.handleMouseMove}
          style={{
            "position": "fixed",
            "height": "100%",
            "width": "100%",
            "background-image": `url("${backgroundImage}")`,
            "background-position": `${finalLeft}px ${finalTop}px`,
            "background-repeat": "repeat",
            "background-size": `${zoom*imageWidth}px`,
          }}
        />
        <div
          onMouseDown={this.handleMouseDown}
          onMouseMove={this.handleMouseMove}
          style={{
            "position": "fixed",
            "transform": `translate(${finalLeft}px, ${finalTop}px) scale(${zoom})`,
            "transform-origin": "top left",
            "height": "100%",
            "width": "100%",
          }}
        >
          {children}
        </div>

        <Stack
          position="absolute"
          width="100%"
        >
          <Stack.Item>
            <Button
              icon="minus"
              onClick={this.handleZoomDecrease}
            />
          </Stack.Item>
          <Stack.Item grow={1}>
            <ProgressBar
              minValue={ZOOM_MIN_VAL}
              value={zoom}
              maxValue={ZOOM_MAX_VAL}
            >
              {zoom}x
            </ProgressBar>
          </Stack.Item>
          <Stack.Item>
            <Button
              icon="plus"
              onClick={this.handleZoomIncrease}
            />
          </Stack.Item>
        </Stack>
      </div>
    );
  }
}


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

import { classes } from 'common/react';
import { Component, createRef } from 'inferno';
import { Box } from './Box';
import { KEY_ESCAPE, KEY_ENTER } from 'common/keycodes';

export const toInputValue = value => (
  typeof value !== 'number' && typeof value !== 'string'
    ? ''
    : String(value)
);

export class Input extends Component {
  constructor() {
    super();
    this.inputRef = createRef();
    this.state = {
      editing: false,
    };
    this.handleInput = e => {
      const { editing } = this.state;
      const { onInput } = this.props;
      if (!editing) {
        this.setEditing(true);
      }
      if (onInput) {
        onInput(e, e.target.value);
      }
    };
    this.handleFocus = e => {
      const { editing } = this.state;
      if (!editing) {
        this.setEditing(true);
      }
    };
    this.handleBlur = e => {
      const { editing } = this.state;
      const { onChange } = this.props;
      if (editing) {
        this.setEditing(false);
        if (onChange) {
          onChange(e, e.target.value);
        }
      }
    };
    this.handleKeyDown = e => {
      const { onInput, onChange, onEnter } = this.props;
      if (e.keyCode === KEY_ENTER) {
        this.setEditing(false);
        if (onChange) {
          onChange(e, e.target.value);
        }
        if (onInput) {
          onInput(e, e.target.value);
        }
        if (onEnter) {
          onEnter(e, e.target.value);
        }
        if (this.props.selfClear) {
          e.target.value = '';
        } else {
          e.target.blur();
        }
        return;
      }
      if (e.keyCode === KEY_ESCAPE) {
        if (this.props.onEscape) {
          this.props.onEscape(e);
          return;
        }

        this.setEditing(false);
        e.target.value = toInputValue(this.props.value);
        e.target.blur();
        return;
      }
    };
  }

  componentDidMount() {
    const nextValue = this.props.value;
    const input = this.inputRef.current;
    if (input) {
      input.value = toInputValue(nextValue);
    }

    if (this.props.autoFocus || this.props.autoSelect) {
      setTimeout(() => {
        input.focus();

        if (this.props.autoSelect) {
          input.select();
        }
      }, 1);
    }
  }

  componentDidUpdate(prevProps, prevState) {
    const { editing } = this.state;
    const prevValue = prevProps.value;
    const nextValue = this.props.value;
    const input = this.inputRef.current;
    if (input && !editing && prevValue !== nextValue) {
      input.value = toInputValue(nextValue);
    }
  }

  setEditing(editing) {
    this.setState({ editing });
  }

  render() {
    const { props } = this;
    // Input only props
    const {
      selfClear,
      onInput,
      onChange,
      onEnter,
      value,
      maxLength,
      placeholder,
      ...boxProps
    } = props;
    // Box props
    const {
      className,
      fluid,
      monospace,
      ...rest
    } = boxProps;
    return (
      <Box
        className={classes([
          'Input',
          fluid && 'Input--fluid',
          monospace && 'Input--monospace',
          className,
        ])}
        {...rest}>
        <div className="Input__baseline">
          .
        </div>
        <input
          ref={this.inputRef}
          className="Input__input"
          placeholder={placeholder}
          onInput={this.handleInput}
          onFocus={this.handleFocus}
          onBlur={this.handleBlur}
          onKeyDown={this.handleKeyDown}
          maxLength={maxLength} />
      </Box>
    );
  }
}


code:
import { Component } from "inferno";
import { KeyEvent } from "../events";
import { listenForKeyEvents } from "../hotkeys";

type KeyListenerProps = Partial<{
  onKey: (key: KeyEvent) => void,
  onKeyDown: (key: KeyEvent) => void,
  onKeyUp: (key: KeyEvent) => void,
}>;

export class KeyListener extends Component<KeyListenerProps> {
  dispose: () => void;

  constructor() {
    super();

    this.dispose = listenForKeyEvents(key => {
      if (this.props.onKey) {
        this.props.onKey(key);
      }

      if (key.isDown() && this.props.onKeyDown) {
        this.props.onKeyDown(key);
      }

      if (key.isUp() && this.props.onKeyUp) {
        this.props.onKeyUp(key);
      }
    });
  }

  componentWillUnmount() {
    this.dispose();
  }

  render() {
    return null;
  }
}


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

import { keyOfMatchingRange, scale } from 'common/math';
import { classes } from 'common/react';
import { computeBoxClassName, computeBoxProps } from './Box';
import { DraggableControl } from './DraggableControl';
import { NumberInput } from './NumberInput';

export const Knob = props => {
  // IE8: I don't want to support a yet another component on IE8.
  // IE8: It also can't handle SVG.
  if (Byond.IS_LTE_IE8) {
    return (
      <NumberInput {...props} />
    );
  }
  const {
    // Draggable props (passthrough)
    animated,
    format,
    maxValue,
    minValue,
    unclamped,
    onChange,
    onDrag,
    step,
    stepPixelSize,
    suppressFlicker,
    unit,
    value,
    // Own props
    className,
    style,
    fillValue,
    color,
    ranges = {},
    size = 1,
    bipolar,
    children,
    ...rest
  } = props;
  return (
    <DraggableControl
      dragMatrix={[0, -1]}
      {...{
        animated,
        format,
        maxValue,
        minValue,
        unclamped,
        onChange,
        onDrag,
        step,
        stepPixelSize,
        suppressFlicker,
        unit,
        value,
      }}>
      {control => {
        const {
          dragging,
          editing,
          value,
          displayValue,
          displayElement,
          inputElement,
          handleDragStart,
        } = control;
        const scaledFillValue = scale(
          fillValue ?? displayValue,
          minValue,
          maxValue);
        const scaledDisplayValue = scale(
          displayValue,
          minValue,
          maxValue);
        const effectiveColor = color
          || keyOfMatchingRange(fillValue ?? value, ranges)
          || 'default';
        const rotation = Math.min((scaledDisplayValue - 0.5) * 270, 225);
        return (

              <div
                className="Knob__cursorBox"
                style={{
                  transform: `rotate(${rotation}deg)`,
                }}>

            {dragging && (

                {displayElement}

            )}
            <svg
              className="Knob__ring Knob__ringTrackPivot"
              viewBox="0 0 100 100">
              <circle
                className="Knob__ringTrack"
                cx="50"
                cy="50"
                r="50" />
            </svg>
            <svg
              className="Knob__ring Knob__ringFillPivot"
              viewBox="0 0 100 100">
              <circle
                className="Knob__ringFill"
                style={{
                  'stroke-dashoffset': (
                    Math.max(((bipolar ? 2.75 : 2.00) - scaledFillValue * 1.5)
                      * Math.PI * 50, 0)
                  ),
                }}
                cx="50"
                cy="50"
                r="50" />
            </svg>
            {inputElement}

        );
      }}
    </DraggableControl>
  );
};


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

import { Flex } from './Flex';

export const LabeledControls = props => {
  const {
    children,
    wrap,
    ...rest
  } = props;
  return (
    <Flex
      mx={-0.5}
      wrap={wrap}
      align="stretch"
      justify="space-between"
      {...rest}>
      {children}
    </Flex>
  );
};

const LabeledControlsItem = props => {
  const {
    label,
    children,
    mx = 1,
    ...rest
  } = props;
  return (
    <Flex.Item mx={mx}>
      <Flex
        height="100%"
        direction="column"
        align="center"
        textAlign="center"
        justify="space-between"
        {...rest}>
        <Flex.Item />
        <Flex.Item>
          {children}
        </Flex.Item>
        <Flex.Item color="label">
          {label}
        </Flex.Item>
      </Flex>
    </Flex.Item>
  );
};

LabeledControls.Item = LabeledControlsItem;


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

import { BooleanLike, classes, pureComponentHooks } from 'common/react';
import { InfernoNode } from 'inferno';
import { Box, unit } from './Box';
import { Divider } from './Divider';

type LabeledListProps = {
  children?: any;
};

export const LabeledList = (props: LabeledListProps) => {
  const { children } = props;
  return (

      {children}

  );
};

LabeledList.defaultHooks = pureComponentHooks;

type LabeledListItemProps = {
  className?: string | BooleanLike;
  label?: string | InfernoNode | BooleanLike;
  labelColor?: string | BooleanLike;
  color?: string | BooleanLike;
  textAlign?: string | BooleanLike;
  buttons?: InfernoNode,
  /** @deprecated */
  content?: any,
  children?: InfernoNode;
  verticalAlign?: string;
};

const LabeledListItem = (props: LabeledListItemProps) => {
  const {
    className,
    label,
    labelColor = 'label',
    color,
    textAlign,
    buttons,
    content,
    children,
    verticalAlign = "baseline",
  } = props;
  return (

      <Box
        as="td"
        color={labelColor}
        className={classes([
          'LabeledList__cell',
          'LabeledList__label',
        ])}
        verticalAlign={verticalAlign}>
        {label ? typeof(label) === "string" ? label + ':' : label : null}
      </Box>
      <Box
        as="td"
        color={color}
        textAlign={textAlign}
        className={classes([
          'LabeledList__cell',
          'LabeledList__content',
        ])}
        colSpan={buttons ? undefined : 2}
        verticalAlign={verticalAlign}>
        {content}
        {children}
      </Box>
      {buttons && (

          {buttons}

      )}

  );
};

LabeledListItem.defaultHooks = pureComponentHooks;

type LabeledListDividerProps = {
  size?: number;
};

const LabeledListDivider = (props: LabeledListDividerProps) => {
  const padding = props.size
    ? unit(Math.max(0, props.size - 1))
    : 0;
  return (

        <Divider />

  );
};

LabeledListDivider.defaultHooks = pureComponentHooks;

LabeledList.Item = LabeledListItem;
LabeledList.Divider = LabeledListDivider;


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

import { classes } from 'common/react';
import { computeBoxClassName, computeBoxProps } from './Box';
import { Dimmer } from './Dimmer';

export const Modal = props => {
  const {
    className,
    children,
    ...rest
  } = props;
  return (
    <Dimmer>
      <div
        className={classes([
          'Modal',
          className,
          computeBoxClassName(rest),
        ])}
        {...computeBoxProps(rest)}>
        {children}
      </div>
    </Dimmer>
  );
};