<commit_before>/*
 * Character Count content script
 */

/*
 * Constants
 */
var ESCAPE_KEY = 27;
var SELECTION_CHANGE_EVENT = 'selectionChange';

/*
 * Creates and controls an element that displays character count information
 */
function CharacterCountPopup() {
  this.ID = 'character-count-popup';
  this.popup = $('
').css({
    'position': 'fixed',
    'z-index': 10000000,
    'top': 0,
    'right': 0,
    'width': 'auto',
    'height': 'auto',
    'margin': 0,
    'padding': '6px',
    'border': 'none',
    'outline': 'none',
    'box-shadow': 'none',
    'background-color': 'rgba(255, 255, 255, 0.8)',
    'color': '#333',
    'font-family': 'Menlo, Consolas, "Liberation Mono", monospace',
    'font-size': '14px',
    'line-height': 1,
    'text-decoration': 'none',
    'vertical-align': 'baseline',
    'user-select': 'none',
    'pointer-events': 'none'
  })[0];
}

/*
 * Return message for given count (required)
 */
CharacterCountPopup.prototype.getMessageForCount = function (count) {
  return count + ' character' + (count === 1 ? '' : 's') + ' selected';
};

/*
 * Display character count popup with given count (default: 0)
 */
CharacterCountPopup.prototype.show = function (count) {
  var message = this.getMessageForCount(count || 0);
  $(this.popup).html(message);
  document.body.appendChild(this.popup);
};

/*
 * Hide character count popup
 */
CharacterCountPopup.prototype.hide = function () {
  var parent = this.popup.parentNode;
  if (parent) {
    parent.removeChild(this.popup);
  }
};

/*
 * An object for observing and comparing current text selections.
 *
 * When instantiated, captures the current selection and computes a count
 * of characters selected.
 *
 * May be compared for equality against other EquatableSelection instances.
 *
 * Should be treated as immutable.
 */
function EquatableSelection() {
  var selection = window.getSelection();
  if (selection && selection.rangeCount === 1) {
    this.text = selection.toString();
    this.count = this._computeCount(selection);
  }
}

/*
 * (Private) Compute the number of characters in a EquatableSelection
 */
EquatableSelection.prototype._computeCount = function (selection) {
  var text = selection.toString();

  if (!text || !text.length) {
    return 0;
  }

  // Consider a groups of newline whitespace to be one space each
  var countWithCompressedNewlines = text.replace(/[\n\r]+/g, ' ').length;

  var anchorNode = selection.anchorNode; // node containing start of selection
  var focusNode = selection.focusNode; // node containing end of selection

  // If focus node is a text node, considerly slightly more accurate counting approaches
  if (focusNode.nodeType == Node.TEXT_NODE) {

    // If the anchor node and the focus node are the same, the count of characters
    // selected is the absolute difference of the anchor and focus offsets
    if (anchorNode === focusNode) {
      return Math.abs(selection.focusOffset - selection.anchorOffset);
    }

    // If focus node follows anchor node, selection.toString() sometimes includes
    // trailing whitespace even if it isn't selected. Decrease count by one if there
    // is a discrepancy with focusOffset.
    if (anchorNode.compareDocumentPosition(focusNode) & Node.DOCUMENT_POSITION_FOLLOWING) {
      if (focusNode.data[selection.focusOffset - 1] !== text[text.length - 1]) {
        return countWithCompressedNewlines - 1;
      }
    }
  }

  return countWithCompressedNewlines;
};

/*
 * Return true if two EquatableSelection objects are equal, false otherwise
 */
EquatableSelection.prototype.isEqual = function (other) {
  return other &&
    this.count === other.count &&
    this.text === other.text;
};

/*
 * An object that listens for selection changes on a target object and triggers
 * a custom jQuery 'selectionChange' event when changes are observed.
 *
 * Observes changes using EquatableSelection.
 *
 * Triggered selectionChange events contain a `selection` property containing an
 * EquatableSelection if a selection exists. In the case of a previously-selected
 * selection being unselected, the triggered selectionChange event will not contain
 * a `selection` property.
 */
function SelectionListener(target) {
  this.target = target;

  // An internal event used to force extra selection change checks
  this.SELECTION_CHANGE_IMMINENT_EVENT = 'selectionChangeImminent';

  // Events which may indicate selection changes
  this.events = [
    'mousemove',
    'mousedown',
    'mouseup',
    'keydown',
    'keyup',
    this.SELECTION_CHANGE_IMMINENT_EVENT
  ];

  this.isListening = false;
}

/*
 * Handle events and check for selection changes.
 *
 * Trigger selectionChange event if a selection change has occurred.
 */
SelectionListener.prototype.handleEvent = function (event) {
  var current = new EquatableSelection();

  if (current && current.count) {
    if (!current.isEqual(this.previous)) {
      // Trigger a selectionChange event if this selection is not equal to
      // the previously-observed selection
      $(this.target).trigger({
        type: SELECTION_CHANGE_EVENT,
        selection: current
      });
      this.previous = current;
    }
  } else if (this.previous) {
    // Trigger empty event to signify that a selection has been "unselected"
    $(this.target).trigger(SELECTION_CHANGE_EVENT);
    this.previous = null;
  }

  // Mouse clicks can cause selections to change, but the selection change
  // might not have completed yet. Check again shortly.
  if (event && (event.type === 'mousedown' || event.type === 'mouseup')) {
    var self = this;
    setTimeout(function () {
      $(self.target).trigger(self.SELECTION_CHANGE_IMMINENT_EVENT);
    }, 20);
  }
};

/*
 * Start listening for selection changes.
 *
 * If there is an existing selection when this method is called, a
 * selectionChange event will be immediately triggered.
 */
SelectionListener.prototype.start = function () {
  if (!this.isListening) {
    _.each(this.events, function (event) {
      $(this.target).on(event, this.handleEvent.bind(this));
    }, this);
    this.isListening = true;

    // Check for existing seletion immediately
    $(this.target).trigger(this.SELECTION_CHANGE_IMMINENT_EVENT);
  }
};

/*
 * Stop listening for selection changes.
 *
 * One empty selectionChange event is immediately triggered.
 */
SelectionListener.prototype.stop = function () {
  if (this.isListening) {
    $(this.target).trigger(SELECTION_CHANGE_EVENT);
    this.previous = null;

    _.each(this.events, function (event) {
      $(this.target).off(event);
    }, this);
    this.isListening = false;
  }
};

/*
 * Instantiate popup and and listeners
 */
var target = window;
var popup = new CharacterCountPopup();
var selectionListener = new SelectionListener(target);

// Listen for messages from other parts of the extension to start/stop selection listening
chrome.runtime.onMessage.addListener(function (message) {
  if (message.active) {
    selectionListener.start();
  } else {
    selectionListener.stop();
  }
});

// Listen for selection changes and show/hide the popup based on the number of
// characters selected
$(target).on(SELECTION_CHANGE_EVENT, function (event) {
  if (event.selection) {
    popup.show(event.selection.count);
  } else {
    popup.hide();
  }
});

// When escape key is down, disable the extension
$(document).on('keydown', function (event) {
  if (event.which === ESCAPE_KEY) {
    chrome.runtime.sendMessage({ active: false });
  }
});
<commit_msg>Fixed bug where leading whitespace is not counted<commit_after>/*
 * Character Count content script
 */

/*
 * Constants
 */
var ESCAPE_KEY = 27;
var SELECTION_CHANGE_EVENT = 'selectionChange';

/*
 * Creates and controls an element that displays character count information
 */
function CharacterCountPopup() {
  this.ID = 'character-count-popup';
  this.popup = $('
').css({
    'position': 'fixed',
    'z-index': 10000000,
    'top': 0,
    'right': 0,
    'width': 'auto',
    'height': 'auto',
    'margin': 0,
    'padding': '6px',
    'border': 'none',
    'outline': 'none',
    'box-shadow': 'none',
    'background-color': 'rgba(255, 255, 255, 0.8)',
    'color': '#333',
    'font-family': 'Menlo, Consolas, "Liberation Mono", monospace',
    'font-size': '14px',
    'line-height': 1,
    'text-decoration': 'none',
    'vertical-align': 'baseline',
    'user-select': 'none',
    'pointer-events': 'none'
  })[0];
}

/*
 * Return message for given count (required)
 */
CharacterCountPopup.prototype.getMessageForCount = function (count) {
  return count + ' character' + (count === 1 ? '' : 's') + ' selected';
};

/*
 * Display character count popup with given count (default: 0)
 */
CharacterCountPopup.prototype.show = function (count) {
  var message = this.getMessageForCount(count || 0);
  $(this.popup).html(message);
  document.body.appendChild(this.popup);
};

/*
 * Hide character count popup
 */
CharacterCountPopup.prototype.hide = function () {
  var parent = this.popup.parentNode;
  if (parent) {
    parent.removeChild(this.popup);
  }
};

/*
 * An object for observing and comparing current text selections.
 *
 * When instantiated, captures the current selection and computes a count
 * of characters selected.
 *
 * May be compared for equality against other EquatableSelection instances.
 *
 * Should be treated as immutable.
 */
function EquatableSelection() {
  var selection = window.getSelection();
  if (selection && selection.rangeCount === 1) {
    this.text = selection.toString();
    this.count = this._computeCount(selection);
  }
}

/*
 * (Private) Compute the number of characters in a EquatableSelection
 */
EquatableSelection.prototype._computeCount = function (selection) {
  var text = selection.toString();

  if (!text || !text.length) {
    return 0;
  }

  // Consider a groups of newline whitespace to be one space each
  var countWithCompressedNewlines = text.replace(/[\n\r]+/g, ' ').length;

  var anchorNode = selection.anchorNode; // node containing start of selection
  var focusNode = selection.focusNode; // node containing end of selection

  // If focus node is a text node, considerly slightly more accurate counting approaches
  if (focusNode.nodeType == Node.TEXT_NODE) {

    // If the anchor node and the focus node are the same, the count of characters
    // selected is the absolute difference of the anchor and focus offsets
    if (anchorNode === focusNode) {
      return Math.abs(selection.focusOffset - selection.anchorOffset);
    }

    // If focus node follows anchor node, selection.toString() sometimes includes
    // trailing whitespace even if it isn't selected. Decrease count by one if there
    // is a discrepancy with focusOffset.
    if (anchorNode.compareDocumentPosition(focusNode) & Node.DOCUMENT_POSITION_FOLLOWING) {
      if (focusNode.data[selection.focusOffset - 1] !== text[text.length - 1]) {
        return countWithCompressedNewlines - 1;
      }
    }

    // If focus node trails anchor node, selection.toString() sometimes fails
    // to include leading whitespace, even if it selected. Increase count by
    // one if there is a discrepancy with focusOffset.
    if (focusNode.compareDocumentPosition(anchorNode) & Node.DOCUMENT_POSITION_FOLLOWING) {
      var leadingCharacter = focusNode.data[selection.focusOffset];
      if (leadingCharacter && leadingCharacter !== text[0] && leadingCharacter === ' ') {
          return countWithCompressedNewlines + 1;
      }
    }
  }

  return countWithCompressedNewlines;
};

/*
 * Return true if two EquatableSelection objects are equal, false otherwise
 */
EquatableSelection.prototype.isEqual = function (other) {
  return other &&
    this.count === other.count &&
    this.text === other.text;
};

/*
 * An object that listens for selection changes on a target object and triggers
 * a custom jQuery 'selectionChange' event when changes are observed.
 *
 * Observes changes using EquatableSelection.
 *
 * Triggered selectionChange events contain a `selection` property containing an
 * EquatableSelection if a selection exists. In the case of a previously-selected
 * selection being unselected, the triggered selectionChange event will not contain
 * a `selection` property.
 */
