<commit_before>/*
Language: YAML
Description: Yet Another Markdown Language
Author: Stefan Wienert <stwienert@gmail.com>
Contributors: Carl Baxter <carl@cbax.tech>
Requires: ruby.js
Website: https://yaml.org
Category: common, config
*/
export default function(hljs) {
  var LITERALS = 'true false yes no null';

  // YAML spec allows non-reserved URI characters in tags.
  var URI_CHARACTERS = '[\\w#;/?:@&=+$,.~*\'()[\\]]+';

  // Define keys as starting with a word character
  // ...containing word chars, spaces, colons, forward-slashes, hyphens and periods
  // ...and ending with a colon followed immediately by a space, tab or newline.
  // The YAML spec allows for much more than this, but this covers most use-cases.
  var KEY = {
    className: 'attr',
    variants: [
      { begin: '\\w[\\w :\\/.-]*:(?=[ \t]|$)' },
      { begin: '"\\w[\\w :\\/.-]*":(?=[ \t]|$)' }, // double quoted keys
      { begin: '\'\\w[\\w :\\/.-]*\':(?=[ \t]|$)' } // single quoted keys
    ]
  };

  var TEMPLATE_VARIABLES = {
    className: 'template-variable',
    variants: [
      { begin: /\{\{/, end: /\}\}/ }, // jinja templates Ansible
      { begin: /%\{/, end: /\}/ } // Ruby i18n
    ]
  };
  var STRING = {
    className: 'string',
    relevance: 0,
    variants: [
      { begin: /'/, end: /'/ },
      { begin: /"/, end: /"/ },
      { begin: /\S+/ }
    ],
    contains: [
      hljs.BACKSLASH_ESCAPE,
      TEMPLATE_VARIABLES
    ]
  };

  // Strings inside of value containers (objects) can't contain braces,
  // brackets, or commas
  var CONTAINER_STRING = hljs.inherit(STRING, {
    variants: [
      { begin: /'/, end: /'/ },
      { begin: /"/, end: /"/ },
      { begin: /[^\s,{}[\]]+/ }
    ]
  });

  var DATE_RE = '[0-9]{4}(-[0-9][0-9]){0,2}';
  var TIME_RE = '([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?';
  var FRACTION_RE = '(\\.[0-9]*)?';
  var ZONE_RE = '([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?';
  var TIMESTAMP = {
    className: 'number',
    begin: '\\b' + DATE_RE + TIME_RE + FRACTION_RE + ZONE_RE + '\\b'
  };

  var VALUE_CONTAINER = {
    end: ',',
    endsWithParent: true,
    excludeEnd: true,
    contains: [],
    keywords: LITERALS,
    relevance: 0
  };
  var OBJECT = {
    begin: /\{/,
    end: /\}/,
    contains: [VALUE_CONTAINER],
    illegal: '\\n',
    relevance: 0
  };
  var ARRAY = {
    begin: '\\[',
    end: '\\]',
    contains: [VALUE_CONTAINER],
    illegal: '\\n',
    relevance: 0
  };

  var MODES = [
    KEY,
    {
      className: 'meta',
      begin: '^---\\s*$',
      relevance: 10
    },
    { // multi line string
      // Blocks start with a | or > followed by a newline
      //
      // Indentation of subsequent lines must be the same to
      // be considered part of the block
      className: 'string',
      begin: '[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^\\n]+\\n(\\2[^\\n]+\\n?)*'
    },
    { // Ruby/Rails erb
      begin: '<%[%=-]?',
      end: '[%-]?%>',
      subLanguage: 'ruby',
      excludeBegin: true,
      excludeEnd: true,
      relevance: 0
    },
    { // named tags
      className: 'type',
      begin: '!\\w+!' + URI_CHARACTERS
    },
    // https://yaml.org/spec/1.2/spec.html#id2784064
    { // verbatim tags
      className: 'type',
      begin: '!<' + URI_CHARACTERS + ">"
    },
    { // primary tags
      className: 'type',
      begin: '!' + URI_CHARACTERS
    },
    { // secondary tags
      className: 'type',
      begin: '!!' + URI_CHARACTERS
    },
    { // fragment id &ref
      className: 'meta',
      begin: '&' + hljs.UNDERSCORE_IDENT_RE + '$'
    },
    { // fragment reference *ref
      className: 'meta',
      begin: '\\*' + hljs.UNDERSCORE_IDENT_RE + '$'
    },
    { // array listing
      className: 'bullet',
      // TODO: remove |$ hack when we have proper look-ahead support
      begin: '-(?=[ ]|$)',
      relevance: 0
    },
    hljs.HASH_COMMENT_MODE,
    {
      beginKeywords: LITERALS,
      keywords: { literal: LITERALS }
    },
    TIMESTAMP,
    // numbers are any valid C-style number that
    // sit isolated from other words
    {
      className: 'number',
      begin: hljs.C_NUMBER_RE + '\\b'
    },
    OBJECT,
    ARRAY,
    STRING
  ];

  var VALUE_MODES = [...MODES];
  VALUE_MODES.pop();
  VALUE_MODES.push(CONTAINER_STRING);
  VALUE_CONTAINER.contains = VALUE_MODES;

  return {
    name: 'YAML',
    case_insensitive: true,
    aliases: ['yml', 'YAML'],
    contains: MODES
  };
}
<commit_msg>fix(autodetect) yaml: simple numbers should not add relevancy<commit_after>/*
Language: YAML
Description: Yet Another Markdown Language
Author: Stefan Wienert <stwienert@gmail.com>
Contributors: Carl Baxter <carl@cbax.tech>
Requires: ruby.js
Website: https://yaml.org
Category: common, config
*/
export default function(hljs) {
  var LITERALS = 'true false yes no null';

  // YAML spec allows non-reserved URI characters in tags.
  var URI_CHARACTERS = '[\\w#;/?:@&=+$,.~*\'()[\\]]+';

  // Define keys as starting with a word character
  // ...containing word chars, spaces, colons, forward-slashes, hyphens and periods
  // ...and ending with a colon followed immediately by a space, tab or newline.
  // The YAML spec allows for much more than this, but this covers most use-cases.
  var KEY = {
    className: 'attr',
    variants: [
      { begin: '\\w[\\w :\\/.-]*:(?=[ \t]|$)' },
      { begin: '"\\w[\\w :\\/.-]*":(?=[ \t]|$)' }, // double quoted keys
      { begin: '\'\\w[\\w :\\/.-]*\':(?=[ \t]|$)' } // single quoted keys
    ]
  };

  var TEMPLATE_VARIABLES = {
    className: 'template-variable',
    variants: [
      { begin: /\{\{/, end: /\}\}/ }, // jinja templates Ansible
      { begin: /%\{/, end: /\}/ } // Ruby i18n
    ]
  };
  var STRING = {
    className: 'string',
    relevance: 0,
    variants: [
      { begin: /'/, end: /'/ },
      { begin: /"/, end: /"/ },
      { begin: /\S+/ }
    ],
    contains: [
      hljs.BACKSLASH_ESCAPE,
      TEMPLATE_VARIABLES
    ]
  };

  // Strings inside of value containers (objects) can't contain braces,
  // brackets, or commas
  var CONTAINER_STRING = hljs.inherit(STRING, {
    variants: [
      { begin: /'/, end: /'/ },
      { begin: /"/, end: /"/ },
      { begin: /[^\s,{}[\]]+/ }
    ]
  });

  var DATE_RE = '[0-9]{4}(-[0-9][0-9]){0,2}';
  var TIME_RE = '([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?';
  var FRACTION_RE = '(\\.[0-9]*)?';
  var ZONE_RE = '([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?';
  var TIMESTAMP = {
    className: 'number',
    begin: '\\b' + DATE_RE + TIME_RE + FRACTION_RE + ZONE_RE + '\\b'
  };

  var VALUE_CONTAINER = {
    end: ',',
    endsWithParent: true,
    excludeEnd: true,
    contains: [],
    keywords: LITERALS,
    relevance: 0
  };
  var OBJECT = {
    begin: /\{/,
    end: /\}/,
    contains: [VALUE_CONTAINER],
    illegal: '\\n',
    relevance: 0
  };
  var ARRAY = {
    begin: '\\[',
    end: '\\]',
    contains: [VALUE_CONTAINER],
    illegal: '\\n',
    relevance: 0
  };

  var MODES = [
    KEY,
    {
      className: 'meta',
      begin: '^---\\s*$',
      relevance: 10
    },
    { // multi line string
      // Blocks start with a | or > followed by a newline
      //
      // Indentation of subsequent lines must be the same to
      // be considered part of the block
      className: 'string',
      begin: '[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^\\n]+\\n(\\2[^\\n]+\\n?)*'
    },
    { // Ruby/Rails erb
      begin: '<%[%=-]?',
      end: '[%-]?%>',
      subLanguage: 'ruby',
      excludeBegin: true,
      excludeEnd: true,
      relevance: 0
    },
    { // named tags
      className: 'type',
      begin: '!\\w+!' + URI_CHARACTERS
    },
    // https://yaml.org/spec/1.2/spec.html#id2784064
    { // verbatim tags
      className: 'type',
      begin: '!<' + URI_CHARACTERS + ">"
    },
    { // primary tags
      className: 'type',
      begin: '!' + URI_CHARACTERS
    },
    { // secondary tags
      className: 'type',
      begin: '!!' + URI_CHARACTERS
    },
    { // fragment id &ref
      className: 'meta',
      begin: '&' + hljs.UNDERSCORE_IDENT_RE + '$'
    },
    { // fragment reference *ref
      className: 'meta',
      begin: '\\*' + hljs.UNDERSCORE_IDENT_RE + '$'
    },
    { // array listing
      className: 'bullet',
      // TODO: remove |$ hack when we have proper look-ahead support
      begin: '-(?=[ ]|$)',
      relevance: 0
    },
    hljs.HASH_COMMENT_MODE,
    {
      beginKeywords: LITERALS,
      keywords: { literal: LITERALS }
    },
    TIMESTAMP,
    // numbers are any valid C-style number that
    // sit isolated from other words
    {
      className: 'number',
      begin: hljs.C_NUMBER_RE + '\\b',
      relevance: 0
    },
    OBJECT,
    ARRAY,
    STRING
  ];

  var VALUE_MODES = [...MODES];
  VALUE_MODES.pop();
  VALUE_MODES.push(CONTAINER_STRING);
  VALUE_CONTAINER.contains = VALUE_MODES;

  return {
    name: 'YAML',
    case_insensitive: true,
    aliases: ['yml', 'YAML'],
    contains: MODES
  };
}
<|endoftext|>


