<commit_before>import * as PIXI from "pixi.js";

import * as assets from "./assets";
import {CELL_SIZE, PLAYER_COLORS} from "./assets";

/**
 * Manages a ship on screen.
 */
export class Ship {
    /**
     *
     * @param visualizer The visualizer object
     * @param record The ship record from the replay
     */
    constructor(visualizer, record) {
        this.sprite = PIXI.Sprite.from(assets.SHIP_IMAGE);
        this.halo = PIXI.Sprite.from(assets.HALO_IMAGE);
        this.exhaust = PIXI.Sprite.from(assets.EXHAUST_IMAGE);
        this.tractorBeam = PIXI.Sprite.from(assets.TRACTOR_BEAM_FRAMES[0]);

        this.dockingFrames = Object.keys(assets.DOCKING_SHEET.data.frames).sort().map(PIXI.Texture.fromFrame);
        this.dockingMirroredFrames = Object.keys(assets.DOCKING_MIRRORED_SHEET.data.frames).sort().map(PIXI.Texture.fromFrame);
        this.leftDocking = PIXI.Sprite.from(this.dockingFrames[0]);
        this.rightDocking = PIXI.Sprite.from(this.dockingMirroredFrames[0]);
        this.container = null;
        this.dockingContainer = null;
        this.visualizer = visualizer;

        this.owner = record.owner;
        this.id = record.id;

        let setupSprite = (sprite, radius) => {
            sprite.width = sprite.height = 2 * radius * this.visualizer.scale;
            sprite.anchor.x = sprite.anchor.y = 0.5;
        };

        const radius = this.visualizer.replay.constants.WEAPON_RADIUS;
        this.sprite.anchor.set(0.5);
        this.sprite.width = 0.7 * radius * this.visualizer.scale;
        this.sprite.height = 0.7 * radius * this.visualizer.scale;
        setupSprite(this.halo, radius);
        setupSprite(this.exhaust, radius);
        this.exhaust.width = 0.3 * radius * this.visualizer.scale;
        this.exhaust.height = (0.6 * 120 / 11) * this.exhaust.width;
        this.exhaust.anchor.x = 0.5;
        this.exhaust.anchor.y = 0;

        this.tractorBeam.anchor.x = 0.5;
        this.tractorBeam.anchor.y = 0.0;
        this.tractorBeam.width = 5;

        // We have to manually adjust the anchor points of the docking arms,
        // since they're not specified in the spritesheet
        // XXX: if the sprite changes, these need to be updated too!
        this.leftDocking.anchor.x = 48/81;
        this.rightDocking.anchor.x = 34/81;
        this.leftDocking.anchor.y = 14/110;
        this.rightDocking.anchor.y = 14/110;
        this.leftDocking.height = (240 / 76) * this.sprite.height;

        this.leftDocking.width = 2 * radius * this.visualizer.scale * CELL_SIZE;
        this.rightDocking.width = 2 * radius * this.visualizer.scale * CELL_SIZE;

        this.sprite.tint = PLAYER_COLORS[this.owner];
        this.halo.tint = PLAYER_COLORS[this.owner];
        this.leftDocking.tint = this.rightDocking.tint = 0xFFFFFF;

        this.halo.interactive = true;
        this.halo.buttonMode = true;
        this.halo.on("pointerdown", this.onClick.bind(this));

        this.update(record);
    }

    /**
     * Add the ship to the visualizer.
     * @param container {PIXI.Container}
     * @param dockingContainer {PIXI.Container} The container to use for the
     * docking arms. (This lets you control their z-order, though right now
     * it doesn't seem to work.)
     */
    attach(container, dockingContainer) {
        dockingContainer.addChildAt(this.leftDocking, 0);
        dockingContainer.addChildAt(this.rightDocking, 0);
        container.addChild(this.halo, this.exhaust, this.tractorBeam, this.sprite);
        this.container = container;
        this.dockingContainer = dockingContainer;
    }

    /**
     * Remove this ship from the visualizer.
     */
    destroy() {
        this.container.removeChild(this.halo);
        this.container.removeChild(this.sprite);
        this.container.removeChild(this.exhaust);
        this.container.removeChild(this.tractorBeam);
        this.dockingContainer.removeChild(this.leftDocking, this.rightDocking);
    }

    onClick() {
        this.visualizer.onSelect("ship", {
            owner: this.owner,
            id: this.id,
        });
    }

    /**
     * Update this ship's display with the latest state from the replay.
     * @param record
     */
    update(record) {
        const max_ship_health = this.visualizer.replay.constants.MAX_SHIP_HEALTH;
        const health_factor = 0.1 + 0.2 * record.health / max_ship_health;

        this.halo.alpha = health_factor;

        let vel_x = record.vel_x;
        let vel_y = record.vel_y;

        // Draw the exhaust trail based on the thrust command issued this turn
        this.exhaust.visible = false;
        if (this.visualizer.frame < this.visualizer.replay.frames.length - 1) {
            let moves = this.visualizer.replay.moves[this.visualizer.frame];
            let move = moves[record.owner][0][record.id];
            if (move && move.type === "thrust") {
                // Adds the command to the ship velocity, though without
                // drag/inertia, this is entirely pointless
                let angle = move.angle * Math.PI / 180;
                vel_x += move.magnitude * Math.cos(angle);
                vel_y += move.magnitude * Math.sin(angle);

                const vel_factor = move.magnitude / this.visualizer.replay.constants.MAX_ACCELERATION;

                this.exhaust.visible = true;
                this.exhaust.rotation = angle + Math.PI / 2;
                this.exhaust.alpha = 0.4 * vel_factor;
                this.exhaust.height = 0.6 * vel_factor * (120 / 11) * this.exhaust.width;
            }
        }

        // Orient the sprite in the direction that it's moving
        let angle = Math.atan2(vel_y, vel_x);
        this.sprite.rotation = angle + Math.PI / 2;

        // Adjust speed to not go over max speed - this also doesn't matter
        // without drag/inertia (it's to make sure our forecasted positions
        // line up with what the game environment did)
        const max_speed = this.visualizer.replay.constants.MAX_SPEED;
        const magnitude = Math.sqrt(vel_x*vel_x + vel_y*vel_y);
        if (magnitude > max_speed) {
            vel_x *= magnitude / max_speed;
            vel_y *= magnitude / max_speed;
        }

        // Adjust our position based on the time (interpolate between frames)
        const x = record.x + this.visualizer.time * vel_x;
        const y = record.y + this.visualizer.time * vel_y;

        const pixelX = this.visualizer.scale * CELL_SIZE * x;
        const pixelY = this.visualizer.scale * CELL_SIZE * y;
        this.halo.position.x = this.sprite.position.x = this.exhaust.position.x = this.leftDocking.position.x = pixelX;
        this.halo.position.y = this.sprite.position.y = this.exhaust.position.y = this.leftDocking.position.y = pixelY;

        this.drawDocking(record);
    }

    drawDocking(ship) {
        if (ship.docking.status !== "undocked") {
            this.leftDocking.visible = true;
            this.rightDocking.visible = true;
            this.tractorBeam.visible = true;

            const side = CELL_SIZE * this.visualizer.scale;

            const dock_turns = this.visualizer.replay.constants.DOCK_TURNS;

            let progress = ship.docking.status === "docked" ?
                dock_turns : dock_turns - ship.docking.turns_left;
            if (ship.docking.status === "undocking") {
                progress = ship.docking.turns_left / dock_turns;
            }
            else {
                if (ship.docking.status === "docking") {
                    progress += this.visualizer.time;
                }
                progress /= dock_turns;
            }

            const planetId = ship.docking.planet_id;
            const planetBase = this.visualizer.replay.planets[planetId];

            const planetX = side * planetBase.x;
            const planetY = side * planetBase.y;

            const cx = this.sprite.position.x;
            const cy = this.sprite.position.y;

            const dx = planetX - cx;
            const dy = planetY - cy;

            const frame = Math.floor(progress * (this.dockingFrames.length - 1));
            const shipPlanetAngle = Math.atan2(ship.y - planetBase.y,
                ship.x - planetBase.x);
            const radius = this.visualizer.replay.constants.WEAPON_RADIUS;
            const pixelRadius = radius * side;

            this.sprite.rotation = shipPlanetAngle + Math.PI / 2;

            this.tractorBeam.position.x = cx;
            this.tractorBeam.position.y = cy;
            this.tractorBeam.height = Math.sqrt(dx*dx + dy*dy);
            this.tractorBeam.rotation = shipPlanetAngle + Math.PI / 2;
            this.tractorBeam.texture = assets.TRACTOR_BEAM_FRAMES[Math.floor(progress * (assets.TRACTOR_BEAM_FRAMES.length - 1))];

            const distanceToPlanet = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
            const armHeight = 0.8 * distanceToPlanet;
            const armAngle = shipPlanetAngle + Math.PI / 2;

            // Orient the docking arms to the planet
            this.leftDocking.texture = this.dockingFrames[frame];
            this.leftDocking.position.x = this.sprite.position.x + pixelRadius * Math.cos(shipPlanetAngle + Math.PI / 2);
            this.leftDocking.position.y = this.sprite.position.y + pixelRadius * Math.sin(shipPlanetAngle + Math.PI / 2);
            this.leftDocking.height = armHeight;
            this.leftDocking.rotation = armAngle;

            this.rightDocking.texture = this.dockingMirroredFrames[frame];
            this.rightDocking.position.x = this.sprite.position.x + pixelRadius * Math.cos(shipPlanetAngle - Math.PI / 2);
            this.rightDocking.position.y = this.sprite.position.y + pixelRadius * Math.sin(shipPlanetAngle - Math.PI / 2);
            this.rightDocking.height = armHeight;
            this.rightDocking.rotation = armAngle;
        }
        else {
            this.leftDocking.visible = false;
            this.rightDocking.visible = false;
            this.tractorBeam.visible = false;
        }
    }
}
<commit_msg>Don't interpolate ship position when docked (#672)<commit_after>import * as PIXI from "pixi.js";