function SelectionListener(target) {
  this.target = target;

  // An internal event used to force extra selection change checks
  this.SELECTION_CHANGE_IMMINENT_EVENT = 'selectionChangeImminent';

  // Events which may indicate selection changes
  this.events = [
    'mousemove',
    'mousedown',
    'mouseup',
    'keydown',
    'keyup',
    this.SELECTION_CHANGE_IMMINENT_EVENT
  ];

  this.isListening = false;
}

/*
 * Handle events and check for selection changes.
 *
 * Trigger selectionChange event if a selection change has occurred.
 */
SelectionListener.prototype.handleEvent = function (event) {
  var current = new EquatableSelection();

  if (current && current.count) {
    if (!current.isEqual(this.previous)) {
      // Trigger a selectionChange event if this selection is not equal to
      // the previously-observed selection
      $(this.target).trigger({
        type: SELECTION_CHANGE_EVENT,
        selection: current
      });
      this.previous = current;
    }
  } else if (this.previous) {
    // Trigger empty event to signify that a selection has been "unselected"
    $(this.target).trigger(SELECTION_CHANGE_EVENT);
    this.previous = null;
  }

  // Mouse clicks can cause selections to change, but the selection change
  // might not have completed yet. Check again shortly.
  if (event && (event.type === 'mousedown' || event.type === 'mouseup')) {
    var self = this;
    setTimeout(function () {
      $(self.target).trigger(self.SELECTION_CHANGE_IMMINENT_EVENT);
    }, 20);
  }
};

/*
 * Start listening for selection changes.
 *
 * If there is an existing selection when this method is called, a
 * selectionChange event will be immediately triggered.
 */
SelectionListener.prototype.start = function () {
  if (!this.isListening) {
    _.each(this.events, function (event) {
      $(this.target).on(event, this.handleEvent.bind(this));
    }, this);
    this.isListening = true;

    // Check for existing seletion immediately
    $(this.target).trigger(this.SELECTION_CHANGE_IMMINENT_EVENT);
  }
};

/*
 * Stop listening for selection changes.
 *
 * One empty selectionChange event is immediately triggered.
 */
SelectionListener.prototype.stop = function () {
  if (this.isListening) {
    $(this.target).trigger(SELECTION_CHANGE_EVENT);
    this.previous = null;

    _.each(this.events, function (event) {
      $(this.target).off(event);
    }, this);
    this.isListening = false;
  }
};

/*
 * Instantiate popup and and listeners
 */
var target = window;
var popup = new CharacterCountPopup();
var selectionListener = new SelectionListener(target);

// Listen for messages from other parts of the extension to start/stop selection listening
chrome.runtime.onMessage.addListener(function (message) {
  if (message.active) {
    selectionListener.start();
  } else {
    selectionListener.stop();
  }
});

// Listen for selection changes and show/hide the popup based on the number of
// characters selected
$(target).on(SELECTION_CHANGE_EVENT, function (event) {
  if (event.selection) {
    popup.show(event.selection.count);
  } else {
    popup.hide();
  }
});

// When escape key is down, disable the extension
$(document).on('keydown', function (event) {
  if (event.which === ESCAPE_KEY) {
    chrome.runtime.sendMessage({ active: false });
  }
});
<|endoftext|>


<commit_before>"use strict";

const normalizer = require("./cli-normalizer");

const detailOptions = normalizer.normalizeDetailOptions({
  "bracket-spacing": {
    type: "boolean",
    category: "format",
    isHidden: true
  },
  color: {
    // The supports-color package (a sub sub dependency) looks directly at
    // `process.argv` for `--no-color` and such-like options. The reason it is
    // listed here is to avoid "Ignored unknown option: --no-color" warnings.
    // See https://github.com/chalk/supports-color/#info for more information.
    type: "boolean",
    default: true,
    isHidden: true
  },
  config: {
    type: "path",
    description:
      "Path to a prettier configuration file (.prettierrc, package.json, prettier.config.js)."
  },
  "config-precedence": {
    type: "choice",
    default: "cli-override",
    choices: ["cli-override", "file-override", "prefer-file"],
    description: dedent(`
      Defines how config file should be evaluated in combination of CLI options
      cli-override  | default config => config file => CLI options
      file-override | default config => CLI options => config file
      prefer-file   | default config => config file (if config file is found) or
                      default config => CLI options (if no config file is found)
      Defaults to cli-override
    `)
  },
  "cursor-offset": {
    type: "int",
    exception: -1,
    category: "format",
    description: dedent(`
      Print (to stderr) where a cursor at the given position would move to after formatting.
      This option cannot be used with --range-start and --range-end
    `)
  },
  "debug-check": {
    type: "boolean",
    isHidden: true
  },
  "debug-print-doc": {
    type: "boolean",
    isHidden: true
  },
  "find-config-path": {
    type: "path",
    description:
      "Finds and prints the path to a configuration file for a given input file."
  },
  "flow-parser": {
    // Deprecated in 0.0.10
    type: "boolean",
    isHidden: true,
    category: "format",
    deprecated: "Use `--parser flow` instead."
  },
  help: {
    type: "boolean",
    alias: "h",
    description: "Show help."
  },
  "ignore-path": {
    type: "path",
    default: ".prettierignore",
    description: dedent(`
      Path to a file containing patterns that describe files to ignore.
      Defaults to ./.prettierignore.
    `)
  },
  "jsx-bracket-same-line": {
    type: "boolean",
    category: "format",
    description: "Put > on the last line instead of at a new line."
  },
  "list-different": {
    type: "boolean",
    alias: "l",
    description:
      "Print filenames of files that are different from Prettier formatting."
  },
  "no-bracket-spacing": {
    type: "boolean",
    isDocsOnly: true,
    description: "Do not print spaces between brackets."
  },
  "no-color": {
    type: "boolean",
    isDocsOnly: true,
    description: "Do not colorize error messages."
  },
  "no-config": {
    type: "boolean",
    isDocsOnly: true,
    description: "Do not look for a configuration file."
  },
  "no-semi": {
    type: "boolean",
    isDocsOnly: true,
    description:
      "Do not print semicolons, except at the beginning of lines which may need them."
  },
  parser: {
    type: "choice",
    exception: value => typeof value === "string",
    category: "format",
    choices: ["flow", "babylon", "typescript", "postcss", "json", "graphql"],
    description: "Specify which parse to use. Defaults to babylon.",
    getter: (value, argv) => (argv["flow-parser"] ? "flow" : value)
  },
  "print-width": {
    type: "int",
    category: "format",
    description:
      "Specify the length of line that the printer will wrap on. Defaults to 80."
  },
  "range-end": {
    type: "int",
    exception: Infinity,
    category: "format",
    description: dedent(`
      Format code ending at a given character offset (exclusive).
      The range will extend forwards to the end of the selected statement.
      This option cannot be used with --cursor-offset.
      Defaults to Infinity.
    `)
  },
  "range-start": {
    type: "int",
    category: "format",
    description: dedent(`
      Format code starting at a given character offset.
      The range will extend backwards to the start of the first line containing the selected statement.
      This option cannot be used with --cursor-offset.
      Defaults to 0.
    `)
  },
  semi: {
    type: "boolean",
    category: "format",
    isHidden: true
  },
  "single-quote": {
    type: "boolean",
    category: "format",
    description: "Use single quotes instead of double quotes."
  },
  stdin: {
    type: "boolean",
    description: "Read input from stdin."
  },
  "stdin-filepath": {
    type: "path",
    description: "Path to the file used to read from stdin."
  },
  "tab-width": {
    type: "int",
    category: "format",
    description:
      "Specify the number of spaces per indentation-level. Defaults to 2."
  },
  "trailing-comma": {
    type: "choice",
    category: "format",
    choices: [
      "none",
      "es5",
      "all",
      { value: "", deprecated: true, redirect: "es5" }
    ],
    description:
      "Print trailing commas wherever possible when multi-line. Defaults to none."
  },
  "use-tabs": {
    type: "boolean",
    category: "format",
    description: "Indent lines with tabs instead of spaces."
  },
  version: {
    type: "boolean",
    alias: "v",
    description: "Print Prettier version."
  },
  "with-node-modules": {
    type: "boolean",
    description: "Process files inside 'node_modules' directory."
  },
  write: {
    type: "boolean",
    description: "Edit the file in-place. (Beware!)"
  }
});

const minimistOptions = {
  boolean: detailOptions
    .filter(option => !option.isDocsOnly && option.type === "boolean")
    .map(option => option.name),
  string: detailOptions
    .filter(option => !option.isDocsOnly && option.type !== "boolean")
    .map(option => option.name),
  default: detailOptions
    .filter(option => option.default !== undefined)
    .reduce(
      (current, option) =>
        Object.assign({ [option.name]: option.default }, current),
      {}
    ),
  alias: detailOptions
    .filter(option => option.alias !== undefined)
    .reduce(
      (current, option) =>
        Object.assign({ [option.name]: option.alias }, current),
      {}
    ),
  unknown: param => {
    if (param.startsWith("-")) {
      console.warn(`Ignored unknown option: ${param}\n`);
      return false;
    }
  }
};

function dedent(str) {
  const spaces = str.match(/\n^( +)/m)[1].length;
  return str.replace(new RegExp(`^ {${spaces}}`, "gm"), "").trim();
}

module.exports = {
  minimistOptions,
  detailOptions
};
<commit_msg>refactor: dont consider prefix `no-` option as real option<commit_after>"use strict";

const normalizer = require("./cli-normalizer");

