<commit_before>describe("Component()", function() {
  var subject = new Component();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(100);
    expect(subject.values.height).toEqual(100);
    expect(subject.values.xLeft).toEqual(0);
    expect(subject.values.xCenter).toEqual(50);
    expect(subject.values.xRight).toEqual(100);
  });

});

describe("AxialComponent()", function() {
  var subject = new AxialComponent();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(100);
    expect(subject.values.height).toEqual(100);
    expect(subject.values.xLeft).toEqual(-50);
    expect(subject.values.xCenter).toEqual(0);
    expect(subject.values.xRight).toEqual(50);
  });

});

describe("Resistor()", function() {
  var subject = new Resistor();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(10);
    expect(subject.values.height).toEqual(60);
  });

});

describe("CeramicCapacitor()", function() {
  var subject = new CeramicCapacitor();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(10);
    expect(subject.values.height).toEqual(25);
  });

});

describe("SpstSwitch()", function() {
  var subject = new SpstSwitch();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(20);
    expect(subject.values.height).toEqual(40);
  });

});

describe("Led()", function() {
  var subject = new Led();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(20);
    expect(subject.values.height).toEqual(30);
    expect(subject.values.color).toEqual('red');
  });

});

describe("LM555()", function() {
  var subject = new LM555();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(100);
    expect(subject.values.height).toEqual(120);
  });

});

describe("PowerRail()", function() {
  var subject = new PowerRail();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(30);
    expect(subject.values.width).toEqual(400);
    expect(subject.values.height).toEqual(0);
    expect(subject.values.label).toEqual('Vcc');
  });

});

describe("GroundRail()", function() {
  var subject = new GroundRail();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(340);
    expect(subject.values.width).toEqual(400);
    expect(subject.values.height).toEqual(0);
    expect(subject.values.label).toEqual('Gnd');
  });

});

describe("ConnectingWire()", function() {
  var subject = new ConnectingWire();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
  });

});
<commit_msg>pin specs<commit_after>describe("Component()", function() {
  var subject = new Component();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(100);
    expect(subject.values.height).toEqual(100);
    expect(subject.values.xLeft).toEqual(0);
    expect(subject.values.xCenter).toEqual(50);
    expect(subject.values.xRight).toEqual(100);
  });

});

describe("AxialComponent()", function() {
  var subject = new AxialComponent();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(100);
    expect(subject.values.height).toEqual(100);
    expect(subject.values.xLeft).toEqual(-50);
    expect(subject.values.xCenter).toEqual(0);
    expect(subject.values.xRight).toEqual(50);
  });

  it("should have two output pins", function() {
    expect(subject.pinPosition('1')).toEqual({ x: 0, y: 0});
    expect(subject.pinPosition('2')).toEqual({ x: 0, y: 100});
  });

});

describe("Resistor()", function() {
  var subject = new Resistor();

  it("should construct object with default values reset", function() {
    expect(subject.values.width).toEqual(10);
    expect(subject.values.height).toEqual(60);
    expect(subject.values.xLeft).toEqual(-5);
    expect(subject.values.xCenter).toEqual(0);
    expect(subject.values.xRight).toEqual(5);
  });

  it("should have two output pins", function() {
    expect(subject.pinPosition('1')).toEqual({ x: 0, y: 0});
    expect(subject.pinPosition('2')).toEqual({ x: 0, y: 60});
  });

});

describe("CeramicCapacitor()", function() {
  var subject = new CeramicCapacitor();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(10);
    expect(subject.values.height).toEqual(25);
  });

  it("should have two output pins", function() {
    expect(subject.pinPosition('1')).toEqual({ x: 0, y: 0});
    expect(subject.pinPosition('2')).toEqual({ x: 0, y: 25});
  });

});

describe("SpstSwitch()", function() {
  var subject = new SpstSwitch();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(20);
    expect(subject.values.height).toEqual(40);
  });

  it("should have two output pins", function() {
    expect(subject.pinPosition('1')).toEqual({ x: 0, y: 0});
    expect(subject.pinPosition('2')).toEqual({ x: 0, y: 40});
  });

});

describe("Led()", function() {
  var subject = new Led();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(20);
    expect(subject.values.height).toEqual(30);
    expect(subject.values.color).toEqual('red');
  });

  it("should have two output pins", function() {
    expect(subject.pinPosition('1')).toEqual({ x: 0, y: 0});
    expect(subject.pinPosition('2')).toEqual({ x: 0, y: 30});
  });

});

describe("LM555()", function() {
  var subject = new LM555();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
    expect(subject.values.width).toEqual(100);
    expect(subject.values.height).toEqual(120);
    expect(subject.values.xLeft).toEqual(0);
    expect(subject.values.xCenter).toEqual(50);
    expect(subject.values.xRight).toEqual(100);
  });

  it("should have eight output pins", function() {
    expect(subject.pinPosition('1')).toEqual({ x: 40,  y: 120 });
    expect(subject.pinPosition('2')).toEqual({ x: 0,   y: 90 });
    expect(subject.pinPosition('3')).toEqual({ x: 100, y: 60 });
    expect(subject.pinPosition('4')).toEqual({ x: 60,  y: 0 });
    expect(subject.pinPosition('5')).toEqual({ x: 60,  y: 120 });
    expect(subject.pinPosition('6')).toEqual({ x: 0,   y: 70 });
    expect(subject.pinPosition('7')).toEqual({ x: 0,   y: 30 });
    expect(subject.pinPosition('8')).toEqual({ x: 40,  y: 0 });
  });

});

describe("PowerRail()", function() {
  var subject = new PowerRail();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(30);
    expect(subject.values.width).toEqual(400);
    expect(subject.values.height).toEqual(0);
    expect(subject.values.label).toEqual('Vcc');
  });

});

describe("GroundRail()", function() {
  var subject = new GroundRail();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(340);
    expect(subject.values.width).toEqual(400);
    expect(subject.values.height).toEqual(0);
    expect(subject.values.label).toEqual('Gnd');
  });

});

describe("ConnectingWire()", function() {
  var subject = new ConnectingWire();

  it("should construct object with default values reset", function() {
    expect(subject.values.x).toEqual(0);
    expect(subject.values.y).toEqual(0);
  });

});
<|endoftext|>


<commit_before>var Packet = require('./packet');
var GameServer = require('./GameServer');

function PlayerTracker(gameServer, socket) {
    this.pID = -1;
    this.disconnect = -1; // Disconnection
    this.name = "";
    this.skin = "";
    this.gameServer = gameServer;
    this.socket = socket;
    this.nodeAdditionQueue = [];
    this.nodeDestroyQueue = [];
    this.visibleNodes = [];
    this.collidingNodes = []; // Perfomance save; all nodes colliding with player's cells
    this.cells = [];
    this.mergeOverride = false; // Triggered by console command
    this.score = 0; // Needed for leaderboard
    this.scale = 1;
    this.isMassChanged = true;

    this.mouse = {
        x: 0,
        y: 0
    };
    this.shouldMoveCells = true; // False if the mouse packet wasn't triggered
    this.notMoved = false; // If one of cells have been moved after splitting this is triggered
    this.movePacketTriggered = false;
    this.mouseCells = []; // For individual cell movement
    this.tickLeaderboard = 0;
    this.tickViewBox = 0;

    this.team = 0;
    this.spectate = false;
    this.freeRoam = false; // Free-roam mode enables player to move in spectate mode

    // Viewing box
    this.sightWidth = 0;
    this.sightHeight = 0;
    this.centerPos = { // Center of map
        x: 3000,
        y: 3000
    };
    this.viewBox = {
        left: 0,
        top: 0,
        right: 0,
        bottom: 0,
        halfWidth: 0,
        halfHeight: 0
    };

    // Scramble the coordinate system for anti-raga
    this.scrambleX = 0;
    this.scrambleY = 0;

    // Gamemode function
    if (gameServer) {
        // Find center
        var width = gameServer.config.borderRight - gameServer.config.borderLeft;
        var height = gameServer.config.borderBottom - gameServer.config.borderTop;
        this.centerPos.x = gameServer.config.borderLeft + width / 2;
        this.centerPos.y = gameServer.config.borderTop + height / 2;
        // Player id
        this.pID = gameServer.getNewPlayerID();
        // Gamemode function
        gameServer.gameMode.onPlayerInit(this);
        // Only scramble if enabled in config
        if (gameServer.config.serverScrambleCoords == 1) {
            // avoid mouse packet limitations
            var maxScrambleX = Math.max(0, 32767 - 2000 - width / 2);
            var maxScrambleY = Math.max(0, 32767 - 2000 - height / 2);
            this.scrambleX = Math.floor(maxScrambleX * Math.random());
            this.scrambleY = Math.floor(maxScrambleY * Math.random());
        }
    }
}

module.exports = PlayerTracker;

// Setters/Getters

PlayerTracker.prototype.setName = function(name) {
    this.name = name;
};

PlayerTracker.prototype.getName = function() {
    return this.name;
};

PlayerTracker.prototype.setSkin = function (skin) {
    this.skin = skin;
};

PlayerTracker.prototype.getSkin = function () {
    return this.skin;
};

PlayerTracker.prototype.getScore = function () {
    if (this.isMassChanged)
        this.updateMass();
    return this.score;
};

PlayerTracker.prototype.getScale = function () {
    if (this.isMassChanged)
        this.updateMass();
    return this.scale;
};

PlayerTracker.prototype.updateMass = function () {
    var totalSize = 0;
    var totalMass = 0;
    for (var i = 0; i < this.cells.length; i++) {
        var node = this.cells[i];
        if (node == null) continue;
        totalSize += node.getSize();
        totalMass += node.getMass();
    }
    if (totalSize == 0) {
        //do not change scale for spectators or not in game players
        this.score = 0;
    } else {
        this.score = totalMass;
        this.scale = Math.pow(Math.min(64 / totalSize, 1), 0.4);
    }
    this.isMassChanged = false;
};

PlayerTracker.prototype.massChanged = function () {
    this.isMassChanged = true;
};

PlayerTracker.prototype.setColor = function(color) {
    this.color.r = color.r;
    this.color.g = color.g;
    this.color.b = color.b;
};

PlayerTracker.prototype.getTeam = function() {
    return this.team;
};

// Functions