<commit_before>// modified from the standard plugin to support a single tool in addition to hands
(function() {
  Leap.plugin('transform', function(scope) {
    var noop, transformDirections, transformMat4Implicit0, transformPositions, transformWithMatrices, _directionTransform;
    if (scope == null) {
      scope = {};
    }
    noop = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
    _directionTransform = new THREE.Matrix4;
    if (scope.vr === true) {
      this.setOptimizeHMD(true);
      scope.quaternion = (new THREE.Quaternion).setFromRotationMatrix((new THREE.Matrix4).set(-1, 0, 0, 0, 0, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 1));
      scope.scale = 0.001;
      scope.position = new THREE.Vector3(0, 0, -0.06);
    }
    else if (scope.vr === 'desktop') {
      scope.scale = 0.001;
    }
    var _scale = new THREE.Vector3(scope.scale, scope.scale, scope.scale).toArray();
    scope.getTransform = function(hand) {
      if (scope.position || scope.quaternion || scope.scale) {
        _directionTransform.set.apply(_directionTransform, noop);
        if (scope.quaternion) {
          _directionTransform.makeRotationFromQuaternion(scope.quaternion);
        }
        if (scope.position) {
          _directionTransform.setPosition(scope.position);
        }
        return _directionTransform.elements;
      } else {
        return noop;
      }
    };

    transformPositions = function(matrix, vec3s) {
      var vec3, _i, _len, _results;
      _results = [];
      for (_i = 0, _len = vec3s.length; _i < _len; _i++) {
        vec3 = vec3s[_i];
        if (vec3) {
          _results.push(Leap.vec3.transformMat4(vec3, vec3, matrix));
        } else {
          _results.push(void 0);
        }
      }
      return _results;
    };
    transformMat4Implicit0 = function(out, a, m) {
      var x, y, z;
      x = a[0];
      y = a[1];
      z = a[2];
      out[0] = m[0] * x + m[4] * y + m[8] * z;
      out[1] = m[1] * x + m[5] * y + m[9] * z;
      out[2] = m[2] * x + m[6] * y + m[10] * z;
      return out;
    };
    transformDirections = function(matrix, vec3s) {
      var vec3, _i, _len, _results;
      _results = [];
      for (_i = 0, _len = vec3s.length; _i < _len; _i++) {
        vec3 = vec3s[_i];
        if (vec3) {
          _results.push(transformMat4Implicit0(vec3, vec3, matrix));
        } else {
          _results.push(void 0);
        }
      }
      return _results;
    };
    transformWithMatrices = function(hand, transform, scale) {
      var finger, scalarScale, _i, _j, _len, _len1, _ref, _ref1;
      transformDirections(transform, [hand.direction, hand.palmNormal, hand.palmVelocity, hand.arm.basis[0], hand.arm.basis[1], hand.arm.basis[2]]);
      _ref = hand.fingers;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        finger = _ref[_i];
        transformDirections(transform, [finger.direction, finger.metacarpal.basis[0], finger.metacarpal.basis[1], finger.metacarpal.basis[2], finger.proximal.basis[0], finger.proximal.basis[1], finger.proximal.basis[2], finger.medial.basis[0], finger.medial.basis[1], finger.medial.basis[2], finger.distal.basis[0], finger.distal.basis[1], finger.distal.basis[2]]);
      }
      Leap.glMatrix.mat4.scale(transform, transform, scale);
      transformPositions(transform, [hand.palmPosition, hand.stabilizedPalmPosition, hand.sphereCenter, hand.arm.nextJoint, hand.arm.prevJoint]);
      _ref1 = hand.fingers;
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        finger = _ref1[_j];
        transformPositions(transform, [finger.carpPosition, finger.mcpPosition, finger.pipPosition, finger.dipPosition, finger.distal.nextJoint, finger.tipPosition]);
      }
      scalarScale = (scale[0] + scale[1] + scale[2]) / 3;
      return hand.arm.width *= scalarScale;
    };

    transformToolWithMatrices = function(tool, transform, scale) {
      transformDirections(transform, [tool.direction, tool.tipVelocity]);
      Leap.glMatrix.mat4.scale(transform, transform, scale);
      transformPositions(transform, [tool.tipPosition, tool.stabilizedTipPosition]);
    };

    var _ones = [1, 1, 1];
    return {
      frame: function(frame) {
        var finger, hand, len, _i, _j, _len, _len1, _ref, _ref1, _results;
        if (!frame.valid || frame.data.transformed) {
          return;
        }
        frame.data.transformed = true;
        _ref = frame.hands;
        _results = [];
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          hand = _ref[_i];
          transformWithMatrices(hand, scope.getTransform(hand), _scale);
          if (scope.effectiveParent) {
            transformWithMatrices(hand, scope.effectiveParent.matrix.elements, _ones);
          }
          len = null;
          _ref1 = hand.fingers;
          for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
            finger = _ref1[_j];
            len = Leap.vec3.create();
            Leap.vec3.sub(len, finger.mcpPosition, finger.carpPosition);
            finger.metacarpal.length = Leap.vec3.length(len);
            Leap.vec3.sub(len, finger.pipPosition, finger.mcpPosition);
            finger.proximal.length = Leap.vec3.length(len);
            Leap.vec3.sub(len, finger.dipPosition, finger.pipPosition);
            finger.medial.length = Leap.vec3.length(len);
            Leap.vec3.sub(len, finger.tipPosition, finger.dipPosition);
            finger.distal.length = Leap.vec3.length(len);
          }
          Leap.vec3.sub(len, hand.arm.prevJoint, hand.arm.nextJoint);
          _results.push(hand.arm.length = Leap.vec3.length(len));
        }

        if (frame.tools.length == 1) {
          var tool = frame.tools[0];
          // TODO: optimize
          transformToolWithMatrices(tool, scope.getTransform(hand), _scale);
          if (scope.effectiveParent) {
             transformToolWithMatrices(tool, scope.effectiveParent.matrix.elements, _ones);
          }
        }

        return _results;
      }
    };
  });

}).call(this);
<commit_msg>need linting<commit_after>// modified from the standard plugin to support a single tool in addition to hands
(function() {
  Leap.plugin('transform', function(scope) {
    var noop, transformDirections, transformMat4Implicit0, transformPositions, transformWithMatrices, _directionTransform;
    if (scope == null) {
      scope = {};
    }
    noop = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
    _directionTransform = new THREE.Matrix4;
    if (scope.vr === true) {
      this.setOptimizeHMD(true);
      scope.quaternion = (new THREE.Quaternion).setFromRotationMatrix((new THREE.Matrix4).set(-1, 0, 0, 0, 0, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 1));
      scope.scale = 0.001;
      scope.position = new THREE.Vector3(0, 0, -0.06);
    }
    else if (scope.vr === 'desktop') {
      scope.scale = 0.001;
    }
    var _scale = new THREE.Vector3(scope.scale, scope.scale, scope.scale).toArray();
    scope.getTransform = function(hand) {
      if (scope.position || scope.quaternion || scope.scale) {
        _directionTransform.set.apply(_directionTransform, noop);
        if (scope.quaternion) {
          _directionTransform.makeRotationFromQuaternion(scope.quaternion);
        }
        if (scope.position) {
          _directionTransform.setPosition(scope.position);
        }
        return _directionTransform.elements;
      } else {
        return noop;
      }
    };

    transformPositions = function(matrix, vec3s) {
      var vec3, _i, _len, _results;
      _results = [];
      for (_i = 0, _len = vec3s.length; _i < _len; _i++) {
        vec3 = vec3s[_i];
        if (vec3) {
          _results.push(Leap.vec3.transformMat4(vec3, vec3, matrix));
        } else {
          _results.push(void 0);
        }
      }
      return _results;
    };
    transformMat4Implicit0 = function(out, a, m) {
      var x, y, z;
      x = a[0];
      y = a[1];
      z = a[2];
      out[0] = m[0] * x + m[4] * y + m[8] * z;
      out[1] = m[1] * x + m[5] * y + m[9] * z;
      out[2] = m[2] * x + m[6] * y + m[10] * z;
      return out;
    };
    transformDirections = function(matrix, vec3s) {
      var vec3, _i, _len, _results;
      _results = [];
      for (_i = 0, _len = vec3s.length; _i < _len; _i++) {
        vec3 = vec3s[_i];
        if (vec3) {
          _results.push(transformMat4Implicit0(vec3, vec3, matrix));
        } else {
          _results.push(void 0);
        }
      }
      return _results;
    };
    transformWithMatrices = function(hand, transform, scale) {
      var finger, scalarScale, _i, _j, _len, _len1, _ref, _ref1;
      transformDirections(transform, [hand.direction, hand.palmNormal, hand.palmVelocity, hand.arm.basis[0], hand.arm.basis[1], hand.arm.basis[2]]);
      _ref = hand.fingers;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        finger = _ref[_i];
        transformDirections(transform, [finger.direction, finger.metacarpal.basis[0], finger.metacarpal.basis[1], finger.metacarpal.basis[2], finger.proximal.basis[0], finger.proximal.basis[1], finger.proximal.basis[2], finger.medial.basis[0], finger.medial.basis[1], finger.medial.basis[2], finger.distal.basis[0], finger.distal.basis[1], finger.distal.basis[2]]);
      }
      Leap.glMatrix.mat4.scale(transform, transform, scale);
      transformPositions(transform, [hand.palmPosition, hand.stabilizedPalmPosition, hand.sphereCenter, hand.arm.nextJoint, hand.arm.prevJoint]);
      _ref1 = hand.fingers;
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        finger = _ref1[_j];
        transformPositions(transform, [finger.carpPosition, finger.mcpPosition, finger.pipPosition, finger.dipPosition, finger.distal.nextJoint, finger.tipPosition]);
      }
      scalarScale = (scale[0] + scale[1] + scale[2]) / 3;
      return hand.arm.width *= scalarScale;
    };

    var transformToolWithMatrices = function(tool, transform, scale) {
      transformDirections(transform, [tool.direction, tool.tipVelocity]);
      Leap.glMatrix.mat4.scale(transform, transform, scale);
      transformPositions(transform, [tool.tipPosition, tool.stabilizedTipPosition]);
    };

    var _ones = [1, 1, 1];
    return {
      frame: function(frame) {
        var finger, hand, len, _i, _j, _len, _len1, _ref, _ref1, _results;
        if (!frame.valid || frame.data.transformed) {
          return;
        }
        frame.data.transformed = true;
        _ref = frame.hands;
        _results = [];
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          hand = _ref[_i];
          transformWithMatrices(hand, scope.getTransform(hand), _scale);
          if (scope.effectiveParent) {
            transformWithMatrices(hand, scope.effectiveParent.matrix.elements, _ones);
          }
          len = null;
          _ref1 = hand.fingers;
          for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
            finger = _ref1[_j];
            len = Leap.vec3.create();
            Leap.vec3.sub(len, finger.mcpPosition, finger.carpPosition);
            finger.metacarpal.length = Leap.vec3.length(len);
            Leap.vec3.sub(len, finger.pipPosition, finger.mcpPosition);
            finger.proximal.length = Leap.vec3.length(len);
            Leap.vec3.sub(len, finger.dipPosition, finger.pipPosition);
            finger.medial.length = Leap.vec3.length(len);
            Leap.vec3.sub(len, finger.tipPosition, finger.dipPosition);
            finger.distal.length = Leap.vec3.length(len);
          }
          Leap.vec3.sub(len, hand.arm.prevJoint, hand.arm.nextJoint);
          _results.push(hand.arm.length = Leap.vec3.length(len));
        }

        if (frame.tools.length == 1) {
          var tool = frame.tools[0];
          // TODO: optimize
          transformToolWithMatrices(tool, scope.getTransform(hand), _scale);
          if (scope.effectiveParent) {
             transformToolWithMatrices(tool, scope.effectiveParent.matrix.elements, _ones);
          }
        }

        return _results;
      }
    };
  });

}).call(this);
<|endoftext|>


<commit_before>"use strict";

var _ = require("underscore");
var Converter = require("substance-converter");
var ImporterError = Converter.ImporterError;
var NLMImporter = Converter.NLMImporter;

var LensImporter = function() {
};