const detailOptions = normalizer.normalizeDetailOptions({
  "bracket-spacing": {
    type: "boolean",
    category: "format",
    isHidden: true
  },
  color: {
    // The supports-color package (a sub sub dependency) looks directly at
    // `process.argv` for `--no-color` and such-like options. The reason it is
    // listed here is to avoid "Ignored unknown option: --no-color" warnings.
    // See https://github.com/chalk/supports-color/#info for more information.
    type: "boolean",
    default: true,
    isHidden: true
  },
  config: {
    type: "path",
    description:
      "Path to a prettier configuration file (.prettierrc, package.json, prettier.config.js)."
  },
  "config-precedence": {
    type: "choice",
    default: "cli-override",
    choices: ["cli-override", "file-override", "prefer-file"],
    description: dedent(`
      Defines how config file should be evaluated in combination of CLI options
      cli-override  | default config => config file => CLI options
      file-override | default config => CLI options => config file
      prefer-file   | default config => config file (if config file is found) or
                      default config => CLI options (if no config file is found)
      Defaults to cli-override
    `)
  },
  "cursor-offset": {
    type: "int",
    exception: -1,
    category: "format",
    description: dedent(`
      Print (to stderr) where a cursor at the given position would move to after formatting.
      This option cannot be used with --range-start and --range-end
    `)
  },
  "debug-check": {
    type: "boolean",
    isHidden: true
  },
  "debug-print-doc": {
    type: "boolean",
    isHidden: true
  },
  "find-config-path": {
    type: "path",
    description:
      "Finds and prints the path to a configuration file for a given input file."
  },
  "flow-parser": {
    // Deprecated in 0.0.10
    type: "boolean",
    isHidden: true,
    category: "format",
    deprecated: "Use `--parser flow` instead."
  },
  help: {
    type: "boolean",
    alias: "h",
    description: "Show help."
  },
  "ignore-path": {
    type: "path",
    default: ".prettierignore",
    description: dedent(`
      Path to a file containing patterns that describe files to ignore.
      Defaults to ./.prettierignore.
    `)
  },
  "jsx-bracket-same-line": {
    type: "boolean",
    category: "format",
    description: "Put > on the last line instead of at a new line."
  },
  "list-different": {
    type: "boolean",
    alias: "l",
    description:
      "Print filenames of files that are different from Prettier formatting."
  },
  "no-bracket-spacing": {
    type: "boolean",
    description: "Do not print spaces between brackets."
  },
  "no-color": {
    type: "boolean",
    description: "Do not colorize error messages."
  },
  "no-config": {
    type: "boolean",
    description: "Do not look for a configuration file."
  },
  "no-semi": {
    type: "boolean",
    description:
      "Do not print semicolons, except at the beginning of lines which may need them."
  },
  parser: {
    type: "choice",
    exception: value => typeof value === "string",
    category: "format",
    choices: ["flow", "babylon", "typescript", "postcss", "json", "graphql"],
    description: "Specify which parse to use. Defaults to babylon.",
    getter: (value, argv) => (argv["flow-parser"] ? "flow" : value)
  },
  "print-width": {
    type: "int",
    category: "format",
    description:
      "Specify the length of line that the printer will wrap on. Defaults to 80."
  },
  "range-end": {
    type: "int",
    exception: Infinity,
    category: "format",
    description: dedent(`
      Format code ending at a given character offset (exclusive).
      The range will extend forwards to the end of the selected statement.
      This option cannot be used with --cursor-offset.
      Defaults to Infinity.
    `)
  },
  "range-start": {
    type: "int",
    category: "format",
    description: dedent(`
      Format code starting at a given character offset.
      The range will extend backwards to the start of the first line containing the selected statement.
      This option cannot be used with --cursor-offset.
      Defaults to 0.
    `)
  },
  semi: {
    type: "boolean",
    category: "format",
    isHidden: true
  },
  "single-quote": {
    type: "boolean",
    category: "format",
    description: "Use single quotes instead of double quotes."
  },
  stdin: {
    type: "boolean",
    description: "Read input from stdin."
  },
  "stdin-filepath": {
    type: "path",
    description: "Path to the file used to read from stdin."
  },
  "tab-width": {
    type: "int",
    category: "format",
    description:
      "Specify the number of spaces per indentation-level. Defaults to 2."
  },
  "trailing-comma": {
    type: "choice",
    category: "format",
    choices: [
      "none",
      "es5",
      "all",
      { value: "", deprecated: true, redirect: "es5" }
    ],
    description:
      "Print trailing commas wherever possible when multi-line. Defaults to none."
  },
  "use-tabs": {
    type: "boolean",
    category: "format",
    description: "Indent lines with tabs instead of spaces."
  },
  version: {
    type: "boolean",
    alias: "v",
    description: "Print Prettier version."
  },
  "with-node-modules": {
    type: "boolean",
    description: "Process files inside 'node_modules' directory."
  },
  write: {
    type: "boolean",
    description: "Edit the file in-place. (Beware!)"
  }
});

const minimistOptions = {
  boolean: detailOptions
    .filter(
      option => !option.name.startsWith("no-") && option.type === "boolean"
    )
    .map(option => option.name),
  string: detailOptions
    .filter(
      option => !option.name.startsWith("no-") && option.type !== "boolean"
    )
    .map(option => option.name),
  default: detailOptions
    .filter(option => option.default !== undefined)
    .reduce(
      (current, option) =>
        Object.assign({ [option.name]: option.default }, current),
      {}
    ),
  alias: detailOptions
    .filter(option => option.alias !== undefined)
    .reduce(
      (current, option) =>
        Object.assign({ [option.name]: option.alias }, current),
      {}
    ),
  unknown: param => {
    if (param.startsWith("-")) {
      console.warn(`Ignored unknown option: ${param}\n`);
      return false;
    }
  }
};

function dedent(str) {
  const spaces = str.match(/\n^( +)/m)[1].length;
  return str.replace(new RegExp(`^ {${spaces}}`, "gm"), "").trim();
}

module.exports = {
  minimistOptions,
  detailOptions
};
<|endoftext|>


<commit_before>LinkedGrid = function(container, columns, rows, maxWidth, maxHeight, debug) {
  this.debug = debug | false;

  this.container = container; // a display container to add gfx elements to

  this.setSize(columns, rows, maxWidth, maxHeight);

  this.colors = [];

  this.cells = new Array(this.columns * this.rows); // array with cell objects
  for (var i = 0; i < this.columns * this.rows; i++) {
    this.cells[i] = null;
  }
  this.maxCellIndex = -1; // maximal used cell index

  this.field = new Array(this.columns); // 2d array with cell references
  for (var column = 0; column < this.columns; column++) {
    this.field[column] = new Array(this.rows);
    for (var row = 0; row < rows; row++) {
      this.field[column][row] = null;
    }
  }
};

LinkedGrid.prototype.constructor = LinkedGrid;

LinkedGrid.prototype.setSize = function(columns, rows, maxWidth, maxHeight) {
  // max/full width of display container
  this.maxWidth = maxWidth;
  this.maxHeight = maxHeight;

  // grid size by columns/rows
  this.columns = columns;
  this.rows = rows;

  // calculate cell size / margin depending on grid size and cell amount
  this.cellWidth = 0;
  this.cellHeight = 0;
  this.cellMarginTop = 0;
  this.cellMarginLeft = 0;
  if (this.rows > this.columns) {
    // calculate cell size to get correct cell proportions
    if (this.maxWidth < this.maxHeight) {
      this.cellWidth = this.maxWidth / this.columns;
      this.cellHeight = this.maxWidth / this.columns;
      // calculate cell margin to center grid cells
      this.cellMarginTop = (this.maxHeight - this.cellHeight * this.rows) / 2;
    } else {
      this.cellWidth = this.maxHeight / this.rows;
      this.cellHeight = this.maxHeight / this.rows;
      // calculate cell margin to center grid cells
      this.cellMarginLeft = (this.maxWidth - this.cellWidth * this.columns) / 2;
    }
  } else if (this.rows < this.columns) {
    // calculate cell size to get correct cell proportions
    if (this.maxWidth > this.maxHeight) {
      this.cellWidth = this.maxHeight / this.rows;
      this.cellHeight = this.maxHeight / this.rows;
      // calculate cell margin to center grid cells
      this.cellMarginLeft = (this.maxWidth - this.cellWidth * this.columns) / 2;
    } else {
      this.cellWidth = this.maxWidth / this.columns;
      this.cellHeight = this.maxWidth / this.columns;
      // calculate cell margin to center grid cells
      this.cellMarginTop = (this.maxHeight - this.cellHeight * this.rows) / 2;
    }
  } else if (this.rows == this.columns) {
    // calculate cell size to get correct cell proportions
    if (this.maxWidth < this.maxHeight) {
      this.cellWidth = this.maxWidth / this.columns;
      this.cellHeight = this.maxWidth / this.columns;
    } else {
      this.cellWidth = this.maxHeight / this.columns;
      this.cellHeight = this.maxHeight / this.columns;
    }
    // calculate cell margin to center grid cells
    this.cellMarginLeft = (this.maxWidth - this.cellWidth * this.columns) / 2;
    this.cellMarginTop = (this.maxHeight - this.cellHeight * this.rows) / 2;
  }

  if (this.debug == true)
    console.log("changed grid size with cell size", this.cellWidth, this.cellHeight);
};

LinkedGrid.prototype.redrawCells = function() {
  // we have to remove all container children and redraw children to get new cells on resize events
  for (var i = 0; i <= this.maxCellIndex; i++) {
    this.container.removeChild(this.cells[i].gfx);

    this.cells[i].gfx = new PIXI.Graphics();
    this.cells[i].gfx.beginFill(this.colors[this.cells[i].colorIndex], 1);
    this.cells[i].gfx.drawRect(
      this.cellMarginLeft + this.cells[i].column * this.cellWidth, this.cellMarginTop + this.cells[i].row * this.cellHeight,
      this.cellWidth, this.cellHeight
    );
    this.container.addChild(this.cells[i].gfx);
  }
};

LinkedGrid.prototype.resize = function(maxWidth, maxHeight) {
  if (this.debug == true)
    console.log("resize grid maximal display size", maxWidth, maxHeight);
  this.setSize(this.columns, this.rows, maxWidth, maxHeight);
  this.redrawCells();
};

LinkedGrid.prototype.addCellColor = function(color) {
  this.colors.push(color);
  return this.colors.length - 1;
};

LinkedGrid.prototype.addCell = function(column, row, colorIndex) {
  // we need a free place in the cells array
  if (this.maxCellIndex < this.cells.length && this.field[column][row] === null) {
    if (this.debug == true)
      console.log("free place in cells array to add");

    // prepare gfx to add it to display container
    var gfx = new PIXI.Graphics();
    gfx.beginFill(this.colors[colorIndex], 1);
    gfx.drawRect(
      this.cellMarginLeft + column * this.cellWidth, this.cellMarginTop + row * this.cellHeight,
      this.cellWidth, this.cellHeight
    );
    this.container.addChild(gfx);

    this.maxCellIndex++;
    this.cells[this.maxCellIndex] = {
      gfx: gfx,
      column: column,
      row: row,
      colorIndex: colorIndex
    };
    this.field[column][row] = this.cells[this.maxCellIndex];

    if (this.debug == true)
      console.log("added cell with maximal cell index", this.maxCellIndex);

    return true;
  } else if (this.debug == true) {
    console.log("no free place in cells array to add");
  }
  return false;
};

LinkedGrid.prototype.removeCell = function(column, row) {
  // we need a field cell which is set
  if (this.maxCellIndex > -1 && this.field[column][row] !== null) {
    if (this.debug == true)
      console.log("valid place in cells array to remove");

    this.container.removeChild(this.field[column][row].gfx);

    // remove cell object and move all remaining cells by one position to keep cells array length
    for (var i = 0; i <= this.maxCellIndex; i++) {
      if (this.cells[i] == this.field[column][row]) {
        this.field[column][row] = null;
        for (var j = i; j < this.cells.length-1; j++) {
          this.cells[j] = this.cells[j+1];
        }
        this.maxCellIndex--;
        if (this.debug == true)
          console.log("removed cell object with new maximal cell index", this.maxCellIndex);
        break;
      }
    }

    return true;
  } else if (this.debug == true) {
    console.log("invalid place in cells array to remove");
  }
  return false;
};

LinkedGrid.prototype.moveCell = function(sColumn, sRow, tColumn, tRow) {
  // we have to determine if the source cell has been set and the target cell is free
  if (this.maxCellIndex > -1 && this.field[sColumn][sRow] !== null && this.field[tColumn][tRow] === null) {
    if (this.debug == true)
      console.log("move cell from / to", sColumn, sRow, tColumn, tRow);
    var colorIndex = this.field[sColumn][sRow].colorIndex;
    this.removeCell(sColumn, sRow);
    this.addCell(tColumn, tRow, colorIndex);
  } else {
    if (this.debug == true)
      console.log("invalid arguments to move cell");
  }
};
<commit_msg>add/set grid width height<commit_after>LinkedGrid = function(container, columns, rows, maxWidth, maxHeight, debug) {
  this.debug = debug | false;

  this.container = container; // a display container to add gfx elements to

  this.setSize(columns, rows, maxWidth, maxHeight);

  this.colors = [];

  this.cells = new Array(this.columns * this.rows); // array with cell objects
  for (var i = 0; i < this.columns * this.rows; i++) {
    this.cells[i] = null;
  }
  this.maxCellIndex = -1; // maximal used cell index

  this.field = new Array(this.columns); // 2d array with cell references
  for (var column = 0; column < this.columns; column++) {
    this.field[column] = new Array(this.rows);
    for (var row = 0; row < rows; row++) {
      this.field[column][row] = null;
    }
  }
};

