<commit_before>(function() {
  var statComponent = Object.create(HTMLElement.prototype);

  var DRAG_SENSITIVITY = -0.6;
  var DRAG_TRIGGER_DISTANCE = 100;

  Object.defineProperty(statComponent, 'player', {
    value: 0,
    writable: true
  });

  Object.defineProperty(statComponent, 'mobile', {
    value: false,
    writable: true
  });

  Object.defineProperty(statComponent, 'cumulative', {
    value: false,
    writable: true
  });

  Object.defineProperty(statComponent, 'value-change', {
    value: '',
    writable: true
  });

  statComponent.createdCallback = function() {
    var DRAW = initDraw(document.body);
    var COMPUTED_FONT_SIZE = window.getComputedStyle(document.body, null).getPropertyValue("font-size");
    var DRAG_CIRCLE_SIZE = Number(COMPUTED_FONT_SIZE.substring(0, COMPUTED_FONT_SIZE.length - 3)) / 2;

    // var PLAYER = this.getAttribute('player');
    // var MOBILE = this.getAttribute('mobile');
    var CUMULATIVE = this.getAttribute('cumulative');
    this.valueChange = window[this.getAttribute('value-change')];
    if (!this.valueChange) this.valueChange = () => {};

    var diffElement = document.createElement('div');
    diffElement.style.cssText = `
      position: absolute;
      font-size: 0.5em;
      color: yellow;
      z-index: 2;
    `;

    this.addEventListener('click', function() {
      var value = Number(this.innerHTML);
      new Audio('mp3/click.mp3').play();
      if (CUMULATIVE) {
        this.innerHTML = value + 1;
        this.valueChange(this, 1);
      } else {
        this.innerHTML = value - 1;
        this.valueChange(this, -1);
      }
    });

    this.addEventListener('mousedown', function(e) {
      if (e.button != 0) return;

      var triggered = false;
      var diff = 0;
      var anchorAngle;
      var startX = this.offsetLeft + this.offsetWidth / 2;
      var startY = this.offsetTop + this.offsetHeight / 2;
      var startValue = Number(this.innerHTML);
      var source = this;

      DRAW.show();
      var line = DRAW.createLine('lightblue', 2);
      DRAW.modifyLine(line, startX, startY, e.pageX, e.pageY);
      var circle = DRAW.createCircle('lightblue', 2, 'lightblue', 0.2);
      var texts = [];
      var textOffsets = [-4, -3, -2, -1, 1, 2, 3, 4];
      for (var textOffset of textOffsets) {
        texts.push(DRAW.createText(textOffset, '6vmin'));
      }

      window.addEventListener('mousemove', handleMouseMove);

      window.addEventListener('mouseup', function() {
        if (e.button != 0) return;
        window.removeEventListener('mousemove', handleMouseMove);
        DRAW.hide();
        if (triggered) {
          document.body.removeChild(diffElement);
          source.valueChange(source, diff);
        }
      }, {'once': true});

      function handleMouseMove(e) {
        var distance = getDistance(startX, startY, e.pageX, e.pageY);
        var currentAngle = getAngle(startX, startY, e.pageX, e.pageY);

        var lineX = e.pageX + DRAG_CIRCLE_SIZE * Math.cos(currentAngle - Math.PI / 2);
        var lineY = e.pageY + DRAG_CIRCLE_SIZE * Math.sin(currentAngle + Math.PI / 2);
        DRAW.modifyLine(line, startX, startY, lineX, lineY);

        if (!triggered) {
          if (distance > DRAG_TRIGGER_DISTANCE) {
            triggered = true;
            anchorAngle = getAngle(startX, startY, e.pageX, e.pageY);
            currentAngle = anchorAngle;
            new Audio('mp3/click.mp3').play();

            document.body.appendChild(diffElement);
            diffElement.innerHTML = diff;
            diffElement.style.color = getColor(-diff, CUMULATIVE);
            diffElement.style.left = (e.pageX - diffElement.offsetWidth / 2) + 'px';
            diffElement.style.top = (e.pageY - diffElement.offsetHeight / 2) + 'px';
          } else return;
        }

        DRAW.modifyCircle(circle, startX, startY, distance + DRAG_CIRCLE_SIZE);
        for (var i = 0; i < textOffsets.length; i++) {
          var offsetAngle = angleWrap(textOffsets[i] * DRAG_SENSITIVITY - Math.PI / 2);
          var angle = -angleWrap(anchorAngle + offsetAngle);
          var x = startX - 20 + 0.9 * distance * Math.cos(angle);
          var y = startY + 10 + 0.9 * distance * Math.sin(angle);
          var textContent = diff - textOffsets[i];
          DRAW.modifyText(texts[i], x, y, getColor(textContent), Math.abs(textContent));
        }

        currentAngle = getAngle(startX, startY, e.pageX, e.pageY);
        var angleDiff = angleWrap(currentAngle - anchorAngle);
        var snap = Math.floor((angleDiff + DRAG_SENSITIVITY / 2) / DRAG_SENSITIVITY);

        diffElement.style.left = (e.pageX - diffElement.offsetWidth / 2) + 'px';
        diffElement.style.top = (e.pageY - diffElement.offsetHeight / 2) + 'px';

        if (snap === 0 || CUMULATIVE && snap < 0) return;

        diff += CUMULATIVE ? snap : -snap;

        diffElement.style.color = getColor(diff, CUMULATIVE);
        diffElement.innerHTML = Math.abs(diff);

        anchorAngle = angleWrap(anchorAngle + snap * DRAG_SENSITIVITY);
        new Audio('mp3/click.mp3').play();
        source.innerHTML = startValue + diff;
      }
    });

  }

  statComponent.attributeChangedCallback = function(attributeName, oldValue, newValue) {
    switch (attributeName) {
      case 'value-change':
        this.valueChange = window[newValue];
        if (!this.valueChange) this.valueChange = () => {};
        break;
    }
  };

  function getAngle(x1, y1, x2, y2) {
    return Math.atan2(x2 - x1, y2 - y1);
  }

  function getDistance(x1, y1, x2, y2) {
    var dx = x2 - x1;
    var dy = y2 - y1;
    return Math.sqrt(dx * dx + dy * dy);
  }

  function angleWrap(angle) {
    if (angle > Math.PI) angle -= 2 * Math.PI;
    if (angle < -Math.PI) angle += 2 * Math.PI;
    return angle;
  }

  function getColor(value, inverted) {
    if (value > 0 || inverted && value < 0) return 'lightgreen';
    else if (value < 0 || inverted && value > 0) return 'red';
    else return 'yellow';
  }

  function initDraw(parent) {
    var SVG_URL = 'http://www.w3.org/2000/svg';
    var SVG = document.createElementNS(SVG_URL, 'svg');
    SVG.style = 'position: absolute; top: 0; left: 0; pointer-events: none;';
    SVG.setAttribute('width', '100%');
    SVG.setAttribute('height', '100%');
    SVG.setAttribute('version', '1.1');

    var DRAW = {};

    DRAW.show = function() {
      if (parent) {
        parent.appendChild(SVG);
      } else {
        console.error("Call setParent on DRAW first.");
      }
    };

    DRAW.hide = function() {
      if (parent) {
        if (SVG.parentNode == parent) {
          parent.removeChild(SVG);
        }
        while (SVG.firstChild) {
          SVG.removeChild(SVG.firstChild);
        }
      }
    };

    DRAW.createLine = function(color, width) {
      var line = document.createElementNS(SVG_URL, 'line');
      line.setAttribute('stroke', color);
      line.setAttribute('stroke-width', width);
      SVG.appendChild(line);
      return line;
    };

    DRAW.modifyLine = function(line, x1, y1, x2, y2) {
      line.setAttribute('x1', x1);
      line.setAttribute('y1', y1);
      line.setAttribute('x2', x2);
      line.setAttribute('y2', y2);
    };

    DRAW.createCircle = function(color, width, fill, fillOpacity) {
      var circle = document.createElementNS(SVG_URL, 'circle');
      circle.setAttribute('stroke', color);
      circle.setAttribute('stroke-width', width);
      circle.setAttribute('fill', fill);
      circle.setAttribute('fill-opacity', fillOpacity);
      SVG.appendChild(circle);
      return circle;
    };

    DRAW.modifyCircle = function(circle, cx, cy, r) {
      circle.setAttribute('cx', cx);
      circle.setAttribute('cy', cy);
      circle.setAttribute('r', r);
    };

    DRAW.createText = function(content, fontSize) {
      var text = document.createElementNS(SVG_URL, 'text');
      text.setAttribute('font-size', fontSize);
      text.textContent = content;
      SVG.appendChild(text);
      return text;
    };

    DRAW.modifyText = function(text, x, y, fill, content) {
      text.setAttribute('x', x);
      text.setAttribute('y', y);
      text.setAttribute('fill', fill);
      if (content !== null) {
        text.textContent = content;
      }
    };

    return DRAW;
  }

  document.registerElement('stat-component', {prototype: statComponent});
}());
<commit_msg>Stat component uses draw from inside itself.<commit_after>(function() {
  var statComponent = Object.create(HTMLElement.prototype);

  var DRAG_SENSITIVITY = -0.6;
  var DRAG_TRIGGER_DISTANCE = 200;

  Object.defineProperty(statComponent, 'player', {
    value: 0,
    writable: true
  });

  Object.defineProperty(statComponent, 'mobile', {
    value: false,
    writable: true
  });

  Object.defineProperty(statComponent, 'cumulative', {
    value: false,
    writable: true
  });

  Object.defineProperty(statComponent, 'value-change', {
    value: '',
    writable: true
  });

  statComponent.createdCallback = function() {
    var DRAW = initDraw(document.body);
    var COMPUTED_FONT_SIZE = window.getComputedStyle(document.body, null).getPropertyValue("font-size");
    var DRAG_CIRCLE_SIZE = Number(COMPUTED_FONT_SIZE.substring(0, COMPUTED_FONT_SIZE.length - 3)) / 2;

    // var PLAYER = this.getAttribute('player');
    // var MOBILE = this.getAttribute('mobile');
    var CUMULATIVE = this.getAttribute('cumulative');
    this.valueChange = window[this.getAttribute('value-change')];
    if (!this.valueChange) this.valueChange = () => {};

    var diffElement = document.createElement('div');
    diffElement.style.cssText = `
      position: absolute;
      font-size: 0.5em;
      color: yellow;
      z-index: 2;
    `;

    this.addEventListener('click', function() {
      var value = Number(this.innerHTML);
      new Audio('mp3/click.mp3').play();
      if (CUMULATIVE) {
        this.innerHTML = value + 1;
        this.valueChange(this, 1);
      } else {
        this.innerHTML = value - 1;
        this.valueChange(this, -1);
      }
    });

    this.addEventListener('mousedown', function(e) {
      if (e.button != 0) return;

      var triggered = false;
      var diff = 0;
      var anchorAngle;
      var startX = this.offsetLeft + this.offsetWidth / 2;
      var startY = this.offsetTop + this.offsetHeight / 2;
      var startValue = Number(this.innerHTML);
      var source = this;

      DRAW.show();
      var line = DRAW.createLine('lightblue', 2);
      DRAW.modifyLine(line, startX, startY, e.pageX, e.pageY);
      var circle = DRAW.createCircle('lightblue', 2, 'lightblue', 0.2);
      var triggerCircle = DRAW.createCircle('darkgrey', 2, '', 0);
      DRAW.modifyCircle(triggerCircle, startX, startY, DRAG_TRIGGER_DISTANCE);
      var texts = [];
      var textOffsets = [-4, -3, -2, -1, 1, 2, 3, 4];
      for (var textOffset of textOffsets) {
        texts.push(DRAW.createText(textOffset, '6vmin'));
      }

      window.addEventListener('mousemove', handleMouseMove);

      window.addEventListener('mouseup', function() {
        if (e.button != 0) return;
        window.removeEventListener('mousemove', handleMouseMove);
        DRAW.hide();
        if (triggered) {
          document.body.removeChild(diffElement);
          source.valueChange(source, diff);
        }
      }, {'once': true});

      function handleMouseMove(e) {
        var distance = getDistance(startX, startY, e.pageX, e.pageY);
        var currentAngle = getAngle(startX, startY, e.pageX, e.pageY);

        if (!triggered) {
          DRAW.modifyLine(line, startX, startY, e.pageX, e.pageY);
          if (distance > DRAG_TRIGGER_DISTANCE) {
            triggered = true;
            anchorAngle = getAngle(startX, startY, e.pageX, e.pageY);
            currentAngle = anchorAngle;
            new Audio('mp3/click.mp3').play();
            triggerCircle.setAttribute('visibility', 'hidden');

            document.body.appendChild(diffElement);
            diffElement.innerHTML = diff;
            diffElement.style.color = getColor(-diff, CUMULATIVE);
            diffElement.style.left = (e.pageX - diffElement.offsetWidth / 2) + 'px';
            diffElement.style.top = (e.pageY - diffElement.offsetHeight / 2) + 'px';
          } else return;
        }
        var lineX = e.pageX + DRAG_CIRCLE_SIZE * Math.cos(currentAngle - Math.PI / 2);
        var lineY = e.pageY + DRAG_CIRCLE_SIZE * Math.sin(currentAngle + Math.PI / 2);
        DRAW.modifyLine(line, startX, startY, lineX, lineY);

        DRAW.modifyCircle(circle, startX, startY, distance + DRAG_CIRCLE_SIZE);
        for (var i = 0; i < textOffsets.length; i++) {
          var offsetAngle = angleWrap(textOffsets[i] * DRAG_SENSITIVITY - Math.PI / 2);
          var angle = -angleWrap(anchorAngle + offsetAngle);
          var x = startX - 20 + 0.9 * distance * Math.cos(angle);
          var y = startY + 10 + 0.9 * distance * Math.sin(angle);
          var textContent = diff - textOffsets[i];
          DRAW.modifyText(texts[i], x, y, getColor(textContent), Math.abs(textContent));
        }

        currentAngle = getAngle(startX, startY, e.pageX, e.pageY);
        var angleDiff = angleWrap(currentAngle - anchorAngle);
        var snap = Math.floor((angleDiff + DRAG_SENSITIVITY / 2) / DRAG_SENSITIVITY);

        diffElement.style.left = (e.pageX - diffElement.offsetWidth / 2) + 'px';
        diffElement.style.top = (e.pageY - diffElement.offsetHeight / 2) + 'px';

        if (snap === 0 || CUMULATIVE && snap < 0) return;

        diff += CUMULATIVE ? snap : -snap;

        diffElement.style.color = getColor(diff, CUMULATIVE);
        diffElement.innerHTML = Math.abs(diff);

        anchorAngle = angleWrap(anchorAngle + snap * DRAG_SENSITIVITY);
        new Audio('mp3/click.mp3').play();
        source.innerHTML = startValue + diff;
      }
    });

  }

  statComponent.attributeChangedCallback = function(attributeName, oldValue, newValue) {
    switch (attributeName) {
      case 'value-change':
        this.valueChange = window[newValue];
        if (!this.valueChange) this.valueChange = () => {};
        break;
    }
  };

  function getAngle(x1, y1, x2, y2) {
    return Math.atan2(x2 - x1, y2 - y1);
  }

  function getDistance(x1, y1, x2, y2) {
    var dx = x2 - x1;
    var dy = y2 - y1;
    return Math.sqrt(dx * dx + dy * dy);
  }

  function angleWrap(angle) {
    if (angle > Math.PI) angle -= 2 * Math.PI;
    if (angle < -Math.PI) angle += 2 * Math.PI;
    return angle;
  }

  function getColor(value, inverted) {
    if (value > 0 || inverted && value < 0) return 'lightgreen';
    else if (value < 0 || inverted && value > 0) return 'red';
    else return 'yellow';
  }

  function initDraw(parent) {
    var SVG_URL = 'http://www.w3.org/2000/svg';
    var SVG = document.createElementNS(SVG_URL, 'svg');
    SVG.style = 'position: absolute; top: 0; left: 0; pointer-events: none;';
    SVG.setAttribute('width', '100%');
    SVG.setAttribute('height', '100%');
    SVG.setAttribute('version', '1.1');

    var DRAW = {};

    DRAW.show = function() {
      if (parent) {
        parent.appendChild(SVG);
      } else {
        console.error("Call setParent on DRAW first.");
      }
    };

    DRAW.hide = function() {
      if (parent) {
        if (SVG.parentNode == parent) {
          parent.removeChild(SVG);
        }
        while (SVG.firstChild) {
          SVG.removeChild(SVG.firstChild);
        }
      }
    };

    DRAW.createLine = function(color, width) {
      var line = document.createElementNS(SVG_URL, 'line');
      line.setAttribute('stroke', color);
      line.setAttribute('stroke-width', width);
      line.setAttribute('visibility', 'hidden');
      SVG.appendChild(line);
      return line;
    };

    DRAW.modifyLine = function(line, x1, y1, x2, y2) {
      line.setAttribute('x1', x1);
      line.setAttribute('y1', y1);
      line.setAttribute('x2', x2);
      line.setAttribute('y2', y2);
      line.setAttribute('visibility', 'visible');
    };

    DRAW.createCircle = function(color, width, fill, fillOpacity) {
      var circle = document.createElementNS(SVG_URL, 'circle');
      circle.setAttribute('stroke', color);
      circle.setAttribute('stroke-width', width);
      circle.setAttribute('fill', fill);
      circle.setAttribute('fill-opacity', fillOpacity);
      circle.setAttribute('visibility', 'hidden');
      SVG.appendChild(circle);
      return circle;
    };

    DRAW.modifyCircle = function(circle, cx, cy, r) {
      circle.setAttribute('cx', cx);
      circle.setAttribute('cy', cy);
      circle.setAttribute('r', r);
      circle.setAttribute('visibility', 'visible');
    };

    DRAW.createText = function(content, fontSize) {
      var text = document.createElementNS(SVG_URL, 'text');
      text.setAttribute('font-size', fontSize);
      text.textContent = content;
      text.setAttribute('visibility', 'hidden');
      SVG.appendChild(text);
      return text;
    };

    DRAW.modifyText = function(text, x, y, fill, content) {
      text.setAttribute('x', x);
      text.setAttribute('y', y);
      text.setAttribute('fill', fill);
      text.setAttribute('visibility', 'visible');
      if (content !== null) {
        text.textContent = content;
      }
    };

    return DRAW;
  }

  document.registerElement('stat-component', {prototype: statComponent});
}());
<|endoftext|>