LensImporter.Prototype = function() {

  var __super__ = NLMImporter.prototype;

  // Overridden to create a Lens Article instance
  this.createDocument = function() {
    var Article = require("lens-article");
    var doc = new Article();
    return doc;
  };

  var _viewMapping = {
    "image": "figures",
    "table": "figures",
    "video": "figures"
  };

  this.show = function(state, nodes) {
    var doc = state.doc;

    _.each(nodes, function(n) {
      var view = _viewMapping[n.type] || "content";
      doc.show(view, n.id);
    });
  };

  this.front = function(state, front) {
    __super__.front.call(this, state, front);

    var doc = state.doc;
    var docNode = doc.get("document");
    var cover = {
      id: "cover",
      type: "cover",
      title: docNode.title,
      authors: docNode.authors,
      abstract: docNode.abstract
    };
    doc.create(cover);
    doc.show("content", cover.id);
  };

  // Annotations
  // --------

  var _annotationTypes = {
    "bold": "strong",
    "italic": "emphasis",
    "monospace": "code",
    "sub": "subscript",
    "sup": "superscript",
    "underline": "underline",
    "xref": ""
  };

  this.isAnnotation = function(type) {
    return _annotationTypes[type] !== undefined;
  };

  this.createAnnotation = function(state, el, start, end) {
    var type = el.tagName.toLowerCase();
    var anno = {
      path: _.last(state.stack).path,
      range: [start, end],
    };
    if (type === "xref") {
      var refType = el.getAttribute("ref-type");
      var targetId = el.getAttribute("rid");
      if (refType === "bibr") {
        anno.type = "citation_reference";
      } else if (refType === "fig" || refType === "table") {
        anno.type = "figure_reference";
      }
      // 'supplementary-material', disp-formula
      else {
        console.log("Ignoring xref: ", refType, el);
        return;
      }
      anno.target = targetId;
    }
    // Common annotations (e.g., emphasis)
    else if (_annotationTypes[type] !== undefined) {
      anno.type = _annotationTypes[type];
    }
    else {
      console.log("Ignoring annotation: ", type, el);
      return;
    }
    anno.id = state.nextId(anno.type);

    state.annotations.push(anno);
  };

  this.figure = function(state, figure) {
    var doc = state.doc;

    var imageNode = {
      type: "image",
      "label": "",
      "title": "",
      "url": "",
      "large_url": "",
      "caption": null
    };
    var id = figure.getAttribute("id") || state.nextId(imageNode.type);
    imageNode.id = id;

    // Caption: is a paragraph
    var caption = figure.querySelector("caption");
    if (caption) {
      var p = caption.querySelector("p");
      var nodes = this.paragraph(state, p);
      if (nodes.length > 1) {
        throw new ImporterError("Ooops. Not ready for that...");
      }
      imageNode.caption = nodes[0].id;
    }

    var graphic = figure.querySelector("graphic");
    var url = graphic.getAttribute("xlink:href");
    imageNode.url = url;
    imageNode.large_url = url;

    var label = figure.querySelector("label");
    if (label) {
      imageNode.label = label.textContent;
    }

    var title = figure.querySelector("title");
    if (title) {
      imageNode.title = title.textContent;
    }

    doc.create(imageNode);
    return imageNode;
  };

  this.refList = function(state, refList) {
    var refs = refList.querySelectorAll("ref");
    for (var i = 0; i < refs.length; i++) {
      this.ref(state, refs[i]);
    }
  };

  this.ref = function(state, ref) {

    var children = ref.children;
    for (var i = 0; i < children.length; i++) {
      var child = children[i];
      var type = this.getNodeType(child);

      if (type === "mixed-citation" || type === "element-citation") {
        this.citation(state, ref, child);
      } else {
        console.log("Not supported in 'ref': ", type);
      }
    }
  };

  var _getName = function(nameEl) {
    var names = [];

    var surnameEl = nameEl.querySelector("surname");
    var givenNamesEl = nameEl.querySelector("given-names");

    if (givenNamesEl) names.push(givenNamesEl.textContent);
    if (surnameEl) names.push(surnameEl.textContent);

    return names.join(" ");
  };

  // TODO: is implemented naively, should be implemented considering the NLM spec
  this.citation = function(state, ref, citation) {
    var doc = state.doc;
    var i;

    var id = ref.getAttribute("id");
    if (!id) {
      throw new ImporterError("Expected 'id' in ref.");
    }

    var articleCitation = {
      id: id,
      type: "article_citation",
      title: "N/A",
      label: "",
      authors: [],
      doi: "",
      source: "",
      volume: "",
      fpage: "",
      lpage: "",
      citation_urls: []
    };

    // TODO: we should consider to have a more structured citation type
    // and let the view decide how to render it instead of blobbing everything here.
    var personGroup = citation.querySelector("person-group");
    var nameElements = personGroup.querySelectorAll("name");
    for (i = 0; i < nameElements.length; i++) {
      articleCitation.authors.push(_getName(nameElements[i]));
    }

    var articleTitle = citation.querySelector("article-title");
    if (articleTitle) {
      articleCitation.title = articleTitle.textContent;
    } else {
      console.error("FIXME: this citation has no title", citation);
    }

    var source = citation.querySelector("source");
    if (source) articleCitation.source = source.textContent;

    var volume = citation.querySelector("volume");
    if (volume) articleCitation.volume = volume.textContent;

    var fpage = citation.querySelector("fpage");
    if (fpage) articleCitation.fpage = fpage.textContent;

    var lpage = citation.querySelector("lpage");
    if (lpage) articleCitation.lpage = lpage.textContent;

    var year = citation.querySelector("year");
    if (year) articleCitation.year = year.textContent;

    // Note: the label is child of 'ref'
    var label = ref.querySelector("label");
    if(label) articleCitation.label = label.textContent;

    var pubIds = citation.querySelectorAll("pub-id");
    for (i = 0; i < pubIds.length; i++) {
      if(pubIds[i].getAttribute("pub-id-type") === "doi") {
        articleCitation.doi = pubIds[i].textContent;
        break;
      }
    }

    doc.create(articleCitation);
    doc.show("citations", id);
  };

};

LensImporter.Prototype.prototype = NLMImporter.prototype;
LensImporter.prototype = new LensImporter.Prototype();

module.exports = {
  Importer: LensImporter
};
<commit_msg>Allow 'mixed-citations' with text only content.<commit_after>"use strict";

var _ = require("underscore");
var Converter = require("substance-converter");
var ImporterError = Converter.ImporterError;
var NLMImporter = Converter.NLMImporter;

var LensImporter = function() {
};

LensImporter.Prototype = function() {

  var __super__ = NLMImporter.prototype;

  // Overridden to create a Lens Article instance
  this.createDocument = function() {
    var Article = require("lens-article");
    var doc = new Article();
    return doc;
  };

  var _viewMapping = {
    "image": "figures",
    "table": "figures",
    "video": "figures"
  };

  this.show = function(state, nodes) {
    var doc = state.doc;

    _.each(nodes, function(n) {
      var view = _viewMapping[n.type] || "content";
      doc.show(view, n.id);
    });
  };

  this.front = function(state, front) {
    __super__.front.call(this, state, front);

    var doc = state.doc;
    var docNode = doc.get("document");
    var cover = {
      id: "cover",
      type: "cover",
      title: docNode.title,
      authors: docNode.authors,
      abstract: docNode.abstract
    };
    doc.create(cover);
    doc.show("content", cover.id);
  };

  // Annotations
  // --------

  var _annotationTypes = {
    "bold": "strong",
    "italic": "emphasis",
    "monospace": "code",
    "sub": "subscript",
    "sup": "superscript",
    "underline": "underline",
    "xref": ""
  };

  this.isAnnotation = function(type) {
    return _annotationTypes[type] !== undefined;
  };

  this.createAnnotation = function(state, el, start, end) {
    var type = el.tagName.toLowerCase();
    var anno = {
      path: _.last(state.stack).path,
      range: [start, end],
    };
    if (type === "xref") {
      var refType = el.getAttribute("ref-type");
      var targetId = el.getAttribute("rid");
      if (refType === "bibr") {
        anno.type = "citation_reference";
      } else if (refType === "fig" || refType === "table") {
        anno.type = "figure_reference";
      }
      // 'supplementary-material', disp-formula
      else {
        console.log("Ignoring xref: ", refType, el);
        return;
      }
      anno.target = targetId;
    }
    // Common annotations (e.g., emphasis)
    else if (_annotationTypes[type] !== undefined) {
      anno.type = _annotationTypes[type];
    }
    else {
      console.log("Ignoring annotation: ", type, el);
      return;
    }
    anno.id = state.nextId(anno.type);

    state.annotations.push(anno);
  };

  this.figure = function(state, figure) {
    var doc = state.doc;

    var imageNode = {
      type: "image",
      "label": "",
      "title": "",
      "url": "",
      "large_url": "",
      "caption": null
    };
    var id = figure.getAttribute("id") || state.nextId(imageNode.type);
    imageNode.id = id;

    // Caption: is a paragraph
    var caption = figure.querySelector("caption");
    if (caption) {
      var p = caption.querySelector("p");
      var nodes = this.paragraph(state, p);
      if (nodes.length > 1) {
        throw new ImporterError("Ooops. Not ready for that...");
      }
      imageNode.caption = nodes[0].id;
    }

    var graphic = figure.querySelector("graphic");
    var url = graphic.getAttribute("xlink:href");
    imageNode.url = url;
    imageNode.large_url = url;

    var label = figure.querySelector("label");
    if (label) {
      imageNode.label = label.textContent;
    }

    var title = figure.querySelector("title");
    if (title) {
      imageNode.title = title.textContent;
    }

    doc.create(imageNode);
    return imageNode;
  };

  this.refList = function(state, refList) {
    var refs = refList.querySelectorAll("ref");
    for (var i = 0; i < refs.length; i++) {
      this.ref(state, refs[i]);
    }
  };

  this.ref = function(state, ref) {

    var children = ref.children;
    for (var i = 0; i < children.length; i++) {
      var child = children[i];
      var type = this.getNodeType(child);

      if (type === "mixed-citation" || type === "element-citation") {
        this.citation(state, ref, child);
      } else if (type === "label") {
        // ignoring it here...
      } else {
        console.error("Not supported in 'ref': ", type);
      }
    }
  };

  var _getName = function(nameEl) {
    var names = [];

    var surnameEl = nameEl.querySelector("surname");
    var givenNamesEl = nameEl.querySelector("given-names");

    if (givenNamesEl) names.push(givenNamesEl.textContent);
    if (surnameEl) names.push(surnameEl.textContent);

    return names.join(" ");
  };

  // TODO: is implemented naively, should be implemented considering the NLM spec
  this.citation = function(state, ref, citation) {
    var doc = state.doc;
    var i;

    var id = ref.getAttribute("id");
    if (!id) {
      throw new ImporterError("Expected 'id' in ref.");
    }

    var citationNode = {
      id: id,
      type: "article_citation",
      title: "N/A",
      label: "",
      authors: [],
      doi: "",
      source: "",
      volume: "",
      fpage: "",
      lpage: "",
      citation_urls: []
    };

    // TODO: we should consider to have a more structured citation type
    // and let the view decide how to render it instead of blobbing everything here.
    var personGroup = citation.querySelector("person-group");

    // HACK: we try to create a 'articleCitation' when there is structured
    // content (ATM, when personGroup is present)
    // Otherwise we create a mixed-citation taking the plain text content of the element
    if (personGroup) {

      citationNode = {
        id: id,
        type: "article_citation",
        title: "N/A",
        label: "",
        authors: [],
        doi: "",
        source: "",
        volume: "",
        fpage: "",
        lpage: "",
        citation_urls: []
      };

      var nameElements = personGroup.querySelectorAll("name");
      for (i = 0; i < nameElements.length; i++) {
        citationNode.authors.push(_getName(nameElements[i]));
      }

      var articleTitle = citation.querySelector("article-title");
      if (articleTitle) {
        citationNode.title = articleTitle.textContent;
      } else {
        console.error("FIXME: this citation has no title", citation);
      }

      var source = citation.querySelector("source");
      if (source) citationNode.source = source.textContent;

      var volume = citation.querySelector("volume");
      if (volume) citationNode.volume = volume.textContent;

      var fpage = citation.querySelector("fpage");
      if (fpage) citationNode.fpage = fpage.textContent;

      var lpage = citation.querySelector("lpage");
      if (lpage) citationNode.lpage = lpage.textContent;

      var year = citation.querySelector("year");
      if (year) citationNode.year = year.textContent;

      // Note: the label is child of 'ref'
      var label = ref.querySelector("label");
      if(label) citationNode.label = label.textContent;

      var pubIds = citation.querySelectorAll("pub-id");
      for (i = 0; i < pubIds.length; i++) {
        if(pubIds[i].getAttribute("pub-id-type") === "doi") {
          citationNode.doi = pubIds[i].textContent;
          break;
        }
      }

    } else {
      console.error("FIXME: there is one of those 'mixed-citation' without any structure.", citation);
      citationNode = {
        id: id,
        type: "mixed_citation",
        citation: citation.textContent,
        doi: ""
      };
    }

    doc.create(citationNode);
    doc.show("citations", id);
  };

};

LensImporter.Prototype.prototype = NLMImporter.prototype;
LensImporter.prototype = new LensImporter.Prototype();

module.exports = {
  Importer: LensImporter
};
<|endoftext|>


<commit_before>import { modifiers as modifierKeys } from './keys';

const modKeys = Object.keys( modifierKeys );

function matchKeys( { keySet: { key, modifiers = [] }, event } ) {
  let keysMatch = false;
  if ( key === event.which ) {
    const evtModKeys = modKeys.filter( modKey => event[ `${modKey}Key` ] ).sort();
    keysMatch = modifiers.length === evtModKeys.length &&
      modifiers.every( ( modKey, index ) => evtModKeys[ index ] );
  }
  return keysMatch;
}

export default matchKeys;
<commit_msg>minor code clean up for key match<commit_after>import { modifiers as modifierKeys } from './keys';