import * as assets from "./assets";
import {CELL_SIZE, PLAYER_COLORS} from "./assets";

/**
 * Manages a ship on screen.
 */
export class Ship {
    /**
     *
     * @param visualizer The visualizer object
     * @param record The ship record from the replay
     */
    constructor(visualizer, record) {
        this.sprite = PIXI.Sprite.from(assets.SHIP_IMAGE);
        this.halo = PIXI.Sprite.from(assets.HALO_IMAGE);
        this.exhaust = PIXI.Sprite.from(assets.EXHAUST_IMAGE);
        this.tractorBeam = PIXI.Sprite.from(assets.TRACTOR_BEAM_FRAMES[0]);

        this.dockingFrames = Object.keys(assets.DOCKING_SHEET.data.frames).sort().map(PIXI.Texture.fromFrame);
        this.dockingMirroredFrames = Object.keys(assets.DOCKING_MIRRORED_SHEET.data.frames).sort().map(PIXI.Texture.fromFrame);
        this.leftDocking = PIXI.Sprite.from(this.dockingFrames[0]);
        this.rightDocking = PIXI.Sprite.from(this.dockingMirroredFrames[0]);
        this.container = null;
        this.dockingContainer = null;
        this.visualizer = visualizer;

        this.owner = record.owner;
        this.id = record.id;

        let setupSprite = (sprite, radius) => {
            sprite.width = sprite.height = 2 * radius * this.visualizer.scale;
            sprite.anchor.x = sprite.anchor.y = 0.5;
        };

        const radius = this.visualizer.replay.constants.WEAPON_RADIUS;
        this.sprite.anchor.set(0.5);
        this.sprite.width = 0.7 * radius * this.visualizer.scale;
        this.sprite.height = 0.7 * radius * this.visualizer.scale;
        setupSprite(this.halo, radius);
        setupSprite(this.exhaust, radius);
        this.exhaust.width = 0.3 * radius * this.visualizer.scale;
        this.exhaust.height = (0.6 * 120 / 11) * this.exhaust.width;
        this.exhaust.anchor.x = 0.5;
        this.exhaust.anchor.y = 0;

        this.tractorBeam.anchor.x = 0.5;
        this.tractorBeam.anchor.y = 0.0;
        this.tractorBeam.width = 5;

        // We have to manually adjust the anchor points of the docking arms,
        // since they're not specified in the spritesheet
        // XXX: if the sprite changes, these need to be updated too!
        this.leftDocking.anchor.x = 48/81;
        this.rightDocking.anchor.x = 34/81;
        this.leftDocking.anchor.y = 14/110;
        this.rightDocking.anchor.y = 14/110;
        this.leftDocking.height = (240 / 76) * this.sprite.height;

        this.leftDocking.width = 2 * radius * this.visualizer.scale * CELL_SIZE;
        this.rightDocking.width = 2 * radius * this.visualizer.scale * CELL_SIZE;

        this.sprite.tint = PLAYER_COLORS[this.owner];
        this.halo.tint = PLAYER_COLORS[this.owner];
        this.leftDocking.tint = this.rightDocking.tint = 0xFFFFFF;

        this.halo.interactive = true;
        this.halo.buttonMode = true;
        this.halo.on("pointerdown", this.onClick.bind(this));

        this.update(record);
    }

    /**
     * Add the ship to the visualizer.
     * @param container {PIXI.Container}
     * @param dockingContainer {PIXI.Container} The container to use for the
     * docking arms. (This lets you control their z-order, though right now
     * it doesn't seem to work.)
     */
    attach(container, dockingContainer) {
        dockingContainer.addChildAt(this.leftDocking, 0);
        dockingContainer.addChildAt(this.rightDocking, 0);
        container.addChild(this.halo, this.exhaust, this.tractorBeam, this.sprite);
        this.container = container;
        this.dockingContainer = dockingContainer;
    }

    /**
     * Remove this ship from the visualizer.
     */
    destroy() {
        this.container.removeChild(this.halo);
        this.container.removeChild(this.sprite);
        this.container.removeChild(this.exhaust);
        this.container.removeChild(this.tractorBeam);
        this.dockingContainer.removeChild(this.leftDocking, this.rightDocking);
    }

    onClick() {
        this.visualizer.onSelect("ship", {
            owner: this.owner,
            id: this.id,
        });
    }

    /**
     * Update this ship's display with the latest state from the replay.
     * @param record
     */
    update(record) {
        const max_ship_health = this.visualizer.replay.constants.MAX_SHIP_HEALTH;
        const health_factor = 0.1 + 0.2 * record.health / max_ship_health;

        this.halo.alpha = health_factor;

        let vel_x = 0;
        let vel_y = 0;

        // Draw the exhaust trail based on the thrust command issued this turn
        this.exhaust.visible = false;
        if (this.visualizer.frame < this.visualizer.replay.frames.length - 1) {
            let moves = this.visualizer.replay.moves[this.visualizer.frame];
            let move = moves[record.owner][0][record.id];
            if (move && move.type === "thrust" && record.docking.status === "undocked") {
                // Adds the command to the ship velocity
                let angle = move.angle * Math.PI / 180;
                vel_x += move.magnitude * Math.cos(angle);
                vel_y += move.magnitude * Math.sin(angle);

                const vel_factor = move.magnitude / this.visualizer.replay.constants.MAX_ACCELERATION;

                this.exhaust.visible = true;
                this.exhaust.rotation = angle + Math.PI / 2;
                this.exhaust.alpha = 0.4 * vel_factor;
                this.exhaust.height = 0.6 * vel_factor * (120 / 11) * this.exhaust.width;
            }
        }

        // Orient the sprite in the direction that it's moving
        let angle = Math.atan2(vel_y, vel_x);
        this.sprite.rotation = angle + Math.PI / 2;

        // Adjust speed to not go over max speed - this also doesn't matter
        // without drag/inertia (it's to make sure our forecasted positions
        // line up with what the game environment did)
        const max_speed = this.visualizer.replay.constants.MAX_SPEED;
        const magnitude = Math.sqrt(vel_x*vel_x + vel_y*vel_y);
        if (magnitude > max_speed) {
            vel_x *= magnitude / max_speed;
            vel_y *= magnitude / max_speed;
        }

        // Adjust our position based on the time (interpolate between frames)
        const x = record.x + this.visualizer.time * vel_x;
        const y = record.y + this.visualizer.time * vel_y;

        const pixelX = this.visualizer.scale * CELL_SIZE * x;
        const pixelY = this.visualizer.scale * CELL_SIZE * y;
        this.halo.position.x = this.sprite.position.x = this.exhaust.position.x = this.leftDocking.position.x = pixelX;
        this.halo.position.y = this.sprite.position.y = this.exhaust.position.y = this.leftDocking.position.y = pixelY;

        this.drawDocking(record);
    }

    drawDocking(ship) {
        if (ship.docking.status !== "undocked") {
            this.leftDocking.visible = true;
            this.rightDocking.visible = true;
            this.tractorBeam.visible = true;

            const side = CELL_SIZE * this.visualizer.scale;

            const dock_turns = this.visualizer.replay.constants.DOCK_TURNS;

            let progress = ship.docking.status === "docked" ?
                dock_turns : dock_turns - ship.docking.turns_left;
            if (ship.docking.status === "undocking") {
                progress = ship.docking.turns_left / dock_turns;
            }
            else {
                if (ship.docking.status === "docking") {
                    progress += this.visualizer.time;
                }
                progress /= dock_turns;
            }

            const planetId = ship.docking.planet_id;
            const planetBase = this.visualizer.replay.planets[planetId];

            const planetX = side * planetBase.x;
            const planetY = side * planetBase.y;

            const cx = this.sprite.position.x;
            const cy = this.sprite.position.y;

            const dx = planetX - cx;
            const dy = planetY - cy;

            const frame = Math.floor(progress * (this.dockingFrames.length - 1));
            const shipPlanetAngle = Math.atan2(ship.y - planetBase.y,
                ship.x - planetBase.x);
            const radius = this.visualizer.replay.constants.WEAPON_RADIUS;
            const pixelRadius = radius * side;

            this.sprite.rotation = shipPlanetAngle + Math.PI / 2;

            this.tractorBeam.position.x = cx;
            this.tractorBeam.position.y = cy;
            this.tractorBeam.height = Math.sqrt(dx*dx + dy*dy);
            this.tractorBeam.rotation = shipPlanetAngle + Math.PI / 2;
            this.tractorBeam.texture = assets.TRACTOR_BEAM_FRAMES[Math.floor(progress * (assets.TRACTOR_BEAM_FRAMES.length - 1))];

            const distanceToPlanet = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
            const armHeight = 0.8 * distanceToPlanet;
            const armAngle = shipPlanetAngle + Math.PI / 2;

            // Orient the docking arms to the planet
            this.leftDocking.texture = this.dockingFrames[frame];
            this.leftDocking.position.x = this.sprite.position.x + pixelRadius * Math.cos(shipPlanetAngle + Math.PI / 2);
            this.leftDocking.position.y = this.sprite.position.y + pixelRadius * Math.sin(shipPlanetAngle + Math.PI / 2);
            this.leftDocking.height = armHeight;
            this.leftDocking.rotation = armAngle;

            this.rightDocking.texture = this.dockingMirroredFrames[frame];
            this.rightDocking.position.x = this.sprite.position.x + pixelRadius * Math.cos(shipPlanetAngle - Math.PI / 2);
            this.rightDocking.position.y = this.sprite.position.y + pixelRadius * Math.sin(shipPlanetAngle - Math.PI / 2);
            this.rightDocking.height = armHeight;
            this.rightDocking.rotation = armAngle;
        }
        else {
            this.leftDocking.visible = false;
            this.rightDocking.visible = false;
            this.tractorBeam.visible = false;
        }
    }
}
<|endoftext|>