LinkedGrid.prototype.constructor = LinkedGrid;

LinkedGrid.prototype.setSize = function(columns, rows, maxWidth, maxHeight) {
  // max/full width of display container
  this.maxWidth = maxWidth;
  this.maxHeight = maxHeight;

  // grid size by columns/rows
  this.columns = columns;
  this.rows = rows;

  // calculate cell size / margin depending on grid size and cell amount
  this.cellWidth = 0;
  this.cellHeight = 0;
  this.cellMarginTop = 0;
  this.cellMarginLeft = 0;
  if (this.rows > this.columns) {
    // calculate cell size to get correct cell proportions
    if (this.maxWidth < this.maxHeight) {
      this.cellWidth = this.maxWidth / this.columns;
      this.cellHeight = this.maxWidth / this.columns;
      // calculate cell margin to center grid cells
      this.cellMarginTop = (this.maxHeight - this.cellHeight * this.rows) / 2;
    } else {
      this.cellWidth = this.maxHeight / this.rows;
      this.cellHeight = this.maxHeight / this.rows;
      // calculate cell margin to center grid cells
      this.cellMarginLeft = (this.maxWidth - this.cellWidth * this.columns) / 2;
    }
  } else if (this.rows < this.columns) {
    // calculate cell size to get correct cell proportions
    if (this.maxWidth > this.maxHeight) {
      this.cellWidth = this.maxHeight / this.rows;
      this.cellHeight = this.maxHeight / this.rows;
      // calculate cell margin to center grid cells
      this.cellMarginLeft = (this.maxWidth - this.cellWidth * this.columns) / 2;
    } else {
      this.cellWidth = this.maxWidth / this.columns;
      this.cellHeight = this.maxWidth / this.columns;
      // calculate cell margin to center grid cells
      this.cellMarginTop = (this.maxHeight - this.cellHeight * this.rows) / 2;
    }
  } else if (this.rows == this.columns) {
    // calculate cell size to get correct cell proportions
    if (this.maxWidth < this.maxHeight) {
      this.cellWidth = this.maxWidth / this.columns;
      this.cellHeight = this.maxWidth / this.columns;
    } else {
      this.cellWidth = this.maxHeight / this.columns;
      this.cellHeight = this.maxHeight / this.columns;
    }
    // calculate cell margin to center grid cells
    this.cellMarginLeft = (this.maxWidth - this.cellWidth * this.columns) / 2;
    this.cellMarginTop = (this.maxHeight - this.cellHeight * this.rows) / 2;
  }
  this.width = this.cellWidth * this.columns;
  this.height = this.cellHeight * this.rows;

  if (this.debug == true)
    console.log("changed grid size with cell size", this.cellWidth, this.cellHeight);
};

LinkedGrid.prototype.redrawCells = function() {
  // we have to remove all container children and redraw children to get new cells on resize events
  for (var i = 0; i <= this.maxCellIndex; i++) {
    this.container.removeChild(this.cells[i].gfx);

    this.cells[i].gfx = new PIXI.Graphics();
    this.cells[i].gfx.beginFill(this.colors[this.cells[i].colorIndex], 1);
    this.cells[i].gfx.drawRect(
      this.cellMarginLeft + this.cells[i].column * this.cellWidth, this.cellMarginTop + this.cells[i].row * this.cellHeight,
      this.cellWidth, this.cellHeight
    );
    this.container.addChild(this.cells[i].gfx);
  }
};

LinkedGrid.prototype.resize = function(maxWidth, maxHeight) {
  if (this.debug == true)
    console.log("resize grid maximal display size", maxWidth, maxHeight);
  this.setSize(this.columns, this.rows, maxWidth, maxHeight);
  this.redrawCells();
};

LinkedGrid.prototype.addCellColor = function(color) {
  this.colors.push(color);
  return this.colors.length - 1;
};

LinkedGrid.prototype.addCell = function(column, row, colorIndex) {
  // we need a free place in the cells array
  if (this.maxCellIndex < this.cells.length && this.field[column][row] === null) {
    if (this.debug == true)
      console.log("free place in cells array to add");

    // prepare gfx to add it to display container
    var gfx = new PIXI.Graphics();
    gfx.beginFill(this.colors[colorIndex], 1);
    gfx.drawRect(
      this.cellMarginLeft + column * this.cellWidth, this.cellMarginTop + row * this.cellHeight,
      this.cellWidth, this.cellHeight
    );
    this.container.addChild(gfx);

    this.maxCellIndex++;
    this.cells[this.maxCellIndex] = {
      gfx: gfx,
      column: column,
      row: row,
      colorIndex: colorIndex
    };
    this.field[column][row] = this.cells[this.maxCellIndex];

    if (this.debug == true)
      console.log("added cell with maximal cell index", this.maxCellIndex);

    return true;
  } else if (this.debug == true) {
    console.log("no free place in cells array to add");
  }
  return false;
};

LinkedGrid.prototype.removeCell = function(column, row) {
  // we need a field cell which is set
  if (this.maxCellIndex > -1 && this.field[column][row] !== null) {
    if (this.debug == true)
      console.log("valid place in cells array to remove");

    this.container.removeChild(this.field[column][row].gfx);

    // remove cell object and move all remaining cells by one position to keep cells array length
    for (var i = 0; i <= this.maxCellIndex; i++) {
      if (this.cells[i] == this.field[column][row]) {
        this.field[column][row] = null;
        for (var j = i; j < this.cells.length-1; j++) {
          this.cells[j] = this.cells[j+1];
        }
        this.maxCellIndex--;
        if (this.debug == true)
          console.log("removed cell object with new maximal cell index", this.maxCellIndex);
        break;
      }
    }

    return true;
  } else if (this.debug == true) {
    console.log("invalid place in cells array to remove");
  }
  return false;
};

LinkedGrid.prototype.moveCell = function(sColumn, sRow, tColumn, tRow) {
  // we have to determine if the source cell has been set and the target cell is free
  if (this.maxCellIndex > -1 && this.field[sColumn][sRow] !== null && this.field[tColumn][tRow] === null) {
    if (this.debug == true)
      console.log("move cell from / to", sColumn, sRow, tColumn, tRow);
    var colorIndex = this.field[sColumn][sRow].colorIndex;
    this.removeCell(sColumn, sRow);
    this.addCell(tColumn, tRow, colorIndex);
  } else {
    if (this.debug == true)
      console.log("invalid arguments to move cell");
  }
};
<|endoftext|>


<commit_before>import axios from 'axios'
import classNames from 'classnames'
import {InfoPanel} from './info-panel'
import React from 'react'
import ReactDOM from 'react-dom'
import {Timeline} from './timeline'
import {createMap, panTo, showRadarFrame} from './map'

const FRAME_DELAY_MS = 500
const FRAME_LIST_RELOAD_MS = 30 * 1000

class SataakoApp extends React.Component {
  constructor() {
    super()
    this.skipUpdate = 0
    this.state = {
      currentTimestamp: null,
      running: true,
      frames: [],
      mapSettings: {
        x: Number(localStorage.getItem('sataako-fi-x')) || 2776307.5078,
        y: Number(localStorage.getItem('sataako-fi-y')) || 8438349.32742,
        zoom: Number(localStorage.getItem('sataako-fi-zoom')) || 7
      },
      collapsed: localStorage.getItem('sataako-fi-collapsed') === 'true'
    }
    this.onResizeHandler = this.onResize.bind(this)
  }

  componentDidMount() {
    this.map = createMap(this.state.mapSettings)
    this.map.on('moveend', this.onMapMove.bind(this, null))

    this.loadFramesList()
    this.loadFramesInterval = setInterval(this.loadFramesList.bind(this, null), FRAME_LIST_RELOAD_MS)
    this.animateRadarInterval = setInterval(this.animateRadar.bind(this, null), FRAME_DELAY_MS)
    window.addEventListener('resize', this.onResizeHandler)

    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(this.onLocation.bind(this))
    }
  }

  componentDidUpdate(_, prevState) {
    if (this.state.currentTimestamp !== prevState.currentTimestamp) {
      const currentFrame = this.state.frames.find(frame => frame.timestamp === this.state.currentTimestamp)
      this.setSkipUpdate(this.state.frames, currentFrame)
      showRadarFrame(this.map, currentFrame)
    }
    if (this.state.collapsed !== prevState.collapsed) {
      localStorage.setItem('sataako-fi-collapsed', String(this.state.collapsed))
    }
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.onResizeHandler)
    clearInterval(this.animateRadarInterval)
    clearInterval(this.loadFramesInterval)
  }

  render() {
    const className = classNames({'app--infopanel-expanded': !this.state.collapsed})
    return

        <InfoPanel collapsed={this.state.collapsed} onInfoPanelToggle={this.onInfoPanelToggle.bind(this)}/>
        {this.state.currentTimestamp && <Timeline
          timestamps={this.state.frames}
          currentTimestamp={this.state.currentTimestamp}
          running={this.state.running}
          onResume={this.onTimelineResume.bind(this)}
          onSelect={this.onTimelineSelect.bind(this)}
        />}

  }

  loadFramesList() {
    axios.get('/frames.json').then(response => {
      const frames = response.data
      this.setState(prevState => (
        {frames, currentTimestamp: this.currentTimestampDefault(prevState, frames)}
      ))
    })
  }

  currentTimestampDefault(state, frames) {
    if (!state.currentTimestamp) {
      return this.newestNonForecastTimestamp(frames)
    }

    const index = frames.findIndex(frame => frame.timestamp === state.currentTimestamp)
    if (index === -1) {
      return frames[0].timestamp
    }
    return state.currentTimestamp
  }

  nextTimestamp(state) {
    if (!state.currentTimestamp) {
      return this.newestNonForecastTimestamp(state.frames)
    }

    const index = state.frames.findIndex(frame => frame.timestamp === state.currentTimestamp)
    if (index === -1) {
      return this.newestNonForecastTimestamp(state.frames)
    }
    if (index === state.frames.length - 1) {
      return state.frames[0].timestamp
    }
    return state.frames[index + 1].timestamp
  }

  newestNonForecastTimestamp(frames) {
    return frames.filter(frame => !frame.isForecast).pop().timestamp
  }

  animateRadar() {
    if (!this.state.running || !this.state.currentTimestamp) {
      return
    }
    if (this.skipUpdate > 0) {
      this.skipUpdate -= 1
    } else {
      this.setState(prevState => ({currentTimestamp: this.nextTimestamp(prevState)}))
    }
  }

  setSkipUpdate(frames, currentFrame) {
    const isLastOfList = frames[frames.length - 1] === currentFrame
    if (isLastOfList) {
      this.skipUpdate = 4
    } else if (currentFrame.isForecast) {
      this.skipUpdate = 2
    } else {
      this.skipUpdate = 0
    }
  }

  onLocation(geolocationResponse) {
    panTo(this.map, [geolocationResponse.coords.longitude, geolocationResponse.coords.latitude])
  }

  onMapMove() {
    const [x, y] = this.map.getView().getCenter()
    const zoom = this.map.getView().getZoom()

    localStorage.setItem('sataako-fi-x', x)
    localStorage.setItem('sataako-fi-y', y)
    localStorage.setItem('sataako-fi-zoom', zoom)
  }

  onTimelineSelect(timestamp) {
    this.setState({currentTimestamp: timestamp, running: false})
  }

  onTimelineResume() {
    this.setState({running: true})
  }

  onInfoPanelToggle() {
    this.setState(prevState => ({collapsed: !prevState.collapsed}))
  }

  onResize() {
    this.forceUpdate(() => this.map.updateSize())
  }
}