const modKeys = Object.keys( modifierKeys );

function matchKeys( { keySet: { key, modifiers = [] }, event } ) {
  let keysMatch = false;
  if ( key === event.which ) {
    const evtModKeys = modKeys.filter( modKey => event[ `${modKey}Key` ] ).sort();
    keysMatch = (
      modifiers.length === evtModKeys.length &&
      modifiers.every( ( modKey, index ) => evtModKeys[ index ] )
    );
  }
  return keysMatch;
}

export default matchKeys;
<|endoftext|>


<commit_before>export default {
  el: {
    colorpicker: {
      confirm: 'باشد',
      clear: 'حذف'
    },
    datepicker: {
      now: 'اکنون',
      today: 'امروز',
      cancel: 'لغو',
      clear: 'حذف',
      confirm: 'باشه',
      selectDate: 'انتخاب تاریخ',
      selectTime: 'انتخاب زمان',
      startDate: 'تاریخ شروع',
      startTime: 'زمان شروع',
      endDate: 'تاریخ پایان',
      endTime: 'زمان پایان',
      prevYear: 'سال قبل',
      nextYear: 'سال بعد',
      prevMonth: 'ماه قبل',
      nextMonth: 'ماه بعد',
      year: 'سال',
      month1: 'ژانویه',
      month2: 'فوریه',
      month3: 'مارس',
      month4: 'آوریل',
      month5: 'مه',
      month6: 'ژوئن',
      month7: 'جولای',
      month8: 'اوت',
      month9: 'سپتامبر',
      month10: 'اکتبر',
      month11: 'نوامبر',
      month12: 'دسامبر',
      // week: 'week',
      weeks: {
        sun: 'یکشنبه',
        mon: 'دوشنبه',
        tue: 'سهشنبه',
        wed: 'چهارشنبه',
        thu: 'پنجشنبه',
        fri: 'جمعه',
        sat: 'شنبه'
      },
      months: {
        jan: 'ژانویه',
        feb: 'فوریه',
        mar: 'مارس',
        apr: 'آوریل',
        may: 'مه',
        jun: 'ژوئن',
        jul: 'جولای',
        aug: 'اوت',
        sep: 'سپتامبر',
        oct: 'اکتبر',
        nov: 'نوامبر',
        dec: 'دسامبر'
      }
    },
    select: {
      loading: 'بارگیری',
      noMatch: 'هیچ داده‌ای پیدا نشد',
      noData: 'اطلاعاتی وجود ندارد',
      placeholder: 'انتخاب کنید'
    },
    cascader: {
      noMatch: 'هیچ داده‌ای پیدا نشد',
      loading: 'بارگیری',
      placeholder: 'انتخاب کنید'
    },
    pagination: {
      goto: 'برو به',
      pagesize: '/صفحه',
      total: 'مجموع {total}',
      pageClassifier: ''
    },
    messagebox: {
      title: 'پیام',
      confirm: 'باشه',
      cancel: 'لغو',
      error: 'ورودی غیر مجاز'
    },
    upload: {
      deleteTip: 'press delete to remove', // to be translated
      delete: 'حذف',
      preview: 'پیش‌نمایش',
      continue: 'ادامه'
    },
    table: {
      emptyText: 'اطلاعاتی وجود ندارد',
      confirmFilter: 'تایید',
      resetFilter: 'حذف',
      clearFilter: 'همه',
      sumText: 'Sum' // to be translated
    },
    tree: {
      emptyText: 'اطلاعاتی وجود ندارد'
    },
    transfer: {
      noMatch: 'هیچ داده‌ای پیدا نشد',
      noData: 'اطلاعاتی وجود ندارد',
      titles: ['List 1', 'List 2'], // to be translated
      filterPlaceholder: 'کلید واژه هارو وارد کن',
      noCheckedFormat: '{total} items', // to be translated
      hasCheckedFormat: '{checked}/{total} checked' // to be translated
    }
  }
};
<commit_msg>i18n: add Persian translation of the remaining items (#12581)<commit_after>export default {
  el: {
    colorpicker: {
      confirm: 'باشد',
      clear: 'حذف'
    },
    datepicker: {
      now: 'اکنون',
      today: 'امروز',
      cancel: 'لغو',
      clear: 'حذف',
      confirm: 'باشه',
      selectDate: 'انتخاب تاریخ',
      selectTime: 'انتخاب زمان',
      startDate: 'تاریخ شروع',
      startTime: 'زمان شروع',
      endDate: 'تاریخ پایان',
      endTime: 'زمان پایان',
      prevYear: 'سال قبل',
      nextYear: 'سال بعد',
      prevMonth: 'ماه قبل',
      nextMonth: 'ماه بعد',
      year: 'سال',
      month1: 'ژانویه',
      month2: 'فوریه',
      month3: 'مارس',
      month4: 'آوریل',
      month5: 'مه',
      month6: 'ژوئن',
      month7: 'جولای',
      month8: 'اوت',
      month9: 'سپتامبر',
      month10: 'اکتبر',
      month11: 'نوامبر',
      month12: 'دسامبر',
      // week: 'week',
      weeks: {
        sun: 'یکشنبه',
        mon: 'دوشنبه',
        tue: 'سهشنبه',
        wed: 'چهارشنبه',
        thu: 'پنجشنبه',
        fri: 'جمعه',
        sat: 'شنبه'
      },
      months: {
        jan: 'ژانویه',
        feb: 'فوریه',
        mar: 'مارس',
        apr: 'آوریل',
        may: 'مه',
        jun: 'ژوئن',
        jul: 'جولای',
        aug: 'اوت',
        sep: 'سپتامبر',
        oct: 'اکتبر',
        nov: 'نوامبر',
        dec: 'دسامبر'
      }
    },
    select: {
      loading: 'بارگیری',
      noMatch: 'هیچ داده‌ای پیدا نشد',
      noData: 'اطلاعاتی وجود ندارد',
      placeholder: 'انتخاب کنید'
    },
    cascader: {
      noMatch: 'هیچ داده‌ای پیدا نشد',
      loading: 'بارگیری',
      placeholder: 'انتخاب کنید'
    },
    pagination: {
      goto: 'برو به',
      pagesize: '/صفحه',
      total: 'مجموع {total}',
      pageClassifier: ''
    },
    messagebox: {
      title: 'پیام',
      confirm: 'باشه',
      cancel: 'لغو',
      error: 'ورودی غیر مجاز'
    },
    upload: {
      deleteTip: 'برای پاک کردن حذف را فشار دهید',
      delete: 'حذف',
      preview: 'پیش‌نمایش',
      continue: 'ادامه'
    },
    table: {
      emptyText: 'اطلاعاتی وجود ندارد',
      confirmFilter: 'تایید',
      resetFilter: 'حذف',
      clearFilter: 'همه',
      sumText: 'جمع'
    },
    tree: {
      emptyText: 'اطلاعاتی وجود ندارد'
    },
    transfer: {
      noMatch: 'هیچ داده‌ای پیدا نشد',
      noData: 'اطلاعاتی وجود ندارد',
      titles: ['لیست 1', 'لیست 2'],
      filterPlaceholder: 'کلید واژه هارو وارد کن',
      noCheckedFormat: '{total} مورد',
      hasCheckedFormat: '{checked} مورد از {total} مورد انتخاب شده است'
    }
  }
};
<|endoftext|>


<commit_before>export default {
  el: {
    datepicker: {
      now: 'Maintenant',
      today: "Auj.",
      cancel: 'Annuler',
      clear: 'Effacer',
      confirm: 'OK',
      selectDate: 'Choisir date',
      selectTime: 'Choisir horaire',
      startDate: 'Date début',
      startTime: 'Horaire début',
      endDate: 'Date fin',
      endTime: 'Horaire fin',
      year: 'Année',
      month1: 'Janvier',
      month2: 'Février',
      month3: 'Mars',
      month4: 'Avril',
      month5: 'Mai',
      month6: 'Juin',
      month7: 'Juillet',
      month8: 'Août',
      month9: 'Septembre',
      month10: 'Octobre',
      month11: 'Novembre',
      month12: 'Décembre',
      // week: 'Semaine',
      weeks: {
        sun: 'Dim',
        mon: 'Lun',
        tue: 'Mar',
        wed: 'Mer',
        thu: 'Jeu',
        fri: 'Ven',
        sat: 'Sam'
      },
      months: {
        jan: 'Jan',
        feb: 'Fév',
        mar: 'Mar',
        apr: 'Avr',
        may: 'Mai',
        jun: 'Jun',
        jul: 'Jul',
        aug: 'Aoû',
        sep: 'Sep',
        oct: 'Oct',
        nov: 'Nov',
        dec: 'Déc'
      }
    },
    select: {
      loading: 'Chargement',
      noMatch: 'Aucune correspondance',
      noData: 'Aucun résultat',
      placeholder: 'Choisir'
    },
    pagination: {
      goto: 'Aller à',
      pagesize: '/page',
      total: 'Total {total}',
      pageClassifier: ''
    },
    messagebox: {
      confirm: 'Confirmer',
      cancel: 'Annuler',
      error: 'Erreur'
    },
    upload: {
      delete: 'Supprimer',
      preview: 'Aperçu',
      continue: 'Continuer'
    },
    table: {
      emptyText: 'Aucune donné',
      confirmFilter: 'Confirmer',
      resetFilter: 'Réinitialiser',
      clearFilter: 'Tous'
    },
    tree: {
      emptyText: 'Aucune donnée'
    }
  }
};
<commit_msg>replace double quote<commit_after>export default {
  el: {
    datepicker: {
      now: 'Maintenant',
      today: 'Auj.',
      cancel: 'Annuler',
      clear: 'Effacer',
      confirm: 'OK',
      selectDate: 'Choisir date',
      selectTime: 'Choisir horaire',
      startDate: 'Date début',
      startTime: 'Horaire début',
      endDate: 'Date fin',
      endTime: 'Horaire fin',
      year: 'Année',
      month1: 'Janvier',
      month2: 'Février',
      month3: 'Mars',
      month4: 'Avril',
      month5: 'Mai',
      month6: 'Juin',
      month7: 'Juillet',
      month8: 'Août',
      month9: 'Septembre',
      month10: 'Octobre',
      month11: 'Novembre',
      month12: 'Décembre',
      // week: 'Semaine',
      weeks: {
        sun: 'Dim',
        mon: 'Lun',
        tue: 'Mar',
        wed: 'Mer',
        thu: 'Jeu',
        fri: 'Ven',
        sat: 'Sam'
      },
      months: {
        jan: 'Jan',
        feb: 'Fév',
        mar: 'Mar',
        apr: 'Avr',
        may: 'Mai',
        jun: 'Jun',
        jul: 'Jul',
        aug: 'Aoû',
        sep: 'Sep',
        oct: 'Oct',
        nov: 'Nov',
        dec: 'Déc'
      }
    },
    select: {
      loading: 'Chargement',
      noMatch: 'Aucune correspondance',
      noData: 'Aucun résultat',
      placeholder: 'Choisir'
    },
    pagination: {
      goto: 'Aller à',
      pagesize: '/page',
      total: 'Total {total}',
      pageClassifier: ''
    },
    messagebox: {
      confirm: 'Confirmer',
      cancel: 'Annuler',
      error: 'Erreur'
    },
    upload: {
      delete: 'Supprimer',
      preview: 'Aperçu',
      continue: 'Continuer'
    },
    table: {
      emptyText: 'Aucune donné',
      confirmFilter: 'Confirmer',
      resetFilter: 'Réinitialiser',
      clearFilter: 'Tous'
    },
    tree: {
      emptyText: 'Aucune donnée'
    }
  }
};
<|endoftext|>


<commit_before>/**
 * Visualization of genes, including exons and coding regions.
 * @flow
 */
'use strict';

import type {Strand} from './Alignment';
import type {Gene} from './BigBedDataSource';

var React = require('react'),
    ReactDOM = require('react-dom'),
    _ = require('underscore'),
    shallowEquals = require('shallow-equals');

var types = require('./react-types'),
    bedtools = require('./bedtools'),
    Interval = require('./Interval'),
    d3utils = require('./d3utils'),
    scale = require('./scale'),
    ContigInterval = require('./ContigInterval'),
    canvasUtils = require('./canvas-utils'),
    dataCanvas = require('data-canvas'),
    style = require('./style');