<commit_before>/*
 Copyright 2014 Gubnota Tech (Shenzhen) Ltd, Art Well Enterprise (HK) Ltd.
 All rights reserved. Licensed under the Apache License,
 Version 2.0 (the "License"); you may not use this file except in
 compliance with the License. You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 Written by: Vladislav Muravyev
 Inspired by: Google inc. cookiechoices.js

 To Load button, call it like:

<script>(function(f,e,n,k,i){if(!e.getElementById('shutter_places_window_helper_script')){var n=e.createElement(n),y=e.getElementsByTagName(i)[0];n.setAttribute('id','shutter_places_window_helper_script');n.async=1;n.src=k;y.appendChild(n);if(typeof(IFrameWindowHelper) == 'undefined'){n.onload = function() {
ShutterPlacesWindowHelperScript.Button("Bing Translate","Collapse");}}}})(window,document,'script','http://git.gubnota.ru/fenki_js_helpers/plugin/monkey/shutter_places/window_helper.js','head');</script>

 Or:

<script src="http://git.gubnota.ru/fenki_js_helpers/plugin/monkey/shutter_places/window_helper.js"></script>
<script>
  document.addEventListener('DOMContentLoaded', function(event) {
    ShutterPlacesWindowHelperScript.Button("Bing Translate","Collapse");
  });
</script>
*/

(function(window) {
if (window !== top) return;
  if (!!window.ShutterPlacesWindowHelperScript) {
    return window.ShutterPlacesWindowHelperScript;
  }

  var document = window.document;
  // IE8 does not support textContent, so we should fallback to innerText.
  var ShutterPlacesWindowHelperScript = (function() {
    var id = Math.ceil(Math.random()*1000);//уникальный id объекта
    var ShutterPlacesWindowHelperScriptId = 'translateChoiceInfo'+id;
    var TranslateIframeId = 'translateChoiceInfoIframe'+id;
    var dismissLinkId = 'translateChoiceDismiss'+id;
    var buttonId = 'translateButtonOpen'+id;
    var fl = 'zh-CHS';
    var tl = 'ru';
    var url = 'http://www.bing.com/translator/?from='+fl+'&to='+tl;
    var closeText = 'Collapse';
    var buttonText = 'Places';
    function _createDialogElement(dismissText, linkHref) {
      var glassStyle = 'position:fixed;width:100%;height:100%;z-index:999;' +
          'top:0;left:0;opacity:0.3;filter:alpha(opacity=30);' +
          'background-color:#000;';
      var dialogStyle = 'z-index:1000;position:fixed;top:0';
      var contentStyle = 'position:fixed;height:100%;width:100%;' +
          'background-color:#5B7F98;padding:10px 0;box-shadow:4px 4px 25px #888;font: 15px/17px "PT Sans",Arial;z-index: 10000;';
//border:5px solid #193441;
      var ShutterPlacesWindowHelperScriptElement = document.createElement('div');
      ShutterPlacesWindowHelperScriptElement.id = ShutterPlacesWindowHelperScriptId;

      var glassPanel = document.createElement('div');
      glassPanel.style.cssText = glassStyle;

      var content = document.createElement('div');
      content.style.cssText = contentStyle;

      var dialog = document.createElement('div');
      dialog.style.cssText = dialogStyle;
      dialog.id = 'translate';
      var dismissLink = _createDismissLink(dismissText||closeText);
      dismissLink.style.cssText = dismissLink.style.cssText+';display:block;background-color:#193441;'+
      'text-align:center;padding:4px;font:normal 15px/17px \'PT Sans\',Arial';

      content.appendChild(dismissLink);
      var iframe = document.createElement('iframe');
      iframe.src = linkHref;
      iframe.id = TranslateIframeId;
      iframe.width = "100%";
      iframe.height = "100%";
      content.appendChild(iframe);

      dialog.appendChild(content);

      ShutterPlacesWindowHelperScriptElement.appendChild(glassPanel);
      ShutterPlacesWindowHelperScriptElement.appendChild(dialog);
      return ShutterPlacesWindowHelperScriptElement;
    }

    function _setElementText(element, text) {
        element.innerHTML = text;
    }

    function ChangeHref(new_href){
      var iframe = document.getElementById(TranslateIframeId);
      iframe.src = new_href;
    }

    function _createDismissLink(dismissText) {
      var dismissLink = document.createElement('a');
      _setElementText(dismissLink, dismissText||closeText);
      dismissLink.id = dismissLinkId;
      dismissLink.href = '#';
      dismissLink.style.cssText = 'color:#fff;margin:4px 10px 10px;text-decoration:none;font:normal 15px/17px \'PT Sans\',Arial;';
      return dismissLink;
    }

    function _dismissLinkClick() {
      _close();
      return false;
    }

    function _showShutterPlacesWindowHelperScript(dismissText, linkHref) {
      var translateChoiceElement = document.getElementById(ShutterPlacesWindowHelperScriptId);
      if (translateChoiceElement != null) {
        translateChoiceElement.style.display = 'block';
      }
      else{
        var consentElement =
            _createDialogElement(dismissText||closeText, linkHref||'/');
        var fragment = document.createDocumentFragment();
        fragment.appendChild(consentElement);
        document.body.appendChild(fragment.cloneNode(true));
        document.getElementById(dismissLinkId).onclick = _dismissLinkClick;
        }
    }

    function Bar(dismissText, linkHref) {
      _showShutterPlacesWindowHelperScript(dismissText, linkHref, false);
    }

    function Dialog(_buttonText,dismissText,_url) {
      Button(_buttonText||buttonText,dismissText||closeText,_url||url);
      _showShutterPlacesWindowHelperScript(dismissText||closeText, _url||url);
    }

    function _close() {
      var translateChoiceElement = document.getElementById(ShutterPlacesWindowHelperScriptId);
      if (translateChoiceElement != null) {
        translateChoiceElement.style.display = 'none';
      }
    }

    function _get_user_lang() {
      var match = document.cookie.match(new RegExp('lang=([^;]+)'));
      if (match && match[1] != undefined) return match[1].toString();
      return fl;
    }

    function Button(_buttonText,_closeText,_url){
      var ButtonElem = document.getElementById(buttonId);
      if (ButtonElem != null) {return;} // уже существует кнопка
      if (_closeText != undefined)
        closeText = _closeText;
      if (_buttonText != undefined)
        buttonText = _buttonText;
      if (_url != undefined)
        {url = _url;}
      else{
      var from_lang = _get_user_lang();
      if (from_lang == 'zh') from_lang = 'zh-CHS';
      (from_lang == tl) ? to_lang = 'zh-CHS' : to_lang = tl;
      url = 'http://www.bing.com/translator/?from='+from_lang+'&to='+to_lang;
      }
      var el = document.createElement('div');

      var css = document.createElement('style');

      css.innerHTML = ['#'+buttonId+'{position:fixed',
      'padding:11px 16px 10px',
      'background:#5B7F98',
      'background: linear-gradient(to bottom, #949494 0, #6B6B6B 100%) repeat scroll 0 0 transparent;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#949494\', endColorstr=\'#6B6B6B\', GradientType=0)',
      'color:#fff',
      'font-size:14px',
      'font-weight:bold',
      'margin:0',
      'border-radius:0 0 5px 0',
      'left:0',
      'max-width:200px',
      'cursor: pointer',
      'font-family:Franklin Gothic Medium, FranklinGothicMedium, Arial, sans-serif',
      'border: 0',
      'top: 0',
      'top: 0',
      'z-index: 999',
      'line-height: 19px',
      'height:19px',
      'overflow:hidden',
      'text-align:center'].join(';')+'}';
      css.innerHTML += '#'+buttonId+':hover{'+['background:#525252'].join(';')+'}';
      el.id = buttonId;
      el.innerHTML = buttonText;
      document.body.appendChild(css.cloneNode(true));
      // document.body.insertBefore(el.cloneNode(true),document.body.firstChild);
      document.body.appendChild(el.cloneNode(true));
      document.getElementById(buttonId).onclick = _buttonIdClick;
    }

    function _buttonIdClick(){
      Dialog(buttonText,closeText,url);
    }

function TranslatorInit(callback,args){
if (typeof Microsoft == 'undefined'){
if (typeof $ == 'undefined') {$={browser:{msie:null}};}
else if (typeof $.browser == 'undefined') {$.browser={msie:null};}
(function(f,e,n,k,i){
if(!e.getElementById('trans_landing_script')){var n=e.createElement(n),y=e.getElementsByTagName(i)[0];n.setAttribute('id','trans_landing_script');n.async=1;n.src=k;y.appendChild(n);if(typeof(Microsoft) == 'undefined'){n.onload = function() {
Microsoft={Translator:{Configurations:{rttAppId:Microsoft.Translator.Configurations.rttAppId}}};e.getElementById('trans_landing_script').remove();
var id = Microsoft.Translator.Configurations.rttAppId;
// callback.call(this,id);
}}}})(window,document,'script','http://www.bing.com/translator/dynamic/js/LandingPage.js','head');
}
}

    var exports={Button:Button,Dialog:Dialog,TranslatorInit:TranslatorInit,ChangeHref:ChangeHref};
    exports.id = {'id':id,'ShutterPlacesWindowHelperScriptId':ShutterPlacesWindowHelperScriptId,'dismissLinkId':dismissLinkId,
'buttonId':buttonId,'fl':fl,'tl':tl,'url':url,'closeText':closeText,'buttonText':buttonText};
    return exports;
  })();

  window.ShutterPlacesWindowHelperScript = ShutterPlacesWindowHelperScript;
  return ShutterPlacesWindowHelperScript;
})(this);
<commit_msg>shutter<commit_after>/*
 Copyright 2014 Gubnota Tech (Shenzhen) Ltd, Art Well Enterprise (HK) Ltd.
 All rights reserved. Licensed under the Apache License,
 Version 2.0 (the "License"); you may not use this file except in
 compliance with the License. You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 Written by: Vladislav Muravyev
 Inspired by: Google inc. cookiechoices.js

 To Load button, call it like:

<script>(function(f,e,n,k,i){if(!e.getElementById('shutter_places_window_helper_script')){var n=e.createElement(n),y=e.getElementsByTagName(i)[0];n.setAttribute('id','shutter_places_window_helper_script');n.async=1;n.src=k;y.appendChild(n);if(typeof(IFrameWindowHelper) == 'undefined'){n.onload = function() {
ShutterPlacesWindowHelperScript.Button("Bing Translate","Collapse");}}}})(window,document,'script','http://git.gubnota.ru/fenki_js_helpers/plugin/monkey/shutter_places/window_helper.js','head');</script>

 Or:

<script src="http://git.gubnota.ru/fenki_js_helpers/plugin/monkey/shutter_places/window_helper.js"></script>
<script>
  document.addEventListener('DOMContentLoaded', function(event) {
    ShutterPlacesWindowHelperScript.Button("Bing Translate","Collapse");
  });
</script>
*/