ReactDOM.render(<SataakoApp />, document.getElementById('app'))
<commit_msg>Show info-panel for all clients<commit_after>import axios from 'axios'
import classNames from 'classnames'
import {InfoPanel} from './info-panel'
import React from 'react'
import ReactDOM from 'react-dom'
import {Timeline} from './timeline'
import {createMap, panTo, showRadarFrame} from './map'

const FRAME_DELAY_MS = 500
const FRAME_LIST_RELOAD_MS = 30 * 1000

class SataakoApp extends React.Component {
  constructor() {
    super()
    this.skipUpdate = 0
    this.state = {
      currentTimestamp: null,
      running: true,
      frames: [],
      mapSettings: {
        x: Number(localStorage.getItem('sataako-fi-x')) || 2776307.5078,
        y: Number(localStorage.getItem('sataako-fi-y')) || 8438349.32742,
        zoom: Number(localStorage.getItem('sataako-fi-zoom')) || 7
      },
      collapsed: localStorage.getItem('sataako-fi-collapsed-v2') === 'true'
    }
    this.onResizeHandler = this.onResize.bind(this)
  }

  componentDidMount() {
    this.map = createMap(this.state.mapSettings)
    this.map.on('moveend', this.onMapMove.bind(this, null))

    this.loadFramesList()
    this.loadFramesInterval = setInterval(this.loadFramesList.bind(this, null), FRAME_LIST_RELOAD_MS)
    this.animateRadarInterval = setInterval(this.animateRadar.bind(this, null), FRAME_DELAY_MS)
    window.addEventListener('resize', this.onResizeHandler)

    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(this.onLocation.bind(this))
    }
  }

  componentDidUpdate(_, prevState) {
    if (this.state.currentTimestamp !== prevState.currentTimestamp) {
      const currentFrame = this.state.frames.find(frame => frame.timestamp === this.state.currentTimestamp)
      this.setSkipUpdate(this.state.frames, currentFrame)
      showRadarFrame(this.map, currentFrame)
    }
    if (this.state.collapsed !== prevState.collapsed) {
      localStorage.setItem('sataako-fi-collapsed-v2', String(this.state.collapsed))
    }
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.onResizeHandler)
    clearInterval(this.animateRadarInterval)
    clearInterval(this.loadFramesInterval)
  }

  render() {
    const className = classNames({'app--infopanel-expanded': !this.state.collapsed})
    return

        <InfoPanel collapsed={this.state.collapsed} onInfoPanelToggle={this.onInfoPanelToggle.bind(this)}/>
        {this.state.currentTimestamp && <Timeline
          timestamps={this.state.frames}
          currentTimestamp={this.state.currentTimestamp}
          running={this.state.running}
          onResume={this.onTimelineResume.bind(this)}
          onSelect={this.onTimelineSelect.bind(this)}
        />}

  }

  loadFramesList() {
    axios.get('/frames.json').then(response => {
      const frames = response.data
      this.setState(prevState => (
        {frames, currentTimestamp: this.currentTimestampDefault(prevState, frames)}
      ))
    })
  }

  currentTimestampDefault(state, frames) {
    if (!state.currentTimestamp) {
      return this.newestNonForecastTimestamp(frames)
    }

    const index = frames.findIndex(frame => frame.timestamp === state.currentTimestamp)
    if (index === -1) {
      return frames[0].timestamp
    }
    return state.currentTimestamp
  }

  nextTimestamp(state) {
    if (!state.currentTimestamp) {
      return this.newestNonForecastTimestamp(state.frames)
    }

    const index = state.frames.findIndex(frame => frame.timestamp === state.currentTimestamp)
    if (index === -1) {
      return this.newestNonForecastTimestamp(state.frames)
    }
    if (index === state.frames.length - 1) {
      return state.frames[0].timestamp
    }
    return state.frames[index + 1].timestamp
  }

  newestNonForecastTimestamp(frames) {
    return frames.filter(frame => !frame.isForecast).pop().timestamp
  }

  animateRadar() {
    if (!this.state.running || !this.state.currentTimestamp) {
      return
    }
    if (this.skipUpdate > 0) {
      this.skipUpdate -= 1
    } else {
      this.setState(prevState => ({currentTimestamp: this.nextTimestamp(prevState)}))
    }
  }

  setSkipUpdate(frames, currentFrame) {
    const isLastOfList = frames[frames.length - 1] === currentFrame
    if (isLastOfList) {
      this.skipUpdate = 4
    } else if (currentFrame.isForecast) {
      this.skipUpdate = 2
    } else {
      this.skipUpdate = 0
    }
  }

  onLocation(geolocationResponse) {
    panTo(this.map, [geolocationResponse.coords.longitude, geolocationResponse.coords.latitude])
  }

  onMapMove() {
    const [x, y] = this.map.getView().getCenter()
    const zoom = this.map.getView().getZoom()

    localStorage.setItem('sataako-fi-x', x)
    localStorage.setItem('sataako-fi-y', y)
    localStorage.setItem('sataako-fi-zoom', zoom)
  }

  onTimelineSelect(timestamp) {
    this.setState({currentTimestamp: timestamp, running: false})
  }

  onTimelineResume() {
    this.setState({running: true})
  }

  onInfoPanelToggle() {
    this.setState(prevState => ({collapsed: !prevState.collapsed}))
  }

  onResize() {
    this.forceUpdate(() => this.map.updateSize())
  }
}

ReactDOM.render(<SataakoApp />, document.getElementById('app'))
<|endoftext|>


<commit_before>//var a2sandbox = require('../lib/a2sandbox.js');

/*
  ======== A Handy Little Nodeunit Reference ========
  https://github.com/caolan/nodeunit

  Test methods:
    test.expect(numAssertions)
    test.done()
  Test assertions:
    test.ok(value, [message])
    test.equal(actual, expected, [message])
    test.notEqual(actual, expected, [message])
    test.deepEqual(actual, expected, [message])
    test.notDeepEqual(actual, expected, [message])
    test.strictEqual(actual, expected, [message])
    test.notStrictEqual(actual, expected, [message])
    test.throws(block, [error], [message])
    test.doesNotThrow(block, [error], [message])
    test.ifError(value)
*/

// exports['awesome'] = {
//   setUp: function(done) {
//     // setup here
//     done();
//   },
//   'no args': function(test) {
//     test.expect(1);
//     // tests here
//     test.equal(a2sandbox.awesome(), 'awesome', 'should be awesome.');
//     test.done();
//   }
// };
<commit_msg>mocha-powered unit tests showing that the openArea/validateArea dance works, including validation at the item type level<commit_after>/*
  ======== A Handy Little Nodeunit Reference ========
  https://github.com/caolan/nodeunit

  Test methods:
    test.expect(numAssertions)
    test.done()
  Test assertions:
    test.ok(value, [message])
    test.equal(actual, expected, [message])
    test.notEqual(actual, expected, [message])
    test.deepEqual(actual, expected, [message])
    test.notDeepEqual(actual, expected, [message])
    test.strictEqual(actual, expected, [message])
    test.notStrictEqual(actual, expected, [message])
    test.throws(block, [error], [message])
    test.doesNotThrow(block, [error], [message])
    test.ifError(value)

  To run them:

  nodeunit a2sandbox_test.js
*/

var _ = require('underscore');

exports['suite'] = {
  setUp: function(done) {
    done();
  },

  'open': function(test) {
    test.expect(10);
    // Mock an express app object as a context
    var app = { locals: {}, use: function() {} };
    var a2 = require('a2-core');
    var config = {
      "defaultItemType": "a2-text",
      "itemTypeNames": [
        "a2-text",
        "a2-files"
      ]
    };

    config.itemTypes = config.itemTypeNames.map(function(itemTypeName) {
      return require(itemTypeName);
    });

    a2.bootstrap(app, config);
    test.ok(app.a2);
    test.ok(app.locals.a2);

    var data = {
      itemInfos: [
        {
          type: 'a2-text',
          data: {
            'text': '
Once I met a man who had no hat.

ForbiddenPermitted
'
          }
        },
        {
          type: 'a2-text',
          data: {
            text: 'I was sad because I had no fork.'
          }
        }
      ]
    };

    app.a2.openArea(data, {}, function(err, data) {
      test.ok(!err);
      test.ok(data);

      app.a2.validateArea(data, {}, function(err, data) {
        test.ok(!err);
        test.ok(data);
        test.ok(data.itemInfos.length === 2);
        _.each(data.itemInfos, function(itemInfo) {
          test.ok(itemInfo.permId.length);
        });
        // The a2-text validator should have successfully blown away the XSS threat
        test.ok(data.itemInfos[0].data.text.indexOf('href="javascript:') === -1);
        // app.a2.log(data);
        test.done();
      });
    });

  }
}
// exports['awesome'] = {
//   setUp: function(done) {
//     // setup here
//     done();
//   },
//   'no args': function(test) {
//     test.expect(1);
//     // tests here
//     test.equal(a2sandbox.awesome(), 'awesome', 'should be awesome.');
//     test.done();
//   }
// };
<|endoftext|>


<commit_before>import "babel-polyfill";

import React from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import {createHistory} from "history";

import App from "../shared/components/App";
import Overlay from "../shared/components/Overlay";
import createRoutes from "../shared/routes";
import createStore from "../shared/store";
import reducer from "../shared/reducer";
import {CENTER, DEFAULT_ZOOM} from "../shared/consts";
import {initMap, initLocs, setProjection, recomputeLocs} from "../shared/actions";
import {loadGoogleMaps} from "../shared/google-maps";
import {subscribeState} from "../shared/util";

let hist = createHistory();
let store = createStore(hist)(reducer);
let router = createRoutes(store);

Promise.all([
    loadGoogleMaps({
        key: process.env.MAPS_API_KEY,
        version: "3.25",
    }).then(google => {
        let map = new google.maps.Map(document.getElementById("map"), {
            center: CENTER,
            zoom: DEFAULT_ZOOM,
        });

        let overlay = Object.assign(new google.maps.OverlayView(), {
            onAdd() {
                ReactDOM.render(<Overlay store={store} />,
                    this.getPanes().overlayMouseTarget);
            },
            draw() {
                store.dispatch(setProjection(this.getProjection()));
            },
        });

        overlay.setMap(map);

        subscribeState(store, ({docTitle}) => docTitle, docTitle => {
            document.title = docTitle;
        });

        return store.dispatch(initMap(google, map, new google.maps.Marker({
            map,
            position: CENTER,
        })));
    }),
    axios.get("/api/records").then(({data}) => store.dispatch(initLocs(
        data.map(loc => Object.assign(loc, {
            jitterLat: (Math.random() - 0.5) * 10.0e-3,
            jitterLng: (Math.random() - 0.5) * 10.0e-3,
        }))
    ))),
]).then(() => {
    return store.dispatch(recomputeLocs());
}).then(() => {
    return router.handlePath(hist.getCurrentLocation());
}).then(() => {
    hist.listen(router.handlePath);
    ReactDOM.render(<App store={store} />, document.getElementById("sidebar"));
});
<commit_msg>Disable random info popups on map<commit_after>import "babel-polyfill";

import React from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import {createHistory} from "history";