// Draw an arrow in the middle of the visible portion of range.
// TODO: right-facing arrows!
function drawArrow(ctx: CanvasRenderingContext2D,
                   clampedScale: (x: number)=>number,
                   range: Interval,
                   tipY: number,
                   strand: Strand) {
  var x1 = clampedScale(range.start),
      x2 = clampedScale(range.stop);

  // it's off-screen or there's not enough room to draw it legibly.
  if (x2 - x1 <= 2 * style.GENE_ARROW_SIZE) return;

  var cx = (x1 + x2) / 2;
  ctx.beginPath();
  if (strand == '-') {
    ctx.moveTo(cx + style.GENE_ARROW_SIZE, tipY - style.GENE_ARROW_SIZE);
    ctx.lineTo(cx, tipY);
    ctx.lineTo(cx + style.GENE_ARROW_SIZE, tipY + style.GENE_ARROW_SIZE);
  } else {
    ctx.moveTo(cx - style.GENE_ARROW_SIZE, tipY - style.GENE_ARROW_SIZE);
    ctx.lineTo(cx, tipY);
    ctx.lineTo(cx - style.GENE_ARROW_SIZE, tipY + style.GENE_ARROW_SIZE);
  }
  ctx.stroke();
}

function drawGeneName(ctx: CanvasRenderingContext2D,
                      clampedScale: (x: number)=>number,
                      geneLineY: number,
                      gene: Gene,
                      textIntervals: Interval[]) {
  var p = gene.position,
      centerX = 0.5 * (clampedScale(p.start()) + clampedScale(p.stop()));
  var name = gene.name || gene.id;
  var textWidth = ctx.measureText(name).width;
  var textInterval = new Interval(centerX - 0.5 * textWidth,
                                  centerX + 0.5 * textWidth);
  if (!_.any(textIntervals, iv => textInterval.intersects(iv))) {
    textIntervals.push(textInterval);
    var baselineY = geneLineY + style.GENE_FONT_SIZE + style.GENE_TEXT_PADDING;
    ctx.fillText(name, centerX, baselineY);
  }
}

var GeneTrack = React.createClass({
  displayName: 'genes',
  propTypes: {
    range: types.GenomeRange.isRequired,
    source: React.PropTypes.object.isRequired,
  },
  getInitialState: function() {
    return {
      genes: ([]: Object[])  // TODO: import Gene type from BigBedDataSource
    };
  },
  render: function(): any {
    return <canvas />;
  },
  componentDidMount: function() {
    // Visualize new reference data as it comes in from the network.
    this.props.source.on('newdata', () => {
      var range = this.props.range,
          ci = new ContigInterval(range.contig, range.start, range.stop);
      this.setState({
        genes: this.props.source.getGenesInRange(ci)
      });
    });

    this.updateVisualization();
  },

  getScale: function() {
    return d3utils.getTrackScale(this.props.range, this.props.width);
  },
  componentDidUpdate: function(prevProps: any, prevState: any) {
    if (!shallowEquals(prevProps, this.props) ||
        !shallowEquals(prevState, this.state)) {
      this.updateVisualization();
    }
  },
  updateVisualization: function() {
    var canvas = ReactDOM.findDOMNode(this),
        {width, height} = this.props;

    // Hold off until height & width are known.
    if (width === 0) return;

    var sc = this.getScale(),
        // We can't clamp scale directly because of offsetPx.
        clampedScale = scale.linear()
            .domain([sc.invert(0), sc.invert(width)])
            .range([0, width])
            .clamp(true);

    d3utils.sizeCanvas(canvas, width, height);

    var ctx = dataCanvas.getDataContext(canvasUtils.getContext(canvas));
    ctx.reset();
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    var geneLineY = height / 4;
    var textIntervals = [];  // x-intervals with rendered gene names, to avoid over-drawing.
    // TODO: don't pull in genes via state.
    ctx.font = `${style.GENE_FONT_SIZE}px ${style.GENE_FONT}`;
    ctx.textAlign = 'center';
    this.state.genes.forEach(gene => {
      ctx.pushObject(gene);
      ctx.lineWidth = 1;
      ctx.strokeStyle = style.GENE_COLOR;
      ctx.fillStyle = style.GENE_COLOR;

      canvasUtils.drawLine(ctx, clampedScale(gene.position.start()), geneLineY,
                                clampedScale(gene.position.stop()), geneLineY);

      // TODO: only compute all these intervals when data becomes available.
      var exons = bedtools.splitCodingExons(gene.exons, gene.codingRegion);
      exons.forEach(exon => {
        ctx.fillRect(sc(exon.start),
                     geneLineY - 3 * (exon.isCoding ? 2 : 1),
                     sc(exon.stop + 1) - sc(exon.start),
                     6 * (exon.isCoding ? 2 : 1));
      });

      var introns = gene.position.interval.complementIntervals(gene.exons);
      introns.forEach(range => {
        drawArrow(ctx, clampedScale, range, geneLineY, gene.strand);
      });
      ctx.strokeStyle = style.GENE_COMPLEMENT_COLOR;
      ctx.lineWidth = 2;
      gene.exons.forEach(range => {
        drawArrow(ctx, clampedScale, range, geneLineY, gene.strand);
      });

      drawGeneName(ctx, clampedScale, geneLineY, gene, textIntervals);

      ctx.popObject();
    });
  }
});

module.exports = GeneTrack;
<commit_msg>Fix issue #288<commit_after>/**
 * Visualization of genes, including exons and coding regions.
 * @flow
 */
'use strict';

import type {Strand} from './Alignment';
import type {Gene} from './BigBedDataSource';

var React = require('react'),
    ReactDOM = require('react-dom'),
    _ = require('underscore'),
    shallowEquals = require('shallow-equals');

var types = require('./react-types'),
    bedtools = require('./bedtools'),
    Interval = require('./Interval'),
    d3utils = require('./d3utils'),
    scale = require('./scale'),
    ContigInterval = require('./ContigInterval'),
    canvasUtils = require('./canvas-utils'),
    dataCanvas = require('data-canvas'),
    style = require('./style');

// Draw an arrow in the middle of the visible portion of range.
// TODO: right-facing arrows!
function drawArrow(ctx: CanvasRenderingContext2D,
                   clampedScale: (x: number)=>number,
                   range: Interval,
                   tipY: number,
                   strand: Strand) {
  var x1 = clampedScale(range.start),
      x2 = clampedScale(range.stop);

  // it's off-screen or there's not enough room to draw it legibly.
  if (x2 - x1 <= 2 * style.GENE_ARROW_SIZE) return;

  var cx = (x1 + x2) / 2;
  ctx.beginPath();
  if (strand == '-') {
    ctx.moveTo(cx + style.GENE_ARROW_SIZE, tipY - style.GENE_ARROW_SIZE);
    ctx.lineTo(cx, tipY);
    ctx.lineTo(cx + style.GENE_ARROW_SIZE, tipY + style.GENE_ARROW_SIZE);
  } else {
    ctx.moveTo(cx - style.GENE_ARROW_SIZE, tipY - style.GENE_ARROW_SIZE);
    ctx.lineTo(cx, tipY);
    ctx.lineTo(cx - style.GENE_ARROW_SIZE, tipY + style.GENE_ARROW_SIZE);
  }
  ctx.stroke();
}

function drawGeneName(ctx: CanvasRenderingContext2D,
                      clampedScale: (x: number)=>number,
                      geneLineY: number,
                      gene: Gene,
                      textIntervals: Interval[]) {
  var p = gene.position,
      centerX = 0.5 * (clampedScale(p.start()) + clampedScale(p.stop()));
  var name = gene.name || gene.id;
  var textWidth = ctx.measureText(name).width;
  var textInterval = new Interval(centerX - 0.5 * textWidth,
                                  centerX + 0.5 * textWidth);
  if (!_.any(textIntervals, iv => textInterval.intersects(iv))) {
    textIntervals.push(textInterval);
    var baselineY = geneLineY + style.GENE_FONT_SIZE + style.GENE_TEXT_PADDING;
    ctx.fillText(name, centerX, baselineY);
  }
}

var GeneTrack = React.createClass({
  displayName: 'genes',
  propTypes: {
    range: types.GenomeRange.isRequired,
    source: React.PropTypes.object.isRequired,
  },
  getInitialState: function() {
    return {
      genes: ([]: Object[])  // TODO: import Gene type from BigBedDataSource
    };
  },
  render: function(): any {
    return <canvas />;
  },
  componentDidMount: function() {
    // Visualize new reference data as it comes in from the network.
    this.props.source.on('newdata', () => {
      var range = this.props.range,
          ci = new ContigInterval(range.contig, range.start, range.stop);
      this.setState({
        genes: this.props.source.getGenesInRange(ci)
      });
    });

    this.updateVisualization();
  },

  getScale: function() {
    return d3utils.getTrackScale(this.props.range, this.props.width);
  },
  componentDidUpdate: function(prevProps: any, prevState: any) {
    if (!shallowEquals(prevProps, this.props) ||
        !shallowEquals(prevState, this.state)) {
      this.updateVisualization();
    }
  },
  updateVisualization: function() {
    var canvas = ReactDOM.findDOMNode(this),
        {width, height} = this.props,
        genomeRange = this.props.range;

    var range = genomeRange ? new ContigInterval(genomeRange.contig, genomeRange.start, genomeRange.stop) : null;

    // Hold off until height & width are known.
    if (width === 0) return;

    var sc = this.getScale(),
        // We can't clamp scale directly because of offsetPx.
        clampedScale = scale.linear()
            .domain([sc.invert(0), sc.invert(width)])
            .range([0, width])
            .clamp(true);

    d3utils.sizeCanvas(canvas, width, height);

    var ctx = dataCanvas.getDataContext(canvasUtils.getContext(canvas));
    ctx.reset();
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    var geneLineY = height / 4;
    var textIntervals = [];  // x-intervals with rendered gene names, to avoid over-drawing.
    // TODO: don't pull in genes via state.
    ctx.font = `${style.GENE_FONT_SIZE}px ${style.GENE_FONT}`;
    ctx.textAlign = 'center';
    this.state.genes.forEach(gene => {
      if (!gene.position.chrIntersects(range)) return;
      ctx.pushObject(gene);
      ctx.lineWidth = 1;
      ctx.strokeStyle = style.GENE_COLOR;
      ctx.fillStyle = style.GENE_COLOR;

      canvasUtils.drawLine(ctx, clampedScale(gene.position.start()), geneLineY,
                                clampedScale(gene.position.stop()), geneLineY);

      // TODO: only compute all these intervals when data becomes available.
      var exons = bedtools.splitCodingExons(gene.exons, gene.codingRegion);
      exons.forEach(exon => {
        ctx.fillRect(sc(exon.start),
                     geneLineY - 3 * (exon.isCoding ? 2 : 1),
                     sc(exon.stop + 1) - sc(exon.start),
                     6 * (exon.isCoding ? 2 : 1));
      });

      var introns = gene.position.interval.complementIntervals(gene.exons);
      introns.forEach(range => {
        drawArrow(ctx, clampedScale, range, geneLineY, gene.strand);
      });
      ctx.strokeStyle = style.GENE_COMPLEMENT_COLOR;
      ctx.lineWidth = 2;
      gene.exons.forEach(range => {
        drawArrow(ctx, clampedScale, range, geneLineY, gene.strand);
      });

      drawGeneName(ctx, clampedScale, geneLineY, gene, textIntervals);

      ctx.popObject();
    });
  }
});

module.exports = GeneTrack;
<|endoftext|>


<commit_before>const OSM_MAP_ATTR = {
  type: 'map',
  name: '©\xa0OpenStreetMap',
  url: 'https://osm.org/',
};

const OSM_DATA_ATTR = {
  type: 'data',
  nameKey: 'mapLayers.attr.osmData',
  url: 'https://osm.org/copyright',
};

const FM_ATTR = {
  type: 'map',
  nameKey: 'mapLayers.attr.freemap',
};

const SRTM_ATTR = {
  type: 'data',
  nameKey: 'mapLayers.attr.srtm',
};

const STRAVA_ATTR = {
  type: 'map',
  name: '©\xa0Strava',
  url: 'https://www.strava.com/',
};

