<commit_before>// extends src/nodes/image.js which extends src/node-box-native-view.js

$(function(){

  var template =
    '
'+
      '<button class="start">start</button>'+
      '<button class="stop">stop</button>'+
    '
';

  Iframework.NativeNodes["image-slitscan"] = Iframework.NativeNodes["image"].extend({

    info: {
      title: "slitscan",
      author: "forresto",
      description: "slitscan in different directions"
    },
    template: _.template(template),
    events: {
      "click .start": "inputstart",
      "click .stop":  "inputstop"
    },
    initializeModule: function(){
      this.$("button").button();
    },
    inputwidth: function (i) {
      this._width = i;
      this.canvas.width = i;
    },
    inputheight: function (i) {
      this._height = i;
      this.canvas.height = i;
    },
    _scanning: false,
    inputstart: function () {
      this._scanning = true;
      if (!this._fixed) {
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
      }
      this._position = 0;
    },
    inputstop: function () {
      this._scanning = false;
      this._position = 0;
    },
    inputclear: function () {
      this.inputstop();
      this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
    },
    inputsend: function () {
      this.send("image", this.canvas);
    },
    _position: 0,
    _speed: 1,
    redraw: function(){
      // Called from NodeBoxNativeView.renderAnimationFrame()
      if (this._stream && this._scanning) {
        var sx, sy, sw, sh, dx, dy, dw, dh;
        if (this._fixed) {
          if (this._direction === "up") {
            this.context.drawImage(this.canvas, 0, 0-this._speed);
            sx = 0;
            sy = Math.floor(this._stream.height/2);
            sw = this._stream.width;
            sh = this._speed;
            dx = 0;
            dy = this.canvas.height-1 - this._speed;
          } else if (this._direction === "down") {
            this.context.drawImage(this.canvas, 0, this._speed);
            sx = 0;
            sy = Math.floor(this._stream.height/2);
            sw = this._stream.width;
            sh = this._speed;
            dx = 0;
            dy = 0;
          } else if (this._direction === "left") {
            this.context.drawImage(this.canvas, 0-this._speed, 0);
            sx = Math.floor(this._stream.width/2);
            sy = 0;
            sw = this._speed;
            sh = this._stream.height;
            dx = this.canvas.width-1 - this._speed;
            dy = 0;
          } else {
            // right
            this.context.drawImage(this.canvas, this._speed, 0);
            sx = Math.floor(this._stream.width/2);
            sy = 0;
            sw = this._speed;
            sh = this._stream.height;
            dx = 0;
            dy = 0;
          }
          dw = sw;
          dh = sh;
          this.context.drawImage(this._stream, sx, sy, sw, sh, dx, dy, dw, dh);
          this.inputsend();
        } else {
          // Not fixed
          if (this._direction === "up") {
            sx = 0;
            sy = this._stream.height-1 - this._position*this._speed;
            sw = this._stream.width;
            sh = this._speed;
          } else if (this._direction === "down") {
            sx = 0;
            sy = this._position*this._speed;
            sw = this._stream.width;
            sh = this._speed;
          } else if (this._direction === "left") {
            sx = this._stream.width-1 - this._position*this._speed;
            sy = 0;
            sw = this._speed;
            sh = this._stream.height;
          } else {
            // right
            sx = this._position*this._speed;
            sy = 0;
            sw = this._speed;
            sh = this._stream.height;
          }
          dx = sx;
          dy = sy;
          dw = sw;
          dh = sh;
          this.context.drawImage(this._stream, sx, sy, sw, sh, dx, dy, dw, dh);
          this._position++;
          var done = false;
          if (this._direction === "up" || this._direction === "down") {
            if (this._position*this._speed >= this._stream.height) {
              done = true;
            }
          } else {
            if (this._position*this._speed >= this._stream.width) {
              done = true;
            }
          }
          if (done) {
            this.inputstop();
            this.inputsend();
          }
        }
      }
    },
    inputs: {
      stream: {
        type: "image",
        description: "video stream to scan"
      },
      width: {
        type: "int",
        description: "image width",
        min: 1,
        "default": 500
      },
      height: {
        type: "int",
        description: "image height",
        min: 1,
        "default": 500
      },
      direction: {
        type: "string",
        description: "direction to scan",
        options: "up right down left".split(" "),
        "default": "right"
      },
      speed: {
        type: "int",
        description: "speed is the thickness of the scan line",
        min: 1,
        "default": 1
      },
      fixed: {
        type: "boolean",
        description: "fixes the input column of pixels for a continuous scan",
        "default": false
      },
      start: {
        type: "bang",
        description: "start scanning"
      },
      stop: {
        type: "bang",
        description: "stop scanning"
      },
      clear: {
        type: "bang",
        description: "clear the image"
      },
      send: {
        type: "bang",
        description: "send the image"
      }
    },
    outputs: {
      image: {
        type: "image",
        description: "the whole grid image"
      }
    }

  });

});
<commit_msg>slitscan speed edge case<commit_after>// extends src/nodes/image.js which extends src/node-box-native-view.js

$(function(){

  var template =
    '
'+
      '<button class="start">start</button>'+
      '<button class="stop">stop</button>'+
    '
';

  Iframework.NativeNodes["image-slitscan"] = Iframework.NativeNodes["image"].extend({

    info: {
      title: "slitscan",
      author: "forresto",
      description: "slitscan in different directions"
    },
    template: _.template(template),
    events: {
      "click .start": "inputstart",
      "click .stop":  "inputstop"
    },
    initializeModule: function(){
      this.$("button").button();
    },
    inputwidth: function (i) {
      this._width = i;
      this.canvas.width = i;
    },
    inputheight: function (i) {
      this._height = i;
      this.canvas.height = i;
    },
    _scanning: false,
    inputstart: function () {
      this._scanning = true;
      if (!this._fixed) {
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
      }
      this._position = 0;
    },
    inputstop: function () {
      this._scanning = false;
      this._position = 0;
    },
    inputclear: function () {
      this.inputstop();
      this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
    },
    inputsend: function () {
      this.send("image", this.canvas);
    },
    _position: 0,
    _speed: 1,
    redraw: function(){
      // Called from NodeBoxNativeView.renderAnimationFrame()
      if (this._stream && this._scanning) {
        var sx, sy, sw, sh, dx, dy, dw, dh;
        if (this._fixed) {
          if (this._direction === "up") {
            this.context.drawImage(this.canvas, 0, 0-this._speed);
            sx = 0;
            sy = Math.floor(this._stream.height/2);
            sw = this._stream.width;
            sh = this._speed;
            dx = 0;
            dy = this.canvas.height-1 - this._speed;
          } else if (this._direction === "down") {
            this.context.drawImage(this.canvas, 0, this._speed);
            sx = 0;
            sy = Math.floor(this._stream.height/2);
            sw = this._stream.width;
            sh = this._speed;
            dx = 0;
            dy = 0;
          } else if (this._direction === "left") {
            this.context.drawImage(this.canvas, 0-this._speed, 0);
            sx = Math.floor(this._stream.width/2);
            sy = 0;
            sw = this._speed;
            sh = this._stream.height;
            dx = this.canvas.width-1 - this._speed;
            dy = 0;
          } else {
            // right
            this.context.drawImage(this.canvas, this._speed, 0);
            sx = Math.floor(this._stream.width/2);
            sy = 0;
            sw = this._speed;
            sh = this._stream.height;
            dx = 0;
            dy = 0;
          }
          dw = sw;
          dh = sh;
          this.context.drawImage(this._stream, sx, sy, sw, sh, dx, dy, dw, dh);
          this.inputsend();
        } else {
          // Not fixed
          if (this._direction === "up") {
            sx = 0;
            sy = this._stream.height-1 - this._position*this._speed;
            sw = this._stream.width;
            sh = this._speed;
          } else if (this._direction === "down") {
            sx = 0;
            sy = this._position*this._speed;
            sw = this._stream.width;
            sh = this._speed;
          } else if (this._direction === "left") {
            sx = this._stream.width-1 - this._position*this._speed;
            sy = 0;
            sw = this._speed;
            sh = this._stream.height;
          } else {
            // right
            sx = this._position*this._speed;
            sy = 0;
            sw = this._speed;
            sh = this._stream.height;
          }
          dx = sx;
          dy = sy;
          dw = sw;
          dh = sh;
          this.context.drawImage(this._stream, sx, sy, sw, sh, dx, dy, dw, dh);
          this._position++;
          var done = false;
          if (this._direction === "up" || this._direction === "down") {
            if (this._position*this._speed >= this._stream.height+this._speed) {
              done = true;
            }
          } else {
            if (this._position*this._speed >= this._stream.width+this._speed) {
              done = true;
            }
          }
          if (done) {
            this.inputstop();
            this.inputsend();
          }
        }
      }
    },
    inputs: {
      stream: {
        type: "image",
        description: "video stream to scan"
      },
      width: {
        type: "int",
        description: "image width",
        min: 1,
        "default": 500
      },
      height: {
        type: "int",
        description: "image height",
        min: 1,
        "default": 500
      },
      direction: {
        type: "string",
        description: "direction to scan",
        options: "up right down left".split(" "),
        "default": "right"
      },
      speed: {
        type: "int",
        description: "speed is the thickness of the scan line",
        min: 1,
        "default": 1
      },
      fixed: {
        type: "boolean",
        description: "fixes the input column of pixels for a continuous scan",
        "default": false
      },
      start: {
        type: "bang",
        description: "start scanning"
      },
      stop: {
        type: "bang",
        description: "stop scanning"
      },
      clear: {
        type: "bang",
        description: "clear the image"
      },
      send: {
        type: "bang",
        description: "send the image"
      }
    },
    outputs: {
      image: {
        type: "image",
        description: "the whole grid image"
      }
    }

  });

});
<|endoftext|>


<commit_before>import { createStore, applyMiddleware } from 'redux';
import { reduxObservable } from 'redux-observable';
// import createLogger from 'redux-logger';
import rootReducer from '../reducers';

// const logger = createLogger();

const exposeState = store => next => action => {
  return next(Object.assign({}, action, {
    getState: store.getState,
  }));
};

export default function configureStore(initialState) {
  return createStore(
    rootReducer,
    initialState,
    applyMiddleware(reduxObservable(), exposeState)
  );
}
<commit_msg>Added triggerUndo middleware<commit_after>import { createStore, applyMiddleware } from 'redux';
import { reduxObservable } from 'redux-observable';
import rootReducer from '../reducers';

import * as constants from '../constants';
import { setBackwardCheckpoint } from '../actions';

const triggerUndo = store => next => action => {
  console.log('triggerUndo');
  if (action.type === constants.REMOVE_CELL) {
    store.dispatch(setBackwardCheckpoint(store.getState().document));
  }
  return next(action);
};

export default function configureStore(initialState) {
  return createStore(
    rootReducer,
    initialState,
    applyMiddleware(reduxObservable(), triggerUndo)
  );
}
<|endoftext|>


<commit_before>module.exports = Account;

function Account(name, balance) {
  this.name = name = || 'John Doe';
  this.balance = balance || 0;
}

Account.prototype.withdraw = function(amount) {
  // Complete funciton here
}