PlayerTracker.prototype.joinGame = function (name, skin) {
    if (this.cells.length > 0) return;
    if (name == null) name = "";
    if (skin == null) skin = "";
    this.setName(name);
    this.setSkin(skin);
    this.spectate = false;

    this.socket.sendPacket(new Packet.ClearNodes());
    this.collidingNodes = [];
    this.visibleNodes = [];         // Reset visible nodes
    this.nodeDestroyQueue = [];     // Reset destroy queue
    this.nodeAdditionQueue = [];    // Reset addition queue

    this.gameServer.gameMode.onPlayerSpawn(this.gameServer, this);
};

PlayerTracker.prototype.update = function () {
    // if initialization is not complete yet then do not update
    if (this.socket.packetHandler.protocol == 0)
        return;

    // First reset colliding nodes
    this.collidingNodes = [];

    // Move packet update
    if (this.movePacketTriggered) {
        this.movePacketTriggered = false;
        this.shouldMoveCells = true;
    } else {
        this.shouldMoveCells = false;
    }
    // Actions buffer (So that people cant spam packets)
    if (this.socket.packetHandler.pressSpace) { // Split cell
        if (!this.mergeOverride) this.gameServer.gameMode.pressSpace(this.gameServer, this);
        this.socket.packetHandler.pressSpace = false;
    }

    if (this.socket.packetHandler.pressW) { // Eject mass
        this.gameServer.gameMode.pressW(this.gameServer, this);
        this.socket.packetHandler.pressW = false;
    }

    if (this.socket.packetHandler.pressQ) { // Q Press
        this.gameServer.gameMode.pressQ(this.gameServer, this);
        this.socket.packetHandler.pressQ = false;
    }

    var updateNodes = []; // Nodes that need to be updated via packet

    // Remove nodes from visible nodes if possible
    var d = 0;
    while (d < this.nodeDestroyQueue.length) {
        var index = this.visibleNodes.indexOf(this.nodeDestroyQueue[d]);
        if (index > -1) {
            this.visibleNodes.splice(index, 1);
            d++; // Increment
        } else {
            // Node was never visible anyways
            this.nodeDestroyQueue.splice(d, 1);
        }
    }

    // Get visible nodes every 400 ms
    var nonVisibleNodes = []; // Nodes that are not visible
    if (this.tickViewBox <= 0) {
        var newVisible = this.getVisibleNodes();
        try { // Add a try block in any case

            // Compare and destroy nodes that are not seen
            for (var i = 0; i < this.visibleNodes.length; i++) {
                var index = newVisible.indexOf(this.visibleNodes[i]);
                if (index == -1) {
                    // Not seen by the client anymore
                    nonVisibleNodes.push(this.visibleNodes[i]);
                }
            }

            // Add nodes to client's screen if client has not seen it already
            for (var i = 0; i < newVisible.length; i++) {
                var index = this.visibleNodes.indexOf(newVisible[i]);
                if (index == -1) {
                    updateNodes.push(newVisible[i]);
                }
            }

            // Scramble minimap
            // TODO: need to send it more rarely (once per second or something like that)
            // also need to make sure that we send it before first cell update
            //var border = {
            //    left: Math.max(this.viewBox.left - this.viewBox.halfWidth, this.gameServer.config.borderLeft) + this.scrambleX,
            //    top: Math.max(this.viewBox.top - this.viewBox.halfHeight, this.gameServer.config.borderTop) + this.scrambleY,
            //    right: Math.min(this.viewBox.right + this.viewBox.halfWidth, this.gameServer.config.borderRight) + this.scrambleX,
            //    bottom: Math.min(this.viewBox.bottom + this.viewBox.halfHeight, this.gameServer.config.borderBottom) + this.scrambleY
            //};
            //this.socket.sendPacket(new Packet.SetBorder(border));
        } catch (err) {
            console.error(err);
        }

        this.visibleNodes = newVisible;
        // Reset Ticks
        this.tickViewBox = 0;
    } else {
        this.tickViewBox--;
        // Add nodes to screen
        for (var i = 0; i < this.nodeAdditionQueue.length; i++) {
            var node = this.nodeAdditionQueue[i];
            this.visibleNodes.push(node);
            updateNodes.push(node);
        }
    }

    // Update moving nodes
    for (var i = 0; i < this.visibleNodes.length; i++) {
        var node = this.visibleNodes[i];
        if (node.sendUpdate()) {
            // Sends an update if cell is moving
            updateNodes.push(node);
        }
    }

    // Send packet
    this.socket.sendPacket(new Packet.UpdateNodes(
        this.nodeDestroyQueue,
        updateNodes,
        nonVisibleNodes,
        this.scrambleX,
        this.scrambleY));

    this.nodeDestroyQueue = []; // Reset destroy queue
    this.nodeAdditionQueue = []; // Reset addition queue

    // Update leaderboard
    if (this.tickLeaderboard <= 0) {
        if (this.gameServer.leaderboardType >= 0) {
            var packet = new Packet.UpdateLeaderboard(this, this.gameServer.leaderboard, this.gameServer.leaderboardType);
            this.socket.sendPacket(packet);
        }
        // 1 / 0.040 = 25 (once per second)
        this.tickLeaderboard = 25;
    } else {
        this.tickLeaderboard--;
    }

    // Handles disconnections
    if (this.disconnect > -1) {
        // Player has disconnected... remove it when the timer hits -1
        this.disconnect--;
        // Also remove it when its cells are completely eaten not to back up dead clients
        if (this.disconnect == -1 || this.cells.length == 0) {
            // Remove all client cells
            var len = this.cells.length;
            for (var i = 0; i < len; i++) {
                var cell = this.socket.playerTracker.cells[0];
                if (cell == null) continue;

                this.gameServer.removeNode(cell);
            }

            // Remove from client list
            var index = this.gameServer.clients.indexOf(this.socket);
            if (index != -1) {
                this.gameServer.clients.splice(index, 1);
            }
        }
    }
};

// Viewing box

PlayerTracker.prototype.updateCenterInGame = function() { // Get center of cells
    var len = this.cells.length;
    if (len <= 0) return;
    var cx = 0;
    var cy = 0;
    var count = 0;
    for (var i = 0; i < len; i++) {
        var node = this.cells[i];
        if (node == null) continue;
        cx += node.position.x;
        cy += node.position.y;
        count++;
    }
    if (count == 0) return;
    this.setCenterPos(cx / count, cy / count);
};

PlayerTracker.prototype.updateCenterFreeRoam = function () {
    var dx = this.mouse.x - this.centerPos.x;
    var dy = this.mouse.y - this.centerPos.x;
    var squared = dx * dx + dy * dy;
    if (squared < 1) return;     // stop threshold

    // distance
    var d = Math.sqrt(squared);

    // Not to break laws of universe by going faster than light speed
    var speed = Math.min(d / 10, 70);
    if (speed <= 0) return;

    var angle = Math.atan2(dx, dy);
    if (isNaN(angle)) return;

    this.setCenterPos(
        this.centerPos.x + speed * Math.sin(angle),
        this.centerPos.y + speed * Math.cos(angle));
};

PlayerTracker.prototype.updateViewBox = function () {
    var scale = this.getScale();
    this.sightWidth = (this.gameServer.config.serverViewBaseX + 100) / scale;
    this.sightHeight = (this.gameServer.config.serverViewBaseY + 100) / scale;
    var halfWidth = this.sightWidth / 2;
    var halfHeight = this.sightHeight / 2;
    this.viewBox = {
        left: this.centerPos.x - halfWidth,
        top: this.centerPos.y - halfHeight,
        right: this.centerPos.x + halfWidth,
        bottom: this.centerPos.y + halfHeight,
        halfWidth: halfWidth,
        halfHeight: halfHeight
    };
};

PlayerTracker.prototype.getVisibleNodes = function () {
    if (this.spectate) {
        var specPlayer = this.gameServer.largestClient;
        if (!this.freeRoam && specPlayer != null) {
            // top player spectate
            this.setCenterPos(specPlayer.centerPos.x, specPlayer.centerPos.y);
            this.scale = specPlayer.getScale();
            this.sendPosPacket();
            this.updateViewBox();
            return specPlayer.visibleNodes.slice(0);
        }
        // free roam spectate
        this.updateCenterFreeRoam();
        this.scale = 0.25;
        this.sendPosPacket();
    } else {
        // in game
        this.updateCenterInGame();
        // scale will be calculated on first call to this.getScale() inside updateViewBox()
    }
    this.updateViewBox();
    return this.calcVisibleNodes();
}

PlayerTracker.prototype.calcVisibleNodes = function() {
    var newVisible = [];
    for (var i = 0; i < this.gameServer.nodes.length; i++) {
        var node = this.gameServer.nodes[i];
        if (node == null) continue;

        var check = node.visibleCheck(this.viewBox, this.centerPos, this.cells);
        if (check > 0 || node.owner == this) {
            // Cell is in range of viewBox
            newVisible.push(node);
            // Check if it's colliding with one of player's cells
            if (check == 2) this.collidingNodes.push(node);
        }
    }
    return newVisible;
};

PlayerTracker.prototype.setCenterPos = function(x, y) {
    this.centerPos.x = x;
    this.centerPos.y = y;
    if (this.freeRoam) this.checkBorderPass();
};

PlayerTracker.prototype.checkBorderPass = function() {
    // A check while in free-roam mode to avoid player going into nothingness
    if (this.centerPos.x < this.gameServer.config.borderLeft)
        this.centerPos.x = this.gameServer.config.borderLeft;
    if (this.centerPos.x > this.gameServer.config.borderRight)
        this.centerPos.x = this.gameServer.config.borderRight;
    if (this.centerPos.y < this.gameServer.config.borderTop)
        this.centerPos.y = this.gameServer.config.borderTop;
    if (this.centerPos.y > this.gameServer.config.borderBottom)
        this.centerPos.y = this.gameServer.config.borderBottom;
};

PlayerTracker.prototype.sendPosPacket = function() {
    // TODO: Send packet elsewhere so it is sent more often
    this.socket.sendPacket(new Packet.UpdatePosition(
        this.centerPos.x + this.scrambleX,
        this.centerPos.y + this.scrambleY,
        this.getScale()
    ));
};