import App from "../shared/components/App";
import Overlay from "../shared/components/Overlay";
import createRoutes from "../shared/routes";
import createStore from "../shared/store";
import reducer from "../shared/reducer";
import {CENTER, DEFAULT_ZOOM} from "../shared/consts";
import {initMap, initLocs, setProjection, recomputeLocs} from "../shared/actions";
import {loadGoogleMaps} from "../shared/google-maps";
import {subscribeState} from "../shared/util";

let hist = createHistory();
let store = createStore(hist)(reducer);
let router = createRoutes(store);

Promise.all([
    loadGoogleMaps({
        key: process.env.MAPS_API_KEY,
        version: "3.25",
    }).then(google => {
        let map = new google.maps.Map(document.getElementById("map"), {
            center: CENTER,
            zoom: DEFAULT_ZOOM,
            styles: [
                {
                    featureType: "poi",
                    elementType: "labels",
                    stylers: [{visibility: "off"}]
                },
                {
                    featureType: "transit",
                    elementType: "labels",
                    stylers: [{visibility: "off"}]
                }
            ]
        });

        let overlay = Object.assign(new google.maps.OverlayView(), {
            onAdd() {
                ReactDOM.render(<Overlay store={store} />,
                    this.getPanes().overlayMouseTarget);
            },
            draw() {
                store.dispatch(setProjection(this.getProjection()));
            },
        });

        overlay.setMap(map);

        subscribeState(store, ({docTitle}) => docTitle, docTitle => {
            document.title = docTitle;
        });

        return store.dispatch(initMap(google, map, new google.maps.Marker({
            map,
            position: CENTER,
        })));
    }),
    axios.get("/api/records").then(({data}) => store.dispatch(initLocs(
        data.map(loc => Object.assign(loc, {
            jitterLat: (Math.random() - 0.5) * 10.0e-3,
            jitterLng: (Math.random() - 0.5) * 10.0e-3,
        }))
    ))),
]).then(() => {
    return store.dispatch(recomputeLocs());
}).then(() => {
    return router.handlePath(hist.getCurrentLocation());
}).then(() => {
    hist.listen(router.handlePath);
    ReactDOM.render(<App store={store} />, document.getElementById("sidebar"));
});
<|endoftext|>


<commit_before>import * as hooks from './hooks';

const defaults = {
  storage: '/storage',
  localEndpoint: '/auth/local',
  tokenEndpoint: '/auth/token'
};

export default function(opts = {}) {
  const authOptions = Object.assign({}, defaults, opts);

  return function() {
    const app = this;
    const storage = () => app.service(authOptions.storage);

    if (!storage) {
      throw new Error(`You need register a local store before you can use feathers-authentication. Did you call app.use('storage', localstorage())`);
    }

    const handleResponse = function (response) {
      return storage().create([{
        id: 'token',
        value: response.token
      }, {
        id: 'user',
        value: response.data
      }]).then(() => response);
    };

    app.authenticate = function(options) {
      if (!options.type) {
        throw new Error('You need to provide a `type` attribute when calling app.authenticate()');
      }

      let endPoint;

      if (options.type === 'local') {
        endPoint = authOptions.localEndpoint;
      } else if (options.type === 'token') {
        endPoint = authOptions.tokenEndpoint;
      } else {
        throw new Error(`Unsupported authentication 'type': ${options.type}`);
      }

      return new Promise(function(resolve, reject) {
        // TODO (EK): Handle OAuth logins
        // If we are using a REST client
        if (app.rest) {
          return app.service(endPoint).create(options).then(handleResponse);
        }

        if (app.io || app.primus) {
          const socket = app.io || app.primus;
          const handleUnauthorized = function(error) {
            // Unleak event handlers
            this.off('disconnect', reject);
            this.off('close', reject);

            reject(error);
          };
          const handleAuthenticated = function(response) {
            // We need to bind and unbind the event handlers that didn't run
            // so that they don't leak around
            this.off('unauthorized', handleUnauthorized);
            this.off('disconnect', reject);
            this.off('close', reject);

            handleResponse(response).then(reponse => resolve(reponse)).catch(error => {
              throw error;
            });
          };

          // Also, binding to events that aren't fired (like `close`)
          // for Socket.io doesn't hurt if we unbind once we're done
          socket.once('disconnect', reject);
          socket.once('close', reject);
          socket.once('unauthorized', handleUnauthorized);
          socket.once('authenticated', handleAuthenticated);
        }

        // If we are using socket.io
        if (app.io) {
          const socket = app.io;

          // If we aren't already connected then throw an error
          if (!socket.connected) {
            throw new Error('Socket not connected');
          }

          socket.emit('authenticate', options);
        }

        // If we are using primus
        if (app.primus) {
          const socket = app.primus;

          // If we aren't already connected then throw an error
          if (socket.readyState !== 3) {
            throw new Error('Socket not connected');
          }

          socket.send('authenticate', options);
        }
      });
    };

    app.user = function() {
      return storage().get('user').then(data => data.value);
    };

    app.token = function() {
      return storage().get('token').then(data => data.value);
    };

    app.logout = function() {
      return storage.remove(null, { id: { $in: ['user', 'token' ] } });
    };

    // Set up hook that adds adds token and user to params so that
    // it they can be accessed by client side hooks and services
    app.mixins.push(function(service) {
      if (!service.before || !service.after) {
        throw new Error(`It looks like feathers-hooks isn't ccnfigured. It is required before you configure feathers-authentication.`);
      }
      service.before(hooks.populateParams(authOptions));
    });

    // Set up hook that adds authorization header for REST provider
    if (app.rest) {
      app.mixins.push(function(service) {
        if (!service.before || !service.after) {
          throw new Error(`It looks like feathers-hooks isn't ccnfigured. It is required before you configure feathers-authentication.`);
        }
        service.before(hooks.populateHeader(authOptions));
      });
    }
  };
}
<commit_msg>adding missing parenthesis. Closes #85<commit_after>import * as hooks from './hooks';

const defaults = {
  storage: '/storage',
  localEndpoint: '/auth/local',
  tokenEndpoint: '/auth/token'
};

export default function(opts = {}) {
  const authOptions = Object.assign({}, defaults, opts);

  return function() {
    const app = this;
    const storage = () => app.service(authOptions.storage);

    if (!storage) {
      throw new Error(`You need register a local store before you can use feathers-authentication. Did you call app.use('storage', localstorage())`);
    }

    const handleResponse = function (response) {
      return storage().create([{
        id: 'token',
        value: response.token
      }, {
        id: 'user',
        value: response.data
      }]).then(() => response);
    };

    app.authenticate = function(options) {
      if (!options.type) {
        throw new Error('You need to provide a `type` attribute when calling app.authenticate()');
      }

      let endPoint;

      if (options.type === 'local') {
        endPoint = authOptions.localEndpoint;
      } else if (options.type === 'token') {
        endPoint = authOptions.tokenEndpoint;
      } else {
        throw new Error(`Unsupported authentication 'type': ${options.type}`);
      }

      return new Promise(function(resolve, reject) {
        // TODO (EK): Handle OAuth logins
        // If we are using a REST client
        if (app.rest) {
          return app.service(endPoint).create(options).then(handleResponse);
        }

        if (app.io || app.primus) {
          const socket = app.io || app.primus;
          const handleUnauthorized = function(error) {
            // Unleak event handlers
            this.off('disconnect', reject);
            this.off('close', reject);

            reject(error);
          };
          const handleAuthenticated = function(response) {
            // We need to bind and unbind the event handlers that didn't run
            // so that they don't leak around
            this.off('unauthorized', handleUnauthorized);
            this.off('disconnect', reject);
            this.off('close', reject);

            handleResponse(response).then(reponse => resolve(reponse)).catch(error => {
              throw error;
            });
          };

          // Also, binding to events that aren't fired (like `close`)
          // for Socket.io doesn't hurt if we unbind once we're done
          socket.once('disconnect', reject);
          socket.once('close', reject);
          socket.once('unauthorized', handleUnauthorized);
          socket.once('authenticated', handleAuthenticated);
        }

        // If we are using socket.io
        if (app.io) {
          const socket = app.io;

          // If we aren't already connected then throw an error
          if (!socket.connected) {
            throw new Error('Socket not connected');
          }

          socket.emit('authenticate', options);
        }

        // If we are using primus
        if (app.primus) {
          const socket = app.primus;

          // If we aren't already connected then throw an error
          if (socket.readyState !== 3) {
            throw new Error('Socket not connected');
          }

          socket.send('authenticate', options);
        }
      });
    };

    app.user = function() {
      return storage().get('user').then(data => data.value);
    };

    app.token = function() {
      return storage().get('token').then(data => data.value);
    };

    app.logout = function() {
      return storage().remove(null, { id: { $in: ['user', 'token' ] } });
    };

    // Set up hook that adds adds token and user to params so that
    // it they can be accessed by client side hooks and services
    app.mixins.push(function(service) {
      if (!service.before || !service.after) {
        throw new Error(`It looks like feathers-hooks isn't ccnfigured. It is required before you configure feathers-authentication.`);
      }
      service.before(hooks.populateParams(authOptions));
    });

    // Set up hook that adds authorization header for REST provider
    if (app.rest) {
      app.mixins.push(function(service) {
        if (!service.before || !service.after) {
          throw new Error(`It looks like feathers-hooks isn't ccnfigured. It is required before you configure feathers-authentication.`);
        }
        service.before(hooks.populateHeader(authOptions));
      });
    }
  };
}
<|endoftext|>


<commit_before>import bs from 'stamp'
import { send, meta } from './send'
import WebSocket from './websocket'
import { create, parse, subscribe, struct, emitterProperty } from 'brisky-struct'
import serialize from '../subscription/serialize'
import hash from 'string-hash'
import createClient from './create'
import { receiveLarge } from '../size'

const connect = (hub, url, reconnect) => {
  const socket = new WebSocket(url)
  const client = hub.client || createClient(hub, {}, false)
  var bufferArray = false

  hub.set({ client }, false)

  hub.reconnect = null

  const close = () => {
    const stamp = bs.create()
    hub.socket = false
    hub.set({ connected: false }, stamp)
    bs.close()
    if (!socket.blockReconnect && hub._url_) {
      reconnect = Math.min((reconnect * 1.5), 2000)
      hub.reconnect = setTimeout(connect, reconnect, hub, url, reconnect)
    }
  }

  socket.onclose = close

  socket.onerror = typeof window === 'undefined'
    ? close
    : () => socket.close()

  socket.onopen = () => {
    const stamp = bs.create()
    hub.socket = socket
    if (hub.emitters && hub.emitters.incoming) {
      enableIncomingListener(socket, hub)
    }
    meta(hub)
    hub.set({ connected: true }, stamp)
    bs.close()
  }

  // use outside function non anon since its slower (according to uws)
  socket.onmessage = (data) => {
    data = data.data

    if (
      typeof data !== 'string' &&
      typeof window === 'undefined' &&
      data instanceof ArrayBuffer
    ) {
      if (!bufferArray) bufferArray = []
      const result = receiveLarge(data, bufferArray)
      if (result) {
        data = result
        bufferArray = false
      } else {
        return
      }
    }

    if (!hub.receiveOnly) {
      if (typeof window !== 'undefined') {
        global.requestAnimationFrame(() => {
          hub.receiveOnly = true
          hub.set(JSON.parse(data), bs.create())
          hub.receiveOnly = null
        })
      } else {
        hub.receiveOnly = true
        hub.set(JSON.parse(data), bs.create())
        hub.receiveOnly = null
      }
    } else {
      if (typeof window !== 'undefined') {
        global.requestAnimationFrame(() => {
          hub.set(JSON.parse(data), false)
        })
      } else {
        hub.set(JSON.parse(data), false)
      }
    }
    bs.close()
  }
}