Account.prototype.deposit = function(amount) {
  // Complete funciton here
}
<commit_msg>Adding comments<commit_after>module.exports = Account;

function Account(name, balance) {
  // We take the name that is passed in or we default to 'John Doe'
  this.name = name = || 'John Doe';

  // We take the balance passed in or set the initial balance to 0
  this.balance = balance || 0;
}

/**
 * Withdraws a given amount from this account.
 *
 * @param amount: The amount to be withdrawn from the bank acocunt.
 */
Account.prototype.withdraw = function(amount) {
  // Complete funciton here
}

/**
 * Deposits a given amount from this account.
 *
 * @param amount: The amount to be deposited in the bank acocunt.
 */
Account.prototype.deposit = function(amount) {
  // Complete funciton here
}
<|endoftext|>


<commit_before>// Definition of Player object.
var Player = function() {
	this.id 		= null;
	this.position 	= null;
	this.team 		= null;
	this.alive		= null;
	this.clock		= null;
}

Player.prototype = {

}

// Definition of Model object
var Model = function() {
	this.players 	= {};
	this.clock		= 0;
}

Model.prototype = {
	getPlayer: function(id) {
		var player = this.players[id];
		if (typeof(player) == 'undefined') {
			player = new Player();
			player.id = id;
			player.alive = true;
			this.players[id] = player;
		}
		return player;
	},
	update: function(frame) {
		// update clock
		if (typeof(frame.clock) == 'undefined') {
			return;
		}

		if (frame.clock != null) {
			this.clock = frame.clock;
		}

		// we are only interested in the packet if it has a clock / player / team values
		if (typeof(frame.position) != 'undefined'
				&& typeof(frame.player_id) != 'undefined') {
			var player = this.getPlayer(frame.player_id);
			player.position = frame.position;
			player.team 	= frame.team;
			player.clock	= this.clock;
		}

		if (typeof(frame.target) != 'undefined'
				&& typeof(frame.destroyed_by) != 'undefined') {
			var player 	 = this.getPlayer(frame.target);
			player.alive = false;
			player.clock = this.clock;
		}
	}
}

var Viewer = function(target, serviceUrl) {
	this.target = target;
	this.serviceUrl = serviceUrl;
	this.overlay = null;
	this.model = null;

	// configure the target element as a replay viewer
	target.classList.add('replay-viewer');

	this.map = document.createElement('img');
	this.map.classList.add('map');
	this.map.width = this.map.height = 500
	this.target.appendChild(this.map);

	this.overlay = document.createElement('canvas');
	this.overlay.classList.add('overlay');
	this.overlay.width = this.overlay .height = 500;
	this.target.appendChild(this.overlay);

	var hash = window.location.hash;
	if (hash.length) {
		var id = hash.substr(1);
		this.fetch(id);
	}
}

Viewer.prototype = {
	replay: function(data) {
		var ctx = this.overlay.getContext("2d");
		var clock = 0, i = 0;
		this.model = new Model();
		var model = this.model;
		var viewer = this;
		var update = function(model, packets, start, window_size, start_ix) {
			var ix;
			for (ix = 0; (start_ix + ix) < packets.length; ix++) {
				var packet = packets[start_ix + ix];

				if (typeof(packet.clock) == 'undefined') {
					continue;
				}

				// escape when outside of window size
				if (packet.clock > (start + window_size)) {
					break;
				}

				// update model with packet
				model.update(packet);
			}

			viewer.show(data, ctx);

			var next_ix = start_ix + ix;
			if (next_ix < packets.length) {
				setTimeout(function() {
					update(model, packets, start + window_size, window_size, next_ix);
				}, 100);
			}
		}

		update(this.model, data.packets, 0.0, 0.2, 0);
	},
	show: function(data, ctx) {
		// reset overlay
		ctx.clearRect(0, 0, 500, 500);

		ctx.fillStyle = "#FFFF00";
		ctx.textBaseline = "top";
		ctx.font = "bold 20px sans-serif";
		ctx.fillText(String(this.model.clock.toFixed(2)), 8, 5);

		for (var player_id in this.model.players) {
			var player = this.model.players[player_id];
			if (typeof(player.team) == undefined
					|| player.position == null
					|| [0,1].indexOf(player.team) < 0) {
				continue;
			}

			var coord = to_2d_coord(player.position, [-500, 500, -500, 500], 500, 500);

			var colors = [
				[0, 255, 0],
				[255, 0, 0]
			];

			var recorder_color = [0, 0, 255];

			ctx.lineWidth = 2;
			var color = player.id == data.recorder_id ?  recorder_color : colors[player.team];
			var age   = player.alive ? ((this.model.clock - player.clock) / 20) : 0;
			age = age > 0.66 ? 0.66 : age;
			var style = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + (1 - age) +  ")";

			ctx.strokeStyle = ctx.fillStyle = style;
			ctx.beginPath();
			ctx.arc(coord.x, coord.y, 3, 0, 2*Math.PI);

			if (player.alive) {
				ctx.fill();
			} else {
				ctx.stroke();
			}
		}
	},
	fetch: function(id) {
		this.serviceRequest({id: id});
	},
	process: function(file) {
		this.serviceRequest({file: file});
	},
	serviceRequest: function(values) {
		// send data request
		var replayRequest = new XMLHttpRequest();
		replayRequest.open("POST", this.serviceUrl, true);

		var map = this.map;
		var viewer = this;
		map.classList.add('loading');
		replayRequest.onreadystatechange = function(state) {
			if(replayRequest.readyState != XMLHttpRequest.DONE) {
				return;
			}

			// proces data
			var data = JSON.parse(replayRequest.response)
			var mapURL = 'maps/' + data["map"] + "_" + data["mode"] + ".png";
			map.setAttribute('src', mapURL);
			map.classList.remove('loading');

			// play
			viewer.replay(data);
		}

		var formData = new FormData();
		for (key in values) {
			formData.append(key, values[key]);
		}

		replayRequest.send(formData);
	}
}

function to_2d_coord(position, map_boundaries, width, height) {
    var x = position[0], y = position[2], z = position[1];
    x = (x - map_boundaries[0]) * (width / (map_boundaries[1] - map_boundaries[0] + 1));
    y = (map_boundaries[3] - y) * (height / (map_boundaries[3] - map_boundaries[2] + 1));
    return { x: x, y: y };
}<commit_msg>fighting with javascript 'this' object, solved with Function.call and Function.bind<commit_after>// Definition of Player object.
var Player = function() {
	this.id 		= null;
	this.position 	= null;
	this.team 		= null;
	this.alive		= null;
	this.clock		= null;
}

Player.prototype = {

}

// Definition of Model object
var Model = function() {
	this.players 	= {};
	this.clock		= 0;
}

Model.prototype = {
	getPlayer: function(id) {
		var player = this.players[id];
		if (typeof(player) == 'undefined') {
			player = new Player();
			player.id = id;
			player.alive = true;
			this.players[id] = player;
		}
		return player;
	},
	update: function(frame) {
		// update clock
		if (typeof(frame.clock) == 'undefined') {
			return;
		}

		if (frame.clock != null) {
			this.clock = frame.clock;
		}

		// we are only interested in the packet if it has a clock / player / team values
		if (typeof(frame.position) != 'undefined'
				&& typeof(frame.player_id) != 'undefined') {
			var player = this.getPlayer(frame.player_id);
			player.position = frame.position;
			player.team 	= frame.team;
			player.clock	= this.clock;
		}

		if (typeof(frame.target) != 'undefined'
				&& typeof(frame.destroyed_by) != 'undefined') {
			var player 	 = this.getPlayer(frame.target);
			player.alive = false;
			player.clock = this.clock;
		}
	}
}

var Viewer = function(target, serviceUrl) {
	this.target = target;
	this.serviceUrl = serviceUrl;
	this.model = null;

	// configure the target element as a replay viewer
	target.classList.add('replay-viewer');

	this.map = document.createElement('img');
	this.map.classList.add('map');
	this.map.width = this.map.height = 500
	this.target.appendChild(this.map);

	this.overlay = document.createElement('canvas');
	this.overlay.classList.add('overlay');
	this.overlay.width = this.overlay .height = 500;
	this.target.appendChild(this.overlay);

	var hash = window.location.hash;
	if (hash.length) {
		var id = hash.substr(1);
		this.fetch(id);
	}
}

Viewer.prototype = {
	replay: function(data) {
		var ctx = this.overlay.getContext("2d");
		this.model = new Model();

		var update = function(packets, window_start, window_size, start_ix) {
			var window_end = window_start + window_size, ix;
			for (ix = start_ix; ix < packets.length; ix++) {
				var packet = packets[ix];

				if (typeof(packet.clock) == 'undefined') {
					continue;
				}

				// escape when outside of window size
				if (packet.clock > window_end) {
					break;
				}

				// update model with packet
				this.model.update(packet);
			}

			this.show(data, ctx);

			if (ix < packets.length) {
				setTimeout(update.bind(this, packets, window_end, window_size, ix), 100);
			}
		}

		update.call(this, data.packets, 0.0, 0.2, 0);
	},
	show: function(data, ctx) {
		// reset overlay
		ctx.clearRect(0, 0, 500, 500);

		ctx.fillStyle = "#FFFF00";
		ctx.textBaseline = "top";
		ctx.font = "bold 20px sans-serif";
		ctx.fillText(String(this.model.clock.toFixed(2)), 8, 5);

		for (var player_id in this.model.players) {
			var player = this.model.players[player_id];
			if (typeof(player.team) == undefined
					|| player.position == null
					|| [0,1].indexOf(player.team) < 0) {
				continue;
			}

			var coord = to_2d_coord(player.position, [-500, 500, -500, 500], 500, 500);

			var colors = [
				[0, 255, 0],
				[255, 0, 0]
			];

			var recorder_color = [0, 0, 255];

			ctx.lineWidth = 2;
			var color = player.id == data.recorder_id ?  recorder_color : colors[player.team];
			var age   = player.alive ? ((this.model.clock - player.clock) / 20) : 0;
			age = age > 0.66 ? 0.66 : age;
			var style = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + (1 - age) +  ")";

			ctx.strokeStyle = ctx.fillStyle = style;
			ctx.beginPath();
			ctx.arc(coord.x, coord.y, 3, 0, 2*Math.PI);

			if (player.alive) {
				ctx.fill();
			} else {
				ctx.stroke();
			}
		}
	},
	fetch: function(id) {
		this.serviceRequest({id: id});
	},
	process: function(file) {
		this.serviceRequest({file: file});
	},
	serviceRequest: function(values) {
		// send data request
		var replayRequest = new XMLHttpRequest();
		replayRequest.open("POST", this.serviceUrl, true);

		var map = this.map;
		var viewer = this;
		map.classList.add('loading');
		replayRequest.onreadystatechange = function(state) {
			if(replayRequest.readyState != XMLHttpRequest.DONE) {
				return;
			}

			// proces data
			var data = JSON.parse(replayRequest.response)
			var mapURL = 'maps/' + data["map"] + "_" + data["mode"] + ".png";
			map.setAttribute('src', mapURL);
			map.classList.remove('loading');

			// play
			viewer.replay(data);
		}

		var formData = new FormData();
		for (key in values) {
			formData.append(key, values[key]);
		}

		replayRequest.send(formData);
	}
}