(function(window) {
if (window !== top) return;
  if (!!window.ShutterPlacesWindowHelperScript) {
    return window.ShutterPlacesWindowHelperScript;
  }

  var document = window.document;
  // IE8 does not support textContent, so we should fallback to innerText.
  var ShutterPlacesWindowHelperScript = (function() {
    var id = Math.ceil(Math.random()*1000);//уникальный id объекта
    var ShutterPlacesWindowHelperScriptId = 'translateChoiceInfo'+id;
    var TranslateIframeId = 'translateChoiceInfoIframe'+id;
    var dismissLinkId = 'translateChoiceDismiss'+id;
    var buttonId = 'translateButtonOpen'+id;
    var fl = 'zh-CHS';
    var tl = 'ru';
    var url = 'http://www.bing.com/translator/?from='+fl+'&to='+tl;
    var closeText = 'Collapse';
    var buttonText = 'Places';
    function _createDialogElement(dismissText, linkHref) {
      var glassStyle = 'position:fixed;width:100%;height:100%;z-index:9999;' +
          'top:0;left:0;opacity:0.3;filter:alpha(opacity=30);' +
          'background-color:#000;';
      var dialogStyle = 'z-index:10000;position:fixed;top:0';
      var contentStyle = 'position:fixed;height:100%;width:100%;' +
          'background-color:#5B7F98;padding:10px 0;box-shadow:4px 4px 25px #888;font: 15px/17px "PT Sans",Arial;z-index: 10000;';
//border:5px solid #193441;
      var ShutterPlacesWindowHelperScriptElement = document.createElement('div');
      ShutterPlacesWindowHelperScriptElement.id = ShutterPlacesWindowHelperScriptId;

      var glassPanel = document.createElement('div');
      glassPanel.style.cssText = glassStyle;

      var content = document.createElement('div');
      content.style.cssText = contentStyle;

      var dialog = document.createElement('div');
      dialog.style.cssText = dialogStyle;
      dialog.id = 'translate';
      var dismissLink = _createDismissLink(dismissText||closeText);
      dismissLink.style.cssText = dismissLink.style.cssText+';display:block;background-color:#193441;'+
      'text-align:center;padding:4px;font:normal 15px/17px \'PT Sans\',Arial';

      content.appendChild(dismissLink);
      var iframe = document.createElement('iframe');
      iframe.src = linkHref;
      iframe.id = TranslateIframeId;
      iframe.width = "100%";
      iframe.height = "100%";
      content.appendChild(iframe);

      dialog.appendChild(content);

      ShutterPlacesWindowHelperScriptElement.appendChild(glassPanel);
      ShutterPlacesWindowHelperScriptElement.appendChild(dialog);
      return ShutterPlacesWindowHelperScriptElement;
    }

    function _setElementText(element, text) {
        element.innerHTML = text;
    }

    function ChangeHref(new_href){
      var iframe = document.getElementById(TranslateIframeId);
      iframe.src = new_href;
    }

    function _createDismissLink(dismissText) {
      var dismissLink = document.createElement('a');
      _setElementText(dismissLink, dismissText||closeText);
      dismissLink.id = dismissLinkId;
      dismissLink.href = '#';
      dismissLink.style.cssText = 'color:#fff;margin:4px 10px 10px;text-decoration:none;font:normal 15px/17px \'PT Sans\',Arial;';
      return dismissLink;
    }

    function _dismissLinkClick() {
      _close();
      return false;
    }

    function _showShutterPlacesWindowHelperScript(dismissText, linkHref) {
      var translateChoiceElement = document.getElementById(ShutterPlacesWindowHelperScriptId);
      if (translateChoiceElement != null) {
        translateChoiceElement.style.display = 'block';
      }
      else{
        var consentElement =
            _createDialogElement(dismissText||closeText, linkHref||'/');
        var fragment = document.createDocumentFragment();
        fragment.appendChild(consentElement);
        document.body.appendChild(fragment.cloneNode(true));
        document.getElementById(dismissLinkId).onclick = _dismissLinkClick;
        }
    }

    function Bar(dismissText, linkHref) {
      _showShutterPlacesWindowHelperScript(dismissText, linkHref, false);
    }

    function Dialog(_buttonText,dismissText,_url) {
      Button(_buttonText||buttonText,dismissText||closeText,_url||url);
      _showShutterPlacesWindowHelperScript(dismissText||closeText, _url||url);
    }

    function _close() {
      var translateChoiceElement = document.getElementById(ShutterPlacesWindowHelperScriptId);
      if (translateChoiceElement != null) {
        translateChoiceElement.style.display = 'none';
      }
    }

    function _get_user_lang() {
      var match = document.cookie.match(new RegExp('lang=([^;]+)'));
      if (match && match[1] != undefined) return match[1].toString();
      return fl;
    }

    function Button(_buttonText,_closeText,_url){
      var ButtonElem = document.getElementById(buttonId);
      if (ButtonElem != null) {return;} // уже существует кнопка
      if (_closeText != undefined)
        closeText = _closeText;
      if (_buttonText != undefined)
        buttonText = _buttonText;
      if (_url != undefined)
        {url = _url;}
      else{
      var from_lang = _get_user_lang();
      if (from_lang == 'zh') from_lang = 'zh-CHS';
      (from_lang == tl) ? to_lang = 'zh-CHS' : to_lang = tl;
      url = 'http://www.bing.com/translator/?from='+from_lang+'&to='+to_lang;
      }
      var el = document.createElement('div');

      var css = document.createElement('style');

      css.innerHTML = ['#'+buttonId+'{position:fixed',
      'padding:11px 16px 10px',
      'background:#5B7F98',
      'background: linear-gradient(to bottom, #949494 0, #6B6B6B 100%) repeat scroll 0 0 transparent;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#949494\', endColorstr=\'#6B6B6B\', GradientType=0)',
      'color:#fff',
      'font-size:14px',
      'font-weight:bold',
      'margin:0',
      'border-radius:0 0 5px 0',
      'left:0',
      'max-width:200px',
      'cursor: pointer',
      'font-family:Franklin Gothic Medium, FranklinGothicMedium, Arial, sans-serif',
      'border: 0',
      'top: 0',
      'top: 0',
      'z-index: 9999',
      'line-height: 19px',
      'height:19px',
      'overflow:hidden',
      'text-align:center'].join(';')+'}';
      css.innerHTML += '#'+buttonId+':hover{'+['background:#525252'].join(';')+'}';
      el.id = buttonId;
      el.innerHTML = buttonText;
      document.body.appendChild(css.cloneNode(true));
      // document.body.insertBefore(el.cloneNode(true),document.body.firstChild);
      document.body.appendChild(el.cloneNode(true));
      document.getElementById(buttonId).onclick = _buttonIdClick;
    }

    function _buttonIdClick(){
      Dialog(buttonText,closeText,url);
    }

function TranslatorInit(callback,args){
if (typeof Microsoft == 'undefined'){
if (typeof $ == 'undefined') {$={browser:{msie:null}};}
else if (typeof $.browser == 'undefined') {$.browser={msie:null};}
(function(f,e,n,k,i){
if(!e.getElementById('trans_landing_script')){var n=e.createElement(n),y=e.getElementsByTagName(i)[0];n.setAttribute('id','trans_landing_script');n.async=1;n.src=k;y.appendChild(n);if(typeof(Microsoft) == 'undefined'){n.onload = function() {
Microsoft={Translator:{Configurations:{rttAppId:Microsoft.Translator.Configurations.rttAppId}}};e.getElementById('trans_landing_script').remove();
var id = Microsoft.Translator.Configurations.rttAppId;
// callback.call(this,id);
}}}})(window,document,'script','http://www.bing.com/translator/dynamic/js/LandingPage.js','head');
}
}

    var exports={Button:Button,Dialog:Dialog,TranslatorInit:TranslatorInit,ChangeHref:ChangeHref};
    exports.id = {'id':id,'ShutterPlacesWindowHelperScriptId':ShutterPlacesWindowHelperScriptId,'dismissLinkId':dismissLinkId,
'buttonId':buttonId,'fl':fl,'tl':tl,'url':url,'closeText':closeText,'buttonText':buttonText};
    return exports;
  })();

  window.ShutterPlacesWindowHelperScript = ShutterPlacesWindowHelperScript;
  return ShutterPlacesWindowHelperScript;
})(this);
<|endoftext|>