const removeUrl = hub => {
  hub.url = hub._url_ = hub.urlIndex = null
  hub.emitters.set({ data: { url$: null } }, false)
}

const removeSocket = hub => {
  if (hub.reconnect) {
    clearTimeout(hub.reconnect)
    hub.reconnect = null
  }
  if (hub.socket) {
    hub.socket.blockReconnect = true
    hub.socket.close()
  }
  hub.socket = false
}

const url = (hub, val, key, stamp) => {
  hub.on((val, stamp, t) => {
    if (val === null && !t._c && t === hub) {
      hub.subscriptions = []
      removeUrl(hub)
      removeSocket(hub)
    }
  }, 'url$')

  if (!val) val = null
  if ((!hub.url && val) || (hub.url.compute() !== val)) {
    removeSocket(hub)
    if (!val) {
      hub.set({ connected: false }, stamp)
      hub._url_ = null
      if (hub.url) hub.url.set(null, stamp)
    } else {
      if (!hub.url) {
        create({
          on: {
            data: {
              url: (val, stamp, struct) => {
                val = struct.compute()
                if (val) {
                  hub.set({ connected: false }, stamp)
                  let i = -1
                  if (hub.key) i++
                  hub.parent(() => { i++ })
                  hub.urlIndex = i // use this for checks
                  hub._url_ = val
                  connect(hub, val, 50)
                } else {
                  hub._url_ = null
                }
              }
            }
          }
        }, stamp, struct, hub, key)
      }
      if (/^https?/.test(val)) val = val.replace(/^http/, 'ws')
      hub.url.set(val, stamp)
    }
  }
}

const removeClients = (hub, stamp) => {
  const clients = hub.clients
  if (clients && clients.keys().length > 1) {
    clients.forEach((client, key) => {
      if (
        client.val !== null &&
        client !== hub.client
      ) {
        client.set(null, stamp)
        delete clients[key]
      }
    })
  }
}

const connected = {
  type: 'struct',
  on: {
    data: {
      removeClients: (val, stamp, t) => {
        if (t.compute() === false) {
          removeClients(t._p, stamp)
        }
      }
    }
  }
}

const contextStruct = struct.create({
  props: {
    default: {
      on: {
        data: {
          updateParent: (val, stamp, t) => {
            t.parent().emit('data', val, stamp)
          }
        }
      }
    }
  }
})

const contextIsNotEqual = (val, context) => {
  if (val && typeof val === 'object') {
    for (let field in val) {
      if (!context[field] || val[field] !== context[field].compute()) {
        return true
      }
    }
  } else {
    return val !== context.compute()
  }
}

const context = (hub, val, key, stamp) => {
  if ((!hub.context && val) || (hub.context && contextIsNotEqual(val, hub.context))) {
    if (!hub.context) {
      create(val, stamp, contextStruct, hub, key)
    } else {
      removeClients(hub, stamp)
      hub.context.set(val, stamp)
    }
    if (hub.connected && hub.connected.compute() === true) meta(hub)
  }
}

const props = {
  reconnect: true,
  socket: true,
  urlIndex: true,
  upstreamSubscriptions: true,
  receiveOnly: true,
  url,
  context,
  connected
}

const stub = () => {}

const define = {
  subscribe (subs, cb, raw, tree, forceUpstream) {
    if (!raw) subs = parse(subs)
    if (!this.receiveOnly || forceUpstream) {
      const parsed = serialize(this, subs)
      if (parsed) {
        if (forceUpstream) {
          parsed.__force__ = true
        }
        // why not keep it stringified? -- saves lots of speed
        const key = hash(JSON.stringify(parsed))
        if (!this.upstreamSubscriptions) {
          this.upstreamSubscriptions = {}
          this.upstreamSubscriptions[key] = parsed
          if (this.url) meta(this)
        } else if (!this.upstreamSubscriptions[key]) {
          this.upstreamSubscriptions[key] = parsed
          if (this.url) meta(this)
        }
      }
    }
    return subscribe(this, subs, cb || stub, tree)
  }
}

const enableIncomingListener = (socket, hub) => {
  if (!socket.incomingOverride) {
    socket.incomingOverride = true
    const field = typeof window === 'undefined' ? 'internalOnMessage' : 'onmessage'
    const msg = hub.socket[field]
    socket[field] = (data) => {
      hub.emit('incoming', data)
      msg(data)
    }
  }
}

const on = {
  data: { send },
  props: {
    incoming: (t, val, key, stamp) => {
      const hub = t._p
      if (hub.socket) enableIncomingListener(hub.socket, hub)
      return emitterProperty(t, val, key, stamp)
    }
  }
}

export { props, on, define }
<commit_msg>bs close<commit_after>import bs from 'stamp'
import { send, meta } from './send'
import WebSocket from './websocket'
import { create, parse, subscribe, struct, emitterProperty } from 'brisky-struct'
import serialize from '../subscription/serialize'
import hash from 'string-hash'
import createClient from './create'
import { receiveLarge } from '../size'

const connect = (hub, url, reconnect) => {
  const socket = new WebSocket(url)
  const client = hub.client || createClient(hub, {}, false)
  var bufferArray = false

  hub.set({ client }, false)

  hub.reconnect = null

  const close = () => {
    const stamp = bs.create()
    hub.socket = false
    hub.set({ connected: false }, stamp)
    bs.close()
    if (!socket.blockReconnect && hub._url_) {
      reconnect = Math.min((reconnect * 1.5), 2000)
      hub.reconnect = setTimeout(connect, reconnect, hub, url, reconnect)
    }
  }

  socket.onclose = close

  socket.onerror = typeof window === 'undefined'
    ? close
    : () => socket.close()

  socket.onopen = () => {
    const stamp = bs.create()
    hub.socket = socket
    if (hub.emitters && hub.emitters.incoming) {
      enableIncomingListener(socket, hub)
    }
    meta(hub)
    hub.set({ connected: true }, stamp)
    bs.close()
  }

  // use outside function non anon since its slower (according to uws)
  socket.onmessage = (data) => {
    data = data.data

    if (
      typeof data !== 'string' &&
      typeof window === 'undefined' &&
      data instanceof ArrayBuffer
    ) {
      if (!bufferArray) bufferArray = []
      const result = receiveLarge(data, bufferArray)
      if (result) {
        data = result
        bufferArray = false
      } else {
        return
      }
    }

    if (!hub.receiveOnly) {
      if (typeof window !== 'undefined') {
        global.requestAnimationFrame(() => {
          hub.receiveOnly = true
          hub.set(JSON.parse(data), bs.create())
          hub.receiveOnly = null
          bs.close()
        })
      } else {
        hub.receiveOnly = true
        hub.set(JSON.parse(data), bs.create())
        hub.receiveOnly = null
        bs.close()
      }
    } else {
      if (typeof window !== 'undefined') {
        global.requestAnimationFrame(() => {
          hub.set(JSON.parse(data), false)
          bs.close()
        })
      } else {
        hub.set(JSON.parse(data), false)
        bs.close()
      }
    }
  }
}

const removeUrl = hub => {
  hub.url = hub._url_ = hub.urlIndex = null
  hub.emitters.set({ data: { url$: null } }, false)
}

const removeSocket = hub => {
  if (hub.reconnect) {
    clearTimeout(hub.reconnect)
    hub.reconnect = null
  }
  if (hub.socket) {
    hub.socket.blockReconnect = true
    hub.socket.close()
  }
  hub.socket = false
}

const url = (hub, val, key, stamp) => {
  hub.on((val, stamp, t) => {
    if (val === null && !t._c && t === hub) {
      hub.subscriptions = []
      removeUrl(hub)
      removeSocket(hub)
    }
  }, 'url$')

  if (!val) val = null
  if ((!hub.url && val) || (hub.url.compute() !== val)) {
    removeSocket(hub)
    if (!val) {
      hub.set({ connected: false }, stamp)
      hub._url_ = null
      if (hub.url) hub.url.set(null, stamp)
    } else {
      if (!hub.url) {
        create({
          on: {
            data: {
              url: (val, stamp, struct) => {
                val = struct.compute()
                if (val) {
                  hub.set({ connected: false }, stamp)
                  let i = -1
                  if (hub.key) i++
                  hub.parent(() => { i++ })
                  hub.urlIndex = i // use this for checks
                  hub._url_ = val
                  connect(hub, val, 50)
                } else {
                  hub._url_ = null
                }
              }
            }
          }
        }, stamp, struct, hub, key)
      }
      if (/^https?/.test(val)) val = val.replace(/^http/, 'ws')
      hub.url.set(val, stamp)
    }
  }
}

const removeClients = (hub, stamp) => {
  const clients = hub.clients
  if (clients && clients.keys().length > 1) {
    clients.forEach((client, key) => {
      if (
        client.val !== null &&
        client !== hub.client
      ) {
        client.set(null, stamp)
        delete clients[key]
      }
    })
  }
}

const connected = {
  type: 'struct',
  on: {
    data: {
      removeClients: (val, stamp, t) => {
        if (t.compute() === false) {
          removeClients(t._p, stamp)
        }
      }
    }
  }
}

const contextStruct = struct.create({
  props: {
    default: {
      on: {
        data: {
          updateParent: (val, stamp, t) => {
            t.parent().emit('data', val, stamp)
          }
        }
      }
    }
  }
})

const contextIsNotEqual = (val, context) => {
  if (val && typeof val === 'object') {
    for (let field in val) {
      if (!context[field] || val[field] !== context[field].compute()) {
        return true
      }
    }
  } else {
    return val !== context.compute()
  }
}

const context = (hub, val, key, stamp) => {
  if ((!hub.context && val) || (hub.context && contextIsNotEqual(val, hub.context))) {
    if (!hub.context) {
      create(val, stamp, contextStruct, hub, key)
    } else {
      removeClients(hub, stamp)
      hub.context.set(val, stamp)
    }
    if (hub.connected && hub.connected.compute() === true) meta(hub)
  }
}

const props = {
  reconnect: true,
  socket: true,
  urlIndex: true,
  upstreamSubscriptions: true,
  receiveOnly: true,
  url,
  context,
  connected
}

const stub = () => {}

const define = {
  subscribe (subs, cb, raw, tree, forceUpstream) {
    if (!raw) subs = parse(subs)
    if (!this.receiveOnly || forceUpstream) {
      const parsed = serialize(this, subs)
      if (parsed) {
        if (forceUpstream) {
          parsed.__force__ = true
        }
        // why not keep it stringified? -- saves lots of speed
        const key = hash(JSON.stringify(parsed))
        if (!this.upstreamSubscriptions) {
          this.upstreamSubscriptions = {}
          this.upstreamSubscriptions[key] = parsed
          if (this.url) meta(this)
        } else if (!this.upstreamSubscriptions[key]) {
          this.upstreamSubscriptions[key] = parsed
          if (this.url) meta(this)
        }
      }
    }
    return subscribe(this, subs, cb || stub, tree)
  }
}

const enableIncomingListener = (socket, hub) => {
  if (!socket.incomingOverride) {
    socket.incomingOverride = true
    const field = typeof window === 'undefined' ? 'internalOnMessage' : 'onmessage'
    const msg = hub.socket[field]
    socket[field] = (data) => {
      hub.emit('incoming', data)
      msg(data)
    }
  }
}

const on = {
  data: { send },
  props: {
    incoming: (t, val, key, stamp) => {
      const hub = t._p
      if (hub.socket) enableIncomingListener(hub.socket, hub)
      return emitterProperty(t, val, key, stamp)
    }
  }
}

export { props, on, define }
<|endoftext|>


<commit_before>var Objects;