function to_2d_coord(position, map_boundaries, width, height) {
    var x = position[0], y = position[2], z = position[1];
    x = (x - map_boundaries[0]) * (width / (map_boundaries[1] - map_boundaries[0] + 1));
    y = (map_boundaries[3] - y) * (height / (map_boundaries[3] - map_boundaries[2] + 1));
    return { x: x, y: y };
}<|endoftext|>


<commit_before>var registerLayerAdjustment = require('../registry').registerLayerAdjustment;
var tileLayer = require('../tileLayer');

var webgl_tileLayer = function () {
  'use strict';

  var geo_event = require('../event');

  var m_this = this,
      s_init = this._init,
      s_exit = this._exit,
      s_update = this._update,
      s_visible = this.visible,
      s_zIndex = this.zIndex,
      m_quadFeature,
      m_nextTileId = 0,
      m_tiles = [];

  /**
   * Add a tile to the list of quads.
   *
   * @param {geo.tile} tile The tile to add and draw.
   */
  this._drawTile = function (tile) {
    if (!m_quadFeature) {
      return;
    }
    var bounds = m_this._tileBounds(tile),
        level = tile.index.level || 0,
        to = m_this._tileOffset(level),
        crop = m_this.tileCropFromBounds(tile),
        quad = {},
        offset, upperTileLayers,
        depthBits = m_this.renderer().contextRenderer().m_depthBits,
        z = 0;  // this will change if tileLayers have topography

    if (crop) {
      quad.crop = {
        x: crop.x / m_this._options.tileWidth,
        y: crop.y / m_this._options.tileHeight
      };
    }
    quad.ul = m_this.fromLocal(m_this.fromLevel({
      x: bounds.left - to.x, y: bounds.top - to.y
    }, level), 0);
    quad.ll = m_this.fromLocal(m_this.fromLevel({
      x: bounds.left - to.x, y: bounds.bottom - to.y
    }, level), 0);
    quad.ur = m_this.fromLocal(m_this.fromLevel({
      x: bounds.right - to.x, y: bounds.top - to.y
    }, level), 0);
    quad.lr = m_this.fromLocal(m_this.fromLevel({
      x: bounds.right - to.x, y: bounds.bottom - to.y
    }, level), 0);
    quad.ul.z = quad.ll.z = quad.ur.z = quad.lr.z = z;

    /* Compute a zOffset per layer and level.  This ensures all tile levels in
     * all tile layers are sorted with the earlier layers and lower levels on
     * the bottom.  Ideally, don't change the z values, since the z-buffer may
     * be expected to have the correct values, but by shifting each layer a
     * small offset, the levels will appear in the correct order and avoid
     * z-fighting.  The zOffset is applied *after* matrix transformations, and
     * is in the webgl clip space of [0-1] with the resolution of the
     * DEPTH_BITS. */
    offset = m_this._options.keepLower ? m_this._options.maxLevel - level + 1 : 1;
    upperTileLayers = m_this.map().listSceneObjects().filter(function (object) {
      return object instanceof tileLayer;
    });
    upperTileLayers = upperTileLayers.slice(upperTileLayers.indexOf(m_this) + 1);
    upperTileLayers.forEach(function (object) {
      offset += object._options.keepLower ? object._options.maxLevel - object._options.minLevel + 1 : 1;
    });
    /* See the definition of `_zOffsetMultiple` for more details. */
    quad.zOffset = offset * Math.pow(2, -depthBits) * tileLayer._zOffsetMultiple;

    m_nextTileId += 1;
    quad.id = m_nextTileId;
    tile.quadId = quad.id;
    quad.image = tile.image;
    m_tiles.push(quad);
    m_quadFeature.data(m_tiles);
    m_quadFeature._update();
    m_this.draw();
  };

  /**
   * Remove a tile from the list of quads.   The quadFeature is redrawn.
   *
   * @param {geo.tile} tile The tile to remove.
   */
  this._remove = function (tile) {
    if (tile.quadId !== undefined && m_quadFeature) {
      for (var i = 0; i < m_tiles.length; i += 1) {
        if (m_tiles[i].id === tile.quadId) {
          m_tiles.splice(i, 1);
          break;
        }
      }
      m_quadFeature.data(m_tiles);
      m_quadFeature._update();
      m_this.draw();
    }
  };

  /**
   * Get/Set visibility of the layer.
   *
   * @param {boolean} [val] If specified, change the visibility, otherwise
   *    return it.
   * @returns {boolean|this} The current visibility or the layer.
   */
  this.visible = function (val) {
    if (val === undefined) {
      return s_visible();
    }
    if (m_this.visible() !== val) {
      s_visible(val);
      if (m_quadFeature) {
        m_quadFeature.visible(m_quadFeature.visible(undefined, true), true);
      }
    }
    return m_this;
  };

  /**
   * Get or set the z-index of the layer.  The z-index controls the display
   * order of the layers in much the same way as the CSS z-index property.
   *
   * @param {number} [zIndex] The new z-index, or undefined to return the
   *    current z-index.
   * @param {boolean} [allowDuplicate] When setting the z index, if this is
   *    truthy, allow other layers to have the same z-index.  Otherwise,
   *    ensure that other layers have distinct z-indices from this one.
   * @returns {number|this}
   */
  this.zIndex = function (zIndex, allowDuplicate) {
    if (zIndex !== undefined) {
      this._clearQuads(true);
    }
    return s_zIndex.apply(m_this, arguments);
  };

  /**
   * If the z-index has changed or layers are added or removed, clear the quads
   * so they are composited in the correct order.
   *
   * @param {boolean} [noredraw] If truthy, don't redraw after clearing the
   *    quads.
   */
  this._clearQuads = function (noredraw) {
    m_this.clear();
    if (m_quadFeature) {
      m_quadFeature.modified();
    }
    if (noredraw !== true) {
      this.draw();
    }
  };

  /**
   * Update layer.
   *
   * @param {object} request A value to pass to the parent class.
   * @returns {this}
   */
  this._update = function (request) {
    s_update.call(m_this, request);
    if (m_quadFeature) {
      m_quadFeature._update();
    }
    return m_this;
  };

  /**
   * Cleanup.  This purges the texture and tile cache.
   */
  this._cleanup = function () {
    var tile;
    if (m_this.cache && m_this.cache._cache) {
      for (var hash in m_this.cache._cache) {
        tile = m_this.cache._cache[hash];
        if (tile._image && tile._image._texture) {
          delete tile._image._texture;
        }
      }
      m_this.cache.clear();
    }
    m_this.clear();
  };

  /**
   * Destroy.
   */
  this._exit = function () {
    m_this._cleanup();
    m_this.deleteFeature(m_quadFeature);
    m_quadFeature = null;
    m_tiles = [];
    s_exit.apply(m_this, arguments);
  };

  /**
   * Initialize after the layer is added to the map.
   */
  this._init = function () {
    s_init.apply(m_this, arguments);
    m_quadFeature = m_this.createFeature('quad', {
      previewColor: m_this._options.previewColor,
      previewImage: m_this._options.previewImage
    });
    m_quadFeature.geoTrigger = undefined;
    m_quadFeature.gcs(m_this._options.gcs || m_this.map().gcs());
    m_quadFeature.data(m_tiles);
    m_quadFeature._update();

    var map = m_this.map();
    map.geoOn(geo_event.layerAdd, this._clearQuads);
    map.geoOn(geo_event.layerRemove, this._clearQuads);
  };

  /* These functions don't need to do anything. */
  this._getSubLayer = function () {};
  this._updateSubLayers = undefined;
};

/* Use a multiple of the minimum z delta provided by DEPTH_BITS so that
 * rounding won't accidentally merge two levels.  This may need to be higher
 * if the tile layer is not flat to the camera. */
tileLayer._zOffsetMultiple = 2;

registerLayerAdjustment('webgl', 'tile', webgl_tileLayer);

module.exports = webgl_tileLayer;
<commit_msg>Only clear tile quads when actually appropriate.<commit_after>var registerLayerAdjustment = require('../registry').registerLayerAdjustment;
var tileLayer = require('../tileLayer');