<commit_before>(function(window, document, ionic) {

  function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
    results = regex.exec(location.search);
    return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
  }

  var IOS = 'ios';
  var ANDROID = 'android';
  var WINDOWS_PHONE = 'windowsphone';
  var requestAnimationFrame = ionic.requestAnimationFrame;

  /**
   * @ngdoc utility
   * @name ionic.Platform
   * @module ionic
   * @description
   * A set of utility methods that can be used to retrieve the device ready state and
   * various other information such as what kind of platform the app is currently installed on.
   *
   * @usage
   * ```js
   * angular.module('PlatformApp', ['ionic'])
   * .controller('PlatformCtrl', function($scope) {
   *
   *   ionic.Platform.ready(function(){
   *     // will execute when device is ready, or immediately if the device is already ready.
   *   });
   *
   *   var deviceInformation = ionic.Platform.device();
   *
   *   var isWebView = ionic.Platform.isWebView();
   *   var isIPad = ionic.Platform.isIPad();
   *   var isIOS = ionic.Platform.isIOS();
   *   var isAndroid = ionic.Platform.isAndroid();
   *   var isWindowsPhone = ionic.Platform.isWindowsPhone();
   *
   *   var currentPlatform = ionic.Platform.platform();
   *   var currentPlatformVersion = ionic.Platform.version();
   *
   *   ionic.Platform.exit(); // stops the app
   * });
   * ```
   */
  var self = ionic.Platform = {

    // Put navigator on platform so it can be mocked and set
    // the browser does not allow window.navigator to be set
    navigator: window.navigator,

    /**
     * @ngdoc property
     * @name ionic.Platform#isReady
     * @returns {boolean} Whether the device is ready.
     */
    isReady: false,
    /**
     * @ngdoc property
     * @name ionic.Platform#isFullScreen
     * @returns {boolean} Whether the device is fullscreen.
     */
    isFullScreen: false,
    /**
     * @ngdoc property
     * @name ionic.Platform#platforms
     * @returns {Array(string)} An array of all platforms found.
     */
    platforms: null,
    /**
     * @ngdoc property
     * @name ionic.Platform#grade
     * @returns {string} What grade the current platform is.
     */
    grade: null,
    ua: navigator.userAgent,

    /**
     * @ngdoc method
     * @name ionic.Platform#ready
     * @description
     * Trigger a callback once the device is ready, or immediately
     * if the device is already ready. This method can be run from
     * anywhere and does not need to be wrapped by any additonal methods.
     * When the app is within a WebView (Cordova), it'll fire
     * the callback once the device is ready. If the app is within
     * a web browser, it'll fire the callback after `window.load`.
     * Please remember that Cordova features (Camera, FileSystem, etc) still
     * will not work in a web browser.
     * @param {function} callback The function to call.
     */
    ready: function(cb) {
      // run through tasks to complete now that the device is ready
      if (self.isReady) {
        cb();
      } else {
        // the platform isn't ready yet, add it to this array
        // which will be called once the platform is ready
        readyCallbacks.push(cb);
      }
    },

    /**
     * @private
     */
    detect: function() {
      self._checkPlatforms();

      requestAnimationFrame(function() {
        // only add to the body class if we got platform info
        for (var i = 0; i < self.platforms.length; i++) {
          document.body.classList.add('platform-' + self.platforms[i]);
        }
      });
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#setGrade
     * @description Set the grade of the device: 'a', 'b', or 'c'. 'a' is the best
     * (most css features enabled), 'c' is the worst.  By default, sets the grade
     * depending on the current device.
     * @param {string} grade The new grade to set.
     */
    setGrade: function(grade) {
      var oldGrade = self.grade;
      self.grade = grade;
      requestAnimationFrame(function() {
        if (oldGrade) {
          document.body.classList.remove('grade-' + oldGrade);
        }
        document.body.classList.add('grade-' + grade);
      });
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#device
     * @description Return the current device (given by cordova).
     * @returns {object} The device object.
     */
    device: function() {
      return window.device || {};
    },

    _checkPlatforms: function() {
      self.platforms = [];
      var grade = 'a';

      if (this.isWebView()) {
        self.platforms.push('webview');
        if(!(!window.cordova && !window.PhoneGap && !window.phonegap)) {
          self.platforms.push('cordova');
        } else if(!!window.forge) {
          self.platforms.push('trigger');
        }
      } else {
        self.platforms.push('browser');
      }
      if (self.isIPad()) self.platforms.push('ipad');

      var platform = self.platform();
      if (platform) {
        self.platforms.push(platform);

        var version = self.version();
        if (version) {
          var v = version.toString();
          if (v.indexOf('.') > 0) {
            v = v.replace('.', '_');
          } else {
            v += '_0';
          }
          self.platforms.push(platform + v.split('_')[0]);
          self.platforms.push(platform + v);

          if (self.isAndroid() && version < 4.4) {
            grade = (version < 4 ? 'c' : 'b');
          } else if (self.isWindowsPhone()) {
            grade = 'b';
          }
        }
      }

      self.setGrade(grade);
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#isWebView
     * @returns {boolean} Check if we are running within a WebView (such as Cordova).
     */
    isWebView: function() {
      return !(!window.cordova && !window.PhoneGap && !window.phonegap && !window.forge);
    },
    /**
     * @ngdoc method
     * @name ionic.Platform#isIPad
     * @returns {boolean} Whether we are running on iPad.
     */
    isIPad: function() {
      if (/iPad/i.test(self.navigator.platform)) {
        return true;
      }
      return /iPad/i.test(self.ua);
    },
    /**
     * @ngdoc method
     * @name ionic.Platform#isIOS
     * @returns {boolean} Whether we are running on iOS.
     */
    isIOS: function() {
      return self.is(IOS);
    },
    /**
     * @ngdoc method
     * @name ionic.Platform#isAndroid
     * @returns {boolean} Whether we are running on Android.
     */
    isAndroid: function() {
      return self.is(ANDROID);
    },
    /**
     * @ngdoc method
     * @name ionic.Platform#isWindowsPhone
     * @returns {boolean} Whether we are running on Windows Phone.
     */
    isWindowsPhone: function() {
      return self.is(WINDOWS_PHONE);
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#platform
     * @returns {string} The name of the current platform.
     */
    platform: function() {
      // singleton to get the platform name
      if (platformName === null) self.setPlatform(self.device().platform);
      return platformName;
    },

    /**
     * @private
     */
    setPlatform: function(n) {
      if (typeof n != 'undefined' && n !== null && n.length) {
        platformName = n.toLowerCase();
      } else if (getParameterByName('ionicplatform')) {
        platformName = getParameterByName('ionicplatform');
      } else if (self.ua.indexOf('Android') > 0) {
        platformName = ANDROID;
      } else if (/iPhone|iPad|iPod/.test(self.ua)) {
        platformName = IOS;
      } else if (self.ua.indexOf('Windows Phone') > -1) {
        platformName = WINDOWS_PHONE;
      } else {
        platformName = self.navigator.platform && navigator.platform.toLowerCase().split(' ')[0] || '';
      }
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#version
     * @returns {number} The version of the current device platform.
     */
    version: function() {
      // singleton to get the platform version
      if (platformVersion === null) self.setVersion(self.device().version);
      return platformVersion;
    },

    /**
     * @private
     */
    setVersion: function(v) {
      if (typeof v != 'undefined' && v !== null) {
        v = v.split('.');
        v = parseFloat(v[0] + '.' + (v.length > 1 ? v[1] : 0));
        if (!isNaN(v)) {
          platformVersion = v;
          return;
        }
      }

      platformVersion = 0;

      // fallback to user-agent checking
      var pName = self.platform();
      var versionMatch = {
        'android': /Android (\d+).(\d+)?/,
        'ios': /OS (\d+)_(\d+)?/,
        'windowsphone': /Windows Phone (\d+).(\d+)?/
      };
      if (versionMatch[pName]) {
        v = self.ua.match(versionMatch[pName]);
        if (v &&  v.length > 2) {
          platformVersion = parseFloat(v[1] + '.' + v[2]);
        }
      }
    },

    // Check if the platform is the one detected by cordova
    is: function(type) {
      type = type.toLowerCase();
      // check if it has an array of platforms
      if (self.platforms) {
        for (var x = 0; x < self.platforms.length; x++) {
          if (self.platforms[x] === type) return true;
        }
      }
      // exact match
      var pName = self.platform();
      if (pName) {
        return pName === type.toLowerCase();
      }

      // A quick hack for to check userAgent
      return self.ua.toLowerCase().indexOf(type) >= 0;
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#exitApp
     * @description Exit the app.
     */
    exitApp: function() {
      self.ready(function() {
        navigator.app && navigator.app.exitApp && navigator.app.exitApp();
      });
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#showStatusBar
     * @description Shows or hides the device status bar (in Cordova). Requires `cordova plugin add org.apache.cordova.statusbar`
     * @param {boolean} shouldShow Whether or not to show the status bar.
     */
    showStatusBar: function(val) {
      // Only useful when run within cordova
      self._showStatusBar = val;
      self.ready(function() {
        // run this only when or if the platform (cordova) is ready
        requestAnimationFrame(function() {
          if (self._showStatusBar) {
            // they do not want it to be full screen
            window.StatusBar && window.StatusBar.show();
            document.body.classList.remove('status-bar-hide');
          } else {
            // it should be full screen
            window.StatusBar && window.StatusBar.hide();
            document.body.classList.add('status-bar-hide');
          }
        });
      });
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#fullScreen
     * @description
     * Sets whether the app is fullscreen or not (in Cordova).
     * @param {boolean=} showFullScreen Whether or not to set the app to fullscreen. Defaults to true. Requires `cordova plugin add org.apache.cordova.statusbar`
     * @param {boolean=} showStatusBar Whether or not to show the device's status bar. Defaults to false.
     */
    fullScreen: function(showFullScreen, showStatusBar) {
      // showFullScreen: default is true if no param provided
      self.isFullScreen = (showFullScreen !== false);

      // add/remove the fullscreen classname to the body
      ionic.DomUtil.ready(function() {
        // run this only when or if the DOM is ready
        requestAnimationFrame(function() {
          if (self.isFullScreen) {
            document.body.classList.add('fullscreen');
          } else {
            document.body.classList.remove('fullscreen');
          }
        });
        // showStatusBar: default is false if no param provided
        self.showStatusBar((showStatusBar === true));
      });
    }

  };

  var platformName = null, // just the name, like iOS or Android
  platformVersion = null, // a float of the major and minor, like 7.1
  readyCallbacks = [],
  windowLoadListenderAttached;

  // setup listeners to know when the device is ready to go
  function onWindowLoad() {
    if (self.isWebView()) {
      // the window and scripts are fully loaded, and a cordova/phonegap
      // object exists then let's listen for the deviceready
      document.addEventListener("deviceready", onPlatformReady, false);
    } else {
      // the window and scripts are fully loaded, but the window object doesn't have the
      // cordova/phonegap object, so its just a browser, not a webview wrapped w/ cordova
      onPlatformReady();
    }
    if (windowLoadListenderAttached) {
      window.removeEventListener("load", onWindowLoad, false);
    }
  }
  if (document.readyState === 'complete') {
    onWindowLoad();
  } else {
    windowLoadListenderAttached = true;
    window.addEventListener("load", onWindowLoad, false);
  }

  function onPlatformReady() {
    // the device is all set to go, init our own stuff then fire off our event
    self.isReady = true;
    self.detect();
    for (var x = 0; x < readyCallbacks.length; x++) {
      // fire off all the callbacks that were added before the platform was ready
      readyCallbacks[x]();
    }
    readyCallbacks = [];
    ionic.trigger('platformready', { target: document });

    requestAnimationFrame(function() {
      document.body.classList.add('platform-ready');
    });
  }

})(this, document, ionic);
<commit_msg>correction to trigger.io merge<commit_after>(function(window, document, ionic) {

  function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
    results = regex.exec(location.search);
    return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
  }

  var IOS = 'ios';
  var ANDROID = 'android';
  var WINDOWS_PHONE = 'windowsphone';
  var requestAnimationFrame = ionic.requestAnimationFrame;

  /**
   * @ngdoc utility
   * @name ionic.Platform
   * @module ionic
   * @description
   * A set of utility methods that can be used to retrieve the device ready state and
   * various other information such as what kind of platform the app is currently installed on.
   *
   * @usage
   * ```js
   * angular.module('PlatformApp', ['ionic'])
   * .controller('PlatformCtrl', function($scope) {
   *
   *   ionic.Platform.ready(function(){
   *     // will execute when device is ready, or immediately if the device is already ready.
   *   });
   *
   *   var deviceInformation = ionic.Platform.device();
   *
   *   var isWebView = ionic.Platform.isWebView();
   *   var isIPad = ionic.Platform.isIPad();
   *   var isIOS = ionic.Platform.isIOS();
   *   var isAndroid = ionic.Platform.isAndroid();
   *   var isWindowsPhone = ionic.Platform.isWindowsPhone();
   *
   *   var currentPlatform = ionic.Platform.platform();
   *   var currentPlatformVersion = ionic.Platform.version();
   *
   *   ionic.Platform.exit(); // stops the app
   * });
   * ```
   */
  var self = ionic.Platform = {

    // Put navigator on platform so it can be mocked and set
    // the browser does not allow window.navigator to be set
    navigator: window.navigator,

    /**
     * @ngdoc property
     * @name ionic.Platform#isReady
     * @returns {boolean} Whether the device is ready.
     */
    isReady: false,
    /**
     * @ngdoc property
     * @name ionic.Platform#isFullScreen
     * @returns {boolean} Whether the device is fullscreen.
     */
    isFullScreen: false,
    /**
     * @ngdoc property
     * @name ionic.Platform#platforms
     * @returns {Array(string)} An array of all platforms found.
     */
    platforms: null,
    /**
     * @ngdoc property
     * @name ionic.Platform#grade
     * @returns {string} What grade the current platform is.
     */
    grade: null,
    ua: navigator.userAgent,

    /**
     * @ngdoc method
     * @name ionic.Platform#ready
     * @description
     * Trigger a callback once the device is ready, or immediately
     * if the device is already ready. This method can be run from
     * anywhere and does not need to be wrapped by any additonal methods.
     * When the app is within a WebView (Cordova), it'll fire
     * the callback once the device is ready. If the app is within
     * a web browser, it'll fire the callback after `window.load`.
     * Please remember that Cordova features (Camera, FileSystem, etc) still
     * will not work in a web browser.
     * @param {function} callback The function to call.
     */
    ready: function(cb) {
      // run through tasks to complete now that the device is ready
      if (self.isReady) {
        cb();
      } else {
        // the platform isn't ready yet, add it to this array
        // which will be called once the platform is ready
        readyCallbacks.push(cb);
      }
    },

    /**
     * @private
     */
    detect: function() {
      self._checkPlatforms();

      requestAnimationFrame(function() {
        // only add to the body class if we got platform info
        for (var i = 0; i < self.platforms.length; i++) {
          document.body.classList.add('platform-' + self.platforms[i]);
        }
      });
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#setGrade
     * @description Set the grade of the device: 'a', 'b', or 'c'. 'a' is the best
     * (most css features enabled), 'c' is the worst.  By default, sets the grade
     * depending on the current device.
     * @param {string} grade The new grade to set.
     */
    setGrade: function(grade) {
      var oldGrade = self.grade;
      self.grade = grade;
      requestAnimationFrame(function() {
        if (oldGrade) {
          document.body.classList.remove('grade-' + oldGrade);
        }
        document.body.classList.add('grade-' + grade);
      });
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#device
     * @description Return the current device (given by cordova).
     * @returns {object} The device object.
     */
    device: function() {
      return window.device || {};
    },

    _checkPlatforms: function() {
      self.platforms = [];
      var grade = 'a';

      if (self.isWebView()) {
        self.platforms.push('webview');
        if(!(!window.cordova && !window.PhoneGap && !window.phonegap)) {
          self.platforms.push('cordova');
        } else if(!!window.forge) {
          self.platforms.push('trigger');
        }
      } else {
        self.platforms.push('browser');
      }
      if (self.isIPad()) self.platforms.push('ipad');

      var platform = self.platform();
      if (platform) {
        self.platforms.push(platform);

        var version = self.version();
        if (version) {
          var v = version.toString();
          if (v.indexOf('.') > 0) {
            v = v.replace('.', '_');
          } else {
            v += '_0';
          }
          self.platforms.push(platform + v.split('_')[0]);
          self.platforms.push(platform + v);

          if (self.isAndroid() && version < 4.4) {
            grade = (version < 4 ? 'c' : 'b');
          } else if (self.isWindowsPhone()) {
            grade = 'b';
          }
        }
      }

      self.setGrade(grade);
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#isWebView
     * @returns {boolean} Check if we are running within a WebView (such as Cordova).
     */
    isWebView: function() {
      return !(!window.cordova && !window.PhoneGap && !window.phonegap && !window.forge);
    },
    /**
     * @ngdoc method
     * @name ionic.Platform#isIPad
     * @returns {boolean} Whether we are running on iPad.
     */
    isIPad: function() {
      if (/iPad/i.test(self.navigator.platform)) {
        return true;
      }
      return /iPad/i.test(self.ua);
    },
    /**
     * @ngdoc method
     * @name ionic.Platform#isIOS
     * @returns {boolean} Whether we are running on iOS.
     */
    isIOS: function() {
      return self.is(IOS);
    },
    /**
     * @ngdoc method
     * @name ionic.Platform#isAndroid
     * @returns {boolean} Whether we are running on Android.
     */
    isAndroid: function() {
      return self.is(ANDROID);
    },
    /**
     * @ngdoc method
     * @name ionic.Platform#isWindowsPhone
     * @returns {boolean} Whether we are running on Windows Phone.
     */
    isWindowsPhone: function() {
      return self.is(WINDOWS_PHONE);
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#platform
     * @returns {string} The name of the current platform.
     */
    platform: function() {
      // singleton to get the platform name
      if (platformName === null) self.setPlatform(self.device().platform);
      return platformName;
    },

    /**
     * @private
     */
    setPlatform: function(n) {
      if (typeof n != 'undefined' && n !== null && n.length) {
        platformName = n.toLowerCase();
      } else if (getParameterByName('ionicplatform')) {
        platformName = getParameterByName('ionicplatform');
      } else if (self.ua.indexOf('Android') > 0) {
        platformName = ANDROID;
      } else if (/iPhone|iPad|iPod/.test(self.ua)) {
        platformName = IOS;
      } else if (self.ua.indexOf('Windows Phone') > -1) {
        platformName = WINDOWS_PHONE;
      } else {
        platformName = self.navigator.platform && navigator.platform.toLowerCase().split(' ')[0] || '';
      }
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#version
     * @returns {number} The version of the current device platform.
     */
    version: function() {
      // singleton to get the platform version
      if (platformVersion === null) self.setVersion(self.device().version);
      return platformVersion;
    },

    /**
     * @private
     */
    setVersion: function(v) {
      if (typeof v != 'undefined' && v !== null) {
        v = v.split('.');
        v = parseFloat(v[0] + '.' + (v.length > 1 ? v[1] : 0));
        if (!isNaN(v)) {
          platformVersion = v;
          return;
        }
      }

      platformVersion = 0;

      // fallback to user-agent checking
      var pName = self.platform();
      var versionMatch = {
        'android': /Android (\d+).(\d+)?/,
        'ios': /OS (\d+)_(\d+)?/,
        'windowsphone': /Windows Phone (\d+).(\d+)?/
      };
      if (versionMatch[pName]) {
        v = self.ua.match(versionMatch[pName]);
        if (v &&  v.length > 2) {
          platformVersion = parseFloat(v[1] + '.' + v[2]);
        }
      }
    },

    // Check if the platform is the one detected by cordova
    is: function(type) {
      type = type.toLowerCase();
      // check if it has an array of platforms
      if (self.platforms) {
        for (var x = 0; x < self.platforms.length; x++) {
          if (self.platforms[x] === type) return true;
        }
      }
      // exact match
      var pName = self.platform();
      if (pName) {
        return pName === type.toLowerCase();
      }

      // A quick hack for to check userAgent
      return self.ua.toLowerCase().indexOf(type) >= 0;
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#exitApp
     * @description Exit the app.
     */
    exitApp: function() {
      self.ready(function() {
        navigator.app && navigator.app.exitApp && navigator.app.exitApp();
      });
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#showStatusBar
     * @description Shows or hides the device status bar (in Cordova). Requires `cordova plugin add org.apache.cordova.statusbar`
     * @param {boolean} shouldShow Whether or not to show the status bar.
     */
    showStatusBar: function(val) {
      // Only useful when run within cordova
      self._showStatusBar = val;
      self.ready(function() {
        // run this only when or if the platform (cordova) is ready
        requestAnimationFrame(function() {
          if (self._showStatusBar) {
            // they do not want it to be full screen
            window.StatusBar && window.StatusBar.show();
            document.body.classList.remove('status-bar-hide');
          } else {
            // it should be full screen
            window.StatusBar && window.StatusBar.hide();
            document.body.classList.add('status-bar-hide');
          }
        });
      });
    },

    /**
     * @ngdoc method
     * @name ionic.Platform#fullScreen
     * @description
     * Sets whether the app is fullscreen or not (in Cordova).
     * @param {boolean=} showFullScreen Whether or not to set the app to fullscreen. Defaults to true. Requires `cordova plugin add org.apache.cordova.statusbar`
     * @param {boolean=} showStatusBar Whether or not to show the device's status bar. Defaults to false.
     */
    fullScreen: function(showFullScreen, showStatusBar) {
      // showFullScreen: default is true if no param provided
      self.isFullScreen = (showFullScreen !== false);

      // add/remove the fullscreen classname to the body
      ionic.DomUtil.ready(function() {
        // run this only when or if the DOM is ready
        requestAnimationFrame(function() {
          if (self.isFullScreen) {
            document.body.classList.add('fullscreen');
          } else {
            document.body.classList.remove('fullscreen');
          }
        });
        // showStatusBar: default is false if no param provided
        self.showStatusBar((showStatusBar === true));
      });
    }

  };

  var platformName = null, // just the name, like iOS or Android
  platformVersion = null, // a float of the major and minor, like 7.1
  readyCallbacks = [],
  windowLoadListenderAttached;

  // setup listeners to know when the device is ready to go
  function onWindowLoad() {
    if (self.isWebView()) {
      // the window and scripts are fully loaded, and a cordova/phonegap
      // object exists then let's listen for the deviceready
      document.addEventListener("deviceready", onPlatformReady, false);
    } else {
      // the window and scripts are fully loaded, but the window object doesn't have the
      // cordova/phonegap object, so its just a browser, not a webview wrapped w/ cordova
      onPlatformReady();
    }
    if (windowLoadListenderAttached) {
      window.removeEventListener("load", onWindowLoad, false);
    }
  }
  if (document.readyState === 'complete') {
    onWindowLoad();
  } else {
    windowLoadListenderAttached = true;
    window.addEventListener("load", onWindowLoad, false);
  }

  function onPlatformReady() {
    // the device is all set to go, init our own stuff then fire off our event
    self.isReady = true;
    self.detect();
    for (var x = 0; x < readyCallbacks.length; x++) {
      // fire off all the callbacks that were added before the platform was ready
      readyCallbacks[x]();
    }
    readyCallbacks = [];
    ionic.trigger('platformready', { target: document });

    requestAnimationFrame(function() {
      document.body.classList.add('platform-ready');
    });
  }

})(this, document, ionic);
<|endoftext|>


<commit_before>(function(window) {
    function ParticleView(modelObject){
        this.model = modelObject;
		var dp = this.model.draw_properties;
		var cs = window.cell_size;
        this.graphics
			.beginRadialGradientFill(dp.colors, dp.ratios,
				dp.inner_center.x * cs, dp.inner_center.y * cs , dp.inner_radius * cs,
				dp.outer_center.x * cs, dp.outer_center.y * cs, dp.outer_radius * cs)
			.drawCircle(0, 0, cs * dp.outer_radius);
    }

    ParticleView.prototype = new createjs.Shape();

    ParticleView.prototype.update = function(){
        this.x = cell_size * this.model.position.x;
        this.y = cell_size * this.model.position.y;
    }

    ParticleView.prototype.animate = function(){
        if(this.model.target){
            var own_view = this;
            this.update();
            this.update = function(){};
            createjs.Tween.get(this).to(
                { x: cell_size * this.model.target.x, y: cell_size * this.model.target.y },
                // We do not want to have magnetically curved particles for now
                // {guide:{ path:[0,0, 0,200,200,200, 200,0,0,0] }},
                this.model.target.time - this.model.start_time
            ).call(function(){
                own_view.update = ParticleView.prototype.update;
                own_view.model.position.x = own_view.model.target.x;
                own_view.model.position.y = own_view.model.target.y;
            });
        }
    }

    window.ParticleView = ParticleView;
}(window));
<commit_msg>correct offset for particles<commit_after>(function(window) {
    function ParticleView(modelObject){
        this.model = modelObject;
		var dp = this.model.draw_properties;
		var cs = window.cell_size;
        this.graphics
			.beginRadialGradientFill(dp.colors, dp.ratios,
				dp.inner_center.x * cs, dp.inner_center.y * cs , dp.inner_radius * cs,
				dp.outer_center.x * cs, dp.outer_center.y * cs, dp.outer_radius * cs)
			.drawCircle(0, 0, cs * dp.outer_radius);
    }

    ParticleView.prototype = new createjs.Shape();

    ParticleView.prototype.update = function(){
        this.x = cell_size * this.model.position.x + cell_size / 2;
        this.y = cell_size * this.model.position.y + cell_size / 2;
    }

    ParticleView.prototype.animate = function(){
        if(this.model.target){
            var own_view = this;
            this.update();
            this.update = function(){};
            createjs.Tween.get(this).to(
                {
                    x: cell_size * this.model.target.x + cell_size / 2,
                    y: cell_size * this.model.target.y + cell_size / 2
                },
                // We do not want to have magnetically curved particles for now
                // {guide:{ path:[0,0, 0,200,200,200, 200,0,0,0] }},
                this.model.target.time - this.model.start_time
            ).call(function(){
                own_view.update = ParticleView.prototype.update;
                own_view.model.position.x = own_view.model.target.x;
                own_view.model.position.y = own_view.model.target.y;
            });
        }
    }

    window.ParticleView = ParticleView;
}(window));
<|endoftext|>


<commit_before>var version = require('./package.json').version;
var webpack = require('webpack');

// Custom webpack loaders are generally the same for all webpack bundles, hence
// stored in a separate local variable.
var rules = [
    {
        test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
        use: 'url-loader'
    },
    {
        test: /\.glsl/,
        use: 'raw-loader'
    }
];

var plugins = [];

// plugins.push(new webpack.optimize.UglifyJsPlugin({
//     compress: {warnings: false},
//     sourceMap: true
// }));

module.exports = [
    {// Notebook extension
        //
        // This bundle only contains the part of the JavaScript that is run on
        // load of the notebook. This section generally only performs
        // some configuration for requirejs, and provides the legacy
        // "load_ipython_extension" function which is required for any notebook
        // extension.
        //
        entry: './src/extension.js',
        output: {
            filename: 'extension.js',
            path: __dirname + '/../k3d/static',
            libraryTarget: 'amd'
        },
        plugins: plugins
    },
    {// Bundle for the notebook containing the custom widget views and models
        //
        // This bundle contains the implementation for the custom widget views and
        // custom widget.
        // It must be an amd module
        //
        entry: './src/index.js',
        output: {
            filename: 'index.js',
            path: __dirname + '/../k3d/static',
            libraryTarget: 'amd'
        },
        plugins: plugins,
        devtool: 'source-map',
        module: {
            rules: rules
        },
        externals: ['@jupyter-widgets/controls']
    },
    {// Embeddable K3D-jupyter bundle
        //
        // This bundle is generally almost identical to the notebook bundle
        // containing the custom widget views and models.
        //
        // The only difference is in the configuration of the webpack public path
        // for the static assets.
        //
        // It will be automatically distributed by unpkg to work with the static
        // widget embedder.
        //
        // The target bundle is always `dist/index.js`, which is the path required
        // by the custom widget embedder.
        //
        entry: './src/embed.js',
        output: {
            filename: 'index.js',
            path: __dirname + '/dist/',
            library: "K3D",
            libraryTarget: 'amd',
            publicPath: 'https://unpkg.com/K3D@' + version + '/dist/'
        },
        devtool: 'source-map',
        module: {
            rules: rules
        },
        plugins: plugins,
        externals: ['@jupyter-widgets/controls']
    }
];
<commit_msg>enable compression in webpack<commit_after>var version = require('./package.json').version;
var webpack = require('webpack');

// Custom webpack loaders are generally the same for all webpack bundles, hence
// stored in a separate local variable.
var rules = [
    {
        test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
        use: 'url-loader'
    },
    {
        test: /\.glsl/,
        use: 'raw-loader'
    }
];

var plugins = [];

plugins.push(new webpack.optimize.UglifyJsPlugin({
    compress: {warnings: false},
    sourceMap: true
}));

module.exports = [
    {// Notebook extension
        //
        // This bundle only contains the part of the JavaScript that is run on
        // load of the notebook. This section generally only performs
        // some configuration for requirejs, and provides the legacy
        // "load_ipython_extension" function which is required for any notebook
        // extension.
        //
        entry: './src/extension.js',
        output: {
            filename: 'extension.js',
            path: __dirname + '/../k3d/static',
            libraryTarget: 'amd'
        },
        plugins: plugins
    },
    {// Bundle for the notebook containing the custom widget views and models
        //
        // This bundle contains the implementation for the custom widget views and
        // custom widget.
        // It must be an amd module
        //
        entry: './src/index.js',
        output: {
            filename: 'index.js',
            path: __dirname + '/../k3d/static',
            libraryTarget: 'amd'
        },
        plugins: plugins,
        devtool: 'source-map',
        module: {
            rules: rules
        },
        externals: ['@jupyter-widgets/controls']
    },
    {// Embeddable K3D-jupyter bundle
        //
        // This bundle is generally almost identical to the notebook bundle
        // containing the custom widget views and models.
        //
        // The only difference is in the configuration of the webpack public path
        // for the static assets.
        //
        // It will be automatically distributed by unpkg to work with the static
        // widget embedder.
        //
        // The target bundle is always `dist/index.js`, which is the path required
        // by the custom widget embedder.
        //
        entry: './src/embed.js',
        output: {
            filename: 'index.js',
            path: __dirname + '/dist/',
            library: "K3D",
            libraryTarget: 'amd',
            publicPath: 'https://unpkg.com/K3D@' + version + '/dist/'
        },
        devtool: 'source-map',
        module: {
            rules: rules
        },
        plugins: plugins,
        externals: ['@jupyter-widgets/controls']
    }
];
<|endoftext|>


<commit_before>var NotImpementedError = require('../lib/NotImplementedError');
var request = require('supertest-as-promised');
var expect = require('expect.js');
var AppFactory = require('../lib/AppFactory');

describe('API endpoint',function(){
	var appFactory = new AppFactory();

	var app = appFactory.NewApp();

	describe('/scene',function(){
		describe('GET',function(){
			var response;

			before(function(done){
				request(app)
					.post('/scene')
					.send({
						resource:{
							type:'url',
							location:'http://www.google.com'
						}
					})
					.then(function(res){
						return request(app)
							.post('/scene')
							.send({
								resource:{
									type:'url',
									location:'http://www.github.com'
								}
							});
					})
					.then(function(res){
						return request(app).get('/scene');
					})
					.then(function(res){
						response = res;
						done();
					})
					.catch(function(err){
						done(err);
					});
			});

			it('should return all scenes in JSON',function(){
				expect(response.body).to.be.an(Array);
				expect(response.body.length).to.be(2);
				expect(response.body[0]._id).to.be(0);
				expect(response.body[0].resource).to.be.ok();
				expect(response.body[0].resource.type).to.be('url');
				expect(response.body[0].resource.location).to.be('http://www.google.com');
				expect(response.body[0].processes).to.be.an(Array);
				expect(response.body[0].processes.length).to.be(0);
			});

			it('should respond with code 200 upon success',function(){
				expect(response.statusCode).to.be(200);
			});
		});
		describe('POST',function(){
			var response;

			before(function(done){
				request(app)
					.post('/scene')
					.send({
						resource:{
							type:'url',
							location:'http://www.youtube.com'
						}
					})
					.then(function(res){
						response = res;
						done();
					})
					.catch(done);
			});

			// it('should create a new scene',function(){
			// 	throw new NotImpementedError();
			// });
			it('should respond with code 201 upon success',function(){
				expect(response.statusCode).to.be(201);
			});
			it('should return a `Location` header with a link to the newly-created resource',function(){
				expect(response.headers).to.have.key('location');
				expect(response.headers.location).to.be.contain('http://127.0.0.1/scene/');
			});
		});
	});
	describe('/scene/{id}/processes',function(){
		describe('POST',function(){
			var response;

			before(function(done){
				request(app)
					.post('/scene/1/processes')
					.send({
						'status':'InProgress'
					})
					.then(function(res){
						response = res;
						done();
					})
					.catch(done);
			});

			it('should create a new process for the desired scene',function(done){
				request(app).get('/scene')
					.then(function(getRes){
						expect(getRes.body[1].processes).to.be.an(Array);
						expect(getRes.body[1].processes.length).to.be(1);
						expect(getRes.body[1].processes[0].status).to.eql('InProgress');
						done();
					})
					.catch(done);
			});

			it('should return a `Location` header with a link to the newly-created resource',function(){
				expect(response.headers).to.have.key('location');
				expect(response.headers.location).to.be('http://127.0.0.1/scene/1/processes/0');
			});

			it('should respond with code 201 upon success',function(){
				expect(response.statusCode).to.be(201);
			});
		});

	});
	describe('/scene/{id}/processes/{id}',function(){
		describe('PUT',function(){
			it('should update process status',function(done){
				request(app)
					.post('/scene')
					.send({
						resource:{
							type:'url',
							location:'http://www.trioxis.com'
						}
					})
					.then(function(res){
						var loc = res.headers.location.replace('http://127.0.0.1','')+'/processes';
						return request(app)
							.post(loc)
							.send({
								'status':'InProgress'
							});
					})
					.then(function(res){
						var loc = res.headers.location.replace('http://127.0.0.1','');
						return request(app)
							.put(loc)
							.send({
								'status':'Complete',
								'result':'http://my.awesome/result'
							});
					})
					.then(function(res){
						return request(app)
							.get('/scene');
					})
					.then(function(res){
						expect(res.body[res.body.length-1].processes[0].status).to.be('Complete');
						expect(res.body[res.body.length-1].processes[0].result).to.be('http://my.awesome/result');
						done();
					})
					.catch(done);
			});
			it('should require result if complete',function(done){
				request(app)
					.post('/scene')
					.send({
						resource:{
							type:'url',
							location:'http://www.trioxis.com'
						}
					})
					.then(function(res){
						var loc = res.headers.location.replace('http://127.0.0.1','')+'/processes';
						return request(app)
							.post(loc)
							.send({
								'status':'InProgress'
							});
					})
					.then(function(res){
						var loc = res.headers.location.replace('http://127.0.0.1','');
						return request(app)
							.put(loc)
							.send({
								'status':'Complete'
							});
					})
					.then(function(res){
						expect(res.statusCode).to.be(400);
						expect(res.body).to.have.key('message');

						done();
					})
					.catch(done);
			});
		});
	});
});<commit_msg>Add test for CORS<commit_after>var NotImpementedError = require('../lib/NotImplementedError');
var request = require('supertest-as-promised');
var expect = require('expect.js');
var AppFactory = require('../lib/AppFactory');

describe('API endpoint',function(){
	var appFactory = new AppFactory();

	var app = appFactory.NewApp();

	it('should enable CORS for web application',function(done){
		request(app).get('/scene')
			.then(function(res){
				expect(res.header).to.have.keys([
					'access-control-allow-origin',
					'access-control-allow-methods',
					'access-control-allow-headers',
					'access-control-allow-credentials']);

				expect(res.header['access-control-allow-origin']).to.be('http://localhost:8080');
				expect(res.header['access-control-allow-methods']).to.be('GET, POST, OPTIONS, PUT, PATCH, DELETE');
				expect(res.header['access-control-allow-headers']).to.be('X-Requested-With,content-type');
				expect(res.header['access-control-allow-credentials']).to.be('true');

				done();
			}).catch(function(err){
				done(err);
			});
	});

	describe('/scene',function(){
		describe('GET',function(){
			var response;

			before(function(done){
				request(app)
					.post('/scene')
					.send({
						resource:{
							type:'url',
							location:'http://www.google.com'
						}
					})
					.then(function(res){
						return request(app)
							.post('/scene')
							.send({
								resource:{
									type:'url',
									location:'http://www.github.com'
								}
							});
					})
					.then(function(res){
						return request(app).get('/scene');
					})
					.then(function(res){
						response = res;
						done();
					})
					.catch(function(err){
						done(err);
					});
			});

			it('should return all scenes in JSON',function(){
				expect(response.body).to.be.an(Array);
				expect(response.body.length).to.be(2);
				expect(response.body[0]._id).to.be(0);
				expect(response.body[0].resource).to.be.ok();
				expect(response.body[0].resource.type).to.be('url');
				expect(response.body[0].resource.location).to.be('http://www.google.com');
				expect(response.body[0].processes).to.be.an(Array);
				expect(response.body[0].processes.length).to.be(0);
			});

			it('should respond with code 200 upon success',function(){
				expect(response.statusCode).to.be(200);
			});
		});
		describe('POST',function(){
			var response;

			before(function(done){
				request(app)
					.post('/scene')
					.send({
						resource:{
							type:'url',
							location:'http://www.youtube.com'
						}
					})
					.then(function(res){
						response = res;
						done();
					})
					.catch(done);
			});

			// it('should create a new scene',function(){
			// 	throw new NotImpementedError();
			// });
			it('should respond with code 201 upon success',function(){
				expect(response.statusCode).to.be(201);
			});
			it('should return a `Location` header with a link to the newly-created resource',function(){
				expect(response.headers).to.have.key('location');
				expect(response.headers.location).to.be.contain('http://127.0.0.1/scene/');
			});
		});
	});
	describe('/scene/{id}/processes',function(){
		describe('POST',function(){
			var response;

			before(function(done){
				request(app)
					.post('/scene/1/processes')
					.send({
						'status':'InProgress'
					})
					.then(function(res){
						response = res;
						done();
					})
					.catch(done);
			});

			it('should create a new process for the desired scene',function(done){
				request(app).get('/scene')
					.then(function(getRes){
						expect(getRes.body[1].processes).to.be.an(Array);
						expect(getRes.body[1].processes.length).to.be(1);
						expect(getRes.body[1].processes[0].status).to.eql('InProgress');
						done();
					})
					.catch(done);
			});

			it('should return a `Location` header with a link to the newly-created resource',function(){
				expect(response.headers).to.have.key('location');
				expect(response.headers.location).to.be('http://127.0.0.1/scene/1/processes/0');
			});

			it('should respond with code 201 upon success',function(){
				expect(response.statusCode).to.be(201);
			});
		});

	});
	describe('/scene/{id}/processes/{id}',function(){
		describe('PUT',function(){
			it('should update process status',function(done){
				request(app)
					.post('/scene')
					.send({
						resource:{
							type:'url',
							location:'http://www.trioxis.com'
						}
					})
					.then(function(res){
						var loc = res.headers.location.replace('http://127.0.0.1','')+'/processes';
						return request(app)
							.post(loc)
							.send({
								'status':'InProgress'
							});
					})
					.then(function(res){
						var loc = res.headers.location.replace('http://127.0.0.1','');
						return request(app)
							.put(loc)
							.send({
								'status':'Complete',
								'result':'http://my.awesome/result'
							});
					})
					.then(function(res){
						return request(app)
							.get('/scene');
					})
					.then(function(res){
						expect(res.body[res.body.length-1].processes[0].status).to.be('Complete');
						expect(res.body[res.body.length-1].processes[0].result).to.be('http://my.awesome/result');
						done();
					})
					.catch(done);
			});
			it('should require result if complete',function(done){
				request(app)
					.post('/scene')
					.send({
						resource:{
							type:'url',
							location:'http://www.trioxis.com'
						}
					})
					.then(function(res){
						var loc = res.headers.location.replace('http://127.0.0.1','')+'/processes';
						return request(app)
							.post(loc)
							.send({
								'status':'InProgress'
							});
					})
					.then(function(res){
						var loc = res.headers.location.replace('http://127.0.0.1','');
						return request(app)
							.put(loc)
							.send({
								'status':'Complete'
							});
					})
					.then(function(res){
						expect(res.statusCode).to.be(400);
						expect(res.body).to.have.key('message');

						done();
					})
					.catch(done);
			});
		});
	});
});<|endoftext|>


<commit_before>/* jslint node: true, esnext: true */
/* eslint-env es6 */
/* eslint valid-jsdoc: 2 */

'use strict';

import {
  ConnectorMixin,
  rejectingReceiver
}
from 'kronos-interceptor';

class Endpoint {
  /**
   * possible options:
   * - opposite endpoint specify opposite endpoint
   * - createOpposite creates an opposite endpoint
   * @param {Object} options
   */
  constructor(name, owner, options = {}) {
    Object.defineProperty(this, 'name', {
      value: name
    });

    Object.defineProperty(this, 'owner', {
      value: owner
    });

    if (options.opposite) {
      Object.defineProperty(this, 'opposite', {
        value: options.opposite
      });
      Object.defineProperty(options.opposite, 'opposite', {
        value: this
      });
    } else if (options.createOpposite) {
      const opposite = this.isIn ? new SendEndpoint(name, owner, {
        opposite: this
      }) : new ReceiveEndpoint(name, owner, {
        opposite: this
      });

      Object.defineProperty(this, 'opposite', {
        value: opposite
      });
    }
  }

  get isDefault() {
    return false;
  }

  toString() {
    return `${this.owner}/${this.name}(connected=${this.isConnected},open=${this.isOpen})`;
  }

  get identifier() {
    return this.owner.endpointIdentifier(this);
  }

  get isIn() {
    return false;
  }

  get isOut() {
    return false;
  }

  get isOpen() {
    return false;
  }

  get isConnected() {
    return false;
  }

  hasBeenConnected() {}
  hasBeenDisConnected(formerConnected) {}
  hasBeenOpened() {}
  willBeClosed() {}

  /**
   * Deliver data flow direction
   * @return {String} delivers data flow direction 'in', 'out' or undefined
   */
  get direction() {
    return this.isIn ? 'in' : this.isOut ? 'out' : undefined;
  }

  /**
   * @return {Endpoint} representing the opposite direction
   */
  get opposite() {
    return undefined;
  }

  toJSON() {
    const json = {};

    if (this.isIn) {
      json.in = true;
    }
    if (this.isOut) {
      json.out = true;
    }

    return json;
  }
}

/**
 * Endpoint with a list of interceptors
 * also provides fistInterceptor and lastInterceptor
 */
class InterceptedEndpoint extends Endpoint {

  get hasInterceptors() {
    return this._firstInterceptor !== undefined;
  }

  get firstInterceptor() {
    return this._firstInterceptor;
  }

  get lastInterceptor() {
    return this._lastInterceptor;
  }

  /**
   * @return {Array} the interceptors or empty array if none are present
   */
  get interceptors() {
    const itcs = [];
    let i = this.firstInterceptor;
    while (i) {
      itcs.push(i);
      if (i === this.lastInterceptor) break;
      i = i.connected;
    }

    return itcs;
  }

  /**
   * Set the interceptors
   * a connected chain from array element 0 over all entries up to the last element
   * in the array is formed.
   * Additionally firstInterceptor and lastInterceptor are set.
   * @param {Array} newInterceptors replaces all interceptors
   */
  set interceptors(newInterceptors) {
    if (newInterceptors === undefined || newInterceptors.length === 0) {
      this._firstInterceptor = undefined;
      this._lastInterceptor = undefined;
    } else {
      this._firstInterceptor = newInterceptors[0];
      this._lastInterceptor = newInterceptors.reduce((previous, current) => previous.connected = current,
        this._firstInterceptor);
    }
  }

  toJSON() {
    const json = super.toJSON();
    const its = this.interceptors;

    if (its && its.length > 0) {
      json.interceptors = its.map(i => i.toJSON());
    }

    return json;
  }
}

/**
 * Receiving Endpoint
 */
class ReceiveEndpoint extends InterceptedEndpoint {

  /**
   * Set dummy rejecting receiver
   */
  constructor(name, owner, options) {
    super(name, owner, options);

    this._receive = rejectingReceiver;
  }

  /**
   * Connect other side to us
   * @param {Endpoint} other endpoint to be connected to
   */
  set connected(other) {
    other.connected = this;
  }

  /**
   * Deliver the sending side Endpoint
   * @return {SendEndpoint} the sending side
   */
  get sender() {
    return this._sender;
  }

  set sender(sender) {
    this._sender = sender;
  }

  get receive() {
    return this._receive;
  }

  /**
   * Are we able to receive requests
   * @return {boolean} true if we are able to receive requests
   */
  get isOpen() {
    return (this.hasInterceptors ? this._internalEndpoint.receive : this._receive) !== rejectingReceiver;
  }

  /**
   * If we know the sender we will inform him about our open/close state
   * by calling willBeClosed() and hasBeenOpened()
   */
  set receive(receive = rejectingReceiver) {
    const s = this.sender;

    if (s && receive === rejectingReceiver) {
      s.willBeClosed();
    }

    if (this.hasInterceptors) {
      this._internalEndpoint.receive = receive;
    } else {
      this._receive = receive;
    }

    if (s && s.isOpen) {
      s.hasBeenOpened();
    }
  }

  set interceptors(newInterceptors) {
    const lastReceive = this.hasInterceptors ? this._internalEndpoint.receive : this.receive;

    super.interceptors = newInterceptors;

    if (this.hasInterceptors) {
      if (!this._internalEndpoint) {
        let internalReceive = lastReceive;
        this._internalEndpoint = Object.create(this, {
          receive: {
            get() {
                return internalReceive;
              },
              set(r) {
                internalReceive = r;
              }
          }
        });
      }

      this.lastInterceptor.connected = this._internalEndpoint;
      this._receive = request => this.firstInterceptor.receive(request);
    } else {
      this._receive = lastReceive;
    }
  }

  /**
   * @return {boolean} always true
   */
  get isIn() {
    return true;
  }
}

class ReceiveEndpointDefault extends ReceiveEndpoint {
  get isDefault() {
    return true;
  }
}

class SendEndpoint extends ConnectorMixin(InterceptedEndpoint) {

  /**
   * supported options:
   * - opposite endpoint
   * - hasBeenConnected() called after connected
   * - hasBeenDisconected() called after disconnected
   * - hasBeenOpened() called after receiver is open
   * - willBeClosed() called before receiver is closed
   * @param {Object} options
   */
  constructor(name, owner, options = {}) {
    super(name, owner, options);

    for (const key of['hasBeenConnected', 'hasBeenDisConnected', 'hasBeenOpened', 'willBeClosed']) {
      if (options[key]) {
        Object.defineProperty(this, key, {
          value: options[key]
        });
      }
    }
  }

  receive(request, formerRequest) {
    return this.connected.receive(request, formerRequest);
  }

  toJSON() {
    const json = super.toJSON();

    if (this.isConnected) {
      const o = this.otherEnd;
      if (o && o.owner) {
        const ei = o.owner.endpointIdentifier(o);
        if (ei !== undefined) {
          json.target = ei;
        }
      }
    }

    return json;
  }

  get isOut() {
    return true;
  }

  set interceptors(newInterceptors) {
    const lastConnected = this.hasInterceptors ? this.lastInterceptor.connected : this._connected;

    super.interceptors = newInterceptors;
    if (this.hasInterceptors) {
      this.lastInterceptor.connected = lastConnected;
      this._connected = this.firstInterceptor;
    } else {
      this._connected = lastConnected;
    }
  }

  set connected(toBeConnected) {
    let formerConnected;

    if (toBeConnected) {
      toBeConnected.sender = this;
    } else {
      if (this.isOpen) {
        this.willBeClosed();
      }
    }

    if (this.hasInterceptors) {
      formerConnected = this.lastInterceptor.connected;
      this.lastInterceptor.connected = toBeConnected;
    } else {
      formerConnected = super.connected;
      super.connected = toBeConnected;
    }

    /* TODO
    if (formerConnected) {
      formerConnected.sender = undefined;
    }
    */

    if (toBeConnected) {
      if (toBeConnected.opposite && this.opposite && toBeConnected.opposite.connected !== this.opposite) {
        toBeConnected.opposite.connected = this.opposite;
      }

      this.hasBeenConnected();

      if (this.isOpen) {
        this.hasBeenOpened();
      }

    } else if (toBeConnected === undefined) {
      this.hasBeenDisConnected(formerConnected);
    }
  }

  get isOpen() {
    return this.isConnected && this.connected.isOpen;
  }

  // TODO why is this required ?
  get connected() {
    return this._connected;
  }
  get interceptors() {
    return super.interceptors;
  }
  get otherEnd() {
    return super.otherEnd;
  }
}

class SendEndpointDefault extends SendEndpoint {
  get isDefault() {
    return true;
  }
}

export {
  Endpoint,
  ReceiveEndpoint,
  ReceiveEndpointDefault,
  SendEndpoint,
  SendEndpointDefault
};
<commit_msg>style: move export attribute to object defintion<commit_after>import { ConnectorMixin, rejectingReceiver } from 'kronos-interceptor';

export class Endpoint {
  /**
   * possible options:
   * - opposite endpoint specify opposite endpoint
   * - createOpposite creates an opposite endpoint
   * @param {Object} options
   */
  constructor(name, owner, options = {}) {
    Object.defineProperty(this, 'name', {
      value: name
    });

    Object.defineProperty(this, 'owner', {
      value: owner
    });

    if (options.opposite) {
      Object.defineProperty(this, 'opposite', {
        value: options.opposite
      });
      Object.defineProperty(options.opposite, 'opposite', {
        value: this
      });
    } else if (options.createOpposite) {
      const opposite = this.isIn
        ? new SendEndpoint(name, owner, {
            opposite: this
          })
        : new ReceiveEndpoint(name, owner, {
            opposite: this
          });

      Object.defineProperty(this, 'opposite', {
        value: opposite
      });
    }
  }

  get isDefault() {
    return false;
  }

  toString() {
    return `${this.owner}/${this.name}(connected=${this.isConnected},open=${this
      .isOpen})`;
  }

  get identifier() {
    return this.owner.endpointIdentifier(this);
  }

  get isIn() {
    return false;
  }

  get isOut() {
    return false;
  }

  get isOpen() {
    return false;
  }

  get isConnected() {
    return false;
  }

  hasBeenConnected() {}
  hasBeenDisConnected(formerConnected) {}
  hasBeenOpened() {}
  willBeClosed() {}

  /**
   * Deliver data flow direction
   * @return {String} delivers data flow direction 'in', 'out' or undefined
   */
  get direction() {
    return this.isIn ? 'in' : this.isOut ? 'out' : undefined;
  }

  /**
   * @return {Endpoint} representing the opposite direction
   */
  get opposite() {
    return undefined;
  }

  toJSON() {
    const json = {};

    if (this.isIn) {
      json.in = true;
    }
    if (this.isOut) {
      json.out = true;
    }

    return json;
  }
}

/**
 * Endpoint with a list of interceptors
 * also provides fistInterceptor and lastInterceptor
 */
export class InterceptedEndpoint extends Endpoint {
  get hasInterceptors() {
    return this._firstInterceptor !== undefined;
  }

  get firstInterceptor() {
    return this._firstInterceptor;
  }

  get lastInterceptor() {
    return this._lastInterceptor;
  }

  /**
   * @return {Array} the interceptors or empty array if none are present
   */
  get interceptors() {
    const itcs = [];
    let i = this.firstInterceptor;
    while (i) {
      itcs.push(i);
      if (i === this.lastInterceptor) break;
      i = i.connected;
    }

    return itcs;
  }

  /**
   * Set the interceptors
   * a connected chain from array element 0 over all entries up to the last element
   * in the array is formed.
   * Additionally firstInterceptor and lastInterceptor are set.
   * @param {Array} newInterceptors replaces all interceptors
   */
  set interceptors(newInterceptors) {
    if (newInterceptors === undefined || newInterceptors.length === 0) {
      this._firstInterceptor = undefined;
      this._lastInterceptor = undefined;
    } else {
      this._firstInterceptor = newInterceptors[0];
      this._lastInterceptor = newInterceptors.reduce(
        (previous, current) => (previous.connected = current),
        this._firstInterceptor
      );
    }
  }

  toJSON() {
    const json = super.toJSON();
    const its = this.interceptors;

    if (its && its.length > 0) {
      json.interceptors = its.map(i => i.toJSON());
    }

    return json;
  }
}

/**
 * Receiving Endpoint
 */
export class ReceiveEndpoint extends InterceptedEndpoint {
  /**
   * Set dummy rejecting receiver
   */
  constructor(name, owner, options) {
    super(name, owner, options);

    this._receive = rejectingReceiver;
  }

  /**
   * Connect other side to us
   * @param {Endpoint} other endpoint to be connected to
   */
  set connected(other) {
    other.connected = this;
  }

  /**
   * Deliver the sending side Endpoint
   * @return {SendEndpoint} the sending side
   */
  get sender() {
    return this._sender;
  }

  set sender(sender) {
    this._sender = sender;
  }

  get receive() {
    return this._receive;
  }

  /**
   * Are we able to receive requests
   * @return {boolean} true if we are able to receive requests
   */
  get isOpen() {
    return (
      (this.hasInterceptors
        ? this._internalEndpoint.receive
        : this._receive) !== rejectingReceiver
    );
  }

  /**
   * If we know the sender we will inform him about our open/close state
   * by calling willBeClosed() and hasBeenOpened()
   */
  set receive(receive = rejectingReceiver) {
    const s = this.sender;

    if (s && receive === rejectingReceiver) {
      s.willBeClosed();
    }

    if (this.hasInterceptors) {
      this._internalEndpoint.receive = receive;
    } else {
      this._receive = receive;
    }

    if (s && s.isOpen) {
      s.hasBeenOpened();
    }
  }

  set interceptors(newInterceptors) {
    const lastReceive = this.hasInterceptors
      ? this._internalEndpoint.receive
      : this.receive;

    super.interceptors = newInterceptors;

    if (this.hasInterceptors) {
      if (!this._internalEndpoint) {
        let internalReceive = lastReceive;
        this._internalEndpoint = Object.create(this, {
          receive: {
            get() {
              return internalReceive;
            },
            set(r) {
              internalReceive = r;
            }
          }
        });
      }

      this.lastInterceptor.connected = this._internalEndpoint;
      this._receive = request => this.firstInterceptor.receive(request);
    } else {
      this._receive = lastReceive;
    }
  }

  /**
   * @return {boolean} always true
   */
  get isIn() {
    return true;
  }
}

export class ReceiveEndpointDefault extends ReceiveEndpoint {
  get isDefault() {
    return true;
  }
}

export class SendEndpoint extends ConnectorMixin(InterceptedEndpoint) {
  /**
   * supported options:
   * - opposite endpoint
   * - hasBeenConnected() called after connected
   * - hasBeenDisconected() called after disconnected
   * - hasBeenOpened() called after receiver is open
   * - willBeClosed() called before receiver is closed
   * @param {Object} options
   */
  constructor(name, owner, options = {}) {
    super(name, owner, options);

    for (const key of [
      'hasBeenConnected',
      'hasBeenDisConnected',
      'hasBeenOpened',
      'willBeClosed'
    ]) {
      if (options[key]) {
        Object.defineProperty(this, key, {
          value: options[key]
        });
      }
    }
  }

  receive(request, formerRequest) {
    return this.connected.receive(request, formerRequest);
  }

  toJSON() {
    const json = super.toJSON();

    if (this.isConnected) {
      const o = this.otherEnd;
      if (o && o.owner) {
        const ei = o.owner.endpointIdentifier(o);
        if (ei !== undefined) {
          json.target = ei;
        }
      }
    }

    return json;
  }

  get isOut() {
    return true;
  }

  set interceptors(newInterceptors) {
    const lastConnected = this.hasInterceptors
      ? this.lastInterceptor.connected
      : this._connected;

    super.interceptors = newInterceptors;
    if (this.hasInterceptors) {
      this.lastInterceptor.connected = lastConnected;
      this._connected = this.firstInterceptor;
    } else {
      this._connected = lastConnected;
    }
  }

  set connected(toBeConnected) {
    let formerConnected;

    if (toBeConnected) {
      toBeConnected.sender = this;
    } else {
      if (this.isOpen) {
        this.willBeClosed();
      }
    }

    if (this.hasInterceptors) {
      formerConnected = this.lastInterceptor.connected;
      this.lastInterceptor.connected = toBeConnected;
    } else {
      formerConnected = super.connected;
      super.connected = toBeConnected;
    }

    /* TODO
    if (formerConnected) {
      formerConnected.sender = undefined;
    }
    */

    if (toBeConnected) {
      if (
        toBeConnected.opposite &&
        this.opposite &&
        toBeConnected.opposite.connected !== this.opposite
      ) {
        toBeConnected.opposite.connected = this.opposite;
      }

      this.hasBeenConnected();

      if (this.isOpen) {
        this.hasBeenOpened();
      }
    } else if (toBeConnected === undefined) {
      this.hasBeenDisConnected(formerConnected);
    }
  }

  get isOpen() {
    return this.isConnected && this.connected.isOpen;
  }

  // TODO why is this required ?
  get connected() {
    return this._connected;
  }
  get interceptors() {
    return super.interceptors;
  }
  get otherEnd() {
    return super.otherEnd;
  }
}

export class SendEndpointDefault extends SendEndpoint {
  get isDefault() {
    return true;
  }
}
<|endoftext|>


<commit_before>var swig = require('../lib/swig'),
  expect = require('expect.js'),
  fs = require('fs'),
  _ = require('lodash'),
  Swig = swig.Swig;

var n = new Swig(),
  oDefaults = n.options;

function resetOptions() {
  swig.setDefaults(oDefaults);
  swig.invalidateCache();
}

describe('options', function () {
  beforeEach(resetOptions);
  afterEach(resetOptions);

  describe('open/close controls', function () {
    it('can have new lines inside', function () {
      expect(swig.render('{{\nfoo\n}}', { locals: { foo: 'tacos' }}))
        .to.equal('tacos');
      expect(swig.render('{%\nif foo\n%}tacos{% endif %}', { locals: { foo: 'tacos' }}))
        .to.equal('tacos');
      expect(swig.render('{#\nfoo\n#}'))
        .to.equal('');
    });

    it('can be set at compile time', function () {
      expect(swig.compile('<%= a %>', { varControls: [ '<%=', '%>' ]})({ a: 'b' })).to.eql('b');
      expect(swig.compile('<* if a *>c<* endif *>', { tagControls: [ '<*', '*>' ]})({ a: 1 })).to.eql('c');
      expect(swig.compile('<!-- hello -->', { cmtControls: [ '<!--', '-->' ]})({})).to.eql('');
    });

    it('can be set at render time', function () {
      expect(swig.render('<%= a %>', { varControls: [ '<%=', '%>' ], locals: { a: 'b' }})).to.eql('b');
      expect(swig.render('<^ if a ^>b<^ endif ^>', { tagControls: [ '<^', '^>' ], locals: { a: 1 }})).to.eql('b');
      expect(swig.render('<!-- hello -->', { cmtControls: [ '<!--', '-->' ]})).to.eql('');
    });

    it('can be set as default', function () {
      swig.setDefaults({
        varControls: ['<=', '=>'],
        tagControls: ['<%', '%>'],
        cmtControls: ['<#', '#>']
      });
      expect(swig.compile('<= a =>')({ a: 'b' })).to.eql('b');
      expect(swig.compile('<% if a %>b<% endif %>')({ a: 1 })).to.eql('b');
      expect(swig.compile('<# hello #>')({})).to.eql('');
    });

    it('must be an array with 2 strings', function () {
      _.each(['varControls', 'tagControls', 'cmtControls'], function (key) {
        expect(function () {
          var o = {};
          o[key] = 'ab';
          swig.compile('', o)();
        }).to.throwError('Option "' + key + '" must be an array containing 2 different control strings.');
        expect(function () {
          var o = {};
          o[key] = ['a'];
          swig.compile('', o)();
        }).to.throwError('Option "' + key + '" must be an array containing 2 different control strings.');
      });
    });

    it('must be different', function () {
      _.each(['varControls', 'tagControls', 'cmtControls'], function (key) {
        var o = {};
        o[key] = ['**', '**'];
        expect(function () {
          swig.compile('', o)();
        }).to.throwError('Option "' + key + '" open and close controls must not be the same.');
      });
    });

    it('must be at least 2 characters', function () {
      _.each(['varControls', 'tagControls', 'cmtControls'], function (key) {
        expect(function () {
          var o = {};
          o[key] = ['&', '**'];
          swig.compile('', o)();
        }).to.throwError('Option "' + key + '" open control must be at least 2 characters. Saw "&" instead.');
        expect(function () {
          var o = {};
          o[key] = ['**', '!'];
          swig.compile('', o)();
        }).to.throwError('Option "' + key + '" close control must be at least 2 characters. Saw "!" instead.');
      });
    });
  });

  describe('locals', function () {
    it('can be set as defaults', function () {
      swig.setDefaults({ locals: { a: 1, b: 2 }});
      var tpl = '{{ a }}{{ b }}{{ c }}';
      expect(swig.compile(tpl)({ c: 3 })).to.equal('123');
      expect(swig.compile(tpl, { locals: { c: 3 }})()).to.equal('123');
      expect(swig.render(tpl, { locals: { c: 3 }})).to.equal('123');
    });
  });

  describe('cache', function () {
    it('can be falsy', function () {
      var s = new Swig({ cache: false });
      s.compile('a', { filename: 'a' })();
      expect(s.cache).to.eql({});

      s.options.cache = null;
      s.compile('a', { filename: 'a' })();
      expect(s.cache).to.eql({});
    });

    it('can be "memory" and is by default', function () {
      var s = new Swig();
      s.compile('a', { filename: 'a' });
      expect(s.cache.a).to.be.a(Function);

      s.compile('b', { cache: 'memory', filename: 'b' });
      expect(s.cache.b).to.be.a(Function);
      // hit the cache to ensure it works
      s.compile('b', { cache: 'memory', filename: 'b' });
    });

    it('can accept custom "get" and "set" methods', function () {
      var c = {};
      swig.setDefaults({ cache: {
        get: function (key) {
          return c[key];
        },
        set: function (key, val) {
          c[key] = val;
        }
      }});

      swig.compile('a', { filename: 'a' });
      expect(c.a).to.be.a(Function);
    });

    it('throws on anything else', function () {
      expect(function () { var s = new Swig({ cache: 'dookie' }); })
        .to.throwError();

      expect(function () { swig.setDefaults({ cache: { a: 1, b: 2 } }); })
        .to.throwError();
    });
  });
});

describe('separate instances', function () {
  it('can be created and don\'t interfere', function () {
    var a = new Swig({ varControls: ['<%', '%>'] }),
      b = new Swig();
    expect(a.options.varControls[0]).to.equal('<%');
    expect(b.options.varControls[0]).to.equal('{{');
  });
});

describe('swig.compileFile', function () {
  var test = __dirname + '/cases/extends_1.test.html';

  beforeEach(resetOptions);
  afterEach(resetOptions);

  it('can run syncronously', function () {
    expect(swig.compileFile(test)())
      .to.be.ok();
  });

  it('can run asynchronously', function (done) {
    expect(swig.compileFile(test, {}, function (err, fn) {
      expect(fn).to.be.a(Function);
      done();
    }));
  });
});

describe('swig.renderFile', function () {
  var test = __dirname + '/cases/extends_1.test.html',
    expectation = fs.readFileSync(test.replace('test.html', 'expectation.html'), 'utf8');

  beforeEach(resetOptions);
  afterEach(resetOptions);

  it('can run syncronously', function () {
    expect(swig.renderFile(test))
      .to.equal(expectation);
  });

  it('can run asynchronously', function (done) {
    expect(swig.renderFile(test, {}, function (err, fn) {
      expect(fn).to.equal(expectation);
      done();
    }));
  });

  it('can use callbacks with errors', function (done) {
    swig.renderFile(__dirname + '/cases/not-existing', {}, function (err, out) {
      expect(err.errno).to.equal(34);
      done();
    });
  });
});
<commit_msg>fix tests for browser-running<commit_after>var swig = require('../lib/swig'),
  expect = require('expect.js'),
  fs = require('fs'),
  _ = require('lodash'),
  Swig = swig.Swig;

var n = new Swig(),
  oDefaults = n.options;

function resetOptions() {
  swig.setDefaults(oDefaults);
  swig.invalidateCache();
}

describe('options', function () {
  beforeEach(resetOptions);
  afterEach(resetOptions);

  describe('open/close controls', function () {
    it('can have new lines inside', function () {
      expect(swig.render('{{\nfoo\n}}', { locals: { foo: 'tacos' }}))
        .to.equal('tacos');
      expect(swig.render('{%\nif foo\n%}tacos{% endif %}', { locals: { foo: 'tacos' }}))
        .to.equal('tacos');
      expect(swig.render('{#\nfoo\n#}'))
        .to.equal('');
    });

    it('can be set at compile time', function () {
      expect(swig.compile('<%= a %>', { varControls: [ '<%=', '%>' ]})({ a: 'b' })).to.eql('b');
      expect(swig.compile('<* if a *>c<* endif *>', { tagControls: [ '<*', '*>' ]})({ a: 1 })).to.eql('c');
      expect(swig.compile('<!-- hello -->', { cmtControls: [ '<!--', '-->' ]})({})).to.eql('');
    });

    it('can be set at render time', function () {
      expect(swig.render('<%= a %>', { varControls: [ '<%=', '%>' ], locals: { a: 'b' }})).to.eql('b');
      expect(swig.render('<^ if a ^>b<^ endif ^>', { tagControls: [ '<^', '^>' ], locals: { a: 1 }})).to.eql('b');
      expect(swig.render('<!-- hello -->', { cmtControls: [ '<!--', '-->' ]})).to.eql('');
    });

    it('can be set as default', function () {
      swig.setDefaults({
        varControls: ['<=', '=>'],
        tagControls: ['<%', '%>'],
        cmtControls: ['<#', '#>']
      });
      expect(swig.compile('<= a =>')({ a: 'b' })).to.eql('b');
      expect(swig.compile('<% if a %>b<% endif %>')({ a: 1 })).to.eql('b');
      expect(swig.compile('<# hello #>')({})).to.eql('');
    });

    it('must be an array with 2 strings', function () {
      _.each(['varControls', 'tagControls', 'cmtControls'], function (key) {
        expect(function () {
          var o = {};
          o[key] = 'ab';
          swig.compile('', o)();
        }).to.throwError('Option "' + key + '" must be an array containing 2 different control strings.');
        expect(function () {
          var o = {};
          o[key] = ['a'];
          swig.compile('', o)();
        }).to.throwError('Option "' + key + '" must be an array containing 2 different control strings.');
      });
    });

    it('must be different', function () {
      _.each(['varControls', 'tagControls', 'cmtControls'], function (key) {
        var o = {};
        o[key] = ['**', '**'];
        expect(function () {
          swig.compile('', o)();
        }).to.throwError('Option "' + key + '" open and close controls must not be the same.');
      });
    });

    it('must be at least 2 characters', function () {
      _.each(['varControls', 'tagControls', 'cmtControls'], function (key) {
        expect(function () {
          var o = {};
          o[key] = ['&', '**'];
          swig.compile('', o)();
        }).to.throwError('Option "' + key + '" open control must be at least 2 characters. Saw "&" instead.');
        expect(function () {
          var o = {};
          o[key] = ['**', '!'];
          swig.compile('', o)();
        }).to.throwError('Option "' + key + '" close control must be at least 2 characters. Saw "!" instead.');
      });
    });
  });

  describe('locals', function () {
    it('can be set as defaults', function () {
      swig.setDefaults({ locals: { a: 1, b: 2 }});
      var tpl = '{{ a }}{{ b }}{{ c }}';
      expect(swig.compile(tpl)({ c: 3 })).to.equal('123');
      expect(swig.compile(tpl, { locals: { c: 3 }})()).to.equal('123');
      expect(swig.render(tpl, { locals: { c: 3 }})).to.equal('123');
    });
  });

  describe('cache', function () {
    it('can be falsy', function () {
      var s = new Swig({ cache: false });
      s.compile('a', { filename: 'a' })();
      expect(s.cache).to.eql({});

      s.options.cache = null;
      s.compile('a', { filename: 'a' })();
      expect(s.cache).to.eql({});
    });

    it('can be "memory" and is by default', function () {
      var s = new Swig();
      s.compile('a', { filename: 'a' });
      expect(s.cache.a).to.be.a(Function);

      s.compile('b', { cache: 'memory', filename: 'b' });
      expect(s.cache.b).to.be.a(Function);
      // hit the cache to ensure it works
      s.compile('b', { cache: 'memory', filename: 'b' });
    });

    it('can accept custom "get" and "set" methods', function () {
      var c = {};
      swig.setDefaults({ cache: {
        get: function (key) {
          return c[key];
        },
        set: function (key, val) {
          c[key] = val;
        }
      }});

      swig.compile('a', { filename: 'a' });
      expect(c.a).to.be.a(Function);
    });

    it('throws on anything else', function () {
      expect(function () { var s = new Swig({ cache: 'dookie' }); })
        .to.throwError();

      expect(function () { swig.setDefaults({ cache: { a: 1, b: 2 } }); })
        .to.throwError();
    });
  });
});

describe('separate instances', function () {
  it('can be created and don\'t interfere', function () {
    var a = new Swig({ varControls: ['<%', '%>'] }),
      b = new Swig();
    expect(a.options.varControls[0]).to.equal('<%');
    expect(b.options.varControls[0]).to.equal('{{');
  });
});

describe('swig.compileFile', function () {
  // The following tests should *not* run in the browser
  if (!fs || !fs.readFileSync) {
    return;
  }
  var test = __dirname + '/cases/extends_1.test.html';

  beforeEach(resetOptions);
  afterEach(resetOptions);

  it('can run syncronously', function () {
    expect(swig.compileFile(test)())
      .to.be.ok();
  });

  it('can run asynchronously', function (done) {
    expect(swig.compileFile(test, {}, function (err, fn) {
      expect(fn).to.be.a(Function);
      done();
    }));
  });
});

describe('swig.renderFile', function () {
  // The following tests should *not* run in the browser
  if (!fs || !fs.readFileSync) {
    return;
  }
  var test = __dirname + '/cases/extends_1.test.html',
    expectation = fs.readFileSync(test.replace('test.html', 'expectation.html'), 'utf8');

  beforeEach(resetOptions);
  afterEach(resetOptions);

  it('can run syncronously', function () {
    expect(swig.renderFile(test))
      .to.equal(expectation);
  });

  it('can run asynchronously', function (done) {
    expect(swig.renderFile(test, {}, function (err, fn) {
      expect(fn).to.equal(expectation);
      done();
    }));
  });

  it('can use callbacks with errors', function (done) {
    swig.renderFile(__dirname + '/cases/not-existing', {}, function (err, out) {
      expect(err.errno).to.equal(34);
      done();
    });
  });
});
<|endoftext|>