const NLC_ATTR = {
  type: 'map',
  name: '©\xa0NLC Zvolen',
  url: 'http://www.nlcsk.org/',
};

export const baseLayers = [
  ...[
    ['A', 'car'],
    ['T', '!icon-hiking'],
    ['C', 'bicycle'],
    ['K', '!icon-skier-skiing'],
  ].map(([type, icon]) => ({
    type,
    icon,
    url: `//{s}.freemap.sk/${type}/{z}/{x}/{y}.{tileFormat}`,
    subdomains: 'abcd',
    attribution: [
      FM_ATTR,
      OSM_DATA_ATTR,
      type !== 'A' && SRTM_ATTR,
    ].filter(a => a),
    minZoom: 8,
    maxNativeZoom: 16,
    key: type.toLowerCase(),
  })),
  {
    type: 'S',
    icon: 'plane',
    minZoom: 0,
    maxNativeZoom: 18,
    key: 's',
    attribution: [
      {
        type: 'map',
        name: '©\xa0Bing',
      },
      {
        type: 'map',
        name: '©\xa0Earthstar Geographics SIO',
      },
      {
        type: 'map',
        name: '©\xa02017 Microsoft Corporation',
      },
    ],
  },
  {
    type: 'O',
    icon: 'globe',
    url: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
    minZoom: 0,
    maxNativeZoom: 19,
    attribution: [
      OSM_MAP_ATTR,
      OSM_DATA_ATTR,
    ],
    key: 'o',
  },
  {
    type: 'M',
    url: 'http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png',
    minZoom: 3,
    maxNativeZoom: 18,
    icon: 'bicycle',
    showOnlyInExpertMode: true,
    attribution: [
      {
        type: 'map',
        name: '©\xa0Martin Tesař',
        url: 'mailto:smmtb@gmail.com',
      },
      OSM_DATA_ATTR,
      SRTM_ATTR,
    ],
    key: 'm',
  },
  {
    type: 'p',
    url: '//tile.opentopomap.org/{z}/{x}/{y}.png',
    minZoom: 3,
    maxNativeZoom: 17,
    icon: 'tree',
    showOnlyInExpertMode: true,
    attribution: [
      {
        type: 'map',
        name: '©\xa0OpenTopoMap',
        url: 'https://tile.opentopomap.org/about#verwendung',
      },
      OSM_DATA_ATTR,
      SRTM_ATTR,
    ],
    key: 'p',
  },
  {
    type: 'd',
    url: '//tile.memomaps.de/tilegen/{z}/{x}/{y}.png',
    minZoom: 0,
    maxNativeZoom: 18,
    icon: 'bus',
    showOnlyInExpertMode: true,
    attribution: [
      {
        type: 'map',
        name: '©\xa0MeMoMaps',
        url: 'https://memomaps.de/en/',
      },
      OSM_DATA_ATTR,
    ],
    key: 'd',
  },
  {
    type: 'h',
    url: 'http://tms.freemap.sk/historicke/{z}/{x}/{y}.png',
    minNativeZoom: 12,
    maxNativeZoom: 12,
    icon: 'institution',
    showOnlyInExpertMode: true,
    attribution: [],
    key: 'h',
  },
  {
    type: 'X',
    icon: 'tree',
    url: scaleUrl([1, 2, 3], 'https://outdoor.tiles.freemap.sk/{z}/{x}/{y}'),
    attribution: [
      FM_ATTR,
      OSM_DATA_ATTR,
      SRTM_ATTR,
    ].filter(a => a),
    minZoom: 6,
    maxNativeZoom: 19,
    key: 'x',
  },
  !process.env.NODE_ENV && (
    {
      type: 'Y',
      icon: 'flask',
      url: scaleUrl([1, 2, 3], 'http://localhost:4000/{z}/{x}/{y}'),
      attribution: [
        FM_ATTR,
        OSM_DATA_ATTR,
        SRTM_ATTR,
      ].filter(a => a),
      minZoom: 6,
      maxNativeZoom: 19,
      key: 'y',
    }
  ),
].filter(x => x);

function findNearestScale(scales, ratio = window.devicePixelRatio || 1) {
  let dif = Number.POSITIVE_INFINITY;
  let prevScale = scales[0];
  for (const scale of scales) {
    const newDif = Math.abs(ratio - scale);
    if (newDif >= dif) {
      return prevScale;
    }
    prevScale = scale;
    dif = newDif;
  }

  return prevScale;
}

function scaleUrl(scales, url) {
  const scale = findNearestScale(scales);
  return scale === 1 ? url : `${url}@${scale}x`;
}