var webgl_tileLayer = function () {
  'use strict';

  var geo_event = require('../event');

  var m_this = this,
      s_init = this._init,
      s_exit = this._exit,
      s_update = this._update,
      s_visible = this.visible,
      s_zIndex = this.zIndex,
      m_quadFeature,
      m_nextTileId = 0,
      m_tiles = [];

  /**
   * Add a tile to the list of quads.
   *
   * @param {geo.tile} tile The tile to add and draw.
   */
  this._drawTile = function (tile) {
    if (!m_quadFeature) {
      return;
    }
    var bounds = m_this._tileBounds(tile),
        level = tile.index.level || 0,
        to = m_this._tileOffset(level),
        crop = m_this.tileCropFromBounds(tile),
        quad = {},
        offset, upperTileLayers,
        depthBits = m_this.renderer().contextRenderer().m_depthBits,
        z = 0;  // this will change if tileLayers have topography

    if (crop) {
      quad.crop = {
        x: crop.x / m_this._options.tileWidth,
        y: crop.y / m_this._options.tileHeight
      };
    }
    quad.ul = m_this.fromLocal(m_this.fromLevel({
      x: bounds.left - to.x, y: bounds.top - to.y
    }, level), 0);
    quad.ll = m_this.fromLocal(m_this.fromLevel({
      x: bounds.left - to.x, y: bounds.bottom - to.y
    }, level), 0);
    quad.ur = m_this.fromLocal(m_this.fromLevel({
      x: bounds.right - to.x, y: bounds.top - to.y
    }, level), 0);
    quad.lr = m_this.fromLocal(m_this.fromLevel({
      x: bounds.right - to.x, y: bounds.bottom - to.y
    }, level), 0);
    quad.ul.z = quad.ll.z = quad.ur.z = quad.lr.z = z;

    /* Compute a zOffset per layer and level.  This ensures all tile levels in
     * all tile layers are sorted with the earlier layers and lower levels on
     * the bottom.  Ideally, don't change the z values, since the z-buffer may
     * be expected to have the correct values, but by shifting each layer a
     * small offset, the levels will appear in the correct order and avoid
     * z-fighting.  The zOffset is applied *after* matrix transformations, and
     * is in the webgl clip space of [0-1] with the resolution of the
     * DEPTH_BITS. */
    offset = m_this._options.keepLower ? m_this._options.maxLevel - level + 1 : 1;
    upperTileLayers = m_this.map().listSceneObjects().filter(function (object) {
      return object instanceof tileLayer;
    });
    upperTileLayers = upperTileLayers.slice(upperTileLayers.indexOf(m_this) + 1);
    upperTileLayers.forEach(function (object) {
      offset += object._options.keepLower ? object._options.maxLevel - object._options.minLevel + 1 : 1;
    });
    /* See the definition of `_zOffsetMultiple` for more details. */
    quad.zOffset = offset * Math.pow(2, -depthBits) * tileLayer._zOffsetMultiple;

    m_nextTileId += 1;
    quad.id = m_nextTileId;
    tile.quadId = quad.id;
    quad.image = tile.image;
    m_tiles.push(quad);
    m_quadFeature.data(m_tiles);
    m_quadFeature._update();
    m_this.draw();
  };

  /**
   * Remove a tile from the list of quads.   The quadFeature is redrawn.
   *
   * @param {geo.tile} tile The tile to remove.
   */
  this._remove = function (tile) {
    if (tile.quadId !== undefined && m_quadFeature) {
      for (var i = 0; i < m_tiles.length; i += 1) {
        if (m_tiles[i].id === tile.quadId) {
          m_tiles.splice(i, 1);
          break;
        }
      }
      m_quadFeature.data(m_tiles);
      m_quadFeature._update();
      m_this.draw();
    }
  };

  /**
   * Get/Set visibility of the layer.
   *
   * @param {boolean} [val] If specified, change the visibility, otherwise
   *    return it.
   * @returns {boolean|this} The current visibility or the layer.
   */
  this.visible = function (val) {
    if (val === undefined) {
      return s_visible();
    }
    if (m_this.visible() !== val) {
      s_visible(val);
      if (m_quadFeature) {
        m_quadFeature.visible(m_quadFeature.visible(undefined, true), true);
      }
    }
    return m_this;
  };

  /**
   * Get or set the z-index of the layer.  The z-index controls the display
   * order of the layers in much the same way as the CSS z-index property.
   *
   * @param {number} [zIndex] The new z-index, or undefined to return the
   *    current z-index.
   * @param {boolean} [allowDuplicate] When setting the z index, if this is
   *    truthy, allow other layers to have the same z-index.  Otherwise,
   *    ensure that other layers have distinct z-indices from this one.
   * @returns {number|this}
   */
  this.zIndex = function (zIndex, allowDuplicate) {
    if (zIndex !== undefined) {
      m_this._clearQuads();
    }
    return s_zIndex.apply(m_this, arguments);
  };

  /**
   * If the z-index has changed or layers are added or removed, clear the quads
   * so they are composited in the correct order.
   *
   * @param {geo.event} [evt] If specified, the layer add or remove event that
   *    triggered this.  If `undefined`, clear the quads but don't redraw.
   */
  this._clearQuads = function (evt) {
    if (evt && (!evt.layer || !(evt.layer instanceof tileLayer))) {
      return;
    }
    m_this.clear();
    if (m_quadFeature) {
      m_quadFeature.modified();
    }
    if (evt) {
      m_this.draw();
    }
  };

  /**
   * Update layer.
   *
   * @param {object} request A value to pass to the parent class.
   * @returns {this}
   */
  this._update = function (request) {
    s_update.call(m_this, request);
    if (m_quadFeature) {
      m_quadFeature._update();
    }
    return m_this;
  };

  /**
   * Cleanup.  This purges the texture and tile cache.
   */
  this._cleanup = function () {
    var tile;
    if (m_this.cache && m_this.cache._cache) {
      for (var hash in m_this.cache._cache) {
        tile = m_this.cache._cache[hash];
        if (tile._image && tile._image._texture) {
          delete tile._image._texture;
        }
      }
      m_this.cache.clear();
    }
    m_this.clear();
  };

  /**
   * Destroy.
   */
  this._exit = function () {
    m_this._cleanup();
    m_this.deleteFeature(m_quadFeature);
    m_quadFeature = null;
    m_tiles = [];
    s_exit.apply(m_this, arguments);
  };

  /**
   * Initialize after the layer is added to the map.
   */
  this._init = function () {
    s_init.apply(m_this, arguments);
    m_quadFeature = m_this.createFeature('quad', {
      previewColor: m_this._options.previewColor,
      previewImage: m_this._options.previewImage
    });
    m_quadFeature.geoTrigger = undefined;
    m_quadFeature.gcs(m_this._options.gcs || m_this.map().gcs());
    m_quadFeature.data(m_tiles);
    m_quadFeature._update();

    var map = m_this.map();
    map.geoOn(geo_event.layerAdd, m_this._clearQuads);
    map.geoOn(geo_event.layerRemove, m_this._clearQuads);
  };

  /* These functions don't need to do anything. */
  this._getSubLayer = function () {};
  this._updateSubLayers = undefined;
};

/* Use a multiple of the minimum z delta provided by DEPTH_BITS so that
 * rounding won't accidentally merge two levels.  This may need to be higher
 * if the tile layer is not flat to the camera. */
tileLayer._zOffsetMultiple = 2;

registerLayerAdjustment('webgl', 'tile', webgl_tileLayer);

module.exports = webgl_tileLayer;
<|endoftext|>


<commit_before>import { AsyncRouter } from 'express-async-router'
import actions from './actions'

const router = AsyncRouter()

// Needs to be async because we are using AsyncRouter
const ensureLoggedIn = (loginPath) => async (req, res, next) => {
  if (!req.user) {
    res.redirect(loginPath)
    res.end()
  } else {
    res.locals.currentUser = req.user
    next()
  }
}

router.use(ensureLoggedIn('/auth/login'))

router.get('/', async (req, res) => {
  res.locals.surveys = await actions.listSurveys(req.user)
  res.render('index')
})

router.get('/surveys/new', async (req, res) => {
  res.locals.rooms = await actions.listRooms(req.user)
  res.render('new')
})

router.get('/surveys/:id', async (req, res) => {
  res.locals.rooms = await actions.listRooms(req.user)
  res.locals.survey = await actions.getSurvey(req.user, req.params.id)
  res.render('show')
})

router.post('/surveys', async (req, res) => {
  const survey = await actions.createSurvey(req.user, req.body)
  res.json(survey)
})

router.put('/surveys/:id', async (req, res) => {
  const survey = await actions.updateSurvey(req.user, req.params.id, req.body)
  res.json(survey)
})

export default router
<commit_msg>Use Promise.all where approps<commit_after>import { AsyncRouter } from 'express-async-router'
import actions from './actions'

const router = AsyncRouter()

// Needs to be async because we are using AsyncRouter
const ensureLoggedIn = (loginPath) => async (req, res, next) => {
  if (!req.user) {
    res.redirect(loginPath)
    res.end()
  } else {
    res.locals.currentUser = req.user
    next()
  }
}

const all = function () { return Promise.all(arguments) }

router.use(ensureLoggedIn('/auth/login'))

router.get('/', async (req, res) => {
  res.locals.surveys = await actions.listSurveys(req.user)
  res.render('index')
})

router.get('/surveys/new', async (req, res) => {
  res.locals.rooms = await actions.listRooms(req.user)
  res.render('new')
})

router.get('/surveys/:id', async (req, res) => {
  [ res.locals.rooms, res.locals.survey ] = await all(
    actions.listRooms(req.user),
    actions.getSurvey(req.user, req.params.id),
  )
  res.render('show')
})

router.post('/surveys', async (req, res) => {
  const survey = await actions.createSurvey(req.user, req.body)
  res.json(survey)
})

router.put('/surveys/:id', async (req, res) => {
  const survey = await actions.updateSurvey(req.user, req.params.id, req.body)
  res.json(survey)
})

export default router
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $, brackets, window, Mustache */

/**
 * Utilities for creating and managing standard modal dialogs.
 */