PlayerTracker.prototype.getAngle = function(x1, y1, x2, y2) {
    var deltaY = y1 - y2;
    var deltaX = x1 - x2;
    return Math.atan2(deltaX, deltaY);
};
<commit_msg>fix free roaming spectate<commit_after>var Packet = require('./packet');
var GameServer = require('./GameServer');

function PlayerTracker(gameServer, socket) {
    this.pID = -1;
    this.disconnect = -1; // Disconnection
    this.name = "";
    this.skin = "";
    this.gameServer = gameServer;
    this.socket = socket;
    this.nodeAdditionQueue = [];
    this.nodeDestroyQueue = [];
    this.visibleNodes = [];
    this.collidingNodes = []; // Perfomance save; all nodes colliding with player's cells
    this.cells = [];
    this.mergeOverride = false; // Triggered by console command
    this.score = 0; // Needed for leaderboard
    this.scale = 1;
    this.isMassChanged = true;

    this.mouse = {
        x: 0,
        y: 0
    };
    this.shouldMoveCells = true; // False if the mouse packet wasn't triggered
    this.notMoved = false; // If one of cells have been moved after splitting this is triggered
    this.movePacketTriggered = false;
    this.mouseCells = []; // For individual cell movement
    this.tickLeaderboard = 0;
    this.tickViewBox = 0;

    this.team = 0;
    this.spectate = false;
    this.freeRoam = false; // Free-roam mode enables player to move in spectate mode

    // Viewing box
    this.sightWidth = 0;
    this.sightHeight = 0;
    this.centerPos = { // Center of map
        x: 3000,
        y: 3000
    };
    this.viewBox = {
        left: 0,
        top: 0,
        right: 0,
        bottom: 0,
        halfWidth: 0,
        halfHeight: 0
    };

    // Scramble the coordinate system for anti-raga
    this.scrambleX = 0;
    this.scrambleY = 0;

    // Gamemode function
    if (gameServer) {
        // Find center
        var width = gameServer.config.borderRight - gameServer.config.borderLeft;
        var height = gameServer.config.borderBottom - gameServer.config.borderTop;
        this.centerPos.x = gameServer.config.borderLeft + width / 2;
        this.centerPos.y = gameServer.config.borderTop + height / 2;
        // Player id
        this.pID = gameServer.getNewPlayerID();
        // Gamemode function
        gameServer.gameMode.onPlayerInit(this);
        // Only scramble if enabled in config
        if (gameServer.config.serverScrambleCoords == 1) {
            // avoid mouse packet limitations
            var maxScrambleX = Math.max(0, 32767 - 2000 - width / 2);
            var maxScrambleY = Math.max(0, 32767 - 2000 - height / 2);
            this.scrambleX = Math.floor(maxScrambleX * Math.random());
            this.scrambleY = Math.floor(maxScrambleY * Math.random());
        }
    }
}

module.exports = PlayerTracker;

// Setters/Getters

PlayerTracker.prototype.setName = function(name) {
    this.name = name;
};

PlayerTracker.prototype.getName = function() {
    return this.name;
};

PlayerTracker.prototype.setSkin = function (skin) {
    this.skin = skin;
};

PlayerTracker.prototype.getSkin = function () {
    return this.skin;
};

PlayerTracker.prototype.getScore = function () {
    if (this.isMassChanged)
        this.updateMass();
    return this.score;
};

PlayerTracker.prototype.getScale = function () {
    if (this.isMassChanged)
        this.updateMass();
    return this.scale;
};

PlayerTracker.prototype.updateMass = function () {
    var totalSize = 0;
    var totalMass = 0;
    for (var i = 0; i < this.cells.length; i++) {
        var node = this.cells[i];
        if (node == null) continue;
        totalSize += node.getSize();
        totalMass += node.getMass();
    }
    if (totalSize == 0) {
        //do not change scale for spectators or not in game players
        this.score = 0;
    } else {
        this.score = totalMass;
        this.scale = Math.pow(Math.min(64 / totalSize, 1), 0.4);
    }
    this.isMassChanged = false;
};

PlayerTracker.prototype.massChanged = function () {
    this.isMassChanged = true;
};

PlayerTracker.prototype.setColor = function(color) {
    this.color.r = color.r;
    this.color.g = color.g;
    this.color.b = color.b;
};

PlayerTracker.prototype.getTeam = function() {
    return this.team;
};

// Functions

PlayerTracker.prototype.joinGame = function (name, skin) {
    if (this.cells.length > 0) return;
    if (name == null) name = "";
    if (skin == null) skin = "";
    this.setName(name);
    this.setSkin(skin);
    this.spectate = false;

    this.socket.sendPacket(new Packet.ClearNodes());
    this.collidingNodes = [];
    this.visibleNodes = [];         // Reset visible nodes
    this.nodeDestroyQueue = [];     // Reset destroy queue
    this.nodeAdditionQueue = [];    // Reset addition queue

    this.gameServer.gameMode.onPlayerSpawn(this.gameServer, this);
};

PlayerTracker.prototype.update = function () {
    // if initialization is not complete yet then do not update
    if (this.socket.packetHandler.protocol == 0)
        return;

    // First reset colliding nodes
    this.collidingNodes = [];

    // Move packet update
    if (this.movePacketTriggered) {
        this.movePacketTriggered = false;
        this.shouldMoveCells = true;
    } else {
        this.shouldMoveCells = false;
    }
    // Actions buffer (So that people cant spam packets)
    if (this.socket.packetHandler.pressSpace) { // Split cell
        if (!this.mergeOverride) this.gameServer.gameMode.pressSpace(this.gameServer, this);
        this.socket.packetHandler.pressSpace = false;
    }

    if (this.socket.packetHandler.pressW) { // Eject mass
        this.gameServer.gameMode.pressW(this.gameServer, this);
        this.socket.packetHandler.pressW = false;
    }

    if (this.socket.packetHandler.pressQ) { // Q Press
        this.gameServer.gameMode.pressQ(this.gameServer, this);
        this.socket.packetHandler.pressQ = false;
    }

    var updateNodes = []; // Nodes that need to be updated via packet

    // Remove nodes from visible nodes if possible
    var d = 0;
    while (d < this.nodeDestroyQueue.length) {
        var index = this.visibleNodes.indexOf(this.nodeDestroyQueue[d]);
        if (index > -1) {
            this.visibleNodes.splice(index, 1);
            d++; // Increment
        } else {
            // Node was never visible anyways
            this.nodeDestroyQueue.splice(d, 1);
        }
    }

    // Get visible nodes every 400 ms
    var nonVisibleNodes = []; // Nodes that are not visible
    if (this.tickViewBox <= 0) {
        var newVisible = this.getVisibleNodes();
        try { // Add a try block in any case

            // Compare and destroy nodes that are not seen
            for (var i = 0; i < this.visibleNodes.length; i++) {
                var index = newVisible.indexOf(this.visibleNodes[i]);
                if (index == -1) {
                    // Not seen by the client anymore
                    nonVisibleNodes.push(this.visibleNodes[i]);
                }
            }

            // Add nodes to client's screen if client has not seen it already
            for (var i = 0; i < newVisible.length; i++) {
                var index = this.visibleNodes.indexOf(newVisible[i]);
                if (index == -1) {
                    updateNodes.push(newVisible[i]);
                }
            }

            // Scramble minimap
            // TODO: need to send it more rarely (once per second or something like that)
            // also need to make sure that we send it before first cell update
            //var border = {
            //    left: Math.max(this.viewBox.left - this.viewBox.halfWidth, this.gameServer.config.borderLeft) + this.scrambleX,
            //    top: Math.max(this.viewBox.top - this.viewBox.halfHeight, this.gameServer.config.borderTop) + this.scrambleY,
            //    right: Math.min(this.viewBox.right + this.viewBox.halfWidth, this.gameServer.config.borderRight) + this.scrambleX,
            //    bottom: Math.min(this.viewBox.bottom + this.viewBox.halfHeight, this.gameServer.config.borderBottom) + this.scrambleY
            //};
            //this.socket.sendPacket(new Packet.SetBorder(border));
        } catch (err) {
            console.error(err);
        }

        this.visibleNodes = newVisible;
        // Reset Ticks
        this.tickViewBox = 0;
    } else {
        this.tickViewBox--;
        // Add nodes to screen
        for (var i = 0; i < this.nodeAdditionQueue.length; i++) {
            var node = this.nodeAdditionQueue[i];
            this.visibleNodes.push(node);
            updateNodes.push(node);
        }
    }

    // Update moving nodes
    for (var i = 0; i < this.visibleNodes.length; i++) {
        var node = this.visibleNodes[i];
        if (node.sendUpdate()) {
            // Sends an update if cell is moving
            updateNodes.push(node);
        }
    }

    // Send packet
    this.socket.sendPacket(new Packet.UpdateNodes(
        this.nodeDestroyQueue,
        updateNodes,
        nonVisibleNodes,
        this.scrambleX,
        this.scrambleY));

    this.nodeDestroyQueue = []; // Reset destroy queue
    this.nodeAdditionQueue = []; // Reset addition queue

    // Update leaderboard
    if (this.tickLeaderboard <= 0) {
        if (this.gameServer.leaderboardType >= 0) {
            var packet = new Packet.UpdateLeaderboard(this, this.gameServer.leaderboard, this.gameServer.leaderboardType);
            this.socket.sendPacket(packet);
        }
        // 1 / 0.040 = 25 (once per second)
        this.tickLeaderboard = 25;
    } else {
        this.tickLeaderboard--;
    }

    // Handles disconnections
    if (this.disconnect > -1) {
        // Player has disconnected... remove it when the timer hits -1
        this.disconnect--;
        // Also remove it when its cells are completely eaten not to back up dead clients
        if (this.disconnect == -1 || this.cells.length == 0) {
            // Remove all client cells
            var len = this.cells.length;
            for (var i = 0; i < len; i++) {
                var cell = this.socket.playerTracker.cells[0];
                if (cell == null) continue;

                this.gameServer.removeNode(cell);
            }

            // Remove from client list
            var index = this.gameServer.clients.indexOf(this.socket);
            if (index != -1) {
                this.gameServer.clients.splice(index, 1);
            }
        }
    }
};

// Viewing box