(function(){

var TestObject = function(position, dimensions) {
	this.position = position;
	this.dimensions = dimensions;

	return this;
}

TestObject.prototype.Draw = function(d) {
	d.fillStyle("#CC0000").fillRect(0, 0, this.dimensions.x, this.dimensions.y);
}

var TestPlatform = function(position, dimensions, point1, point2, speed) {
	this.position = position;
	this.dimensions = dimensions;
	this.behaviour = new game.Behaviours.TwoPoint(point1, point2, speed);
	this.animationCollection = new game.AnimationCollection([
		new game.Animation("animation", [
			new Sprite(game.Assets.get("texture.png"), {x:0,y:0}, {w:72,h:72})
		], 16)
	]);
}

TestPlatform.prototype.Update = function() {
	this.behaviour.update(this);
	this.animationCollection.getCurrent().update();
}

TestPlatform.prototype.Draw = function(d) {
	d.drawSprite(this.animationCollection.getCurrent().currentFrame);
}

Objects.TestObject

})();<commit_msg>Updated Objects.js<commit_after>var Objects;

(function(){

var TestObject = function(position, dimensions) {
	this.position = position;
	this.dimensions = dimensions;

	return this;
}

TestObject.prototype.Draw = function(d) {
	d.fillStyle("#CC0000").fillRect(0, 0, this.dimensions.x, this.dimensions.y);
}

var TestPlatform = function(position, dimensions, point1, point2, speed) {
	this.position = position;
	this.dimensions = dimensions;
	this.behaviour = new game.Behaviours.TwoPoint(point1, point2, speed);
	this.animationCollection = new game.AnimationCollection([
		new game.Animation("animation", [
			new Sprite(game.Assets.get("texture.png"), {x:0,y:0}, {w:72,h:72})
		], 16)
	]);
}

TestPlatform.prototype.Update = function() {
	this.behaviour.update(this);
	this.animationCollection.getCurrent().update();
}

TestPlatform.prototype.Draw = function(d) {
	d.drawSprite(this.animationCollection.getCurrent().currentFrame);
}

Objects.TestObject = TestObject;
Objects.TestPlatform = TestPlatform;

})();<|endoftext|>


<commit_before>//----------------------------------------------------------------------
//
// This source file is part of the furipota project.
//
// Licensed under MIT. See LICENCE for full licence information.
// See CONTRIBUTORS for the list of contributors to the project.
//
//----------------------------------------------------------------------

const ast = require('../ast');

const desugarApplication = (node) =>
  node.matchWith({
    Seq: ({ items }) =>
      ast.Seq(items.map(desugarApplication)),

    Identifier: ({ name }) =>
      ast.Identifier(name),

    Hole: () =>
      ast.Hole(),

    Keyword: ({ name }) =>
      ast.Keyword(name),

    Text: ({ value }) =>
      ast.Text(value),

    Character: ({ character }) =>
      ast.Character(character),

    InterpolateExpression: ({ expression }) =>
      ast.InterpolateExpression(desugarApplication(expression)),

    Interpolate: ({ items }) =>
      ast.Interpolate(items.map(desugarApplication)),

    Integer: ({ sign, value }) =>
      ast.Integer(sign, value),

    Decimal: ({ sign, integral, decimal, exponent }) =>
      ast.Decimal(sign, integral, decimal, exponent),

    Boolean: ({ value }) =>
      ast.Boolean(value),

    Vector: ({ items }) =>
      ast.Vector(items.map(desugarApplication)),

    VectorSpread: ({ expression }) =>
      ast.VectorSpread(desugarApplication(expression)),

    VectorElement: ({ expression }) =>
      ast.VectorElement(desugarApplication(expression)),

    Record: ({ pairs }) =>
      ast.Record(
        pairs.map(([k, v]) => [k, desugarApplication(v)])
      ),

    Lambda: ({ value, options, expression }) =>
      ast.Lambda(value, options, desugarApplication(expression)),

    Tagged: ({ tag, value }) =>
      ast.Tagged(tag, desugarApplication(value)),

    Define: ({ id, expression, documentation }) =>
      ast.Define(id, desugarApplication(expression), documentation),

    Import: ({ path, kind }) =>
      ast.Import(path, kind),

    ImportAliasing: ({ path, alias, kind }) =>
      ast.ImportAliasing(path, alias, kind),

    Export: ({ identifier }) =>
      ast.Export(identifier),

    ExportAliasing: ({ identifier, alias }) =>
      ast.ExportAliasing(identifier, alias),

    Invoke: ({ callee, input, options }) =>
      ast.Invoke(desugarApplication(callee), desugarApplication(input), desugarApplication(options)),

    Pipe: ({ input, transformation }) =>
      ast.Pipe(desugarApplication(input), desugarApplication(transformation)),

    Variable: ({ id }) =>
      ast.Variable(id),

    Let: ({ binding, value, expression }) =>
      ast.Let(binding, desugarApplication(value), desugarApplication(expression)),

    IfThenElse: ({ condition, consequent, alternate }) =>
      ast.IfThenElse(
        desugarApplication(condition),
        desugarApplication(consequent),
        desugarApplication(alternate)
      ),

    Get: ({ expression, property }) =>
      ast.Get(desugarApplication(expression), property),

    Infix: ({ operator, left, right }) =>
      ast.Invoke(
        ast.Invoke(
          operator,
          desugarApplication(left),
          ast.Record([])
        ),
        desugarApplication(right),
        ast.Record([])
      ),

    Prefix: ({ operator, expression }) =>
      ast.Invoke(operator, desugarApplication(expression)),

    Shell: ({ command, args, options }) =>
      ast.Shell(desugarApplication(command), args.map(desugarApplication), desugarApplication(options)),

    ShellSymbol: ({ symbol }) =>
      ast.ShellSymbol(symbol),

    ShellSpread: ({ items }) =>
      ast.ShellSpread(items.map(desugarApplication)),

    ShellExpression: ({ expression }) =>
      ast.ShellExpression(desugarApplication(expression)),

    Do: ({ instructions }) =>
      ast.Do(instructions.map(desugarApplication)),

    DoCall: ({ expression }) =>
      ast.DoCall(desugarApplication(expression)),

    DoAction: ({ expression }) =>
      ast.DoAction(desugarApplication(expression)),

    DoReturn: ({ expression }) =>
      ast.DoReturn(desugarApplication(expression)),

    DoBind: ({ id, expression }) =>
      ast.DoBind(id, desugarApplication(expression)),

    DoLet: ({ id, expression }) =>
      ast.DoLet(id, expression),

    DoIfThenElse: ({ condition, consequent, alternate }) =>
      ast.DoIfThenElse(
        desugarApplication(condition),
        desugarApplication(consequent),
        desugarApplication(alternate)
      ),

    Program: ({ declarations }) =>
      ast.Program(declarations.map(desugarApplication))
  });

module.exports = desugarApplication;<commit_msg>Fixes desugaring of Prefix in desugarApplication<commit_after>//----------------------------------------------------------------------
//
// This source file is part of the furipota project.
//
// Licensed under MIT. See LICENCE for full licence information.
// See CONTRIBUTORS for the list of contributors to the project.
//
//----------------------------------------------------------------------

const ast = require('../ast');

const desugarApplication = (node) =>
  node.matchWith({
    Seq: ({ items }) =>
      ast.Seq(items.map(desugarApplication)),

    Identifier: ({ name }) =>
      ast.Identifier(name),

    Hole: () =>
      ast.Hole(),

    Keyword: ({ name }) =>
      ast.Keyword(name),

    Text: ({ value }) =>
      ast.Text(value),

    Character: ({ character }) =>
      ast.Character(character),

    InterpolateExpression: ({ expression }) =>
      ast.InterpolateExpression(desugarApplication(expression)),

    Interpolate: ({ items }) =>
      ast.Interpolate(items.map(desugarApplication)),

    Integer: ({ sign, value }) =>
      ast.Integer(sign, value),

    Decimal: ({ sign, integral, decimal, exponent }) =>
      ast.Decimal(sign, integral, decimal, exponent),

    Boolean: ({ value }) =>
      ast.Boolean(value),

    Vector: ({ items }) =>
      ast.Vector(items.map(desugarApplication)),

    VectorSpread: ({ expression }) =>
      ast.VectorSpread(desugarApplication(expression)),

    VectorElement: ({ expression }) =>
      ast.VectorElement(desugarApplication(expression)),

    Record: ({ pairs }) =>
      ast.Record(
        pairs.map(([k, v]) => [k, desugarApplication(v)])
      ),

    Lambda: ({ value, options, expression }) =>
      ast.Lambda(value, options, desugarApplication(expression)),

    Tagged: ({ tag, value }) =>
      ast.Tagged(tag, desugarApplication(value)),

    Define: ({ id, expression, documentation }) =>
      ast.Define(id, desugarApplication(expression), documentation),

    Import: ({ path, kind }) =>
      ast.Import(path, kind),

    ImportAliasing: ({ path, alias, kind }) =>
      ast.ImportAliasing(path, alias, kind),

    Export: ({ identifier }) =>
      ast.Export(identifier),

    ExportAliasing: ({ identifier, alias }) =>
      ast.ExportAliasing(identifier, alias),

    Invoke: ({ callee, input, options }) =>
      ast.Invoke(desugarApplication(callee), desugarApplication(input), desugarApplication(options)),

    Pipe: ({ input, transformation }) =>
      ast.Pipe(desugarApplication(input), desugarApplication(transformation)),

    Variable: ({ id }) =>
      ast.Variable(id),

    Let: ({ binding, value, expression }) =>
      ast.Let(binding, desugarApplication(value), desugarApplication(expression)),

    IfThenElse: ({ condition, consequent, alternate }) =>
      ast.IfThenElse(
        desugarApplication(condition),
        desugarApplication(consequent),
        desugarApplication(alternate)
      ),

    Get: ({ expression, property }) =>
      ast.Get(desugarApplication(expression), property),

    Infix: ({ operator, left, right }) =>
      ast.Invoke(
        ast.Invoke(
          operator,
          desugarApplication(left),
          ast.Record([])
        ),
        desugarApplication(right),
        ast.Record([])
      ),

    Prefix: ({ operator, expression }) =>
      ast.Invoke(operator, desugarApplication(expression), ast.Record([])),

    Shell: ({ command, args, options }) =>
      ast.Shell(desugarApplication(command), args.map(desugarApplication), desugarApplication(options)),

    ShellSymbol: ({ symbol }) =>
      ast.ShellSymbol(symbol),

    ShellSpread: ({ items }) =>
      ast.ShellSpread(items.map(desugarApplication)),

    ShellExpression: ({ expression }) =>
      ast.ShellExpression(desugarApplication(expression)),

    Do: ({ instructions }) =>
      ast.Do(instructions.map(desugarApplication)),

    DoCall: ({ expression }) =>
      ast.DoCall(desugarApplication(expression)),

    DoAction: ({ expression }) =>
      ast.DoAction(desugarApplication(expression)),

    DoReturn: ({ expression }) =>
      ast.DoReturn(desugarApplication(expression)),

    DoBind: ({ id, expression }) =>
      ast.DoBind(id, desugarApplication(expression)),

    DoLet: ({ id, expression }) =>
      ast.DoLet(id, expression),

    DoIfThenElse: ({ condition, consequent, alternate }) =>
      ast.DoIfThenElse(
        desugarApplication(condition),
        desugarApplication(consequent),
        desugarApplication(alternate)
      ),

    Program: ({ declarations }) =>
      ast.Program(declarations.map(desugarApplication))
  });

module.exports = desugarApplication;<|endoftext|>