define(function (require, exports, module) {
    "use strict";

    require("utils/Global");

    var KeyBindingManager = require("command/KeyBindingManager"),
        KeyEvent          = require("utils/KeyEvent"),
        NativeApp         = require("utils/NativeApp"),
        Strings           = require("strings"),
        DialogTemplate    = require("text!htmlContent/dialog-template.html");

    /**
     * Dialog Buttons IDs
     * @const {string}
     */
    var DIALOG_BTN_CANCEL           = "cancel",
        DIALOG_BTN_OK               = "ok",
        DIALOG_BTN_DONTSAVE         = "dontsave",
        DIALOG_BTN_SAVE_AS          = "save_as",
        DIALOG_CANCELED             = "_canceled",
        DIALOG_BTN_DOWNLOAD         = "download";

    /**
     * Dialog Buttons Class Names
     * @const {string}
     */
    var DIALOG_BTN_CLASS_PRIMARY    = "primary",
        DIALOG_BTN_CLASS_NORMAL     = "",
        DIALOG_BTN_CLASS_LEFT       = "left";

    /** @type {number} The z-index used for the dialogs. Each new dialog increase this number by 2 */
    var zIndex = 1050;

    /**
     * @private
     * Dismises a modal dialog
     * @param {$.Element} $dlg
     * @param {string} buttonId
     */
    function _dismissDialog($dlg, buttonId) {
        $dlg.data("buttonId", buttonId);
        $dlg.modal("hide");
    }

    /**
     * @private
     * If autoDismiss is true, then dismisses the dialog. Otherwise just raises an event that the
     * given button was clicked.
     * @param {$.Element} $dlg The dialog element to be dismissed.
     * @param {string} buttonId The ID of the button that was clicked.
     * @param {boolean} autoDismiss Whether to autodismiss the dialog on a button click.
     */
    function _processButton($dlg, buttonId, autoDismiss) {
        if (autoDismiss) {
            _dismissDialog($dlg, buttonId);
        } else {
            $dlg.triggerHandler("buttonClick", buttonId);
        }
    }

    /**
     * @private
     * Returns true if the modal dialog has a button with the given ID
     * @param {$.Element} $dlg
     * @param {string} buttonId
     * @return {boolean}
     */
    function _hasButton($dlg, buttonId) {
        return ($dlg.find("[data-button-id='" + buttonId + "']").length > 0);
    }

    /**
     * @private
     * Handles the use of Tab so that it stays inside the Dialog
     * @param {$.Event} event
     * @param {$.Element} $dlg
     */
    function _handleTab(event, $dlg) {
        var $inputs = $(":input:enabled, a", $dlg).filter(":visible");

        if ($(event.target).closest($dlg).length) {
            // If it's the first or last tabbable element, focus the last/first element
            if ((!event.shiftKey && event.target === $inputs[$inputs.length - 1]) ||
                    (event.shiftKey && event.target === $inputs[0])) {
                $inputs.filter(event.shiftKey ? ":last" : ":first").focus();
                event.preventDefault();

            // If there is no element to focus, don't let it focus outside of the dialog
            } else if (!$inputs.length) {
                event.preventDefault();
            }

        // If the focus left the dialog, focus the first element in the dialog
        } else {
            $inputs.first().focus();
            event.preventDefault();
        }
    }

    /**
     * Handles the keyDown event for the dialogs
     * @param {$.Event} e
     * @param {boolean} autoDismiss
     * @return {boolean}
     */
    var _keydownHook = function (e, autoDismiss) {
        var $primaryBtn     = this.find(".primary"),
            buttonId        = null,
            which           = String.fromCharCode(e.which),
            $focusedElement = this.find(".dialog-button:focus, a:focus");

        // There might be a textfield in the dialog's UI; don't want to mistake normal typing for dialog dismissal
        var inTextArea    = (e.target.tagName === "TEXTAREA"),
            inTypingField = inTextArea || ($(e.target).filter(":text, :password").length > 0);

        if (e.which === KeyEvent.DOM_VK_TAB) {
            _handleTab(e, this);
        } else if (e.which === KeyEvent.DOM_VK_ESCAPE) {
            buttonId = DIALOG_BTN_CANCEL;
        } else if (e.which === KeyEvent.DOM_VK_RETURN && (!inTextArea || e.ctrlKey)) {
            // Enter key in single-line text input always dismisses; in text area, only Ctrl+Enter dismisses
            // Click primary
            $primaryBtn.click();
            // prevent event from being used to enter a newline into editor
            e.preventDefault();
        } else if (e.which === KeyEvent.DOM_VK_SPACE) {
            // Space bar on focused button or link
            $focusedElement.click();
        } else if (brackets.platform === "mac") {
            // CMD+D Don't Save
            if (e.metaKey && (which === "D")) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            // FIXME (issue #418) CMD+. Cancel swallowed by native shell
            } else if (e.metaKey && (e.which === KeyEvent.DOM_VK_PERIOD)) {
                buttonId = DIALOG_BTN_CANCEL;
            }
        } else { // if (brackets.platform === "win") {
            // 'N' Don't Save
            if (which === "N" && !inTypingField) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            }
        }

        if (buttonId) {
            _processButton(this, buttonId, autoDismiss);
        }

        // Stop any other global hooks from processing the event (but
        // allow it to continue bubbling if we haven't otherwise stopped it).
        return true;
    };

    /**
     * @constructor
     * @private
     *
     * @param {$.Element} $dlg The dialog jQuery element
     * @param {$.Promise} promise A promise that will be resolved with the ID of the clicked button when the dialog
     *     is dismissed. Never rejected.
     */
    function Dialog($dlg, promise) {
        this._$dlg    = $dlg;
        this._promise = promise;
    }

    /** @type {$.Element} The dialog jQuery element */
    Dialog.prototype.getElement = function () {
        return this._$dlg;
    };

    /** @type {$.Promise} The dialog promise */
    Dialog.prototype.getPromise = function () {
        return this._promise;
    };

    /**
     * Closes the dialog if is visible
     */
    Dialog.prototype.close = function () {
        if (this._$dlg.is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
            _dismissDialog(this._$dlg, DIALOG_CANCELED);
        }
    };

    /**
     * Adds a done callback to the dialog promise
     */
    Dialog.prototype.done = function (callback) {
        this._promise.done(callback);
    };

    /**
     * Don't allow dialog to exceed viewport size
     */
    function setDialogMaxSize() {
        var maxWidth, maxHeight,
            $dlgs = $(".modal-inner-wrapper > .instance");

        // Verify 1 or more modal dialogs are showing
        if ($dlgs.length > 0) {
            maxWidth  = $("body").width();
            maxHeight = $("body").height();

            $dlgs.css({
                "max-width":  maxWidth,
                "max-height": maxHeight,
                "overflow":   "auto"
            });
        }
    }

    /**
     * Creates a new modal dialog from a given template.
     * The template can either be a string or a jQuery object representing a DOM node that is *not* in the current DOM.
     *
     * @param {string} template A string template or jQuery object to use as the dialog HTML.
     * @param {boolean=} autoDismiss Whether to automatically dismiss the dialog when one of the buttons
     *      is clicked. Default true. If false, you'll need to manually handle button clicks and the Esc
     *      key, and dismiss the dialog yourself when ready by calling `close()` on the returned dialog.
     * @return {Dialog}
     */
    function showModalDialogUsingTemplate(template, autoDismiss) {
        if (autoDismiss === undefined) {
            autoDismiss = true;
        }

        $("body").append("

");

        var result    = $.Deferred(),
            promise   = result.promise(),
            $dlg      = $(template)
                .addClass("instance")
                .appendTo(".modal-inner-wrapper:last");

        // Don't allow dialog to exceed viewport size
        setDialogMaxSize();

        // Save the dialog promise for unit tests
        $dlg.data("promise", promise);

        var keydownHook = function (e) {
            return _keydownHook.call($dlg, e, autoDismiss);
        };

        // Pipe dialog-closing notification back to client code
        $dlg.one("hidden", function () {
            var buttonId = $dlg.data("buttonId");
            if (!buttonId) {    // buttonId will be undefined if closed via Bootstrap's "x" button
                buttonId = DIALOG_BTN_CANCEL;
            }

            // Let call stack return before notifying that dialog has closed; this avoids issue #191
            // if the handler we're triggering might show another dialog (as long as there's no
            // fade-out animation)
            window.setTimeout(function () {
                result.resolve(buttonId);
            }, 0);

            // Remove the dialog instance from the DOM.
            $dlg.remove();

            // Remove our global keydown handler.
            KeyBindingManager.removeGlobalKeydownHook(keydownHook);

            //Remove wrapper
            $(".modal-wrapper:last").remove();
        }).one("shown", function () {
            // Set focus to the default button
            var primaryBtn = $dlg.find(".primary");

            if (primaryBtn) {
                primaryBtn.focus();
            }

            // Push our global keydown handler onto the global stack of handlers.
            KeyBindingManager.addGlobalKeydownHook(keydownHook);
        });

        // Click handler for buttons
        $dlg.one("click", ".dialog-button", function (e) {
            _processButton($dlg, $(this).attr("data-button-id"), autoDismiss);
        });

        // Run the dialog
        $dlg
            .modal({
                backdrop: "static",
                show:     true,
                selector: ".modal-inner-wrapper:last",
                keyboard: false // handle the ESC key ourselves so we can deal with nested dialogs
            })
            // Updates the z-index of the modal dialog and the backdrop
            .css("z-index", zIndex + 1)
            .next()
            .css("z-index", zIndex);

        zIndex += 2;

        return (new Dialog($dlg, promise));
    }

    /**
     * Creates a new general purpose modal dialog using the default template and the template variables given
     * as parameters as described.
     *
     * @param {string} dlgClass A class name identifier for the dialog. Typically one of DefaultDialogs.*
     * @param {string=} title The title of the dialog. Can contain HTML markup. Defaults to "".
     * @param {string=} message The message to display in the dialog. Can contain HTML markup. Defaults to "".
     * @param {Array.<{className: string, id: string, text: string}>=} buttons An array of buttons where each button
     *      has a class, id and text property. The id is used in "data-button-id". Defaults to a single Ok button.
     *      Typically className is one of DIALOG_BTN_CLASS_*, id is one of DIALOG_BTN_*
     * @param {boolean=} autoDismiss Whether to automatically dismiss the dialog when one of the buttons
     *      is clicked. Default true. If false, you'll need to manually handle button clicks and the Esc
     *      key, and dismiss the dialog yourself when ready by calling `close()` on the returned dialog.
     * @return {Dialog}
     */
    function showModalDialog(dlgClass, title, message, buttons, autoDismiss) {
        var templateVars = {
            dlgClass: dlgClass,
            title:    title   || "",
            message:  message || "",
            buttons:  buttons || [{ className: DIALOG_BTN_CLASS_PRIMARY, id: DIALOG_BTN_OK, text: Strings.OK }]
        };
        var template = Mustache.render(DialogTemplate, templateVars);

        return showModalDialogUsingTemplate(template, autoDismiss);
    }

    /**
     * Immediately closes any dialog instances with the given class. The dialog callback for each instance will
     * be called with the special buttonId DIALOG_CANCELED (note: callback is run asynchronously).
     * @param {string} dlgClass The class name identifier for the dialog.
     * @param {string=} buttonId The button id to use when closing the dialog. Defaults to DIALOG_CANCELED
     */
    function cancelModalDialogIfOpen(dlgClass, buttonId) {
        $("." + dlgClass + ".instance").each(function () {
            if ($(this).is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
                _dismissDialog($(this), buttonId || DIALOG_CANCELED);
            }
        });
    }

    window.addEventListener("resize", setDialogMaxSize);

    exports.DIALOG_BTN_CANCEL            = DIALOG_BTN_CANCEL;
    exports.DIALOG_BTN_OK                = DIALOG_BTN_OK;
    exports.DIALOG_BTN_DONTSAVE          = DIALOG_BTN_DONTSAVE;
    exports.DIALOG_BTN_SAVE_AS           = DIALOG_BTN_SAVE_AS;
    exports.DIALOG_CANCELED              = DIALOG_CANCELED;
    exports.DIALOG_BTN_DOWNLOAD          = DIALOG_BTN_DOWNLOAD;

    exports.DIALOG_BTN_CLASS_PRIMARY     = DIALOG_BTN_CLASS_PRIMARY;
    exports.DIALOG_BTN_CLASS_NORMAL      = DIALOG_BTN_CLASS_NORMAL;
    exports.DIALOG_BTN_CLASS_LEFT        = DIALOG_BTN_CLASS_LEFT;

    exports.showModalDialog              = showModalDialog;
    exports.showModalDialogUsingTemplate = showModalDialogUsingTemplate;
    exports.cancelModalDialogIfOpen      = cancelModalDialogIfOpen;
});
<commit_msg>Stop keydown events that we handle in dialogs, so no one else tries to handle the keyup<commit_after>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $, brackets, window, Mustache */

/**
 * Utilities for creating and managing standard modal dialogs.
 */