PlayerTracker.prototype.updateCenterInGame = function() { // Get center of cells
    var len = this.cells.length;
    if (len <= 0) return;
    var cx = 0;
    var cy = 0;
    var count = 0;
    for (var i = 0; i < len; i++) {
        var node = this.cells[i];
        if (node == null) continue;
        cx += node.position.x;
        cy += node.position.y;
        count++;
    }
    if (count == 0) return;
    this.setCenterPos(cx / count, cy / count);
};

PlayerTracker.prototype.updateCenterFreeRoam = function () {
    var dx = this.mouse.x - this.centerPos.x;
    var dy = this.mouse.y - this.centerPos.y;
    var squared = dx * dx + dy * dy;
    if (squared < 1) return;     // stop threshold

    // distance
    var d = Math.sqrt(squared);

    var speed = Math.min(d, 32);
    if (speed <= 0) return;

    var angle = Math.atan2(dx, dy);
    if (isNaN(angle)) return;

    this.setCenterPos(
        this.centerPos.x + speed * Math.sin(angle),
        this.centerPos.y + speed * Math.cos(angle));
};

PlayerTracker.prototype.updateViewBox = function () {
    var scale = this.getScale();
    this.sightWidth = (this.gameServer.config.serverViewBaseX + 100) / scale;
    this.sightHeight = (this.gameServer.config.serverViewBaseY + 100) / scale;
    var halfWidth = this.sightWidth / 2;
    var halfHeight = this.sightHeight / 2;
    this.viewBox = {
        left: this.centerPos.x - halfWidth,
        top: this.centerPos.y - halfHeight,
        right: this.centerPos.x + halfWidth,
        bottom: this.centerPos.y + halfHeight,
        halfWidth: halfWidth,
        halfHeight: halfHeight
    };
};

PlayerTracker.prototype.getVisibleNodes = function () {
    if (this.spectate) {
        var specPlayer = this.gameServer.largestClient;
        if (!this.freeRoam && specPlayer != null) {
            // top player spectate
            this.setCenterPos(specPlayer.centerPos.x, specPlayer.centerPos.y);
            this.scale = specPlayer.getScale();
            this.sendPosPacket();
            this.updateViewBox();
            return specPlayer.visibleNodes.slice(0);
        }
        // free roam spectate
        this.updateCenterFreeRoam();
        this.scale = 0.25;
        this.sendPosPacket();
    } else {
        // in game
        this.updateCenterInGame();
        // scale will be calculated on first call to this.getScale() inside updateViewBox()
    }
    this.updateViewBox();
    return this.calcVisibleNodes();
}

PlayerTracker.prototype.calcVisibleNodes = function() {
    var newVisible = [];
    for (var i = 0; i < this.gameServer.nodes.length; i++) {
        var node = this.gameServer.nodes[i];
        if (node == null) continue;

        var check = node.visibleCheck(this.viewBox, this.centerPos, this.cells);
        if (check > 0 || node.owner == this) {
            // Cell is in range of viewBox
            newVisible.push(node);
            // Check if it's colliding with one of player's cells
            if (check == 2) this.collidingNodes.push(node);
        }
    }
    return newVisible;
};

PlayerTracker.prototype.setCenterPos = function(x, y) {
    this.centerPos.x = x;
    this.centerPos.y = y;
    if (this.freeRoam) this.checkBorderPass();
};

PlayerTracker.prototype.checkBorderPass = function() {
    // A check while in free-roam mode to avoid player going into nothingness
    if (this.centerPos.x < this.gameServer.config.borderLeft)
        this.centerPos.x = this.gameServer.config.borderLeft;
    if (this.centerPos.x > this.gameServer.config.borderRight)
        this.centerPos.x = this.gameServer.config.borderRight;
    if (this.centerPos.y < this.gameServer.config.borderTop)
        this.centerPos.y = this.gameServer.config.borderTop;
    if (this.centerPos.y > this.gameServer.config.borderBottom)
        this.centerPos.y = this.gameServer.config.borderBottom;
};

PlayerTracker.prototype.sendPosPacket = function() {
    // TODO: Send packet elsewhere so it is sent more often
    this.socket.sendPacket(new Packet.UpdatePosition(
        this.centerPos.x + this.scrambleX,
        this.centerPos.y + this.scrambleY,
        this.getScale()
    ));
};

PlayerTracker.prototype.getAngle = function(x1, y1, x2, y2) {
    var deltaY = y1 - y2;
    var deltaX = x1 - x2;
    return Math.atan2(deltaX, deltaY);
};
<|endoftext|>


<commit_before>/**
 * @copyright 2015, Prometheus Research, LLC
 */

import React, {PropTypes} from 'react';
import invariant          from './invariant';
import keyPath            from './keyPath';

const hasParentContext = !!/0\.14\.[0-9]+/.exec(React.version);

export const ContextTypes = {
  formValue: PropTypes.object
};

/**
 * Base class for form components.
 *
 * It exposes form value via `this.formValue` which is provided either via
 * `this.props.formValue` or via context.
 */
export default class Component extends React.Component {

  static contextTypes = ContextTypes;
  static childContextTypes = ContextTypes;

  static propTypes = {
    formValue: PropTypes.object,
    select: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number, PropTypes.bool])
  };

  getChildContext() {
    return {formValue: this.formValue};
  }

  get _parentContext() {
    if (hasParentContext) {
      return this.context;
    } else {
      return this._reactInternalInstance._context;
    }
  }

  get formValue() {
    let formValue = this.props.formValue || this._parentContext.formValue;

    invariant(
      formValue,
      'A form component <%s /> should receive form value via props ' +
      'or be used as a part of element hierarchy which ' +
      'includes <Form /> component in its ancestors',
      this.constructor.displayName || this.constructor.name
    );

    let select = this.props.select;
    // We check for select === true to keep compatability we eariler
    // versions of React Forms where we needed to rebuild element tree to
    // propagate values to form.
    if (select) {
      select = keyPath(select);
      formValue = formValue.select(select);
    }

    return formValue;
  }
}
<commit_msg>Provide backward compatibility with Rex code<commit_after>/**
 * @copyright 2015, Prometheus Research, LLC
 */

import React, {PropTypes} from 'react';
import invariant          from './invariant';
import keyPath            from './keyPath';

const hasParentContext = !!/0\.14\.[0-9]+/.exec(React.version);

export const ContextTypes = {
  formValue: PropTypes.object
};

let selectPropType = PropTypes.oneOfType([
  PropTypes.array,
  PropTypes.string,
  PropTypes.number,
  PropTypes.bool
]);

/**
 * Base class for form components.
 *
 * It exposes form value via `this.formValue` which is provided either via
 * `this.props.formValue` or via context.
 */
export default class Component extends React.Component {

  static contextTypes = ContextTypes;
  static childContextTypes = ContextTypes;

  static propTypes = {
    formValue: PropTypes.object,
    select: selectPropType,
    selectFormValue: selectPropType
  };

  getChildContext() {
    return {formValue: this.formValue};
  }

  get _parentContext() {
    if (hasParentContext) {
      return this.context;
    } else {
      return this._reactInternalInstance._context;
    }
  }

  get formValue() {
    let formValue = this.props.formValue || this._parentContext.formValue;

    invariant(
      formValue,
      'A form component <%s /> should receive form value via props ' +
      'or be used as a part of element hierarchy which ' +
      'includes <Form /> component in its ancestors',
      this.constructor.displayName || this.constructor.name
    );

    let select = this.props.select || this.props.selectFormValue;
    // We check for select !== true to keep compatability we eariler
    // versions of React Forms where we needed to rebuild element tree to
    // propagate values to form.
    if (select && select !== true) {
      select = keyPath(select);
      formValue = formValue.select(select);
    }

    return formValue;
  }
}
<|endoftext|>


<commit_before>var express = require('express');
var app = express(),
  server = require('http').createServer(app);

var path = require("path");
var application_root = __dirname;

var interfaces = require('os').networkInterfaces();

var LOCAL_IP
if (interfaces.wlan0) {
  LOCAL_IP = interfaces.wlan0[0].address;
} else if (interfaces.en1) {
  LOCAL_IP = interfaces.en1[0].address;
} else if (interfaces.eth0) {
  LOCAL_IP = interfaces.eth0[0].address;
} else {
  LOCAL_IP = "127.0.0.1";
}
var PORT = 3000;

var webduino = require("./webduino");

var webdinoApi = webduino(server);

app.use(express.static('public'));

webdinoApi.on("ready", function() {
  console.log("WEBDUINO READY")
  // pin API
  app.get('/pins/:id', getPin);
  app.get('/pins', getPins);
  app.post('/pins/:id', putPin);
  app.put('/pins/:id', putPin);

  app.put('/sensors/:id', putSensor);
  app.get('/sensors', getSensors);
  app.get('/sensors/:id', getSensor);

  app.put('/leds/:id', putLed);
  app.get('/leds/:id', getLed);
  app.get('/leds', getLeds);

  server.listen(PORT, function() {
    console.log("Listening on ip "+LOCAL_IP+" on port "+PORT)
  });

});

app.configure(function () {
  app.use(express.bodyParser());
});

// getPin - GET /pin/:id
var getPin = function(req, res){
  webdinoApi.getPin(req, res);
}

// getPins - GET /pin
var getPins = function(req, res){
  webdinoApi.getPins(req, res);
};

// putPin - PUT /pin/:id
var putPin = function(req, res){
  webdinoApi.putPin(req, res);
};

// postSensor
var getSensors = function(req, res){
  webdinoApi.getSensors(req, res);
};

// postSensor
var getSensor = function(req, res){
  webdinoApi.getSensor(req, res);
};

// putSensor
var putSensor = function(req, res){
  webdinoApi.putSensor(req, res);
};

// postLed
var putLed = function(req, res){
  webdinoApi.putLed(req, res);
};

// getLed
var getLed = function(req, res){
  webdinoApi.getLed(req, res);
};

// getLed
var getLeds = function(req, res){
  webdinoApi.getLeds(req, res);
};

console.log(getLocalIPs())
<commit_msg>Remove log<commit_after>var express = require('express');
var app = express(),
  server = require('http').createServer(app);

var path = require("path");
var application_root = __dirname;

var interfaces = require('os').networkInterfaces();

var LOCAL_IP
if (interfaces.wlan0) {
  LOCAL_IP = interfaces.wlan0[0].address;
} else if (interfaces.en1) {
  LOCAL_IP = interfaces.en1[0].address;
} else if (interfaces.eth0) {
  LOCAL_IP = interfaces.eth0[0].address;
} else {
  LOCAL_IP = "127.0.0.1";
}
var PORT = 3000;