<commit_before>import { createTest, createVue, destroyVM } from '../util';
import Switch from 'packages/switch';
import Vue from 'vue';

describe('Switch', () => {
  let vm;
  afterEach(() => {
    destroyVM(vm);
  });

  it('create', () => {
    vm = createTest(Switch, {
      onText: 'on',
      offText: 'off',
      onColor: '#0f0',
      offColor: '#f00',
      width: 100
    });

    const core = vm.$el.querySelector('.el-switch__core');
    expect(core.style.backgroundColor).to.equal('rgb(0, 255, 0)');
    expect(core.style.width).to.equal('100px');
    expect(vm.$el.querySelector('.el-switch__label--left').querySelector('span').textContent).to.equal('on');
  });

  it('switch with icons', () => {
    vm = createTest(Switch, {
      onIconClass: 'el-icon-check',
      offIconClass: 'el-icon-close'
    });

    const icon = vm.$el.querySelector('.el-switch__label--left').querySelector('i');
    expect(icon.classList.contains('el-icon-check')).to.true;
  });

  it('value correctly update', done => {
    vm = createVue({
      template: `

          <el-switch v-model="value"></el-switch>

      `,

      data() {
        return {
          value: true
        };
      }
    }, true);

    const core = vm.$el.querySelector('.el-switch__core');
    core.click();
    setTimeout(() => {
      expect(vm.value).to.equal(false);
      core.click();
      setTimeout(() => {
        expect(vm.value).to.equal(true);
        done();
      }, 10);
    }, 10);
  });

  it('change event', done => {
    vm = createVue({
      template: `

          <el-switch
            v-model="value"
            @change="handleChange">
          </el-switch>

      `,
      mounted() {
        setTimeout(() => {
          this.value = false;
        }, 10);
      },
      methods: {
        handleChange(val) {
          this.target = val;
        }
      },
      data() {
        return {
          target: 1,
          value: true
        };
      }
    }, true);

    setTimeout(() => {
      const core = vm.$el.querySelector('.el-switch__core');
      expect(vm.target).to.equal(1);
      core.click();
      setTimeout(() => {
        expect(vm.target).to.equal(true);
        done();
      }, 10);
    }, 50);
  });

  it('disabled switch should not respond to user click', done => {
    vm = createVue({
      template: `

          <el-switch disabled v-model="value"></el-switch>

      `,

      data() {
        return {
          value: true
        };
      }
    }, true);

    vm.$el.querySelector('.el-switch__core').click();
    Vue.nextTick(() => {
      expect(vm.value).to.true;
      done();
    });
  });

  it('expand switch value', done => {
    vm = createVue({
      template: `

          <el-switch v-model="value" :on-value="onValue" :off-value="offValue"></el-switch>

      `,
      data() {
        return {
          value: '100',
          onValue: '100',
          offValue: '0'
        };
      }
    }, true);

    const core = vm.$el.querySelector('.el-switch__core');
    core.click();
    setTimeout(() => {
      expect(vm.value).to.equal('0');
      core.click();
      setTimeout(() => {
        expect(vm.value).to.equal('100');
        done();
      }, 10);
    }, 10);
  });
});
<commit_msg>Switch: add test for single source of truth<commit_after>import { createTest, createVue, destroyVM } from '../util';
import Switch from 'packages/switch';
import Vue from 'vue';

describe('Switch', () => {
  let vm;
  afterEach(() => {
    destroyVM(vm);
  });

  it('create', () => {
    vm = createTest(Switch, {
      onText: 'on',
      offText: 'off',
      onColor: '#0f0',
      offColor: '#f00',
      width: 100
    });

    const core = vm.$el.querySelector('.el-switch__core');
    expect(core.style.backgroundColor).to.equal('rgb(0, 255, 0)');
    expect(core.style.width).to.equal('100px');
    expect(vm.$el.querySelector('.el-switch__label--left').querySelector('span').textContent).to.equal('on');
  });

  it('switch with icons', () => {
    vm = createTest(Switch, {
      onIconClass: 'el-icon-check',
      offIconClass: 'el-icon-close'
    });

    const icon = vm.$el.querySelector('.el-switch__label--left').querySelector('i');
    expect(icon.classList.contains('el-icon-check')).to.true;
  });

  it('value correctly update', done => {
    vm = createVue({
      template: `

          <el-switch v-model="value"></el-switch>

      `,

      data() {
        return {
          value: true
        };
      }
    }, true);

    const core = vm.$el.querySelector('.el-switch__core');
    core.click();
    setTimeout(() => {
      expect(vm.value).to.equal(false);
      core.click();
      setTimeout(() => {
        expect(vm.value).to.equal(true);
        done();
      }, 10);
    }, 10);
  });

  it('change event', done => {
    vm = createVue({
      template: `

          <el-switch
            v-model="value"
            @change="handleChange">
          </el-switch>

      `,
      mounted() {
        setTimeout(() => {
          this.value = false;
        }, 10);
      },
      methods: {
        handleChange(val) {
          this.target = val;
        }
      },
      data() {
        return {
          target: 1,
          value: true
        };
      }
    }, true);

    setTimeout(() => {
      const core = vm.$el.querySelector('.el-switch__core');
      expect(vm.target).to.equal(1);
      core.click();
      setTimeout(() => {
        expect(vm.target).to.equal(true);
        done();
      }, 10);
    }, 50);
  });

  it('disabled switch should not respond to user click', done => {
    vm = createVue({
      template: `

          <el-switch disabled v-model="value"></el-switch>

      `,

      data() {
        return {
          value: true
        };
      }
    }, true);

    vm.$el.querySelector('.el-switch__core').click();
    Vue.nextTick(() => {
      expect(vm.value).to.true;
      done();
    });
  });

  it('expand switch value', done => {
    vm = createVue({
      template: `

          <el-switch v-model="value" :on-value="onValue" :off-value="offValue"></el-switch>

      `,
      data() {
        return {
          value: '100',
          onValue: '100',
          offValue: '0'
        };
      }
    }, true);

    const core = vm.$el.querySelector('.el-switch__core');
    core.click();
    setTimeout(() => {
      expect(vm.value).to.equal('0');
      core.click();
      setTimeout(() => {
        expect(vm.value).to.equal('100');
        done();
      }, 10);
    }, 10);
  });

  it('value is the single source of truth', done => {
    vm = createVue({
      template: `

          <el-switch :value="true"></el-switch>

      `
    }, true);

    const component = vm.$children[0];
    const input = vm.$el.querySelector('input');
    const core = vm.$el.querySelector('.el-switch__core');
    core.click();
    setTimeout(() => {
      expect(component.checked).to.equal(true);
      expect(component.$el.classList.contains('is-checked')).to.equal(true);
      expect(input.checked).to.equal(true);
      core.click();
      setTimeout(() => {
        expect(component.checked).to.equal(true);
        expect(component.$el.classList.contains('is-checked')).to.equal(true);
        expect(input.checked).to.equal(true);
        done();
      }, 10);
    }, 10);
  });
});
<|endoftext|>


<commit_before>import Double from '../../../src/value/double'
import Call from '../../../src/value/call'
import CallLog from '../../../src/value/call-log'

let subject, ensureDemonstration, didCallOccur, notifySatisfiedMatchers,
    warnIfAlsoStubbed, fail
module.exports = {
  beforeEach: () => {
    ensureDemonstration = td.replace('../../../src/verify/ensure-demonstration').default
    didCallOccur = td.replace('../../../src/verify/did-call-occur').default
    notifySatisfiedMatchers = td.replace('../../../src/verify/notify-satisfied-matchers').default
    warnIfAlsoStubbed = td.replace('../../../src/verify/warn-if-also-stubbed').default
    fail = td.replace('../../../src/verify/fail').default
    subject = require('../../../src/verify/index').default
  },
  'verified to have occurred as configured': () => {
    const double = new Double()
    const call = new Call()
    CallLog.instance.log(double, call)
    const config = {some: 'option'}
    td.when(didCallOccur(double, call, config)).thenReturn(true)

    subject(/*imagine double('a','b','c')*/ undefined, config)

    td.verify(ensureDemonstration(call))
    td.verify(notifySatisfiedMatchers(double, call, config))
    td.verify(warnIfAlsoStubbed(double, call, config))
    assert.equal(td.explain(fail).callCount, 0)
  },
  'demonstrated call DID NOT occur, failing test': () => {
  },
  'demonstration missing blows up': () => {
  }

}
<commit_msg>Step 5: Write the second failing test<commit_after>import Double from '../../../src/value/double'
import Call from '../../../src/value/call'
import CallLog from '../../../src/value/call-log'