define(function (require, exports, module) {
    "use strict";

    require("utils/Global");

    var KeyBindingManager = require("command/KeyBindingManager"),
        KeyEvent          = require("utils/KeyEvent"),
        NativeApp         = require("utils/NativeApp"),
        Strings           = require("strings"),
        DialogTemplate    = require("text!htmlContent/dialog-template.html");

    /**
     * Dialog Buttons IDs
     * @const {string}
     */
    var DIALOG_BTN_CANCEL           = "cancel",
        DIALOG_BTN_OK               = "ok",
        DIALOG_BTN_DONTSAVE         = "dontsave",
        DIALOG_BTN_SAVE_AS          = "save_as",
        DIALOG_CANCELED             = "_canceled",
        DIALOG_BTN_DOWNLOAD         = "download";

    /**
     * Dialog Buttons Class Names
     * @const {string}
     */
    var DIALOG_BTN_CLASS_PRIMARY    = "primary",
        DIALOG_BTN_CLASS_NORMAL     = "",
        DIALOG_BTN_CLASS_LEFT       = "left";

    /** @type {number} The z-index used for the dialogs. Each new dialog increase this number by 2 */
    var zIndex = 1050;

    /**
     * @private
     * Dismises a modal dialog
     * @param {$.Element} $dlg
     * @param {string} buttonId
     */
    function _dismissDialog($dlg, buttonId) {
        $dlg.data("buttonId", buttonId);
        $dlg.modal("hide");
    }

    /**
     * @private
     * If autoDismiss is true, then dismisses the dialog. Otherwise just raises an event that the
     * given button was clicked.
     * @param {$.Element} $dlg The dialog element to be dismissed.
     * @param {string} buttonId The ID of the button that was clicked.
     * @param {boolean} autoDismiss Whether to autodismiss the dialog on a button click.
     */
    function _processButton($dlg, buttonId, autoDismiss) {
        if (autoDismiss) {
            _dismissDialog($dlg, buttonId);
        } else {
            $dlg.triggerHandler("buttonClick", buttonId);
        }
    }

    /**
     * @private
     * Returns true if the modal dialog has a button with the given ID
     * @param {$.Element} $dlg
     * @param {string} buttonId
     * @return {boolean}
     */
    function _hasButton($dlg, buttonId) {
        return ($dlg.find("[data-button-id='" + buttonId + "']").length > 0);
    }

    /**
     * @private
     * Handles the use of Tab so that it stays inside the Dialog
     * @param {$.Event} event
     * @param {$.Element} $dlg
     */
    function _handleTab(event, $dlg) {
        var $inputs = $(":input:enabled, a", $dlg).filter(":visible");

        function stopEvent() {
            event.stopPropagation();
            event.preventDefault();
        }

        if ($(event.target).closest($dlg).length) {
            // If it's the first or last tabbable element, focus the last/first element
            if ((!event.shiftKey && event.target === $inputs[$inputs.length - 1]) ||
                    (event.shiftKey && event.target === $inputs[0])) {
                $inputs.filter(event.shiftKey ? ":last" : ":first").focus();
                stopEvent();

            // If there is no element to focus, don't let it focus outside of the dialog
            } else if (!$inputs.length) {
                stopEvent();
            }

        // If the focus left the dialog, focus the first element in the dialog
        } else {
            $inputs.first().focus();
            stopEvent();
        }
    }

    /**
     * Handles the keyDown event for the dialogs
     * @param {$.Event} e
     * @param {boolean} autoDismiss
     * @return {boolean}
     */
    var _keydownHook = function (e, autoDismiss) {
        var $primaryBtn     = this.find(".primary"),
            buttonId        = null,
            which           = String.fromCharCode(e.which),
            $focusedElement = this.find(".dialog-button:focus, a:focus");

        function stopEvent() {
            e.preventDefault();
            e.stopPropagation();
        }

        // There might be a textfield in the dialog's UI; don't want to mistake normal typing for dialog dismissal
        var inTextArea    = (e.target.tagName === "TEXTAREA"),
            inTypingField = inTextArea || ($(e.target).filter(":text, :password").length > 0);

        if (e.which === KeyEvent.DOM_VK_TAB) {
            // We don't want to stopEvent() in this case since we might want the default behavior.
            // _handleTab takes care of stopping/preventing default as necessary.
            _handleTab(e, this);
        } else if (e.which === KeyEvent.DOM_VK_ESCAPE) {
            buttonId = DIALOG_BTN_CANCEL;
        } else if (e.which === KeyEvent.DOM_VK_RETURN && (!inTextArea || e.ctrlKey)) {
            // Enter key in single-line text input always dismisses; in text area, only Ctrl+Enter dismisses
            // Click primary
            stopEvent();
            $primaryBtn.click();
        } else if (e.which === KeyEvent.DOM_VK_SPACE) {
            if ($focusedElement.length) {
                // Space bar on focused button or link
                stopEvent();
                $focusedElement.click();
            }
        } else if (brackets.platform === "mac") {
            // CMD+D Don't Save
            if (e.metaKey && (which === "D")) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            // FIXME (issue #418) CMD+. Cancel swallowed by native shell
            } else if (e.metaKey && (e.which === KeyEvent.DOM_VK_PERIOD)) {
                buttonId = DIALOG_BTN_CANCEL;
            }
        } else { // if (brackets.platform === "win") {
            // 'N' Don't Save
            if (which === "N" && !inTypingField) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            }
        }

        if (buttonId) {
            stopEvent();
            _processButton(this, buttonId, autoDismiss);
        }

        // Stop any other global hooks from processing the event (but
        // allow it to continue bubbling if we haven't otherwise stopped it).
        return true;
    };

    /**
     * @constructor
     * @private
     *
     * @param {$.Element} $dlg The dialog jQuery element
     * @param {$.Promise} promise A promise that will be resolved with the ID of the clicked button when the dialog
     *     is dismissed. Never rejected.
     */
    function Dialog($dlg, promise) {
        this._$dlg    = $dlg;
        this._promise = promise;
    }

    /** @type {$.Element} The dialog jQuery element */
    Dialog.prototype.getElement = function () {
        return this._$dlg;
    };

    /** @type {$.Promise} The dialog promise */
    Dialog.prototype.getPromise = function () {
        return this._promise;
    };

    /**
     * Closes the dialog if is visible
     */
    Dialog.prototype.close = function () {
        if (this._$dlg.is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
            _dismissDialog(this._$dlg, DIALOG_CANCELED);
        }
    };

    /**
     * Adds a done callback to the dialog promise
     */
    Dialog.prototype.done = function (callback) {
        this._promise.done(callback);
    };

    /**
     * Don't allow dialog to exceed viewport size
     */
    function setDialogMaxSize() {
        var maxWidth, maxHeight,
            $dlgs = $(".modal-inner-wrapper > .instance");

        // Verify 1 or more modal dialogs are showing
        if ($dlgs.length > 0) {
            maxWidth  = $("body").width();
            maxHeight = $("body").height();

            $dlgs.css({
                "max-width":  maxWidth,
                "max-height": maxHeight,
                "overflow":   "auto"
            });
        }
    }

    /**
     * Creates a new modal dialog from a given template.
     * The template can either be a string or a jQuery object representing a DOM node that is *not* in the current DOM.
     *
     * @param {string} template A string template or jQuery object to use as the dialog HTML.
     * @param {boolean=} autoDismiss Whether to automatically dismiss the dialog when one of the buttons
     *      is clicked. Default true. If false, you'll need to manually handle button clicks and the Esc
     *      key, and dismiss the dialog yourself when ready by calling `close()` on the returned dialog.
     * @return {Dialog}
     */
    function showModalDialogUsingTemplate(template, autoDismiss) {
        if (autoDismiss === undefined) {
            autoDismiss = true;
        }

        $("body").append("

");

        var result    = $.Deferred(),
            promise   = result.promise(),
            $dlg      = $(template)
                .addClass("instance")
                .appendTo(".modal-inner-wrapper:last");

        // Don't allow dialog to exceed viewport size
        setDialogMaxSize();

        // Save the dialog promise for unit tests
        $dlg.data("promise", promise);

        var keydownHook = function (e) {
            return _keydownHook.call($dlg, e, autoDismiss);
        };

        // Pipe dialog-closing notification back to client code
        $dlg.one("hidden", function () {
            var buttonId = $dlg.data("buttonId");
            if (!buttonId) {    // buttonId will be undefined if closed via Bootstrap's "x" button
                buttonId = DIALOG_BTN_CANCEL;
            }

            // Let call stack return before notifying that dialog has closed; this avoids issue #191
            // if the handler we're triggering might show another dialog (as long as there's no
            // fade-out animation)
            window.setTimeout(function () {
                result.resolve(buttonId);
            }, 0);

            // Remove the dialog instance from the DOM.
            $dlg.remove();

            // Remove our global keydown handler.
            KeyBindingManager.removeGlobalKeydownHook(keydownHook);

            //Remove wrapper
            $(".modal-wrapper:last").remove();
        }).one("shown", function () {
            // Set focus to the default button
            var primaryBtn = $dlg.find(".primary");

            if (primaryBtn) {
                primaryBtn.focus();
            }

            // Push our global keydown handler onto the global stack of handlers.
            KeyBindingManager.addGlobalKeydownHook(keydownHook);
        });

        // Click handler for buttons
        $dlg.one("click", ".dialog-button", function (e) {
            _processButton($dlg, $(this).attr("data-button-id"), autoDismiss);
        });

        // Run the dialog
        $dlg
            .modal({
                backdrop: "static",
                show:     true,
                selector: ".modal-inner-wrapper:last",
                keyboard: false // handle the ESC key ourselves so we can deal with nested dialogs
            })
            // Updates the z-index of the modal dialog and the backdrop
            .css("z-index", zIndex + 1)
            .next()
            .css("z-index", zIndex);

        zIndex += 2;

        return (new Dialog($dlg, promise));
    }

    /**
     * Creates a new general purpose modal dialog using the default template and the template variables given
     * as parameters as described.
     *
     * @param {string} dlgClass A class name identifier for the dialog. Typically one of DefaultDialogs.*
     * @param {string=} title The title of the dialog. Can contain HTML markup. Defaults to "".
     * @param {string=} message The message to display in the dialog. Can contain HTML markup. Defaults to "".
     * @param {Array.<{className: string, id: string, text: string}>=} buttons An array of buttons where each button
     *      has a class, id and text property. The id is used in "data-button-id". Defaults to a single Ok button.
     *      Typically className is one of DIALOG_BTN_CLASS_*, id is one of DIALOG_BTN_*
     * @param {boolean=} autoDismiss Whether to automatically dismiss the dialog when one of the buttons
     *      is clicked. Default true. If false, you'll need to manually handle button clicks and the Esc
     *      key, and dismiss the dialog yourself when ready by calling `close()` on the returned dialog.
     * @return {Dialog}
     */
    function showModalDialog(dlgClass, title, message, buttons, autoDismiss) {
        var templateVars = {
            dlgClass: dlgClass,
            title:    title   || "",
            message:  message || "",
            buttons:  buttons || [{ className: DIALOG_BTN_CLASS_PRIMARY, id: DIALOG_BTN_OK, text: Strings.OK }]
        };
        var template = Mustache.render(DialogTemplate, templateVars);

        return showModalDialogUsingTemplate(template, autoDismiss);
    }

    /**
     * Immediately closes any dialog instances with the given class. The dialog callback for each instance will
     * be called with the special buttonId DIALOG_CANCELED (note: callback is run asynchronously).
     * @param {string} dlgClass The class name identifier for the dialog.
     * @param {string=} buttonId The button id to use when closing the dialog. Defaults to DIALOG_CANCELED
     */
    function cancelModalDialogIfOpen(dlgClass, buttonId) {
        $("." + dlgClass + ".instance").each(function () {
            if ($(this).is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
                _dismissDialog($(this), buttonId || DIALOG_CANCELED);
            }
        });
    }

    window.addEventListener("resize", setDialogMaxSize);

    exports.DIALOG_BTN_CANCEL            = DIALOG_BTN_CANCEL;
    exports.DIALOG_BTN_OK                = DIALOG_BTN_OK;
    exports.DIALOG_BTN_DONTSAVE          = DIALOG_BTN_DONTSAVE;
    exports.DIALOG_BTN_SAVE_AS           = DIALOG_BTN_SAVE_AS;
    exports.DIALOG_CANCELED              = DIALOG_CANCELED;
    exports.DIALOG_BTN_DOWNLOAD          = DIALOG_BTN_DOWNLOAD;

    exports.DIALOG_BTN_CLASS_PRIMARY     = DIALOG_BTN_CLASS_PRIMARY;
    exports.DIALOG_BTN_CLASS_NORMAL      = DIALOG_BTN_CLASS_NORMAL;
    exports.DIALOG_BTN_CLASS_LEFT        = DIALOG_BTN_CLASS_LEFT;

    exports.showModalDialog              = showModalDialog;
    exports.showModalDialogUsingTemplate = showModalDialogUsingTemplate;
    exports.cancelModalDialogIfOpen      = cancelModalDialogIfOpen;
});
<|endoftext|>


<commit_before>// @flow