var webduino = require("./webduino");

var webdinoApi = webduino(server);

app.use(express.static('public'));

webdinoApi.on("ready", function() {
  console.log("WEBDUINO READY")
  // pin API
  app.get('/pins/:id', getPin);
  app.get('/pins', getPins);
  app.post('/pins/:id', putPin);
  app.put('/pins/:id', putPin);

  app.put('/sensors/:id', putSensor);
  app.get('/sensors', getSensors);
  app.get('/sensors/:id', getSensor);

  app.put('/leds/:id', putLed);
  app.get('/leds/:id', getLed);
  app.get('/leds', getLeds);

  server.listen(PORT, function() {
    console.log("Listening on ip "+LOCAL_IP+" on port "+PORT)
  });

});

app.configure(function () {
  app.use(express.bodyParser());
});

// getPin - GET /pin/:id
var getPin = function(req, res){
  webdinoApi.getPin(req, res);
}

// getPins - GET /pin
var getPins = function(req, res){
  webdinoApi.getPins(req, res);
};

// putPin - PUT /pin/:id
var putPin = function(req, res){
  webdinoApi.putPin(req, res);
};

// postSensor
var getSensors = function(req, res){
  webdinoApi.getSensors(req, res);
};

// postSensor
var getSensor = function(req, res){
  webdinoApi.getSensor(req, res);
};

// putSensor
var putSensor = function(req, res){
  webdinoApi.putSensor(req, res);
};

// postLed
var putLed = function(req, res){
  webdinoApi.putLed(req, res);
};

// getLed
var getLed = function(req, res){
  webdinoApi.getLed(req, res);
};

// getLed
var getLeds = function(req, res){
  webdinoApi.getLeds(req, res);
};

<|endoftext|>