let subject, ensureDemonstration, didCallOccur, notifySatisfiedMatchers,
    warnIfAlsoStubbed, fail
module.exports = {
  beforeEach: () => {
    ensureDemonstration = td.replace('../../../src/verify/ensure-demonstration').default
    didCallOccur = td.replace('../../../src/verify/did-call-occur').default
    notifySatisfiedMatchers = td.replace('../../../src/verify/notify-satisfied-matchers').default
    warnIfAlsoStubbed = td.replace('../../../src/verify/warn-if-also-stubbed').default
    fail = td.replace('../../../src/verify/fail').default
    subject = require('../../../src/verify/index').default
  },
  'verified to have occurred as configured': () => {
    const double = new Double()
    const call = new Call()
    CallLog.instance.log(double, call)
    const config = {some: 'option'}
    td.when(didCallOccur(double, call, config)).thenReturn(true)

    subject(/*imagine double('a','b','c')*/ undefined, config)

    td.verify(ensureDemonstration(call))
    td.verify(notifySatisfiedMatchers(double, call, config))
    td.verify(warnIfAlsoStubbed(double, call, config))
    assert.equal(td.explain(fail).callCount, 0)
  },
  'demonstrated call DID NOT occur, failing test': () => {
    const double = new Double()
    const call = new Call()
    CallLog.instance.log(double, call)
    const config = {some: 'option'}
    td.when(didCallOccur(double, call, config)).thenReturn(false)

    subject(/*imagine double('a','b','X')*/ undefined, config)

    td.verify(ensureDemonstration(call))
    td.verify(fail(double, call, config))
    assert.equal(td.explain(notifySatisfiedMatchers).callCount, 0)
    assert.equal(td.explain(warnIfAlsoStubbed).callCount, 0)
  },
  'demonstration missing blows up': () => {
  }

}
<|endoftext|>


<commit_before>describe("VglTextGeometry component", function() {
    const {VglTextGeometry, VglNamespace, VglFont} = VueGL;
    const assert = chai.assert;
    before(function() {
        this.typeface = encodeURIComponent('{"glyphs":{"a":{"x_min":0,"x_max":698.609375,"ha":794,"o":"m 698 0 q 661 -12 679 -7 q 615 -17 643 -17 q 536 12 564 -17 q 500 96 508 41 q 384 6 456 37 q 236 -25 312 -25 q 65 31 130 -25 q 0 194 0 88 q 118 390 0 334 q 328 435 180 420 q 488 483 476 451 q 495 523 495 504 q 442 619 495 584 q 325 654 389 654 q 209 617 257 654 q 152 513 161 580 l 33 513 q 123 705 33 633 q 332 772 207 772 q 528 712 448 772 q 617 531 617 645 l 617 163 q 624 108 617 126 q 664 90 632 90 l 698 94 l 698 0 m 491 262 l 491 372 q 272 329 350 347 q 128 201 128 294 q 166 113 128 144 q 264 83 205 83 q 414 130 346 83 q 491 262 491 183 "}},"cssFontWeight":"normal","ascender":1189,"underlinePosition":-100,"cssFontStyle":"normal","boundingBox":{"yMin":-334,"xMin":-111,"yMax":1189,"xMax":1672},"resolution":1000,"original_font_information":{"postscript_name":"Helvetiker-Regular","version_string":"Version 1.00 2004 initial release","vendor_url":"http://www.magenta.gr/","full_font_name":"Helvetiker","font_family_name":"Helvetiker","copyright":"Copyright (c) Μagenta ltd, 2004","description":"","trademark":"","designer":"","designer_url":"","unique_font_identifier":"Μagenta ltd:Helvetiker:22-10-104","license_url":"http://www.ellak.gr/fonts/MgOpen/license.html","license_description":"Copyright (c) 2004 by MAGENTA Ltd. All Rights Reserved.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (\"Fonts\") and associated documentation files (the \"Font Software\"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: \r\n\r\nThe above copyright and this permission notice shall be included in all copies of one or more of the Font Software typefaces.\r\n\r\nThe Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing the word \"MgOpen\", or if the modifications are accepted for inclusion in the Font Software itself by the each appointed Administrator.\r\n\r\nThis License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the \"MgOpen\" name.\r\n\r\nThe Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. \r\n\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL MAGENTA OR PERSONS OR BODIES IN CHARGE OF ADMINISTRATION AND MAINTENANCE OF THE FONT SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.","manufacturer_name":"Μagenta ltd","font_sub_family_name":"Regular"},"descender":-334,"familyName":"Helvetiker","lineHeight":1522,"underlineThickness":50}');
    });
    describe("Parameters of a instance should be same as the component properties.", function() {
        it("When properties are number.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font ref="font" name="font" src="data:,${this.typeface}" /><vgl-text-geometry ref="geo" font="font" :size="10" :height="6" :curve-segments="8" :bevel-enabled="true" :bevel-thickness="3" :bevel-size="6" :bevel-segments="2">a</vgl-text-geometry></vgl-namespace>`,
                components: {VglTextGeometry, VglNamespace, VglFont}
            }).$mount();
            vm.$refs.font.$watch("inst", () => {
                vm.$nextTick(() => {
                    try {
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.size, 10);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.height, 6);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.curveSegments, 8);
                        assert.isTrue(vm.$refs.geo.inst.parameters.parameters.bevelEnabled);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelThickness, 3);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelSize, 6);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelSegments, 2);
                        done();
                    } catch(e) {
                        done(e);
                    }
                });
            });
        });
        it("When properties are string.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font ref="font" name="font" src="data:,${this.typeface}" /><vgl-text-geometry ref="geo" font="font" size="10" height="6" curve-segments="8" bevel-enabled bevel-thickness="3" bevel-size="6" bevel-segments="2">a</vgl-text-geometry></vgl-namespace>`,
                components: {VglTextGeometry, VglNamespace, VglFont}
            }).$mount();
            vm.$refs.font.$watch("inst", () => {
                vm.$nextTick(() => {
                    try {
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.size, 10);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.height, 6);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.curveSegments, 8);
                        assert.isTrue(vm.$refs.geo.inst.parameters.parameters.bevelEnabled);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelThickness, 3);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelSize, 6);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelSegments, 2);
                        done();
                    } catch(e) {
                        done(e);
                    }
                });
            });
        });
        it("When properties are undefined.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font ref="font" name="font" src="${this.typeface}" /><vgl-text-geometry ref="geo" font="font">a</vgl-text-geometry></vgl-namespace>`,
                components: {VglTextGeometry, VglNamespace, VglFont}
            }).$mount();
            vm.$refs.font.$watch("inst", () => {
                vm.$nextTick(() => {
                    try {
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.size, 100);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.height, 50);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.curveSegments, 12);
                        assert.isFalse(vm.$refs.geo.inst.parameters.parameters.bevelEnabled);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelThickness, 10);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelSize, 8);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelSegments, 3);
                        done();
                    } catch(e) {
                        done(e);
                    }
                });
            });
        });
    });
    describe("Instance should be recreated when a property changed.", function() {
        it("When the width property changes.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font ref="font" name="font" src="base/test/helvetiker_regular.typeface.json" /><vgl-text-geometry ref="geo" font="font" :size="size">a</vgl-text-geometry></vgl-namespace>`,
                components: {VglTextGeometry, VglNamespace, VglFont},
                data: {size: 120}
            }).$mount();
            vm.$refs.font.$watch("inst", () => {
                vm.$nextTick(() => {
                    const before = vm.$refs.geo.inst;
                    vm.size = 110;
                    vm.$nextTick(() => {
                        try {
                            assert.notEqual(before, vm.$refs.geo.inst);
                            done();
                        } catch(e) {
                            done(e);
                        }
                    });
                });
            });
        });
    });
});
<commit_msg>Pre-load and pass typefaces as data uris.<commit_after>describe("VglTextGeometry component", function() {
    const {VglTextGeometry, VglNamespace, VglFont} = VueGL;
    const assert = chai.assert;
    before(function(done) {
        const xhr = new XMLHttpRequest();
        xhr.addEventListener("load", () => {
            this.typeface = `data:,${encodeURIComponent(xhr.responseText)}`;
            done();
        }, false);
        xhr.open("GET", "base/test/helvetiker_regular.typeface.json");
        xhr.send();
    });
    describe("Parameters of a instance should be same as the component properties.", function() {
        it("When properties are number.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font ref="font" name="font" src="${this.typeface}" /><vgl-text-geometry ref="geo" font="font" :size="10" :height="6" :curve-segments="8" :bevel-enabled="true" :bevel-thickness="3" :bevel-size="6" :bevel-segments="2">a</vgl-text-geometry></vgl-namespace>`,
                components: {VglTextGeometry, VglNamespace, VglFont}
            }).$mount();
            vm.$refs.font.$watch("inst", () => {
                vm.$nextTick(() => {
                    try {
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.size, 10);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.height, 6);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.curveSegments, 8);
                        assert.isTrue(vm.$refs.geo.inst.parameters.parameters.bevelEnabled);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelThickness, 3);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelSize, 6);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelSegments, 2);
                        done();
                    } catch(e) {
                        done(e);
                    }
                });
            });
        });
        it("When properties are string.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font ref="font" name="font" src="${this.typeface}" /><vgl-text-geometry ref="geo" font="font" size="10" height="6" curve-segments="8" bevel-enabled bevel-thickness="3" bevel-size="6" bevel-segments="2">a</vgl-text-geometry></vgl-namespace>`,
                components: {VglTextGeometry, VglNamespace, VglFont}
            }).$mount();
            vm.$refs.font.$watch("inst", () => {
                vm.$nextTick(() => {
                    try {
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.size, 10);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.height, 6);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.curveSegments, 8);
                        assert.isTrue(vm.$refs.geo.inst.parameters.parameters.bevelEnabled);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelThickness, 3);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelSize, 6);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelSegments, 2);
                        done();
                    } catch(e) {
                        done(e);
                    }
                });
            });
        });
        it("When properties are undefined.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font ref="font" name="font" src="${this.typeface}" /><vgl-text-geometry ref="geo" font="font">a</vgl-text-geometry></vgl-namespace>`,
                components: {VglTextGeometry, VglNamespace, VglFont}
            }).$mount();
            vm.$refs.font.$watch("inst", () => {
                vm.$nextTick(() => {
                    try {
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.size, 100);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.height, 50);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.curveSegments, 12);
                        assert.isFalse(vm.$refs.geo.inst.parameters.parameters.bevelEnabled);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelThickness, 10);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelSize, 8);
                        assert.strictEqual(vm.$refs.geo.inst.parameters.parameters.bevelSegments, 3);
                        done();
                    } catch(e) {
                        done(e);
                    }
                });
            });
        });
    });
    describe("Instance should be recreated when a property changed.", function() {
        it("When the width property changes.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font ref="font" name="font" src="base/test/helvetiker_regular.typeface.json" /><vgl-text-geometry ref="geo" font="font" :size="size">a</vgl-text-geometry></vgl-namespace>`,
                components: {VglTextGeometry, VglNamespace, VglFont},
                data: {size: 120}
            }).$mount();
            vm.$refs.font.$watch("inst", () => {
                vm.$nextTick(() => {
                    const before = vm.$refs.geo.inst;
                    vm.size = 110;
                    vm.$nextTick(() => {
                        try {
                            assert.notEqual(before, vm.$refs.geo.inst);
                            done();
                        } catch(e) {
                            done(e);
                        }
                    });
                });
            });
        });
    });
});
<|endoftext|>