// This file is injected as a regular script in all pages and overrides
// `.addEventListener` (and friends) so we can detect click listeners.
// This is a bit fiddly because we try to cover our tracks as good as possible.

// Everything in this file has to be inside this function, since `.toString()`
// is called on it. This also means that `import`s cannot be used in this file.
export default () => {
  const fnMap = new Map();
  const resetFns = [];

  // When this was written, <https://jsfiddle.net/> overrides `Event` (which was
  // used before switching to `CustomEvent`) with a buggy implementation that
  // throws an error. To work around that, make sure that the page can't
  // interfere with the crucial parts by saving copies of important things.
  // Remember that this runs _before_ any page scripts.
  const CustomEvent2 = CustomEvent;
  const HTMLElement2 = HTMLElement;
  const { error: logError } = console;
  const { dispatchEvent } = EventTarget.prototype;
  const { apply, defineProperty, getOwnPropertyDescriptor } = Reflect;
  const { get: mapGet } = Map.prototype;

  // Reset the overridden methods when the extension is shut down. This listener
  // is registered before we override in order not to trigger our hook.
  window.addEventListener("message", reset, true);

  // Hook into whenever `obj[name]()` (such as
  // `EventTarget.prototype["addEventListener"]`) is called, by calling `hook`.
  // This is done by overriding the method, but in a way that makes it really
  // difficult to detect that the method has been overridden. There are two
  // reasons for covering our tracks:
  //
  // 1. We don't want to break websites. For example, naively overriding
  //    `addEventListener` breaks CKEditor: <https://jsfiddle.net/tv5x6zuj/>
  //    See also: <https://github.com/philc/vimium/issues/2900> (and its linked
  //    issues and PRs).
  // 2. We don't want developers to see strange things in the console when they
  //    debug stuff.
  function hookInto(obj: Object, name: string, hook: ?Function = undefined) {
    const desc = getOwnPropertyDescriptor(obj, name);

    // Chrome doesn't support `toSource`.
    if (desc == null) {
      return;
    }

    const prop = "value" in desc ? "value" : "set";
    const orig = desc[prop];

    // To please Flow.
    if (orig == null) {
      return;
    }

    // `f = { [name]: function(){} }[name]` is a way of creating a dynamically
    // named function (where `f.name === name`).
    const fn =
      hook == null
        ? {
            [orig.name](...args: Array<any>): any {
              // In the cases where no hook is provided we just want to make sure
              // that the method (such as `toString`) is called with the
              // _original_ function, not the overriding function.
              return apply(orig, apply(mapGet, fnMap, [this]) || this, args);
            },
          }[orig.name]
        : {
            [orig.name](...args: Array<any>): any {
              // In case there's a mistake in `hook` it shouldn't cause the entire
              // overridden method to fail and potentially break the whole page.
              try {
                // Remember that `hook` can be called with _anything,_ because the
                // user can pass invalid arguments and use `.call`.
                // $FlowIgnore: `hook` isn't undefined here.
                const result = hook(this, ...args);
                if (result != null && typeof result.then === "function") {
                  result.catch(error => {
                    logHookError(error, obj, name);
                  });
                }
              } catch (error) {
                logHookError(error, obj, name);
              }
              return apply(orig, this, args);
            },
          }[orig.name];

    // Save the overriding and original functions so we can map overriding to
    // original in the case with no `hook` above.
    fnMap.set(fn, orig);

    // Make sure that `.length` is correct.
    defineProperty(fn, "length", {
      ...getOwnPropertyDescriptor(Function.prototype, "length"),
      value: orig.length,
    });

    // Finally override the method with the created function.
    defineProperty(obj, name, {
      ...desc,
      [prop]: fn,
    });

    // Save a function that will reset the method back again.
    resetFns.push(() => {
      defineProperty(obj, name, {
        ...desc,
        [prop]: orig,
      });
    });
  }

  function logHookError(error: Error, obj: Object, name: string) {
    // Don't use the usual `log` function here, too keep this file small.
    logError(`[synth]: Failed to run hook for ${name} on`, obj, error);
  }

  function reset(event: MessageEvent) {
    if (event.data !== INJECTED_RESET) {
      return;
    }

    // Reset all overridden methods.
    for (const resetFn of resetFns) {
      resetFn();
    }

    window.removeEventListener("message", reset, true);
  }

  type ClickListenersByElement = Map<HTMLElement, OptionsByListener>;
  type OptionsByListener = Map<mixed, OptionsSet>;
  type OptionsSet = Set<string>;
  const clickListenersByElement: ClickListenersByElement = new Map();

  hookInto(
    EventTarget.prototype,
    "addEventListener",
    (element: mixed, eventName: mixed, listener: mixed, options: mixed) => {
      if (
        !(
          eventName === "click" &&
          element instanceof HTMLElement2 &&
          (typeof listener === "function" ||
            (typeof listener === "object" &&
              listener != null &&
              typeof listener.handleEvent === "function"))
        )
      ) {
        return;
      }

      const optionsString = stringifyOptions(options);
      const optionsByListener = clickListenersByElement.get(element);

      // No previous click listeners.
      if (optionsByListener == null) {
        clickListenersByElement.set(
          element,
          new Map([[listener, new Set([optionsString])]])
        );

        if (typeof element.onclick !== "function") {
          // The element went from no click listeners to one.
          reportClickable(element);
        }

        return;
      }

      const optionsSet = optionsByListener.get(listener);

      // New listener function.
      if (optionsSet == null) {
        optionsByListener.set(listener, new Set([optionsString]));
        return;
      }

      // Already seen listener function, but new options.
      if (!optionsSet.has(optionsString)) {
        optionsSet.add(optionsString);
      }

      // Duplicate listener. Nothing to do.
    }
  );

  hookInto(
    EventTarget.prototype,
    "removeEventListener",
    (element: mixed, eventName: mixed, listener: mixed, options: mixed) => {
      if (
        !(
          eventName === "click" &&
          element instanceof HTMLElement2 &&
          (typeof listener === "function" ||
            (typeof listener === "object" &&
              listener != null &&
              typeof listener.handleEvent === "function"))
        )
      ) {
        return;
      }

      const optionsString = stringifyOptions(options);
      const optionsByListener = clickListenersByElement.get(element);

      // The element has no click listeners.
      if (optionsByListener == null) {
        return;
      }

      const optionsSet = optionsByListener.get(listener);

      // The element has click listeners, but not with `listener` as a callback.
      if (optionsSet == null) {
        return;
      }

      // The element has `listener` as a click callback, but with different
      // options.
      if (!optionsSet.has(optionsString)) {
        return;
      }

      // Match! Remove the current options.
      optionsSet.delete(optionsString);

      // If that was the last options for `listener`, remove `listener`.
      if (optionsSet.size === 0) {
        optionsByListener.delete(listener);

        // If that was the last `listener` for `element`, remove `element`.
        if (optionsByListener.size === 0) {
          clickListenersByElement.delete(element);

          if (typeof element.onclick !== "function") {
            // The element went from one listener to none.
            reportUnclickable(element);
          }
        }
      }
    }
  );

  hookInto(
    HTMLElement.prototype,
    "onclick",
    async (element: mixed, value: mixed) => {
      // If the element has click listeners added via `.addEventListener` changing
      // `.onclick` can't affect whether the element has at least one click
      // listener.
      if (
        !(element instanceof HTMLElement2) ||
        clickListenersByElement.has(element)
      ) {
        return;
      }

      const hasListenerAlready = typeof element.onclick === "function";

      // Let the setter take effect. Then dispatch events (if any). The dispatched
      // event would reach the ElementManager _before_ the new `.onclick` value is
      // actually set otherwise, which could make it take the wrong decision on
      // clickability.
      await undefined;

      if (typeof value === "function") {
        if (!hasListenerAlready) {
          // The element went from no click listeners to one.
          reportClickable(element);
        }
      } else if (hasListenerAlready) {
        // The element went from one click listeners to none.
        reportUnclickable(element);
      }
    }
  );

  // Make sure that `Function.prototype.toString.call(element.addEventListener)`
  // returns "[native code]". This is used by lodash's `_.isNative`.
  // `.toLocaleString` is automatically taken care of when patching `.toString`.
  hookInto(Function.prototype, "toString");
  hookInto(Function.prototype, "toSource"); // Firefox specific.

  const optionNames: Array<string> = ["capture", "once", "passive"];

  function stringifyOptions(options: mixed): string {
    const normalized =
      options == null || typeof options !== "object"
        ? { capture: Boolean(options) }
        : options;
    return optionNames.map(name => String(Boolean(normalized[name]))).join(",");
  }

  // The events are dispatched on `window` rather than on `element`, since
  // `element` might not be inserted into the DOM (yet/anymore), which causes
  // the event not to fire.
  function reportClickable(element: HTMLElement) {
    apply(dispatchEvent, window, [
      new CustomEvent2(INJECTED_CLICKABLE_EVENT, { detail: { element } }),
    ]);
  }

  function reportUnclickable(element: HTMLElement) {
    apply(dispatchEvent, window, [
      new CustomEvent2(INJECTED_UNCLICKABLE_EVENT, { element }),
    ]);
  }
};
<commit_msg>Fix reporting removed click listeners<commit_after>// @flow

// This file is injected as a regular script in all pages and overrides
// `.addEventListener` (and friends) so we can detect click listeners.
// This is a bit fiddly because we try to cover our tracks as good as possible.