export const overlayLayers = [
  {
    type: 'I',
    icon: 'picture-o',
    minZoom: 0,
    key: 'F',
    zIndex: 3,
    attribution: [
      {
        type: 'photos',
        name: 'CC-BY-SA',
      },
    ],
  },
  {
    type: 'n',
    icon: 'tree',
    url: '//tiles.freemap.sk/nlc2017/{z}/{x}/{y}.png',
    attribution: [NLC_ATTR],
    minZoom: 11,
    maxNativeZoom: 15,
    key: 'N',
    zIndex: 2,
    adminOnly: true,
  },
  ...['both', 'ride', 'run', /* 'water', */ 'winter'].map((type, i) => ({
    type: `s${i}`,
    icon: 'scribd', // TODO use correct logo
    url: `//heatmap-external-{s}.strava.com/tiles-auth/${type}/bluered/{z}/{x}/{y}.png?px=256`,
    attribution: [STRAVA_ATTR],
    minZoom: 0,
    maxNativeZoom: 16,
    key: type === 'both' ? 'H' : undefined,
    showOnlyInExpertMode: true,
    zIndex: 2,
    strava: true,
  })),
  {
    type: 'g',
    icon: '!icon-gps-device',
    url: '//gps-{s}.tile.openstreetmap.org/lines/{z}/{x}/{y}.png',
    attribution: [
      OSM_MAP_ATTR,
      OSM_DATA_ATTR,
    ],
    minZoom: 0,
    maxNativeZoom: 20,
    key: 'G',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    type: 't',
    icon: '!icon-hiking',
    url: '//tiles.freemap.sk/trails/{z}/{x}/{y}.png',
    attribution: [
      FM_ATTR,
      OSM_DATA_ATTR,
    ],
    minZoom: 8,
    maxNativeZoom: 16,
    key: 'T',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    type: 'c',
    icon: 'bicycle',
    url: '//tiles.freemap.sk/cycle/{z}/{x}/{y}.png',
    attribution: [
      FM_ATTR,
      OSM_DATA_ATTR,
    ],
    minZoom: 8,
    maxNativeZoom: 16,
    key: 'C',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    type: 'q',
    icon: 'snowflake-o',
    url: '//www.opensnowmap.org/pistes/{z}/{x}/{y}.png',
    attribution: [
      {
        type: 'map',
        name: '©\xa0OpenSnowMap.org',
      },
      OSM_DATA_ATTR,
    ],
    minZoom: 0,
    maxNativeZoom: 18,
    key: 'S',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  ...[['1', '1', ''], ['2', '2', 'h'], ['3', '3', 'c']].map(([type, key, suffix]) => ({
    type,
    icon: 'font',
    url: `//tiles.freemap.sk/names${suffix}/{z}/{x}/{y}.png`,
    attribution: [
      FM_ATTR,
      OSM_DATA_ATTR,
    ],
    minZoom: 8,
    maxNativeZoom: 16,
    key,
    showOnlyInExpertMode: true,
    zIndex: 2,
  })),
  {
    type: 'r',
    icon: 'pencil-square-o',
    url: '//old.freemap.sk/layers/renderedby/?/{z}/{x}/{y}',
    minZoom: 8,
    maxNativeZoom: 12,
    key: 'R',
    showOnlyInExpertMode: true,
    zIndex: 4,
    attribution: [
      FM_ATTR,
    ],
  },
];
<commit_msg>honored outdoor map<commit_after>const OSM_MAP_ATTR = {
  type: 'map',
  name: '©\xa0OpenStreetMap',
  url: 'https://osm.org/',
};

const OSM_DATA_ATTR = {
  type: 'data',
  nameKey: 'mapLayers.attr.osmData',
  url: 'https://osm.org/copyright',
};

const FM_ATTR = {
  type: 'map',
  nameKey: 'mapLayers.attr.freemap',
};

const SRTM_ATTR = {
  type: 'data',
  nameKey: 'mapLayers.attr.srtm',
};

const STRAVA_ATTR = {
  type: 'map',
  name: '©\xa0Strava',
  url: 'https://www.strava.com/',
};

const NLC_ATTR = {
  type: 'map',
  name: '©\xa0NLC Zvolen',
  url: 'http://www.nlcsk.org/',
};

export const baseLayers = [
  {
    type: 'X',
    icon: 'tree',
    url: scaleUrl([1, 2, 3], 'https://outdoor.tiles.freemap.sk/{z}/{x}/{y}'),
    attribution: [
      FM_ATTR,
      OSM_DATA_ATTR,
      SRTM_ATTR,
    ].filter(a => a),
    minZoom: 6,
    maxNativeZoom: 19,
    key: 'x',
  },
  ...[
    ['A', 'car'],
    ['T', '!icon-hiking'],
    ['C', 'bicycle'],
    ['K', '!icon-skier-skiing'],
  ].map(([type, icon]) => ({
    type,
    icon,
    url: `//{s}.freemap.sk/${type}/{z}/{x}/{y}.{tileFormat}`,
    subdomains: 'abcd',
    attribution: [
      FM_ATTR,
      OSM_DATA_ATTR,
      type !== 'A' && SRTM_ATTR,
    ].filter(a => a),
    minZoom: 8,
    maxNativeZoom: 16,
    key: type.toLowerCase(),
  })),
  {
    type: 'S',
    icon: 'plane',
    minZoom: 0,
    maxNativeZoom: 18,
    key: 's',
    attribution: [
      {
        type: 'map',
        name: '©\xa0Bing',
      },
      {
        type: 'map',
        name: '©\xa0Earthstar Geographics SIO',
      },
      {
        type: 'map',
        name: '©\xa02017 Microsoft Corporation',
      },
    ],
  },
  {
    type: 'O',
    icon: 'globe',
    url: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
    minZoom: 0,
    maxNativeZoom: 19,
    attribution: [
      OSM_MAP_ATTR,
      OSM_DATA_ATTR,
    ],
    key: 'o',
  },
  {
    type: 'M',
    url: 'http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png',
    minZoom: 3,
    maxNativeZoom: 18,
    icon: 'bicycle',
    showOnlyInExpertMode: true,
    attribution: [
      {
        type: 'map',
        name: '©\xa0Martin Tesař',
        url: 'mailto:smmtb@gmail.com',
      },
      OSM_DATA_ATTR,
      SRTM_ATTR,
    ],
    key: 'm',
  },
  {
    type: 'p',
    url: '//tile.opentopomap.org/{z}/{x}/{y}.png',
    minZoom: 3,
    maxNativeZoom: 17,
    icon: 'tree',
    showOnlyInExpertMode: true,
    attribution: [
      {
        type: 'map',
        name: '©\xa0OpenTopoMap',
        url: 'https://tile.opentopomap.org/about#verwendung',
      },
      OSM_DATA_ATTR,
      SRTM_ATTR,
    ],
    key: 'p',
  },
  {
    type: 'd',
    url: '//tile.memomaps.de/tilegen/{z}/{x}/{y}.png',
    minZoom: 0,
    maxNativeZoom: 18,
    icon: 'bus',
    showOnlyInExpertMode: true,
    attribution: [
      {
        type: 'map',
        name: '©\xa0MeMoMaps',
        url: 'https://memomaps.de/en/',
      },
      OSM_DATA_ATTR,
    ],
    key: 'd',
  },
  {
    type: 'h',
    url: 'http://tms.freemap.sk/historicke/{z}/{x}/{y}.png',
    minNativeZoom: 12,
    maxNativeZoom: 12,
    icon: 'institution',
    showOnlyInExpertMode: true,
    attribution: [],
    key: 'h',
  },
  !process.env.NODE_ENV && (
    {
      type: 'Y',
      icon: 'flask',
      url: scaleUrl([1, 2, 3], 'http://localhost:4000/{z}/{x}/{y}'),
      attribution: [
        FM_ATTR,
        OSM_DATA_ATTR,
        SRTM_ATTR,
      ].filter(a => a),
      minZoom: 6,
      maxNativeZoom: 19,
      key: 'y',
    }
  ),
].filter(x => x);

function findNearestScale(scales, ratio = window.devicePixelRatio || 1) {
  let dif = Number.POSITIVE_INFINITY;
  let prevScale = scales[0];
  for (const scale of scales) {
    const newDif = Math.abs(ratio - scale);
    if (newDif >= dif) {
      return prevScale;
    }
    prevScale = scale;
    dif = newDif;
  }

  return prevScale;
}

function scaleUrl(scales, url) {
  const scale = findNearestScale(scales);
  return scale === 1 ? url : `${url}@${scale}x`;
}

export const overlayLayers = [
  {
    type: 'I',
    icon: 'picture-o',
    minZoom: 0,
    key: 'F',
    zIndex: 3,
    attribution: [
      {
        type: 'photos',
        name: 'CC-BY-SA',
      },
    ],
  },
  {
    type: 'n',
    icon: 'tree',
    url: '//tiles.freemap.sk/nlc2017/{z}/{x}/{y}.png',
    attribution: [NLC_ATTR],
    minZoom: 11,
    maxNativeZoom: 15,
    key: 'N',
    zIndex: 2,
    adminOnly: true,
  },
  ...['both', 'ride', 'run', /* 'water', */ 'winter'].map((type, i) => ({
    type: `s${i}`,
    icon: 'scribd', // TODO use correct logo
    url: `//heatmap-external-{s}.strava.com/tiles-auth/${type}/bluered/{z}/{x}/{y}.png?px=256`,
    attribution: [STRAVA_ATTR],
    minZoom: 0,
    maxNativeZoom: 16,
    key: type === 'both' ? 'H' : undefined,
    showOnlyInExpertMode: true,
    zIndex: 2,
    strava: true,
  })),
  {
    type: 'g',
    icon: '!icon-gps-device',
    url: '//gps-{s}.tile.openstreetmap.org/lines/{z}/{x}/{y}.png',
    attribution: [
      OSM_MAP_ATTR,
      OSM_DATA_ATTR,
    ],
    minZoom: 0,
    maxNativeZoom: 20,
    key: 'G',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    type: 't',
    icon: '!icon-hiking',
    url: '//tiles.freemap.sk/trails/{z}/{x}/{y}.png',
    attribution: [
      FM_ATTR,
      OSM_DATA_ATTR,
    ],
    minZoom: 8,
    maxNativeZoom: 16,
    key: 'T',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    type: 'c',
    icon: 'bicycle',
    url: '//tiles.freemap.sk/cycle/{z}/{x}/{y}.png',
    attribution: [
      FM_ATTR,
      OSM_DATA_ATTR,
    ],
    minZoom: 8,
    maxNativeZoom: 16,
    key: 'C',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    type: 'q',
    icon: 'snowflake-o',
    url: '//www.opensnowmap.org/pistes/{z}/{x}/{y}.png',
    attribution: [
      {
        type: 'map',
        name: '©\xa0OpenSnowMap.org',
      },
      OSM_DATA_ATTR,
    ],
    minZoom: 0,
    maxNativeZoom: 18,
    key: 'S',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  ...[['1', '1', ''], ['2', '2', 'h'], ['3', '3', 'c']].map(([type, key, suffix]) => ({
    type,
    icon: 'font',
    url: `//tiles.freemap.sk/names${suffix}/{z}/{x}/{y}.png`,
    attribution: [
      FM_ATTR,
      OSM_DATA_ATTR,
    ],
    minZoom: 8,
    maxNativeZoom: 16,
    key,
    showOnlyInExpertMode: true,
    zIndex: 2,
  })),
  {
    type: 'r',
    icon: 'pencil-square-o',
    url: '//old.freemap.sk/layers/renderedby/?/{z}/{x}/{y}',
    minZoom: 8,
    maxNativeZoom: 12,
    key: 'R',
    showOnlyInExpertMode: true,
    zIndex: 4,
    attribution: [
      FM_ATTR,
    ],
  },
];
<|endoftext|>


<commit_before>var SecretList = {
    oninit: function() {
        Secret.loadList();
    },

    view: function() {
        var showCopiedToClipboardNotification = function(x, y) {
            var notification = document.createElement("div");
            notification.style =
                "position: fixed;"
                + "left: " + x + "px;"
                + "top: " + y + "px;"
                + "background-color: white;"
                + "padding: 2px;"
                + "border: 1px solid gray;";
            notification.innerText = "Copied to clipboard";
            document.body.appendChild(notification);

            setTimeout(function() {
                notification.style =
                    notification.getAttribute("style")
                    + "visibility: hidden;"
                    + "opacity: 0;"
                    +" transition: visibility 0s 0.5s, opacity 0.5s linear;"

                setTimeout(function() {
                    notification.remove();
                }, 500);
            }, 1500);
        }

        var copyToClipboard = function(value) {
            var input = document.createElement("input");
            input.style = "position: fixed; left: -1000px; top: -1000px";
            document.body.appendChild(input);
            input.value = value;
            input.focus();
            input.select();
            document.execCommand("Copy");
            input.remove();
        }

        var table = table = "No secrets found.";

        if (Secret.list.length > 0) {
            table = m("table.pure-table.pure-table-horizontal.secretslist", [
                m("thead", [
                    m("tr", [
                        m("th", {style: ""}, "Title"),
                        m("th", {style: ""}, "Username"),
                        m("th", {style: "width: 5em;"}, "Password"),
                        m("th", {style: "width: 12em;"}, "Modified"),
                    ])
                ]),
                m("tbody", Secret.list.map(function(row) {
                    return m("tr", { style: row.hidden ? "display: none" : "" }, [
                        m("td", m("a", {href: "/secrets/" + row.id, oncreate: m.route.link}, row.title)),
                        m("td", [
                            m("a[href=]", {
                                onclick: function(e) {
                                    copyToClipboard(row.username);
                                    showCopiedToClipboardNotification(
                                        e.clientX,
                                        e.clientY);
                                    return false;
                                }
                            }, m("span.copyicon")),
                            " ",
                            row.username,
                        ]),
                        m("td", [
                            m("a[href=]", {
                                onclick: function(e) {
                                    Secret.loadSync(row.id).then(function() {
                                        copyToClipboard(Secret.current.password);
                                    showCopiedToClipboardNotification(
                                        e.clientX,
                                        e.clientY);
                                    });

                                    return false;
                                }
                            },
                            m("span.copyicon")),
                            " *****",
                        ]),
                        m("td", row.modified),
                    ]);
                }))
            ]);
        }

        var search = function() {
            var term = document.getElementById("searchbox").value;

            for (var i = 0; i < Secret.list.length; i++) {
                var row = Secret.list[i];
                row.hidden = false;
                if (row.title.indexOf(term) == -1) {
                    row.hidden = true;
                }
            }
        }

        return [
            m("h2.content-subhead", "Secrets"),

            m("p", m("a[href=/secrets/new]", {oncreate: m.route.link}, "New Secret")),

            m("form.pure-form.pure-form-aligned", {
                onsubmit: function(e) {
                    e.preventDefault();
                    search();
                }}, [
                m("input#searchbox[type=text][placeholder=Search][autofocus]", { oncreate: function(vnode) { setTimeout(function() { vnode.dom.focus(); }, 0); } }),
                " ",
                m("button[type=submit].pure-button", { onclick: search }, "Search")
            ]),
            m("br"),
            table
        ];
    }
}
<commit_msg>Improved date formatting on secretslist<commit_after>var SecretList = {
    oninit: function() {
        Secret.loadList();
    },

    view: function() {
        var showCopiedToClipboardNotification = function(x, y) {
            var notification = document.createElement("div");
            notification.style =
                "position: fixed;"
                + "left: " + x + "px;"
                + "top: " + y + "px;"
                + "background-color: white;"
                + "padding: 2px;"
                + "border: 1px solid gray;";
            notification.innerText = "Copied to clipboard";
            document.body.appendChild(notification);

            setTimeout(function() {
                notification.style =
                    notification.getAttribute("style")
                    + "visibility: hidden;"
                    + "opacity: 0;"
                    +" transition: visibility 0s 0.5s, opacity 0.5s linear;"

                setTimeout(function() {
                    notification.remove();
                }, 500);
            }, 1500);
        }

        var copyToClipboard = function(value) {
            var input = document.createElement("input");
            input.style = "position: fixed; left: -1000px; top: -1000px";
            document.body.appendChild(input);
            input.value = value;
            input.focus();
            input.select();
            document.execCommand("Copy");
            input.remove();
        }

        function formatDate(date) {
            if (typeof(date) === "string") {
                date = new Date(date);
            }

            function pad(n) {
                var str = "" + n;
                var pad = "00";
                return pad.substring(0, pad.length - str.length) + str;
            }

            return date.getFullYear()
                + "-" + pad(date.getMonth())
                + "-" + pad(date.getDate())
                + " " + pad(date.getHours())
                + ":" + pad(date.getMinutes());
        }

        var table = table = "No secrets found.";

        if (Secret.list.length > 0) {
            table = m("table.pure-table.pure-table-horizontal.secretslist", [
                m("thead", [
                    m("tr", [
                        m("th", {style: ""}, "Title"),
                        m("th", {style: ""}, "Username"),
                        m("th", {style: "width: 5em;"}, "Password"),
                        m("th", {style: "width: 8em;"}, "Modified"),
                    ])
                ]),
                m("tbody", Secret.list.map(function(row) {
                    return m("tr", { style: row.hidden ? "display: none" : "" }, [
                        m("td", m("a", {href: "/secrets/" + row.id, oncreate: m.route.link}, row.title)),
                        m("td", [
                            m("a[href=]", {
                                onclick: function(e) {
                                    copyToClipboard(row.username);
                                    showCopiedToClipboardNotification(
                                        e.clientX,
                                        e.clientY);
                                    return false;
                                }
                            }, m("span.copyicon")),
                            " ",
                            row.username,
                        ]),
                        m("td", [
                            m("a[href=]", {
                                onclick: function(e) {
                                    Secret.loadSync(row.id).then(function() {
                                        copyToClipboard(Secret.current.password);
                                    showCopiedToClipboardNotification(
                                        e.clientX,
                                        e.clientY);
                                    });

                                    return false;
                                }
                            },
                            m("span.copyicon")),
                            " *****",
                        ]),
                        m("td", formatDate(row.modified)),
                    ]);
                }))
            ]);
        }

        var search = function() {
            var term = document.getElementById("searchbox").value;

            for (var i = 0; i < Secret.list.length; i++) {
                var row = Secret.list[i];
                row.hidden = false;
                if (row.title.indexOf(term) == -1) {
                    row.hidden = true;
                }
            }
        }

        return [
            m("h2.content-subhead", "Secrets"),

            m("p", m("a[href=/secrets/new]", {oncreate: m.route.link}, "New Secret")),

            m("form.pure-form.pure-form-aligned", {
                onsubmit: function(e) {
                    e.preventDefault();
                    search();
                }}, [
                m("input#searchbox[type=text][placeholder=Search][autofocus]", { oncreate: function(vnode) { setTimeout(function() { vnode.dom.focus(); }, 0); } }),
                " ",
                m("button[type=submit].pure-button", { onclick: search }, "Search")
            ]),
            m("br"),
            table
        ];
    }
}
<|endoftext|>


<commit_before>import { omit } from 'lodash';
import { camelizeKeys } from 'humps';
import fetch from 'isomorphic-fetch';

const BASE_URL = 'http://localhost:8000/api';

function callApi(endpoint, callConfig = {}) {

  // Hook for adding auth token, headers or other config common stuff
  const config = { ...callConfig };

  return fetch(BASE_URL + endpoint, config)
    .then(response =>
      response.json().then(json => ({ response, json: camelizeKeys(json) }))
    ).then(({ json, response }) => {
      if (!response.ok) {
        return Promise.reject(json);
      }

      return json;
    });
}

export const CALL_API = Symbol('Call API');

export default store => next => action => {

  const callAPI = action[CALL_API];

  // Not an api action
  if (typeof callAPI === 'undefined') {
    return next(action);
  }

  const { types, endpoint, config = {} } = callAPI;
  const [ requestType, successType, failureType ] = types;

  const actionWith = data => Object.assign({}, omit(action, [CALL_API]), data);

  next(actionWith({ type: requestType }));

  //return new Promise((resolve, reject) => setTimeout(() => {

  return callApi(endpoint, config).then(
    data => next(actionWith({
      data,
      type: successType
    })),
    error => next(actionWith({
      error, // Not sure of what error is
      type: failureType
    }))
  )//;
  //.then(resolve, reject)

  //}, 2000));
};
<commit_msg>Consistency error object in api middleware<commit_after>import { omit, pick } from 'lodash';
import { camelizeKeys } from 'humps';
import fetch from 'isomorphic-fetch';

const BASE_URL = 'http://localhost:8000/api';

function callApi(endpoint, callConfig = {}) {

  // Hook for adding auth token, headers or other config common stuff
  const config = { ...callConfig };

  // Pick util response props
  const pickResponse = (r) => pick(r, ['status', 'statusText']);

  return fetch(BASE_URL + endpoint, config)
    .catch(message => {
      // Connection error, name resolving problem and other stuff like
      // Godzilla attack datacenters...
      // Simple wrap the message in a plain object for consistency
      return Promise.reject({ message });
    })
    .then(response =>
      response.json().then(
        json => ({ response, json: camelizeKeys(json) }),
        message => {
          // Error while parsing json
          return Promise.reject(({ ...pickResponse(response), message }));
        }
      )
    )
    .then(({ json, response }) => {
      if (!response.ok) {
        // HTTP Error with json ok
        return Promise.reject({ ...pickResponse(response), data: json });
      }

      return json;
    });
}

export const CALL_API = Symbol('Call API');

export default store => next => action => {

  const callAPI = action[CALL_API];

  // Not an api action
  if (typeof callAPI === 'undefined') {
    return next(action);
  }

  const { types, endpoint, config = {} } = callAPI;
  const [ requestType, successType, failureType ] = types;

  const actionWith = data => Object.assign({}, omit(action, [CALL_API]), data);

  next(actionWith({ type: requestType }));

  //return new Promise((resolve, reject) => setTimeout(() => {

  return callApi(endpoint, config).then(
    data => next(actionWith({
      data,
      type: successType
    })),
    error => next(actionWith({
      error, // Object describe error
      type: failureType
    }))
  )//;
  //.then(resolve, reject)

  //}, 2000));
};
<|endoftext|>


<commit_before>import Victor from 'victor'
import Shape, { shapeOptions } from './Shape'

const options = {
  ...shapeOptions,
  ...{
    polygonSides: {
      title: 'Number of sides',
      min: 3
    }
  }
}

export default class Polygon extends Shape {
  constructor() {
    super('Polygon')
  }

  getInitialState() {
    return {
      ...super.getInitialState(),
      ...{
        type: 'polygon',
        polygonSides: 4
      }
    }
  }

  getVertices(state) {
    let points = []
    for (let i=0; i<=state.shape.polygonSides; i++) {
      let angle = Math.PI * 2.0 / state.shape.polygonSides * (0.5 + i)
      points.push(new Victor(Math.cos(angle), Math.sin(angle)))
    }
    return points
  }

  getOptions() {
    return options
  }
}
<commit_msg>Rounded corners on polygons.<commit_after>import Victor from 'victor'
import Shape, { shapeOptions } from './Shape'

const options = {
  ...shapeOptions,
  ...{
    polygonSides: {
      title: 'Number of sides',
      min: 3
    },
    roundCorners: {
      title: 'Round Corners',
      type: 'checkbox',
    },
    roundFraction: {
      title: 'Round Fraction',
      min: 0.0,
      max: 1.0,
      step: 0.05,
      isVisible: (state) => { return state.roundCorners }
    },
  }
}

export default class Polygon extends Shape {
  constructor() {
    super('Polygon')
  }

  getInitialState() {
    return {
      ...super.getInitialState(),
      ...{
        type: 'polygon',
        polygonSides: 4,
        roundCorners: false,
        roundFraction: 0.5,
      }
    }
  }

  // Returns a list of points from (start, end] along the line.
  getLineVertices(startPoint, endPoint, numberOfPoints) {
    const resolution = 1.0/numberOfPoints
    let points = []
    for (let d=resolution; d<=1.0; d+=resolution) {
      points.push(new Victor(startPoint.x + (endPoint.x - startPoint.x)*d,
                             startPoint.y + (endPoint.y - startPoint.y)*d))
    }
    return points
  }

  getVertices(state) {
    // beta is the fraction to have rounded.
    const beta = state.shape.roundFraction
    // alpha is the fration to have straight.
    const alpha = (1.0-beta)
    let points = []
    for (let i=0; i<=state.shape.polygonSides; i++) {
      const angle = Math.PI * 2.0 / state.shape.polygonSides * (0.5 + i)
      if (state.shape.roundCorners && beta !== 0.0) {
        // angles that make up the arc.
        const angleStart = Math.PI * 2.0 / state.shape.polygonSides * i
        const angleEnd = Math.PI * 2.0 / state.shape.polygonSides * (i + 1)
        const angleResolution = 0.10
        if (points.length > 0) {
          // Start with a line. We use a bunch of points for this, so they get stretch about evenly
          // as the curves do.
          const numberOfLinePoints = (angleEnd - angleStart)/angleResolution/beta
          points = points.concat(this.getLineVertices(points[points.length-1],
                                                      new Victor(alpha * Math.cos(angle) + beta * Math.cos(angleStart),
                                                                 alpha * Math.sin(angle) + beta * Math.sin(angleStart)),
                                                      numberOfLinePoints))
        }
        if (i !== state.shape.polygonSides) {
          // Create the arc.
          for (let arcAngle=angleStart + angleResolution; arcAngle<=angleEnd; arcAngle += angleResolution) {
            points.push(new Victor(alpha * Math.cos(angle) + beta * Math.cos(arcAngle), alpha * Math.sin(angle) + beta * Math.sin(arcAngle)))
          }
        }
      } else {
        // Not rounded corners.
        points.push(new Victor(Math.cos(angle), Math.sin(angle)))
      }
    }
    return points
  }

  getOptions() {
    return options
  }
}
<|endoftext|>


<commit_before>/**
 * Actions.
 *
 * Manages a collection of Actions.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

var Things = require('../models/things');
var AdapterManager = require('../adapter-manager');

var Actions = {

  /**
   * A map of action requests.
   */
  actions: {},

  /**
   * A counter to generate action IDs.
   */
  nextId: 0,

  /**
   * Reset actions state.
   */
  clearState() {
    this.nextId = 0;
    for (let id in this.actions) {
      this.remove(id);
    }
  },

  /**
   * Generate an ID for a new action.
   *
   * @returns {integer} An id.
   */
  generateId: function() {
    return ++this.nextId;
  },

  /**
   * Get a particular action.
   *
   * @returns {Object} The specified action, or udefined if the action
   * doesn't exist.
   */
  get: function(id) {
    return this.actions[id];
  },

  /**
   * Get a list of all current actions.
   *
   * @returns {Array} A list of current actions.
   */
  getAll: function() {
    return Object.keys(this.actions).map(id => {
      return this.actions[id];
    });
  },

  /**
   * Add a new action.
   *
   * @param {Action} action An Action object.
   */
  add: function(action) {
    var id = action.id;
    this.actions[id] = action;
    action.status = 'pending';
    switch(action.name) {
      case 'pair':
        AdapterManager.addNewThing().then(function() {
          action.status = 'completed';
        }).catch(function(error) {
          action.status = 'error';
          action.error = error;
          console.error('Thing was not added');
          console.error(error);
        });
        break;
      case 'unpair':
        if (action.parameters.id) {
          AdapterManager.removeThing(action.parameters.id)
            .then(function(thingIdUnpaired) {
              console.log('unpair: thing:', thingIdUnpaired, 'was unpaired');
              Things.handleRemovedThing(thingIdUnpaired);
              action.status = 'completed';
            }).catch(function(error) {
              action.status = 'error';
              action.error = error;
              console.error('unpair of thing:',
                            action.parameters.id, 'failed.');
              console.error(error);
            });
        } else {
          var msg = 'unpair missing "id" parameter.';
          action.status = 'error';
          action.error = msg;
          console.error(msg);
        }
        break;
      default:
        delete this.actions[id];
        throw 'Invalid action name: "' + action.name + '"';
    }
  },

  /**
   * Remove an action from the action list.
   *
   * @param integer id Action ID.
   *
   * If the action has not yet been completed, it is cancelled.
   */
  remove: function(id) {
    var action = this.actions[id];
    if(!action) {
      throw 'Invalid action id: ' + id;
    }

    if (action.status === 'pending') {
      switch(action.name) {
        case 'pair':
          AdapterManager.cancelAddNewThing();
          break;
        case 'unpair':
          AdapterManager.cancelRemoveThing(action.parameters.id);
          break;
        default:
          throw 'Invalid action name: "' + action.name + '"';
      }
    }

    action.status = 'deleted';
    delete this.actions[id];
  }
};

module.exports = Actions;
<commit_msg>Convert Actions to class<commit_after>/**
 * Actions.
 *
 * Manages a collection of Actions.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

var Things = require('../models/things');
var AdapterManager = require('../adapter-manager');
const Constants = require('../constants');
const EventEmitter = require('events');

class Actions extends EventEmitter {

  constructor() {
    super();

    /**
     * A map of action requests.
     */
    this.actions = {};

    /**
     * A counter to generate action IDs.
     */
    this.nextId = 0;

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

  /**
   * Reset actions state.
   */
  clearState() {
    this.nextId = 0;
    for (let id in this.actions) {
      this.remove(id);
    }
  }

  /**
   * Generate an ID for a new action.
   *
   * @returns {integer} An id.
   */
  generateId() {
    return ++this.nextId;
  }

  /**
   * Get a particular action.
   *
   * @returns {Object} The specified action, or udefined if the action
   * doesn't exist.
   */
  get(id) {
    return this.actions[id];
  }

  /**
   * Get a list of all current actions.
   *
   * @returns {Array} A list of current actions.
   */
  getAll() {
    return Object.keys(this.actions).map(id => {
      return this.actions[id];
    });
  }

  /**
   * Add a new action.
   *
   * @param {Action} action An Action object.
   */
  add(action) {
    var id = action.id;
    this.actions[id] = action;
    action.status = 'pending';
    switch(action.name) {
      case 'pair':
        AdapterManager.addNewThing().then(function() {
          action.status = 'completed';
        }).catch(function(error) {
          action.status = 'error';
          action.error = error;
          console.error('Thing was not added');
          console.error(error);
        });
        break;
      case 'unpair':
        if (action.parameters.id) {
          AdapterManager.removeThing(action.parameters.id)
            .then(function(thingIdUnpaired) {
              console.log('unpair: thing:', thingIdUnpaired, 'was unpaired');
              Things.handleRemovedThing(thingIdUnpaired);
              action.status = 'completed';
            }).catch(function(error) {
              action.status = 'error';
              action.error = error;
              console.error('unpair of thing:',
                            action.parameters.id, 'failed.');
              console.error(error);
            });
        } else {
          var msg = 'unpair missing "id" parameter.';
          action.status = 'error';
          action.error = msg;
          console.error(msg);
        }
        break;
      default:
        delete this.actions[id];
        throw 'Invalid action name: "' + action.name + '"';
    }
  }

  /**
   * Forward the actionStatus event
   */
  onActionStatus(action) {
    this.emit(Constants.ACTION_STATUS, action);
  }

  /**
   * Remove an action from the action list.
   *
   * @param integer id Action ID.
   *
   * If the action has not yet been completed, it is cancelled.
   */
  remove(id) {
    var action = this.actions[id];
    if(!action) {
      throw 'Invalid action id: ' + id;
    }

    if (action.status === 'pending') {
      switch(action.name) {
        case 'pair':
          AdapterManager.cancelAddNewThing();
          break;
        case 'unpair':
          AdapterManager.cancelRemoveThing(action.parameters.id);
          break;
        default:
          throw 'Invalid action name: "' + action.name + '"';
      }
    }

    action.status = 'deleted';
    delete this.actions[id];
  }
}

module.exports = new Actions();
<|endoftext|>