<commit_before>/**
 * Copyright 2004-present Facebook. All Rights Reserved.
 *
 * @flow
 * @providesModule XMLHttpRequestBase
 */
'use strict';

/**
 * Shared base for platform-specific XMLHttpRequest implementations.
 */
class XMLHttpRequestBase {

  UNSENT: number;
  OPENED: number;
  HEADERS_RECEIVED: number;
  LOADING: number;
  DONE: number;

  onreadystatechange: ?Function;
  onload: ?Function;
  upload: any;
  readyState: number;
  responseHeaders: ?Object;
  responseText: ?string;
  status: ?string;

  _method: ?string;
  _url: ?string;
  _headers: Object;
  _sent: boolean;
  _aborted: boolean;

  constructor() {
    this.UNSENT = 0;
    this.OPENED = 1;
    this.HEADERS_RECEIVED = 2;
    this.LOADING = 3;
    this.DONE = 4;

    this.onreadystatechange = undefined;
    this.upload = undefined; /* Upload not supported */
    this.readyState = this.UNSENT;
    this.responseHeaders = undefined;
    this.responseText = undefined;
    this.status = undefined;

    this._method = null;
    this._url = null;
    this._headers = {};
    this._sent = false;
    this._aborted = false;
  }

  getAllResponseHeaders(): ?string {
    if (this.responseHeaders) {
      var headers = [];
      for (var headerName in this.responseHeaders) {
        headers.push(headerName + ': ' + this.responseHeaders[headerName]);
      }
      return headers.join('\n');
    }
    return null;
  }

  getResponseHeader(header: string): ?string {
    if (this.responseHeaders) {
      var value = this.responseHeaders[header];
      return value !== undefined ? value : null;
    }
    return null;
  }

  setRequestHeader(header: string, value: any): void {
    this._headers[header] = value;
  }

  open(method: string, url: string, async: ?boolean): void {
    /* Other optional arguments are not supported */
    if (this.readyState !== this.UNSENT) {
      throw new Error('Cannot open, already sending');
    }
    if (async !== undefined && !async) {
      // async is default
      throw new Error('Synchronous http requests are not supported');
    }
    this._method = method;
    this._url = url;
    this._aborted = false;
    this._setReadyState(this.OPENED);
  }

  sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
    throw new Error('Subclass must define sendImpl method');
  }

  abortImpl(): void {
    throw new Error('Subclass must define abortImpl method');
  }

  send(data: any): void {
    if (this.readyState !== this.OPENED) {
      throw new Error('Request has not been opened');
    }
    if (this._sent) {
      throw new Error('Request has already been sent');
    }
    this._sent = true;
    this.sendImpl(this._method, this._url, this._headers, data);
  }

  abort(): void {
    this.abortImpl();
    // only call onreadystatechange if there is something to abort,
    // below logic is per spec
    if (!(this.readyState === this.UNSENT ||
        (this.readyState === this.OPENED && !this._sent) ||
        this.readyState === this.DONE)) {
      this._sent = false;
      this._setReadyState(this.DONE);
    }
    if (this.readyState === this.DONE) {
      this._sendLoad();
    }
    this.readyState = this.UNSENT;
    this._aborted = true;
  }

  callback(status: string, responseHeaders: ?Object, responseText: string): void {
    if (this._aborted) {
      return;
    }
    this.status = status;
    this.responseHeaders = responseHeaders;
    this.responseText = responseText;
    this._setReadyState(this.DONE);
    this._sendLoad();
  }

  _setReadyState(newState: number): void {
    this.readyState = newState;
    // TODO: workaround flow bug with nullable function checks
    var onreadystatechange = this.onreadystatechange;
    if (onreadystatechange) {
      // We should send an event to handler, but since we don't process that
      // event anywhere, let's leave it empty
      onreadystatechange(null);
    }
  }

  _sendLoad(): void {
    // TODO: workaround flow bug with nullable function checks
    var onload = this.onload;
    if (onload) {
      // We should send an event to handler, but since we don't process that
      // event anywhere, let's leave it empty
      onload(null);
    }
  }
}

module.exports = XMLHttpRequestBase;
<commit_msg>[react_native] JS files from D2012956:     [react_native] Never return null from XHR.getAllResponseHeaders if request has completed<commit_after>/**
 * Copyright 2004-present Facebook. All Rights Reserved.
 *
 * @flow
 * @providesModule XMLHttpRequestBase
 */
'use strict';

/**
 * Shared base for platform-specific XMLHttpRequest implementations.
 */
class XMLHttpRequestBase {

  UNSENT: number;
  OPENED: number;
  HEADERS_RECEIVED: number;
  LOADING: number;
  DONE: number;

  onreadystatechange: ?Function;
  onload: ?Function;
  upload: any;
  readyState: number;
  responseHeaders: ?Object;
  responseText: ?string;
  status: ?string;

  _method: ?string;
  _url: ?string;
  _headers: Object;
  _sent: boolean;
  _aborted: boolean;

  constructor() {
    this.UNSENT = 0;
    this.OPENED = 1;
    this.HEADERS_RECEIVED = 2;
    this.LOADING = 3;
    this.DONE = 4;

    this.onreadystatechange = undefined;
    this.upload = undefined; /* Upload not supported */
    this.readyState = this.UNSENT;
    this.responseHeaders = undefined;
    this.responseText = undefined;
    this.status = undefined;

    this._method = null;
    this._url = null;
    this._headers = {};
    this._sent = false;
    this._aborted = false;
  }

  getAllResponseHeaders(): ?string {
    if (this.responseHeaders) {
      var headers = [];
      for (var headerName in this.responseHeaders) {
        headers.push(headerName + ': ' + this.responseHeaders[headerName]);
      }
      return headers.join('\n');
    }
    // according to the spec, return null <==> no response has been received
    return null;
  }

  getResponseHeader(header: string): ?string {
    if (this.responseHeaders) {
      var value = this.responseHeaders[header];
      return value !== undefined ? value : null;
    }
    return null;
  }

  setRequestHeader(header: string, value: any): void {
    this._headers[header] = value;
  }

  open(method: string, url: string, async: ?boolean): void {
    /* Other optional arguments are not supported */
    if (this.readyState !== this.UNSENT) {
      throw new Error('Cannot open, already sending');
    }
    if (async !== undefined && !async) {
      // async is default
      throw new Error('Synchronous http requests are not supported');
    }
    this._method = method;
    this._url = url;
    this._aborted = false;
    this._setReadyState(this.OPENED);
  }

  sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
    throw new Error('Subclass must define sendImpl method');
  }

  abortImpl(): void {
    throw new Error('Subclass must define abortImpl method');
  }

  send(data: any): void {
    if (this.readyState !== this.OPENED) {
      throw new Error('Request has not been opened');
    }
    if (this._sent) {
      throw new Error('Request has already been sent');
    }
    this._sent = true;
    this.sendImpl(this._method, this._url, this._headers, data);
  }

  abort(): void {
    this.abortImpl();
    // only call onreadystatechange if there is something to abort,
    // below logic is per spec
    if (!(this.readyState === this.UNSENT ||
        (this.readyState === this.OPENED && !this._sent) ||
        this.readyState === this.DONE)) {
      this._sent = false;
      this._setReadyState(this.DONE);
    }
    if (this.readyState === this.DONE) {
      this._sendLoad();
    }
    this.readyState = this.UNSENT;
    this._aborted = true;
  }

  callback(status: string, responseHeaders: ?Object, responseText: string): void {
    if (this._aborted) {
      return;
    }
    this.status = status;
    this.responseHeaders = responseHeaders || {};
    this.responseText = responseText;
    this._setReadyState(this.DONE);
    this._sendLoad();
  }

  _setReadyState(newState: number): void {
    this.readyState = newState;
    // TODO: workaround flow bug with nullable function checks
    var onreadystatechange = this.onreadystatechange;
    if (onreadystatechange) {
      // We should send an event to handler, but since we don't process that
      // event anywhere, let's leave it empty
      onreadystatechange(null);
    }
  }

  _sendLoad(): void {
    // TODO: workaround flow bug with nullable function checks
    var onload = this.onload;
    if (onload) {
      // We should send an event to handler, but since we don't process that
      // event anywhere, let's leave it empty
      onload(null);
    }
  }
}

module.exports = XMLHttpRequestBase;
<|endoftext|>


<commit_before>/**
* The `Matter.Pairs` module contains methods for creating and manipulating collision pair sets.
*
* @class Pairs
*/

var Pairs = {};

module.exports = Pairs;

var Pair = require('./Pair');
var Common = require('../core/Common');

(function() {

    var _pairMaxIdleLife = 1000;

    /**
     * Creates a new pairs structure.
     * @method create
     * @param {object} options
     * @return {pairs} A new pairs structure
     */
    Pairs.create = function(options) {
        return Common.extend({
            table: {},
            list: [],
            collisionStart: [],
            collisionActive: [],
            collisionEnd: []
        }, options);
    };

    /**
     * Updates pairs given a list of collisions.
     * @method update
     * @param {object} pairs
     * @param {collision[]} collisions
     * @param {number} timestamp
     */
    Pairs.update = function(pairs, collisions, timestamp) {
        var pairsList = pairs.list,
            pairsTable = pairs.table,
            collisionStart = pairs.collisionStart,
            collisionEnd = pairs.collisionEnd,
            collisionActive = pairs.collisionActive,
            collision,
            pairId,
            pair,
            i;

        // clear collision state arrays, but maintain old reference
        collisionStart.length = 0;
        collisionEnd.length = 0;
        collisionActive.length = 0;

        for (i = 0; i < pairsList.length; i++) {
            pairsList[i].confirmedActive = false;
        }

        for (i = 0; i < collisions.length; i++) {
            collision = collisions[i];

            if (collision.collided) {
                pairId = Pair.id(collision.bodyA, collision.bodyB);

                pair = pairsTable[pairId];

                if (pair) {
                    // pair already exists (but may or may not be active)
                    if (pair.isActive) {
                        // pair exists and is active
                        collisionActive.push(pair);
                    } else {
                        // pair exists but was inactive, so a collision has just started again
                        collisionStart.push(pair);
                    }

                    // update the pair
                    Pair.update(pair, collision, timestamp);
                    pair.confirmedActive = true;
                } else {
                    // pair did not exist, create a new pair
                    pair = Pair.create(collision, timestamp);
                    pairsTable[pairId] = pair;

                    // push the new pair
                    collisionStart.push(pair);
                    pairsList.push(pair);
                }
            }
        }

        // deactivate previously active pairs that are now inactive
        for (i = 0; i < pairsList.length; i++) {
            pair = pairsList[i];
            if (!pair.confirmedActive) {
                Pair.setActive(pair, false, timestamp);
                collisionEnd.push(pair);
            }
        }
    };

    /**
     * Finds and removes pairs that have been inactive for a set amount of time.
     * @method removeOld
     * @param {object} pairs
     * @param {number} timestamp
     */
    Pairs.removeOld = function(pairs, timestamp) {
        var pairsList = pairs.list,
            pairsTable = pairs.table,
            indexesToRemove = [],
            pair,
            collision,
            pairIndex,
            i;

        for (i = 0; i < pairsList.length; i++) {
            pair = pairsList[i];
            collision = pair.collision;

            // never remove sleeping pairs
            if (collision.bodyA.isSleeping || collision.bodyB.isSleeping) {
                pair.timeUpdated = timestamp;
                continue;
            }

            // if pair is inactive for too long, mark it to be removed
            if (timestamp - pair.timeUpdated > _pairMaxIdleLife) {
                indexesToRemove.push(i);
            }
        }

        // remove marked pairs
        for (i = 0; i < indexesToRemove.length; i++) {
            pairIndex = indexesToRemove[i] - i;
            pair = pairsList[pairIndex];
            delete pairsTable[pair.id];
            pairsList.splice(pairIndex, 1);
        }
    };

    /**
     * Clears the given pairs structure.
     * @method clear
     * @param {pairs} pairs
     * @return {pairs} pairs
     */
    Pairs.clear = function(pairs) {
        pairs.table = {};
        pairs.list.length = 0;
        pairs.collisionStart.length = 0;
        pairs.collisionActive.length = 0;
        pairs.collisionEnd.length = 0;
        return pairs;
    };

})();
<commit_msg>Fixed end collision triggering more than once<commit_after>/**
* The `Matter.Pairs` module contains methods for creating and manipulating collision pair sets.
*
* @class Pairs
*/

var Pairs = {};

module.exports = Pairs;

var Pair = require('./Pair');
var Common = require('../core/Common');

(function() {

    var _pairMaxIdleLife = 1000;

    /**
     * Creates a new pairs structure.
     * @method create
     * @param {object} options
     * @return {pairs} A new pairs structure
     */
    Pairs.create = function(options) {
        return Common.extend({
            table: {},
            list: [],
            collisionStart: [],
            collisionActive: [],
            collisionEnd: []
        }, options);
    };

    /**
     * Updates pairs given a list of collisions.
     * @method update
     * @param {object} pairs
     * @param {collision[]} collisions
     * @param {number} timestamp
     */
    Pairs.update = function(pairs, collisions, timestamp) {
        var pairsList = pairs.list,
            pairsTable = pairs.table,
            collisionStart = pairs.collisionStart,
            collisionEnd = pairs.collisionEnd,
            collisionActive = pairs.collisionActive,
            collision,
            pairId,
            pair,
            i;

        // clear collision state arrays, but maintain old reference
        collisionStart.length = 0;
        collisionEnd.length = 0;
        collisionActive.length = 0;

        for (i = 0; i < pairsList.length; i++) {
            pairsList[i].confirmedActive = false;
        }

        for (i = 0; i < collisions.length; i++) {
            collision = collisions[i];

            if (collision.collided) {
                pairId = Pair.id(collision.bodyA, collision.bodyB);

                pair = pairsTable[pairId];

                if (pair) {
                    // pair already exists (but may or may not be active)
                    if (pair.isActive) {
                        // pair exists and is active
                        collisionActive.push(pair);
                    } else {
                        // pair exists but was inactive, so a collision has just started again
                        collisionStart.push(pair);
                    }

                    // update the pair
                    Pair.update(pair, collision, timestamp);
                    pair.confirmedActive = true;
                } else {
                    // pair did not exist, create a new pair
                    pair = Pair.create(collision, timestamp);
                    pairsTable[pairId] = pair;

                    // push the new pair
                    collisionStart.push(pair);
                    pairsList.push(pair);
                }
            }
        }

        // deactivate previously active pairs that are now inactive
        for (i = 0; i < pairsList.length; i++) {
            pair = pairsList[i];
            if (pair.isActive && !pair.confirmedActive) {
                Pair.setActive(pair, false, timestamp);
                collisionEnd.push(pair);
            }
        }
    };

    /**
     * Finds and removes pairs that have been inactive for a set amount of time.
     * @method removeOld
     * @param {object} pairs
     * @param {number} timestamp
     */
    Pairs.removeOld = function(pairs, timestamp) {
        var pairsList = pairs.list,
            pairsTable = pairs.table,
            indexesToRemove = [],
            pair,
            collision,
            pairIndex,
            i;

        for (i = 0; i < pairsList.length; i++) {
            pair = pairsList[i];
            collision = pair.collision;

            // never remove sleeping pairs
            if (collision.bodyA.isSleeping || collision.bodyB.isSleeping) {
                pair.timeUpdated = timestamp;
                continue;
            }

            // if pair is inactive for too long, mark it to be removed
            if (timestamp - pair.timeUpdated > _pairMaxIdleLife) {
                indexesToRemove.push(i);
            }
        }

        // remove marked pairs
        for (i = 0; i < indexesToRemove.length; i++) {
            pairIndex = indexesToRemove[i] - i;
            pair = pairsList[pairIndex];
            delete pairsTable[pair.id];
            pairsList.splice(pairIndex, 1);
        }
    };

    /**
     * Clears the given pairs structure.
     * @method clear
     * @param {pairs} pairs
     * @return {pairs} pairs
     */
    Pairs.clear = function(pairs) {
        pairs.table = {};
        pairs.list.length = 0;
        pairs.collisionStart.length = 0;
        pairs.collisionActive.length = 0;
        pairs.collisionEnd.length = 0;
        return pairs;
    };

})();
<|endoftext|>