// Everything in this file has to be inside this function, since `.toString()`
// is called on it. This also means that `import`s cannot be used in this file.
export default () => {
  const fnMap = new Map();
  const resetFns = [];

  // When this was written, <https://jsfiddle.net/> overrides `Event` (which was
  // used before switching to `CustomEvent`) with a buggy implementation that
  // throws an error. To work around that, make sure that the page can't
  // interfere with the crucial parts by saving copies of important things.
  // Remember that this runs _before_ any page scripts.
  const CustomEvent2 = CustomEvent;
  const HTMLElement2 = HTMLElement;
  const { error: logError } = console;
  const { dispatchEvent } = EventTarget.prototype;
  const { apply, defineProperty, getOwnPropertyDescriptor } = Reflect;
  const { get: mapGet } = Map.prototype;

  // Reset the overridden methods when the extension is shut down. This listener
  // is registered before we override in order not to trigger our hook.
  window.addEventListener("message", reset, true);

  // Hook into whenever `obj[name]()` (such as
  // `EventTarget.prototype["addEventListener"]`) is called, by calling `hook`.
  // This is done by overriding the method, but in a way that makes it really
  // difficult to detect that the method has been overridden. There are two
  // reasons for covering our tracks:
  //
  // 1. We don't want to break websites. For example, naively overriding
  //    `addEventListener` breaks CKEditor: <https://jsfiddle.net/tv5x6zuj/>
  //    See also: <https://github.com/philc/vimium/issues/2900> (and its linked
  //    issues and PRs).
  // 2. We don't want developers to see strange things in the console when they
  //    debug stuff.
  function hookInto(obj: Object, name: string, hook: ?Function = undefined) {
    const desc = getOwnPropertyDescriptor(obj, name);

    // Chrome doesn't support `toSource`.
    if (desc == null) {
      return;
    }

    const prop = "value" in desc ? "value" : "set";
    const orig = desc[prop];

    // To please Flow.
    if (orig == null) {
      return;
    }

    // `f = { [name]: function(){} }[name]` is a way of creating a dynamically
    // named function (where `f.name === name`).
    const fn =
      hook == null
        ? {
            [orig.name](...args: Array<any>): any {
              // In the cases where no hook is provided we just want to make sure
              // that the method (such as `toString`) is called with the
              // _original_ function, not the overriding function.
              return apply(orig, apply(mapGet, fnMap, [this]) || this, args);
            },
          }[orig.name]
        : {
            [orig.name](...args: Array<any>): any {
              // In case there's a mistake in `hook` it shouldn't cause the entire
              // overridden method to fail and potentially break the whole page.
              try {
                // Remember that `hook` can be called with _anything,_ because the
                // user can pass invalid arguments and use `.call`.
                // $FlowIgnore: `hook` isn't undefined here.
                const result = hook(this, ...args);
                if (result != null && typeof result.then === "function") {
                  result.catch(error => {
                    logHookError(error, obj, name);
                  });
                }
              } catch (error) {
                logHookError(error, obj, name);
              }
              return apply(orig, this, args);
            },
          }[orig.name];

    // Save the overriding and original functions so we can map overriding to
    // original in the case with no `hook` above.
    fnMap.set(fn, orig);

    // Make sure that `.length` is correct.
    defineProperty(fn, "length", {
      ...getOwnPropertyDescriptor(Function.prototype, "length"),
      value: orig.length,
    });

    // Finally override the method with the created function.
    defineProperty(obj, name, {
      ...desc,
      [prop]: fn,
    });

    // Save a function that will reset the method back again.
    resetFns.push(() => {
      defineProperty(obj, name, {
        ...desc,
        [prop]: orig,
      });
    });
  }

  function logHookError(error: Error, obj: Object, name: string) {
    // Don't use the usual `log` function here, too keep this file small.
    logError(`[synth]: Failed to run hook for ${name} on`, obj, error);
  }

  function reset(event: MessageEvent) {
    if (event.data !== INJECTED_RESET) {
      return;
    }

    // Reset all overridden methods.
    for (const resetFn of resetFns) {
      resetFn();
    }

    window.removeEventListener("message", reset, true);
  }

  type ClickListenersByElement = Map<HTMLElement, OptionsByListener>;
  type OptionsByListener = Map<mixed, OptionsSet>;
  type OptionsSet = Set<string>;
  const clickListenersByElement: ClickListenersByElement = new Map();

  hookInto(
    EventTarget.prototype,
    "addEventListener",
    (element: mixed, eventName: mixed, listener: mixed, options: mixed) => {
      if (
        !(
          eventName === "click" &&
          element instanceof HTMLElement2 &&
          (typeof listener === "function" ||
            (typeof listener === "object" &&
              listener != null &&
              typeof listener.handleEvent === "function"))
        )
      ) {
        return;
      }

      const optionsString = stringifyOptions(options);
      const optionsByListener = clickListenersByElement.get(element);

      // No previous click listeners.
      if (optionsByListener == null) {
        clickListenersByElement.set(
          element,
          new Map([[listener, new Set([optionsString])]])
        );

        if (typeof element.onclick !== "function") {
          // The element went from no click listeners to one.
          reportClickable(element);
        }

        return;
      }

      const optionsSet = optionsByListener.get(listener);

      // New listener function.
      if (optionsSet == null) {
        optionsByListener.set(listener, new Set([optionsString]));
        return;
      }

      // Already seen listener function, but new options.
      if (!optionsSet.has(optionsString)) {
        optionsSet.add(optionsString);
      }

      // Duplicate listener. Nothing to do.
    }
  );

  hookInto(
    EventTarget.prototype,
    "removeEventListener",
    (element: mixed, eventName: mixed, listener: mixed, options: mixed) => {
      if (
        !(
          eventName === "click" &&
          element instanceof HTMLElement2 &&
          (typeof listener === "function" ||
            (typeof listener === "object" &&
              listener != null &&
              typeof listener.handleEvent === "function"))
        )
      ) {
        return;
      }

      const optionsString = stringifyOptions(options);
      const optionsByListener = clickListenersByElement.get(element);

      // The element has no click listeners.
      if (optionsByListener == null) {
        return;
      }

      const optionsSet = optionsByListener.get(listener);

      // The element has click listeners, but not with `listener` as a callback.
      if (optionsSet == null) {
        return;
      }

      // The element has `listener` as a click callback, but with different
      // options.
      if (!optionsSet.has(optionsString)) {
        return;
      }

      // Match! Remove the current options.
      optionsSet.delete(optionsString);

      // If that was the last options for `listener`, remove `listener`.
      if (optionsSet.size === 0) {
        optionsByListener.delete(listener);

        // If that was the last `listener` for `element`, remove `element`.
        if (optionsByListener.size === 0) {
          clickListenersByElement.delete(element);

          if (typeof element.onclick !== "function") {
            // The element went from one listener to none.
            reportUnclickable(element);
          }
        }
      }
    }
  );

  hookInto(
    HTMLElement.prototype,
    "onclick",
    async (element: mixed, value: mixed) => {
      // If the element has click listeners added via `.addEventListener` changing
      // `.onclick` can't affect whether the element has at least one click
      // listener.
      if (
        !(element instanceof HTMLElement2) ||
        clickListenersByElement.has(element)
      ) {
        return;
      }

      const hasListenerAlready = typeof element.onclick === "function";

      // Let the setter take effect. Then dispatch events (if any). The dispatched
      // event would reach the ElementManager _before_ the new `.onclick` value is
      // actually set otherwise, which could make it take the wrong decision on
      // clickability.
      await undefined;

      if (typeof value === "function") {
        if (!hasListenerAlready) {
          // The element went from no click listeners to one.
          reportClickable(element);
        }
      } else if (hasListenerAlready) {
        // The element went from one click listeners to none.
        reportUnclickable(element);
      }
    }
  );

  // Make sure that `Function.prototype.toString.call(element.addEventListener)`
  // returns "[native code]". This is used by lodash's `_.isNative`.
  // `.toLocaleString` is automatically taken care of when patching `.toString`.
  hookInto(Function.prototype, "toString");
  hookInto(Function.prototype, "toSource"); // Firefox specific.

  const optionNames: Array<string> = ["capture", "once", "passive"];

  function stringifyOptions(options: mixed): string {
    const normalized =
      options == null || typeof options !== "object"
        ? { capture: Boolean(options) }
        : options;
    return optionNames.map(name => String(Boolean(normalized[name]))).join(",");
  }

  // The events are dispatched on `window` rather than on `element`, since
  // `element` might not be inserted into the DOM (yet/anymore), which causes
  // the event not to fire.
  function reportClickable(element: HTMLElement) {
    apply(dispatchEvent, window, [
      new CustomEvent2(INJECTED_CLICKABLE_EVENT, { detail: { element } }),
    ]);
  }

  function reportUnclickable(element: HTMLElement) {
    apply(dispatchEvent, window, [
      new CustomEvent2(INJECTED_UNCLICKABLE_EVENT, { detail: { element } }),
    ]);
  }
};
<|endoftext|>


<commit_before>// import _ from 'messages'
import ActionBar from 'action-bar'
import React, { Component } from 'react'
import { connectStore } from 'utils'

// ===================================================================

const vmActionBarByState = {
  Running: ({ handlers, vm }) => (
    <ActionBar
      actions={[
        {
          label: 'stopVmLabel',
          handler: () => handlers.stopVm(vm.id)
        }
      ]}
    />
  ),
  Halted: ({ handlers, vm }) => (
    <ActionBar
      actions={[
        {
          label: 'startVmLabel',
          handler: () => handlers.startVm(vm.id)
        }
      ]}
    />
  )
}

const VmActionBar = ({
  vm,
  handlers
}) => {
  const ActionBar = vmActionBarByState[vm.power_state]

  if (!ActionBar) {
    return
No action bar for state {vm.power_state}

  }

  return <ActionBar vm={vm} handlers={handlers} />
}

// ===================================================================

@connectStore((state, props) => {
  const { objects } = state
  const { id } = props.params

  const vm = objects[id]
  if (!vm) {
    return {}
  }

  return {
    container: objects[vm.$container],
    pool: objects[vm.$pool],
    vm
  }
})
export default class extends Component {
  render () {
    const {
      container,
      pool,
      vm
    } = this.props
    console.log(this.props)

    if (!vm) {
      return
Loading…

    }

    return

{vm.name_label} - {container.name_label} ({pool.name_label})

{vm.name_description}

      <VmActionBar vm={vm} handlers={this.props} />

  }
}
<commit_msg>More work on VM view<commit_after>// import _ from 'messages'
import ActionBar from 'action-bar'
import React, { Component } from 'react'
import { connectStore } from 'utils'
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'

// ===================================================================

const vmActionBarByState = {
  Running: ({ handlers, vm }) => (
    <ActionBar
      actions={[
        {
          label: 'stopVmLabel',
          handler: () => handlers.stopVm(vm.id)
        }
      ]}
    />
  ),
  Halted: ({ handlers, vm }) => (
    <ActionBar
      actions={[
        {
          label: 'startVmLabel',
          handler: () => handlers.startVm(vm.id)
        }
      ]}
    />
  )
}

const VmActionBar = ({
  vm,
  handlers
}) => {
  const ActionBar = vmActionBarByState[vm.power_state]

  if (!ActionBar) {
    return
No action bar for state {vm.power_state}

  }

  return <ActionBar vm={vm} handlers={handlers} />
}

// ===================================================================

@connectStore((state, props) => {
  const { objects } = state
  const { id } = props.params

  const vm = objects[id]
  if (!vm) {
    return {}
  }

  return {
    container: objects[vm.$container],
    pool: objects[vm.$pool],
    vm
  }
})
export default class extends Component {
  render () {
    const {
      container,
      pool,
      vm
    } = this.props
    console.log(this.props)

    if (!vm) {
      return
Loading…

    }

    return

{vm.name_label} - {container.name_label} ({pool.name_label})

{vm.name_description}

      <VmActionBar vm={vm} handlers={this.props} />
      <Tabs>
        <TabList>
          <Tab>General</Tab>
          <Tab>Stats</Tab>
          <Tab>Console</Tab>
          <Tab>Disks</Tab>
          <Tab>Network</Tab>
          <Tab>Snapshot</Tab>
          <Tab>Logs</Tab>
          <Tab>Advanced</Tab>
        </TabList>
        <TabPanel>

{vm.CPUs.number}x vCPU

              { /* TODO: compute nicely RAM units */ }

{vm.memory.size / 1073741824}GB RAM

              { /* TODO: compute total disk usage */ }

{vm.$VBDs.length}x Disks

{vm.VIFs.length}x Networks

{vm.addresses['0/ip']}

{vm.os_version.name}

              { /* TODO: tag display component */ }

Tags:

        </TabPanel>
        <TabPanel>

Hello from Bar

        </TabPanel>
        <TabPanel>

{JSON.stringify(vm, null, 2)}

        </TabPanel>
      </Tabs>

  }
}
<|endoftext|>