<commit_before>(function (Q, $, window, document, undefined) {

/**
 * Q Tools
 * @module Q-tools
 */

/**
 * Plugin Creates gallery of of images
 * @class Q gallery
 * @constructor
 * @param {Object} [options] options is an Object with function parameters
 * @param {Array} [options.images] images an array of objects containing object  { src: String , caption: String , interval: Number, transition: Object}
 *   @param {String} [options.images.N.name] optional, put a name here, that is used for css classes
 *   @param {String} [options.images.N.src] src url of the image, will be fed through Q.url(). Required.
 *   @param {String} [options.images.N.caption] caption for the image. Optional
 *   @param {String} [options.images.N.customCaptionPosition] set to true if you're overriding the position with your own css
 *   @param {Number} [options.images.N.interval] interval duration overriding default interval duration. Optional.
 *   @param {Object} [options.images.N.transition] transition object overriding default transition. Optional.
 * @param {Object} [options.transition] transition object that contains properties for making transitions
 *   @param {Number} [options.transition.duration] duration the number of milliseconds the transition should take (where the intervals overlap)
 *   @param {String} [options.transition.ease] ease the type of easing function to apply from Q.Animation.ease object
 *   @default 'smooth'
 *   @param {String} [options.transition.type] type the type of transition. Can only be 'crossfade'
 *   @default 'crossfade'
 * @param {Object} [options.interval] interval object that contains properties for intervals
 *   @param {Number} [options.interval.duration] duration number of milliseconds between beginning times of consecutive transitions.
 *      It's recommended to make this at least 2x the duration of the transitions!
 *   @param {String} [options.interval.ease] ease the type of easing function to apply from Q.Animation.ease object.
 *   @default 'smooth'
 *   @param {String} [options.interval.type] type is a what to do during this interval. Can be empty or 'kenburns'.
 *   @default ''
 *   @param {Object} [options.interval.from] from is for "kenburns", an object containing "left", "top", "width" and "height"
 *     @param {Number} [options.interval.from.left] left number for "from" object
 *     @param {Number} [options.interval.from.top] top number for "from" object
 *     @param {Number} [options.interval.from.width] width number for "from" object
 *     @param {Number} [options.interval.from.height] height number for "from" object
 *   @param {Object} [options.interval.to] to is for "kenburns", an object containing "left", "top", "width" and "height"
 *     @param {Number} [options.interval.to.left] left number for "to" object
 *     @param {Number} [options.interval.to.top] top number for "to" object
 *     @param {Number} [options.interval.to.width] width number for "to" object
 *     @param {Number} [options.interval.to.height] height number for "to" object
 * @param {Boolean} [options.autoplay] autoplay for  whether to start playing the gallery when it loads.
 * @default true
 * @param {Boolean} [options.transitionToFirst] transitionToFirst for whether to use a transition to show the first image.
 * @default false
 * @param {Boolean} [options.loop] loop for whether to show first image after last image is done.
 * @default true
 * @param {Q.Event} [options.onLoad] onLoad event fires when an image loads, also passes all loaded images
 * @param {Q.Event} [options.onTransition] onTransition event fires when an image loads, also passes all loaded images
 */
Q.Tool.jQuery('Q/gallery', function _Q_gallery(o) {

	o = o || {};
	var $this = this, i, image, imgs=[], caps=[], current, tm, gallery;
	var animTransition, animInterval, animPreviousInterval;
	var intervals = {
		"": function (x, y, params) {

		},
		kenburns: function (x, y, params) {
			var image = o.images[params.current];
			var img = imgs[params.current];
			var interval = image.interval || {};
			var from = Q.extend({}, 2, o.interval.from, 2, interval.from);
			var to = Q.extend({}, 2, o.interval.to, 2, interval.to);
			var z = y;
			var widthFactor = from.width + z*(to.width - from.width);
			var heightFactor = from.height + z*(to.height - from.height);
			var leftFactor = (from.left + z*(to.left - from.left));
			var topFactor = (from.top + z*(to.top - from.top));

			// crop to fit aspect ratio of the gallery
			var iw = img[0].naturalWidth;
			var ih = img[0].naturalHeight;
			var w = iw * widthFactor;
			var h = ih * heightFactor;
			var l = iw * leftFactor;
			var t = ih * topFactor;
			var r = w/h;
			var $w = $this.width();
			var $h = $this.height();
			var $r = $w/$h;
			if ($r < r) {
				var smallerW = h * $r;
				l += (w - smallerW) / 2;
				widthFactor = smallerW / iw;
				leftFactor = l / iw;
			} else {
				var smallerH = w / $r;
				t += (h - smallerH) / 2;
				heightFactor = smallerH / ih;
				topFactor = t / ih;
			}

			// scale and place the image to expose the clipped area
			var width = $w / widthFactor;
			var height = $h / heightFactor;
			var left = -leftFactor * width;
			var top = -topFactor * height;
			img.css({
				left: left+'px',
				top: top+'px',
				width: width+'px',
				height: height+'px',
				visibility: 'visible'
			});
			caps[params.current].css('visibility', 'visible');
		}
	};
	var transitions = {
		crossfade: function (x, y, params) {
			imgs[params.current]
			.add(caps[params.current])
			.css({
				display: 'block',
				visibility: 'visible',
				opacity: y
			});
			if (params.previous < 0) return;
			if (y !== 1) {
				imgs[params.previous]
				.add(caps[params.previous])
				.css({
					opacity: 1-y
				});
			} else {
				for (var i=0; i<imgs.length; ++i) {
					if (i === params.current) continue;
					imgs[i].add(caps[i]).css({
						display: 'none'
					});
				}
			}
			if (y === 1) {
				animPreviousInterval && animPreviousInterval.pause();
			}
		}
	};

	if (gallery = $this.data('gallery')) {
		gallery.pause();
		$this.empty();
		if (o === null) {
			return false;
		}
	}

	current = -1;
	var css = {
		overflow: 'hidden'
	};
	if ($this.css('position') === 'static') {
		css.position = 'relative';
	}
	if (!parseInt($this.css('height'))) {
		$this.css('height', $this.parent().innerHeight());
	}
	$this.css(css);

	function loadImage(index, callback) {
		if (imgs[index]) {
			if (callback) callback(index, imgs);
			return;
		}
		var image = o.images[index];
		if (!image) {
			image = {};
		}
		if (!image.src) {
			image.src = Q.url('{{Q}}/img/throbbers/transparent.gif');
		}
		var name = image.name ? Q.normalize(image.name) : '';
		var img = $('').attr({
			alt: image.caption ? image.caption : 'image ' + index,
			src: Q.url(image.src)
		}).css({
			visibility: 'hidden',
			position: 'absolute',
			top: '0px',
			left: '0px'
		}).appendTo($this)
		.load(onLoad);
		if (name) {
			img.addClass('Q_gallery_caption_' + name);
		}
		imgs[index] = img;
		img.each(function () {
			if (this.complete) {
				$(this).unbind('load');
				onLoad();
			}
		});
		if (image.caption) {
			var css = image.style ? image.style : {};
			css['visibility'] = 'hidden';
			var cap = $('
')
				.css(css)
				.html(image.caption)
				.appendTo($this);
			if (!image.customCaptionPosition) {
				cap.addClass('Q_gallery_caption_centered');
			}
			caps[index] = cap;
		} else {
			caps[index] = $([]);
		}
		if (name) {
			cap.addClass('Q_gallery_caption_' + name);
		}
		function onLoad() {
			imgs[index] = img;
			Q.handle(o.onLoad, $this, [$(this), imgs, o]);
			if (callback) callback(index, imgs);
		}
	}

	var gallery = {
		options: o,
		onLoad: o.onLoad,
		play: function () {
			this.next(true);
		},
		next: function (keepGoing) {
			var previous = current;
			++current;
			if (current >= o.images.length) {
				if (!o.loop) return;
				current = 0;
			}
			loadImage(current, function () {
				beginTransition();
			});
			function beginTransition() {
				var t = Q.extend({}, 2, o.transition, 2, o.images[current].transition);
				var transition = transitions[o.transition.type || ""];
				Q.handle(o.onTransition, $this, [current, imgs, o]);
				if (!o.transitionToFirst && previous === -1) {
					transition(1, 1, { current: current, previous: previous });
					beginInterval();
					return;
				}
				// animTransition && animTransition.pause();
				animTransition = Q.Animation.play(
					transition,
					t.duration,
					t.ease,
					{ current: current, previous: previous }
				);
				beginInterval();
			}
			function beginInterval() {
				var transition = Q.extend({}, 2, o.transition, 2, o.images[current].transition);
				var interval = Q.extend({}, 2, o.interval, 2, o.images[current].interval);
				animPreviousInterval = animInterval;
				animInterval = Q.Animation.play(
					intervals[interval.type || ""],
					interval.duration,
					interval.ease,
					{ current: current, previous: previous }
				);
				loadImage((current+1) % o.images.length, null); // preload next image
				if (keepGoing) {
					tm = setTimeout(function () {
						gallery.next(keepGoing);
					}, interval.duration - transition.duration);
				}
			}
		},
		pause: function () {
			animTransition && animTransition.pause();
			animInterval && animInterval.pause();
			clearTimeout(tm);
		},
		resume: function () {
			animTransition && animTransition.play();
			animInterval && animInterval.play();
		},
		rewind: function () {
			this.pause();
			current = -1;
			animTransition = null;
			animInterval = null;
		}
	};

	if (o.autoplay) {
		gallery.play();
	} else {
		gallery.next(false);
	}

	$this.data('gallery', gallery);

	return this;

},

{
	images: [],
	transition: {
		duration: 1000,
		ease: "smooth",
		type: "crossfade"
	},
	interval: {
		duration: 2000,
		ease: "smooth",
		type: "",
		from: { left: 0, top: 0, width: 1, height: 1 },
		to: { left: 0, top: 0, width: 1, height: 1 }
	},
	autoplay: true,
	transitionToFirst: false,
	loop: true,
	onLoad: null,
	onTransition: null
},

{
	remove: function () {
		$(this).data('gallery').pause();
	}
}

);

})(Q, Q.$, window, document);<commit_msg>Fixed gallery.js to use latest jQuery<commit_after>(function (Q, $, window, document, undefined) {

/**
 * Q Tools
 * @module Q-tools
 */

/**
 * Plugin Creates gallery of of images
 * @class Q gallery
 * @constructor
 * @param {Object} [options] options is an Object with function parameters
 * @param {Array} [options.images] images an array of objects containing object  { src: String , caption: String , interval: Number, transition: Object}
 *   @param {String} [options.images.N.name] optional, put a name here, that is used for css classes
 *   @param {String} [options.images.N.src] src url of the image, will be fed through Q.url(). Required.
 *   @param {String} [options.images.N.caption] caption for the image. Optional
 *   @param {String} [options.images.N.customCaptionPosition] set to true if you're overriding the position with your own css
 *   @param {Number} [options.images.N.interval] interval duration overriding default interval duration. Optional.
 *   @param {Object} [options.images.N.transition] transition object overriding default transition. Optional.
 * @param {Object} [options.transition] transition object that contains properties for making transitions
 *   @param {Number} [options.transition.duration] duration the number of milliseconds the transition should take (where the intervals overlap)
 *   @param {String} [options.transition.ease] ease the type of easing function to apply from Q.Animation.ease object
 *   @default 'smooth'
 *   @param {String} [options.transition.type] type the type of transition. Can only be 'crossfade'
 *   @default 'crossfade'
 * @param {Object} [options.interval] interval object that contains properties for intervals
 *   @param {Number} [options.interval.duration] duration number of milliseconds between beginning times of consecutive transitions.
 *      It's recommended to make this at least 2x the duration of the transitions!
 *   @param {String} [options.interval.ease] ease the type of easing function to apply from Q.Animation.ease object.
 *   @default 'smooth'
 *   @param {String} [options.interval.type] type is a what to do during this interval. Can be empty or 'kenburns'.
 *   @default ''
 *   @param {Object} [options.interval.from] from is for "kenburns", an object containing "left", "top", "width" and "height"
 *     @param {Number} [options.interval.from.left] left number for "from" object
 *     @param {Number} [options.interval.from.top] top number for "from" object
 *     @param {Number} [options.interval.from.width] width number for "from" object
 *     @param {Number} [options.interval.from.height] height number for "from" object
 *   @param {Object} [options.interval.to] to is for "kenburns", an object containing "left", "top", "width" and "height"
 *     @param {Number} [options.interval.to.left] left number for "to" object
 *     @param {Number} [options.interval.to.top] top number for "to" object
 *     @param {Number} [options.interval.to.width] width number for "to" object
 *     @param {Number} [options.interval.to.height] height number for "to" object
 * @param {Boolean} [options.autoplay] autoplay for  whether to start playing the gallery when it loads.
 * @default true
 * @param {Boolean} [options.transitionToFirst] transitionToFirst for whether to use a transition to show the first image.
 * @default false
 * @param {Boolean} [options.loop] loop for whether to show first image after last image is done.
 * @default true
 * @param {Q.Event} [options.onLoad] onLoad event fires when an image loads, also passes all loaded images
 * @param {Q.Event} [options.onTransition] onTransition event fires when an image loads, also passes all loaded images
 */
Q.Tool.jQuery('Q/gallery', function _Q_gallery(o) {

	o = o || {};
	var $this = this, i, image, imgs=[], caps=[], current, tm, gallery;
	var animTransition, animInterval, animPreviousInterval;
	var intervals = {
		"": function (x, y, params) {

		},
		kenburns: function (x, y, params) {
			var image = o.images[params.current];
			var img = imgs[params.current];
			var interval = image.interval || {};
			var from = Q.extend({}, 2, o.interval.from, 2, interval.from);
			var to = Q.extend({}, 2, o.interval.to, 2, interval.to);
			var z = y;
			var widthFactor = from.width + z*(to.width - from.width);
			var heightFactor = from.height + z*(to.height - from.height);
			var leftFactor = (from.left + z*(to.left - from.left));
			var topFactor = (from.top + z*(to.top - from.top));

			// crop to fit aspect ratio of the gallery
			var iw = img[0].naturalWidth;
			var ih = img[0].naturalHeight;
			var w = iw * widthFactor;
			var h = ih * heightFactor;
			var l = iw * leftFactor;
			var t = ih * topFactor;
			var r = w/h;
			var $w = $this.width();
			var $h = $this.height();
			var $r = $w/$h;
			if ($r < r) {
				var smallerW = h * $r;
				l += (w - smallerW) / 2;
				widthFactor = smallerW / iw;
				leftFactor = l / iw;
			} else {
				var smallerH = w / $r;
				t += (h - smallerH) / 2;
				heightFactor = smallerH / ih;
				topFactor = t / ih;
			}

			// scale and place the image to expose the clipped area
			var width = $w / widthFactor;
			var height = $h / heightFactor;
			var left = -leftFactor * width;
			var top = -topFactor * height;
			img.css({
				left: left+'px',
				top: top+'px',
				width: width+'px',
				height: height+'px',
				visibility: 'visible'
			});
			caps[params.current].css('visibility', 'visible');
		}
	};
	var transitions = {
		crossfade: function (x, y, params) {
			imgs[params.current]
			.add(caps[params.current])
			.css({
				display: 'block',
				visibility: 'visible',
				opacity: y
			});
			if (params.previous < 0) return;
			if (y !== 1) {
				imgs[params.previous]
				.add(caps[params.previous])
				.css({
					opacity: 1-y
				});
			} else {
				for (var i=0; i<imgs.length; ++i) {
					if (i === params.current) continue;
					imgs[i].add(caps[i]).css({
						display: 'none'
					});
				}
			}
			if (y === 1) {
				animPreviousInterval && animPreviousInterval.pause();
			}
		}
	};

	if (gallery = $this.data('gallery')) {
		gallery.pause();
		$this.empty();
		if (o === null) {
			return false;
		}
	}

	current = -1;
	var css = {
		overflow: 'hidden'
	};
	if ($this.css('position') === 'static') {
		css.position = 'relative';
	}
	if (!parseInt($this.css('height'))) {
		$this.css('height', $this.parent().innerHeight());
	}
	$this.css(css);

	function loadImage(index, callback) {
		if (imgs[index]) {
			if (callback) callback(index, imgs);
			return;
		}
		var image = o.images[index];
		if (!image) {
			image = {};
		}
		if (!image.src) {
			image.src = Q.url('{{Q}}/img/throbbers/transparent.gif');
		}
		var name = image.name ? Q.normalize(image.name) : '';
		var img = $('').attr({
			alt: image.caption ? image.caption : 'image ' + index,
			src: Q.url(image.src)
		}).css({
			visibility: 'hidden',
			position: 'absolute',
			top: '0px',
			left: '0px'
		}).appendTo($this)
		.on('load', onLoad);
		if (name) {
			img.addClass('Q_gallery_caption_' + name);
		}
		imgs[index] = img;
		img.each(function () {
			if (this.complete) {
				$(this).unbind('load');
				onLoad();
			}
		});
		if (image.caption) {
			var css = image.style ? image.style : {};
			css['visibility'] = 'hidden';
			var cap = $('
')
				.css(css)
				.html(image.caption)
				.appendTo($this);
			if (!image.customCaptionPosition) {
				cap.addClass('Q_gallery_caption_centered');
			}
			caps[index] = cap;
		} else {
			caps[index] = $([]);
		}
		if (name) {
			cap.addClass('Q_gallery_caption_' + name);
		}
		function onLoad() {
			imgs[index] = img;
			Q.handle(o.onLoad, $this, [$(this), imgs, o]);
			if (callback) callback(index, imgs);
		}
	}

	var gallery = {
		options: o,
		onLoad: o.onLoad,
		play: function () {
			this.next(true);
		},
		next: function (keepGoing) {
			var previous = current;
			++current;
			if (current >= o.images.length) {
				if (!o.loop) return;
				current = 0;
			}
			loadImage(current, function () {
				beginTransition();
			});
			function beginTransition() {
				var t = Q.extend({}, 2, o.transition, 2, o.images[current].transition);
				var transition = transitions[o.transition.type || ""];
				Q.handle(o.onTransition, $this, [current, imgs, o]);
				if (!o.transitionToFirst && previous === -1) {
					transition(1, 1, { current: current, previous: previous });
					beginInterval();
					return;
				}
				// animTransition && animTransition.pause();
				animTransition = Q.Animation.play(
					transition,
					t.duration,
					t.ease,
					{ current: current, previous: previous }
				);
				beginInterval();
			}
			function beginInterval() {
				var transition = Q.extend({}, 2, o.transition, 2, o.images[current].transition);
				var interval = Q.extend({}, 2, o.interval, 2, o.images[current].interval);
				animPreviousInterval = animInterval;
				animInterval = Q.Animation.play(
					intervals[interval.type || ""],
					interval.duration,
					interval.ease,
					{ current: current, previous: previous }
				);
				loadImage((current+1) % o.images.length, null); // preload next image
				if (keepGoing) {
					tm = setTimeout(function () {
						gallery.next(keepGoing);
					}, interval.duration - transition.duration);
				}
			}
		},
		pause: function () {
			animTransition && animTransition.pause();
			animInterval && animInterval.pause();
			clearTimeout(tm);
		},
		resume: function () {
			animTransition && animTransition.play();
			animInterval && animInterval.play();
		},
		rewind: function () {
			this.pause();
			current = -1;
			animTransition = null;
			animInterval = null;
		}
	};

	if (o.autoplay) {
		gallery.play();
	} else {
		gallery.next(false);
	}

	$this.data('gallery', gallery);

	return this;

},

{
	images: [],
	transition: {
		duration: 1000,
		ease: "smooth",
		type: "crossfade"
	},
	interval: {
		duration: 2000,
		ease: "smooth",
		type: "",
		from: { left: 0, top: 0, width: 1, height: 1 },
		to: { left: 0, top: 0, width: 1, height: 1 }
	},
	autoplay: true,
	transitionToFirst: false,
	loop: true,
	onLoad: null,
	onTransition: null
},

{
	remove: function () {
		$(this).data('gallery').pause();
	}
}

);

})(Q, Q.$, window, document);<|endoftext|>


<commit_before><commit_msg>Add tests<commit_after>import { expect } from 'chai';

import mkobj from '../lib/mkobj';

describe('mkobj', () => {
  it('should be exported as function', () => {
    // eslint-disable-next-line no-unused-expressions
    expect(mkobj).to.be.function;
  });

  it('should return empty object when no arguments provided', () => {
    expect(mkobj()).to.eql({});
  });

  it('should provide empty object if none specified', () => {
    expect(mkobj([true, 'a', 'a'])).to.eql({ a: 'a' });
  });

  it('should copy props into specified object', () => {
    const dst = {};
    const obj = mkobj(dst, [true, 'a', 'a']);
    expect(obj).to.equal(dst);
    expect(obj).to.eql({ a: 'a' });
  });

  it('should correctly combine truthy and falsy props', () => {
    const prop = 'd';
    const val = () => {};

    const obj = mkobj(
      [7, 'a', 1],
      ['s', 'b', 'b'],
      [false, 'c', 'c'],
      [{}, prop, -9],
      [0, 4, 'e'],
      [5, 5, 'f'],
      [val, 'fn', val],
    );

    expect(obj).to.eql({
      a: 1,
      b: 'b',
      d: -9,
      5: 'f',
      fn: val,
    });
  });

  it('should copy props in input order', () => {
    expect(mkobj(
      [true, 'a', 1],
      [true, 'a', 2],
    )).to.eql({ a: 2 });
  });

  it('should ignore all falsy condition values', () => {
    expect(mkobj(
      [false, 'a', 'a'],
      [null, 'b', 'b'],
      [undefined, 'c', 'c'],
      [NaN, 'f', 'f'],
      ['', 'd', 'd'],
      [0, 'e', 'e'],
    )).to.eql({});
  });
});
<|endoftext|>


<commit_before>var Fresh = require('../build/fresh.js');

describe("Fresh.Router", function() {

  beforeEach(function() {
    spyOn(Fresh.Router.prototype, '_bindPopStateEvent');
    spyOn(Fresh.Router.prototype, '_replaceInitialState');
    spyOn(Fresh.Router.prototype, '_pushState');
    spyOn(Fresh.url, 'isPushStateSupported').andReturn(true);
  });

  it("should save a reference to the DOM container", function() {
    // Ignore initial rendering in this test
    spyOn(Fresh.Router.prototype, 'render');
    var router = new Fresh.Router({container: '<body>'});
    expect(router.container).toEqual('<body>');
  });

  it("should create a new RouterHistory instance", function() {
    // Ignore initial rendering in this test
    spyOn(Fresh.Router.prototype, 'render');
    var router = new Fresh.Router({});
    expect(router.history).toEqual(jasmine.any(Fresh.RouterHistory));
  });

  describe("Router.render", function() {

    beforeEach(function() {
      spyOn(Fresh.Router.prototype, 'render');
    });

    it("should be called with constructor props and container", function() {
      var router = new Fresh.Router({
        props: {component: 'List', dataUrl: 'users.json'},
        container: '<body>'
      });
      expect(Fresh.Router.prototype.render.callCount).toEqual(1);
      expect(Fresh.Router.prototype.render.mostRecentCall.args[0]).toEqual({
        component: 'List', dataUrl: 'users.json'});
    });

    it("should be called with props extracted from query string on .goTo", function() {
      var router = new Fresh.Router({});
      router.goTo('?component=List&dataUrl=users.json');
      expect(Fresh.Router.prototype.render.callCount).toEqual(2);
      expect(Fresh.Router.prototype.render.mostRecentCall.args[0]).toEqual({
        component: 'List',
        dataUrl: 'users.json'
      });
    });

    it("should be called with with props from event state on PopState event", function() {
      var router = new Fresh.Router({});
      router._onPopState({
        state: {
          component: 'List',
          dataUrl: 'users.json'
        }
      });
      expect(Fresh.Router.prototype.render.callCount).toEqual(2);
      expect(Fresh.Router.prototype.render.mostRecentCall.args[0]).toEqual({
        component: 'List',
        dataUrl: 'users.json'
      });
    });
  });
});
<commit_msg>Assert external calls instead of of private methods in Router specs #8<commit_after>var Fresh = require('../build/fresh.js');

describe("Fresh.Router", function() {

  beforeEach(function() {
    // Uses window event binding
    spyOn(Fresh.Router.prototype, '_bindPopStateEvent');
    // Use window.history
    spyOn(Fresh.Router.prototype, '_replaceHistoryState');
    spyOn(Fresh.Router.prototype, '_pushHistoryState');
    // Uses window.location
    spyOn(Fresh.Router.prototype, '_replaceInitialState');
    // Methods using jQuery
    spyOn(Fresh.Router.prototype, '_resetContainer');
    spyOn(Fresh.Router.prototype, '_getPreviousContainer');
    spyOn(Fresh.Router.prototype, '_createComponentContainer');
    // TODO: Mock narrower methods
    spyOn(Fresh.Router.prototype, '_transitionComponentContainers');
    // Out of Router scope
    spyOn(Fresh, 'render');
    spyOn(Fresh.url, 'isPushStateSupported').andReturn(true);
  });

  it("should save a reference to the DOM container", function() {
    var router = new Fresh.Router({container: '<body>'});
    expect(router.container).toEqual('<body>');
  });

  it("should create a new RouterHistory instance", function() {
    var router = new Fresh.Router({});
    expect(router.history).toEqual(jasmine.any(Fresh.RouterHistory));
  });

  describe("rendering new Components", function() {

    it("should use constructor props and container", function() {
      var router = new Fresh.Router({
            props: {component: 'List', dataUrl: 'users.json'},
            container: '<body>'
          });
      expect(Fresh.render.callCount).toEqual(1);
      expect(Fresh.render.mostRecentCall.args[0]).toEqual({
        component: 'List', dataUrl: 'users.json'
      });
    });

    it("should use props extracted from query string on .goTo", function() {
      var router = new Fresh.Router({});
      router.goTo('?component=List&dataUrl=users.json');
      expect(Fresh.render.callCount).toEqual(2);
      expect(Fresh.render.mostRecentCall.args[0]).toEqual({
        component: 'List',
        dataUrl: 'users.json'
      });
    });

    it("should use props from event state on PopState event", function() {
      var router = new Fresh.Router({});
      router._onPopState({
        state: {
          component: 'List',
          dataUrl: 'users.json'
        }
      });
      expect(Fresh.render.callCount).toEqual(2);
      expect(Fresh.render.mostRecentCall.args[0]).toEqual({
        component: 'List',
        dataUrl: 'users.json'
      });
    });
  });
});
<|endoftext|>


<commit_before>import Log from "./Log";
import MasterPlugin from "./MasterPlugin";
import Plugin from "./Plugin";
import {EventEmitter} from "events";

export default class PluginManager {

    constructor(bot) {
        this.bot = bot;
        this.log = Log.get("PluginManager");
        this.plugins = [];
        this.emitter = new EventEmitter();

        this.masterPlugin = new MasterPlugin(this.emitter, this);
        this.addPlugin(this.masterPlugin);

        var handleReply = function(chatId, reply) {
            switch (reply.type) {
            case "text":
                return bot.sendMessage(chatId, reply.text, reply.options);

            case "audio":
                return bot.sendAudio(chatId, reply.audio, reply.options);
            case "document":
                return bot.sendDocument(chatId, reply.document, reply.options);
            case "photo":
                return bot.sendPhoto(chatId, reply.photo, reply.options);
            case "sticker":
                return bot.sendSticker(chatId, reply.sticker, reply.options);
            case "video":
                return bot.sendVideo(chatId, reply.video, reply.options);
            case "voice":
                return bot.sendVoice(chatId, reply.voice, reply.options);

            case "status": case "chatAction":
                return bot.sendChatAction(chatId, reply.status, reply.options);

            default:
                this.log.error(`Unrecognized reply type ${reply.type}`);
            }
        };

        const events = ["text", "audio", "document", "photo", "sticker", "video", "voice", "contact", "location", "new_chat_participant", "left_chat_participant", "new_chat_title", "new_chat_photo", "delete_chat_photo", "group_chat_created"];
        // Registers a handler for every Telegram event.
        // It runs the message through the proxy and forwards it to the plugin manager.
        for (let eventName of events) {
            bot.on(
                eventName,
                message => Promise.resolve(message)
                    .then(
                        message => Promise.all(
                            this.plugins
                                .filter(plugin => (plugin.plugin.type & Plugin.Type.PROXY) === Plugin.Type.PROXY)
                                .map(plugin => plugin.proxy(eventName, message))
                        )
                    )
                    .then(array => array.length > 0 ? array[0] : message)
                    .then(message => {
                        const chatID = message.chat.id;
                        this.emit(eventName, message, reply => handleReply(chatID, reply));
                    })
                    .catch(err => {
                        if (err) this.log.error("Message rejected with error", err);
                    })
            );
        }
    }

    // Instantiates the plugin and runs its health check
    // Returns a promise resolving to the plugin itself.
    loadPlugin(pluginName) {
        // default because of es6 classes
        let ThisPlugin = require('./plugins/' + pluginName).default;

        this.log.debug(`Required ${pluginName}`);

        let loadedPlugin = new ThisPlugin(this.emitter, this.bot);
        this.log.debug(`Created ${pluginName}.`);

        if (!this.validatePlugin(loadedPlugin))
            return Promise.reject(`Invalid ${pluginName}.`);

        this.log.verbose(`Validated ${pluginName}.`);
        return Promise.resolve(loadedPlugin);
    }

    // Adds the plugin to the list of active plugins
    addPlugin(loadedPlugin) {
        if (loadedPlugin === null)
            // todo: find out plugin name
            return Promise.reject("Invalid plugin passed.");

        this.plugins.push(loadedPlugin);
        this.log.verbose(`Added ${loadedPlugin.plugin.name}.`);
        return Promise.resolve();
    }

    loadAndAdd(pluginName) {
        this.log.verbose(`Loading and adding ${pluginName}...`);

        return Promise.resolve(pluginName)
        .then(name => this.loadPlugin(name))
        .then(name => this.addPlugin(name));
    }

    // Load and add every plugin in the list.
    // Returns an array of promises.
    loadPlugins(pluginNames) {
        this.log.verbose(`Loading and adding ${pluginNames.length} plugins...`);

        return Promise.all(pluginNames.map(name => this.loadAndAdd(name)));
    }

    startPlugins() {
        return Promise.all(this.plugins.map(pl => pl.start()));
    }

    stopPlugins() {
        return Promise.all(this.plugins.map(pl => pl.stop()));
    }

    emit(event, message, callback) {
        this.log.debug(`Triggered event ${event}`);
        this.emitter.emit(event, message, callback);
    }

    validatePlugin(loadedPlugin) {
        return loadedPlugin.check();
    }
}<commit_msg>Improve plugin.check() management in PluginManager<commit_after>import Log from "./Log";
import MasterPlugin from "./MasterPlugin";
import Plugin from "./Plugin";
import {EventEmitter} from "events";

export default class PluginManager {

    constructor(bot) {
        this.bot = bot;
        this.log = Log.get("PluginManager");
        this.plugins = [];
        this.emitter = new EventEmitter();

        this.masterPlugin = new MasterPlugin(this.emitter, this);
        this.addPlugin(this.masterPlugin);

        var handleReply = function(chatId, reply) {
            switch (reply.type) {
            case "text":
                return bot.sendMessage(chatId, reply.text, reply.options);

            case "audio":
                return bot.sendAudio(chatId, reply.audio, reply.options);
            case "document":
                return bot.sendDocument(chatId, reply.document, reply.options);
            case "photo":
                return bot.sendPhoto(chatId, reply.photo, reply.options);
            case "sticker":
                return bot.sendSticker(chatId, reply.sticker, reply.options);
            case "video":
                return bot.sendVideo(chatId, reply.video, reply.options);
            case "voice":
                return bot.sendVoice(chatId, reply.voice, reply.options);

            case "status": case "chatAction":
                return bot.sendChatAction(chatId, reply.status, reply.options);

            default:
                this.log.error(`Unrecognized reply type ${reply.type}`);
            }
        };

        const events = ["text", "audio", "document", "photo", "sticker", "video", "voice", "contact", "location", "new_chat_participant", "left_chat_participant", "new_chat_title", "new_chat_photo", "delete_chat_photo", "group_chat_created"];
        // Registers a handler for every Telegram event.
        // It runs the message through the proxy and forwards it to the plugin manager.
        for (let eventName of events) {
            bot.on(
                eventName,
                message => Promise.resolve(message)
                    .then(
                        message => Promise.all(
                            this.plugins
                                .filter(plugin => (plugin.plugin.type & Plugin.Type.PROXY) === Plugin.Type.PROXY)
                                .map(plugin => plugin.proxy(eventName, message))
                        )
                    )
                    .then(array => array.length > 0 ? array[0] : message)
                    .then(message => {
                        const chatID = message.chat.id;
                        this.emit(eventName, message, reply => handleReply(chatID, reply));
                    })
                    .catch(err => {
                        if (err) this.log.error("Message rejected with error", err);
                    })
            );
        }
    }

    // Instantiates the plugin and runs its health check
    // Returns a promise resolving to the plugin itself.
    loadPlugin(pluginName) {
        // default because of es6 classes
        let ThisPlugin = require('./plugins/' + pluginName).default;

        this.log.debug(`Required ${pluginName}`);

        let loadedPlugin = new ThisPlugin(this.emitter, this.bot);
        this.log.debug(`Created ${pluginName}.`);

        try {
            // The plugin will throw an error if its state is invalid.
            loadedPlugin.check();
            this.log.debug(`Health check for plugin ${pluginName} passed.`);
        } catch (e) {
            this.log.warn(e);
            this.log.warn(`Health check for plugin ${pluginName} failed.`);
        }

        return loadedPlugin;
    }

    // Adds the plugin to the list of active plugins
    addPlugin(loadedPlugin) {
        this.plugins.push(loadedPlugin);
        this.log.verbose(`Added ${loadedPlugin.plugin.name}.`);
    }

    loadAndAdd(pluginName) {
        this.log.verbose(`Loading and adding ${pluginName}...`);

        return Promise.resolve(pluginName)
        .then(name => this.loadPlugin(name))
        .then(name => this.addPlugin(name));
    }

    // Load and add every plugin in the list.
    // Returns an array of promises.
    loadPlugins(pluginNames) {
        this.log.verbose(`Loading and adding ${pluginNames.length} plugins...`);

        return Promise.all(pluginNames.map(name => this.loadAndAdd(name)));
    }

    startPlugins() {
        return Promise.all(this.plugins.map(pl => pl.start()));
    }

    stopPlugins() {
        return Promise.all(this.plugins.map(pl => pl.stop()));
    }

    emit(event, message, callback) {
        this.log.debug(`Triggered event ${event}`);
        this.emitter.emit(event, message, callback);
    }
}<|endoftext|>


<commit_before>import React from 'react';
import {Observable, Disposable} from 'rx';
import invariant from 'invariant';
import assign from 'object-assign';

export default class TransmitterContainer extends React.Component {
	constructor(props) {
		super(props);

		this.state = {
			status: 'pending',
			error: null,
		};
		this.subscription = Disposable.create();
	}

	fetch() {
		const fragments = this.observe();

		const streams = Object.keys(fragments)
			.map(name => {
				const fragment = fragments[name];
				const observable = fromEverything(fragment);

				invariant(name !== 'status', 'Name "status" is reserved by Transmitter. Please choose another name');
				invariant(name !== 'error', 'Name "error" is reserved by Transmitter. Please choose another name');

				return observable.map(data => wrapFragment(name, data));
			});

		return Observable.combineLatest(streams)
			.map(fragments => fragments.reduce((a, b) => assign(a, b), {}))
			.subscribe(
				results => this.success(results),
				error => this.failure(error)
			);
	}

	refetch() {
		this.pending();
		this.subscription.dispose();
		this.subscription = this.fetch();
	}

	observe() {
		invariant(false, 'Transmitter.Container requires `observe` method to be implemented');
	}

	success(fragments) {
		this.setState({status: 'success', error: null, ...fragments});
	}

	failure(error) {
		this.setState({status: 'failure', error});
	}

	pending() {
		this.setState({status: 'pending', error: null});
	}

	componentWillMount() {
		this.subscription = this.fetch();
	}

	componentWillUnmount() {
		this.subscription.dispose();
	}

	componentWillReceiveProps() {
		this.refetch();
	}

	render() {
		invariant(false, 'Transmitter.Container requires `render` method to be implemented');
	}
}

function fromEverything(object) {
	if (object instanceof Promise) return Observable.fromPromise(object);
	if (Observable.isObservable(object)) return object;

	return Observable.just(object);
}

function wrapFragment(name, value) {
	return {[name]: value};
}
<commit_msg>use direct link to Component<commit_after>import React, {Component} from 'react';
import {Observable, Disposable} from 'rx';
import invariant from 'invariant';
import assign from 'object-assign';

export default class TransmitterContainer extends Component {
	constructor(props) {
		super(props);

		this.state = {
			status: 'pending',
			error: null,
		};
		this.subscription = Disposable.create();
	}

	fetch() {
		const fragments = this.observe();

		const streams = Object.keys(fragments)
			.map(name => {
				const fragment = fragments[name];
				const observable = fromEverything(fragment);

				invariant(name !== 'status', 'Name "status" is reserved by Transmitter. Please choose another name');
				invariant(name !== 'error', 'Name "error" is reserved by Transmitter. Please choose another name');

				return observable.map(data => wrapFragment(name, data));
			});

		return Observable.combineLatest(streams)
			.map(fragments => fragments.reduce((a, b) => assign(a, b), {}))
			.subscribe(
				results => this.success(results),
				error => this.failure(error)
			);
	}

	refetch() {
		this.pending();
		this.subscription.dispose();
		this.subscription = this.fetch();
	}

	observe() {
		invariant(false, 'Transmitter.Container requires `observe` method to be implemented');
	}

	success(fragments) {
		this.setState({status: 'success', error: null, ...fragments});
	}

	failure(error) {
		this.setState({status: 'failure', error});
	}

	pending() {
		this.setState({status: 'pending', error: null});
	}

	componentWillMount() {
		this.subscription = this.fetch();
	}

	componentWillUnmount() {
		this.subscription.dispose();
	}

	componentWillReceiveProps() {
		this.refetch();
	}

	render() {
		invariant(false, 'Transmitter.Container requires `render` method to be implemented');
	}
}

function fromEverything(object) {
	if (object instanceof Promise) return Observable.fromPromise(object);
	if (Observable.isObservable(object)) return object;

	return Observable.just(object);
}

function wrapFragment(name, value) {
	return {[name]: value};
}
<|endoftext|>