<commit_before>/*
 * accessToken may be embedded in payload but that should not
 *   be assumed
 *
 * options: {
 *   hostname
 *   protocol
 *   path
 *   port
 *   method
 * }
 *
 * payload is an unserialized object
 */
module.exports = {
  get: function (accessToken, options, payload, callback) {
  },

  post: function (accessToken, options, payload, callback) {
  }
};
<commit_msg>write the browser transport<commit_after>var _ = require('../utility');

/*
 * accessToken may be embedded in payload but that should not
 *   be assumed
 *
 * options: {
 *   hostname
 *   protocol
 *   path
 *   port
 *   method
 * }
 *
 *  params is an object containing key/value pairs. These
 *    will be appended to the path as 'key=value&key=value'
 *
 * payload is an unserialized object
 */

function get(accessToken, options, params, callback) {
  if (!callback || !_.isFunction(callback)) {
    callback = function() {};
  }
  _.addParamsAndAccessTokenToPath(accessToken, options, params);

  var method = 'GET';
  var url = _.formatUrl(options);
  _makeRequest(accessToken, url, method, null, callback);
}

function post(accessToken, options, payload, callback) {
  if (!callback || !_.isFunction(callback)) {
    callback = function() {};
  }

  if (!payload) {
    return callback(new Error('Cannot send empty request'));
  }

  var stringifyResult = _.stringify(payload, RollbarJSON);
  if (stringifyResult.error) {
    return callback(stringifyResult.error);
  }

  var writeData = stringifyResult.value;
  var method = 'POST';
  var url = _.formatUrl(options);
  _makeRequest(accessToken, url, method, writeData, callback);
}

function _makeRequest(accessToken, url, method, data, callback) {
  var request = _createXMLHTTPObject();
  if (!request) {
    // Give up, no way to send requests
    return callback(new Error('No way to send a request'));
  }
  try {
    try {
      var onreadystatechange = function() {
        try {
          if (onreadystatechange && request.readyState === 4) {
            onreadystatechange = undefined;

            var jsonResponse = RollbarJSON.parse(request.responseText);
            if (_isSuccess(request)) {
              callback(null, jsonResponse);
            } else if (_isNormalFailure(request)) {
              if (request.status === 403) {
                // likely caused by using a server access token
                _.consoleError('[Rollbar]:' + jsonResponse.message);
              }
              // return valid http status codes
              callback(new Error(String(request.status)));
            } else {
              // IE will return a status 12000+ on some sort of connection failure,
              // so we return a blank error
              // http://msdn.microsoft.com/en-us/library/aa383770%28VS.85%29.aspx
              var msg = 'XHR response had no status code (likely connection failure)';
              callback(_newRetriableError(msg));
            }
          }
        } catch (ex) {
          //jquery source mentions firefox may error out while accessing the
          //request members if there is a network error
          //https://github.com/jquery/jquery/blob/a938d7b1282fc0e5c52502c225ae8f0cef219f0a/src/ajax/xhr.js#L111
          var exc;
          if (ex && ex.stack) {
            exc = ex;
          } else {
            exc = new Error(ex);
          }
          callback(exc);
        }
      };

      request.open(method, url, true);
      if (request.setRequestHeader) {
        request.setRequestHeader('Content-Type', 'application/json');
        request.setRequestHeader('X-Rollbar-Access-Token', accessToken);
      }
      request.onreadystatechange = onreadystatechange;
      request.send(data);
    } catch (e1) {
      // Sending using the normal xmlhttprequest object didn't work, try XDomainRequest
      if (typeof XDomainRequest !== 'undefined') {

        // Assume we are in a really old browser which has a bunch of limitations:
        // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx

        // Extreme paranoia: if we have XDomainRequest then we have a window, but just in case
        if (!window || !window.location) {
          return callback(new Error('No window available during request, unknown environment'));
        }

        // If the current page is http, try and send over http
        if (window.location.href.substring(0, 5) === 'http:' && url.substring(0, 5) === 'https') {
          url = 'http' + url.substring(5);
        }

        var xdomainrequest = new XDomainRequest();
        xdomainrequest.onprogress = function() {};
        xdomainrequest.ontimeout = function() {
          var msg = 'Request timed out';
          var code = 'ETIMEDOUT';
          callback(_newRetriableError(msg, code));
        };
        xdomainrequest.onerror = function() {
          callback(new Error('Error during request'));
        };
        xdomainrequest.onload = function() {
          callback(null, RollbarJSON.parse(xdomainrequest.responseText));
        };
        xdomainrequest.open(method, url, true);
        xdomainrequest.send(data);
      } else {
        callback(new Error('Cannot find a method to transport a request'));
      }
    }
  } catch (e2) {
    callback(e2);
  }
}

var _XMLHttpFactories = [
function () {
    return new XMLHttpRequest();
  },
  function () {
    return new ActiveXObject('Msxml2.XMLHTTP');
  },
  function () {
    return new ActiveXObject('Msxml3.XMLHTTP');
  },
  function () {
    return new ActiveXObject('Microsoft.XMLHTTP');
  }
];

function _createXMLHTTPObject() {
  var xmlhttp;
  var factories = _XMLHttpFactories;
  var i;
  var numFactories = factories.length;
  for (i = 0; i < numFactories; i++) {
    /* eslint-disable no-empty */
    try {
      xmlhttp = factories[i]();
      break;
    } catch (e) {
      // pass
    }
    /* eslint-enable no-empty */
  }
  return xmlhttp;
};

function _isSuccess(r) {
  return r && r.status && r.status === 200;
}

function _isNormalFailure(r) {
  return r && _.isType(r.status, 'number') && r.status >= 400 && r.status < 600;
}

function _newRetriableError(message, code) {
  var err = new Error(message);
  err.code = code || 'ENOTFOUND';
  return err;
}

module.exports = {
  get: get,
  post: post
};
<|endoftext|>


<commit_before>//--------------------------------------------------------------------------------------
// These libraries can be downloaded via npm ("npm install express mysql rwlock serialport body-parser")
var express = require("express"),
	mysql = require("mysql"),
	rwlock = require("rwlock"),
	serialPort = require("serialport")
	bodyParser = require("body-parser"),
	fs = require("fs"),
	util = require("util")
;

//--------------------------------------------------------------------------------------
// Constants
var ERROR_CODE_INVALID_JSON = 1000;
var ERROR_MYSQL_ERROR = 1001;
var ERROR_BAD_FS20_CODE = 1002;
var ERROR_NO_HOUSE_FOR_ID = 1003;
var ERROR_INVALID_DEVICE = 1004;
var ERROR_BAD_FS20_EXECUTION = 1005;
var ERROR_INVALID_COMMAND = 1006;

var FS20_EXIT_CODES_TEXT = [
	/* 0x00 */ "Standby",
	/* 0x01 */ "Send Active",
	/* 0x02 */ "Invalid Command ID",
	/* 0x03 */ "Invalid Command Length",
	/* 0x04 */ "Invalid Parameters",
	/* 0x05 */ "Duty Cycle Active",
	/* 0x06 */ "Invalid Start Sign",
	/* 0x07 */ "Invalid FS20 Command",
	/* 0x08 */ "Too Slow Data Transmission",
	/* 0x09 */ "Pause was < 10ms"
];
//--------------------------------------------------------------------------------------
// Read and parse configuration file
var config = {};
try {
	config = JSON.parse(fs.readFileSync("config.json"));
} catch(e) {
	console.log("Error while reading configuration file: " + e);
	process.exit(1);
}

//--------------------------------------------------------------------------------------
// Create MySQL connection
var mysqlConnection = mysql.createConnection(config.mysqlData);

mysqlConnection.connect(function(error){
	if(error) {
		console.log("Error while connecting to the main database: " + error);
		process.exit(1);
	}
	console.log("Connection to the database was successfull!");
});

//--------------------------------------------------------------------------------------
// Connection to the FS20 serial interface
function fs20(data, callback) {
	if(typeof fs20.device === "undefined")
		return false;

	if(!fs20.device.isOpen())
		return false;

	if(typeof fs20.mutex === "undefined")
		fs20.mutex = new rwlock();

	fs20.mutex.writeLock(function(release) {
		fs20.device.write(data, function(err) {
			if(err) {
				release();
				return callback(err, null);
			}

			fs20.device.on("data", function(recvData) {
				release();
				callback(null, recvData);
				fs20.device.removeAllListeners("data");
			});
		});
	});

	return true;
}

fs20.device = new serialPort.SerialPort(config.serialInterface.dev, {
	baudrate: config.serialInterface.baud,
	parser: serialPort.parsers.byteLength(4)
}, false);

fs20.isValidCode = function(code) {
	var c = parseInt(code);
	if(isNaN(c))
		return false;

	if(c < 1111 || c > 4444)
		return false;

	for(var i = 1; i <= 4; i++, c = Math.floor(c / 10))
		if(c % 10 < 1 || c % 10 > 4)
			return false;

	return true;
}

fs20.convertCode = function(code) {
	if(!fs20.isValidCode(code))
		return -1;

	for(var i = 1111, hex = 0x00; i<=4444; i++) {
		if(!fs20.isValidCode(i))
			continue;

		if(code == i)
			return hex;

		hex++;
	}

	return -1;
}
fs20.device.open(function(error) {
	if(error) {
		console.log("Error while opening the serial interface: " + error);
		process.exit(1);
	}

	console.log("Serial interface opened!");
});

//--------------------------------------------------------------------------------------
// Create express server
var app = express();

// Parse application/json
app.use(bodyParser.json());

// Middleware function for all requests
app.use(function(req, res, next) {
	res.sendObject = function(obj) {
		res.setHeader("Content-Type", "application/json");
		this.end(JSON.stringify(obj));
	};

	res.sendError = function(code, msg) {
		res.setHeader("Content-Type", "application/json");
		this.sendObject({ error: {code: code, msg: msg }});
	};

	req.validJSON = function(members) {
		var that = this, error = false;
		Object.keys(members).forEach(function(key){
			if(!(key in that.body))
				error = true;

			if(!error && typeof that.body[key] != members[key])
				error = true;
		});

		return !error;
	};

	req.validateJSONAndSendError = function(members) {
		if(!this.validJSON(members)) {
			res.sendError(ERROR_CODE_INVALID_JSON, "Some keys weren't found or they've wrong data types!");
			return true;
		}

		return false;
	};

	next();
});

// Router for the HTTP application
app.post("/", function(req, res) {
	res.sendObject({x: req.validJSON({"a": "number", "b": "string"})});
});

app.get("/house", function(req, res) {
	mysqlConnection.query("SELECT * FROM house", function(err, rows) {
		if(err)
			return res.sendError(ERROR_MYSQL_ERROR, "Database error!");
		res.sendObject(rows);
	});
});

app.post("/house", function(req, res) {
	if(req.validateJSONAndSendError({ "name": "string", "house_code_1": "number", "house_code_2": "number" }))
		return;

	if(!fs20.isValidCode(req.body.house_code_1) || !fs20.isValidCode(req.body.house_code_2))
		return res.sendError(ERROR_BAD_FS20_CODE, "Bad FS20 code!");

	var insertValues = [req.body.name, req.body.house_code_1, req.body.house_code_2];
	mysqlConnection.query("INSERT INTO house (name, house_code_1, house_code_2) VALUES (?, ?, ?)", insertValues, function(err, result) {
		if(err)
			return res.sendError(ERROR_MYSQL_ERROR, "Database error!");
		res.sendObject({ id: result.insertId });
	});
});

app.get("/house/:house_id/device", function(req, res) {
	mysqlConnection.query("SELECT * FROM device WHERE house_id = ?", [req.params.house_id], function(err, rows){
		if(err)
			return res.sendError(ERROR_MYSQL_ERROR, "Database error!");
		res.sendObject(rows);
	});
});

app.post("/house/:house_id/device", function(req, res) {
	if(req.validateJSONAndSendError({ "name": "string", "device_code": "number" }))
		return;

	if(!fs20.isValidCode(req.body.device_code))
		return res.sendError(ERROR_BAD_FS20_CODE, "Bad FS20 code!");

	mysqlConnection.query("SELECT * FROM house WHERE id = ?", [req.params.house_id], function(err, rows) {
		if(err)
			return res.sendError(ERROR_MYSQL_ERROR, "Database error!");

		if(rows.length === 0)
			return res.sendError(ERROR_NO_HOUSE_FOR_ID, "No house for the given id!");

		var insertValues = [req.params.house_id, req.body.name, req.body.device_code];
		mysqlConnection.query("INSERT INTO device (house_id, name, device_code) VALUES (?, ?, ?)", insertValues, function(err, result) {
			if(err)
				return res.sendError(ERROR_MYSQL_ERROR, "Database error!");
			res.sendObject({ id: result.insertId });
		});
	});
});

app.get("/house/:house_id/device/:device_id/:command", function(req, res) {
	var command = req.params.command;

	// Check, if :command is valid
	if(command != "enable" && command != "disable")
		return res.sendError(ERROR_INVALID_COMMAND, "Invalid command. Valid commands: enable | disable");

	var sqlArgs = [req.params.house_id, req.params.device_id];
	mysqlConnection.query("SELECT * FROM house INNER JOIN device WHERE house.id = ? AND device.id = ?", sqlArgs, function(err, rows) {
		if(err)
			return res.sendError(ERROR_MYSQL_ERROR, "Database error!");

		// Check if the requested device exists
		if(rows.length === 0)
			return res.sendError(ERROR_INVALID_DEVICE, "No device found!");

		// Convert the codes into FS20 codes, e.g. 1111 --> 0x00, 1112 --> 0x01
		var hc1 = fs20.convertCode(rows[0].house_code_1),
			hc2 = fs20.convertCode(rows[0].house_code_2),
			dc = fs20.convertCode(rows[0].device_code)
		;

		// Map the command to the fs20 appropriate BEF byte
		var commandByte = {
			"enable": 0x11,
			"disable": 0x00
		} [command];

		// Check if the codes are valid
		if(hc1 == -1 || hc2 == -1 || dc == -1)
			return res.sendError(ERROR_BAD_FS20_CODE, "Bad FS20 code!");

		// Execute the command on the FS20 module
		fs20([0x02, 0x06, 0xF1, hc1, hc2, dc, commandByte, 0x00], function(err, data) {
			/*
				The FS20's response should be 4 bytes long
				0: 0x2 (start opcode)
				1: 0x2 (length)
				2: exitcode (should be between 0x00 and 0x09)
				3: baudrate (should be between 0x00 and 0x04)
			*/
			// Check if an error occured and the response's length
			if(err || data.length != 4)
				return res.sendObject(ERROR_BAD_FS20_EXECUTION, "Bad FS20 command execution!");

			// Check if the exitcode is valid
			if(data[2] < 0x00 || data[2] > 0x09)
				return res.sendObject(ERROR_BAD_FS20_EXECUTION, "Bad FS20 command execution!");

			// Send the exitcode and its appropriate exitcode text
			res.sendObject({ exitCode: data[2], exitText: FS20_EXIT_CODES_TEXT[data[2]] });
		});
	});
});

app.get("/web/*", function(req, res) {
	res.sendFile(__dirname + "/web/" + (req.params["0"] || "index.html"), function(err) {
		if(err) {
			res.sendStatus(err.status);
		}
	});
});

try {
	app.listen(config.web.port);
	console.log("HTTP server has been successfully started!");
}
catch(e) {
	console.log("Error while starting HTTP server: " + e);
	process.exit(1);
}
<commit_msg>House data and its devices can be fetched json encoded Changed all API requests to path "/api/*"<commit_after>//--------------------------------------------------------------------------------------
// These libraries can be downloaded via npm ("npm install express mysql rwlock serialport body-parser")
var express = require("express"),
	mysql = require("mysql"),
	rwlock = require("rwlock"),
	serialPort = require("serialport")
	bodyParser = require("body-parser"),
	fs = require("fs"),
	util = require("util")
;

//--------------------------------------------------------------------------------------
// Constants
var ERROR_CODE_INVALID_JSON = 1000;
var ERROR_MYSQL_ERROR = 1001;
var ERROR_BAD_FS20_CODE = 1002;
var ERROR_NO_HOUSE_FOR_ID = 1003;
var ERROR_INVALID_DEVICE = 1004;
var ERROR_BAD_FS20_EXECUTION = 1005;
var ERROR_INVALID_COMMAND = 1006;

var FS20_EXIT_CODES_TEXT = [
	/* 0x00 */ "Standby",
	/* 0x01 */ "Send Active",
	/* 0x02 */ "Invalid Command ID",
	/* 0x03 */ "Invalid Command Length",
	/* 0x04 */ "Invalid Parameters",
	/* 0x05 */ "Duty Cycle Active",
	/* 0x06 */ "Invalid Start Sign",
	/* 0x07 */ "Invalid FS20 Command",
	/* 0x08 */ "Too Slow Data Transmission",
	/* 0x09 */ "Pause was < 10ms"
];
//--------------------------------------------------------------------------------------
// Read and parse configuration file
var config = {};
try {
	config = JSON.parse(fs.readFileSync("config.json"));
} catch(e) {
	console.log("Error while reading configuration file: " + e);
	process.exit(1);
}

//--------------------------------------------------------------------------------------
// Create MySQL connection
var mysqlConnection = mysql.createConnection(config.mysqlData);

mysqlConnection.connect(function(error){
	if(error) {
		console.log("Error while connecting to the main database: " + error);
		process.exit(1);
	}
	console.log("Connection to the database was successfull!");
});

//--------------------------------------------------------------------------------------
// Connection to the FS20 serial interface
function fs20(data, callback) {
	if(typeof fs20.device === "undefined")
		return false;

	if(!fs20.device.isOpen())
		return false;

	if(typeof fs20.mutex === "undefined")
		fs20.mutex = new rwlock();

	fs20.mutex.writeLock(function(release) {
		fs20.device.write(data, function(err) {
			if(err) {
				release();
				return callback(err, null);
			}

			fs20.device.on("data", function(recvData) {
				release();
				callback(null, recvData);
				fs20.device.removeAllListeners("data");
			});
		});
	});

	return true;
}

fs20.device = new serialPort.SerialPort(config.serialInterface.dev, {
	baudrate: config.serialInterface.baud,
	parser: serialPort.parsers.byteLength(4)
}, false);

fs20.isValidCode = function(code) {
	var c = parseInt(code);
	if(isNaN(c))
		return false;

	if(c < 1111 || c > 4444)
		return false;

	for(var i = 1; i <= 4; i++, c = Math.floor(c / 10))
		if(c % 10 < 1 || c % 10 > 4)
			return false;

	return true;
}

fs20.convertCode = function(code) {
	if(!fs20.isValidCode(code))
		return -1;

	for(var i = 1111, hex = 0x00; i<=4444; i++) {
		if(!fs20.isValidCode(i))
			continue;

		if(code == i)
			return hex;

		hex++;
	}

	return -1;
}
fs20.device.open(function(error) {
	if(error) {
		console.log("Error while opening the serial interface: " + error);
		process.exit(1);
	}

	console.log("Serial interface opened!");
});

//--------------------------------------------------------------------------------------
// Create express server
var app = express();

// Parse application/json
app.use(bodyParser.json());

// Middleware function for all requests
app.use(function(req, res, next) {
	res.sendObject = function(obj) {
		res.setHeader("Content-Type", "application/json");
		this.end(JSON.stringify(obj));
	};

	res.sendError = function(code, msg) {
		res.setHeader("Content-Type", "application/json");
		this.sendObject({ error: {code: code, msg: msg }});
	};

	req.validJSON = function(members) {
		var that = this, error = false;
		Object.keys(members).forEach(function(key){
			if(!(key in that.body))
				error = true;

			if(!error && typeof that.body[key] != members[key])
				error = true;
		});

		return !error;
	};

	req.validateJSONAndSendError = function(members) {
		if(!this.validJSON(members)) {
			res.sendError(ERROR_CODE_INVALID_JSON, "Some keys weren't found or they've wrong data types!");
			return true;
		}

		return false;
	};

	next();
});

// Router for the HTTP application
app.get("/api/house", function(req, res) {
	mysqlConnection.query("SELECT * FROM house", function(err, rows) {

		res.sendObject(rows);
	});
});

app.get("/api/", function(req, res) {
	var queryStr = "SELECT house.id as house_id, house.name as house_name, house.house_code_1 as hc1, house.house_code_2 as hc2, device.id as device_id, device.name as device_name, device.device_code, device.house_id as device_house_id FROM house INNER JOIN device";
	mysqlConnection.query(queryStr, function(err, rows) {
		if(err)
			return res.sendError(ERROR_MYSQL_ERROR, "Database error!");

		var houses = [];
		rows.forEach(function(device){
			var houseIndex = -1;
			var isHouseInArray = houses.some(function(house, index) {
				if(device.house_id === house.id) {
					houseIndex = index;
					return true;
				}
				return false;
			});

			if(!isHouseInArray) {
				houseIndex = houses.push({
					id: device.house_id,
					name: device.house_name,
					code1: device.hc1,
					code2: device.hc2,
					devices: []
				}) - 1;
			}

			houses[houseIndex].devices.push({
				id: device.device_id,
				name: device.device_name,
				code: device.device_code
			});
		});

		res.sendObject(houses);
	});
});

app.post("/api/house", function(req, res) {
	if(req.validateJSONAndSendError({ "name": "string", "house_code_1": "number", "house_code_2": "number" }))
		return;

	if(!fs20.isValidCode(req.body.house_code_1) || !fs20.isValidCode(req.body.house_code_2))
		return res.sendError(ERROR_BAD_FS20_CODE, "Bad FS20 code!");

	var insertValues = [req.body.name, req.body.house_code_1, req.body.house_code_2];
	mysqlConnection.query("INSERT INTO house (name, house_code_1, house_code_2) VALUES (?, ?, ?)", insertValues, function(err, result) {
		if(err)
			return res.sendError(ERROR_MYSQL_ERROR, "Database error!");
		res.sendObject({ id: result.insertId });
	});
});

app.get("/api/house/:house_id/device", function(req, res) {
	mysqlConnection.query("SELECT * FROM device WHERE house_id = ?", [req.params.house_id], function(err, rows){
		if(err)
			return res.sendError(ERROR_MYSQL_ERROR, "Database error!");
		res.sendObject(rows);
	});
});

app.post("/api/house/:house_id/device", function(req, res) {
	if(req.validateJSONAndSendError({ "name": "string", "device_code": "number" }))
		return;

	if(!fs20.isValidCode(req.body.device_code))
		return res.sendError(ERROR_BAD_FS20_CODE, "Bad FS20 code!");

	mysqlConnection.query("SELECT * FROM house WHERE id = ?", [req.params.house_id], function(err, rows) {
		if(err)
			return res.sendError(ERROR_MYSQL_ERROR, "Database error!");

		if(rows.length === 0)
			return res.sendError(ERROR_NO_HOUSE_FOR_ID, "No house for the given id!");

		var insertValues = [req.params.house_id, req.body.name, req.body.device_code];
		mysqlConnection.query("INSERT INTO device (house_id, name, device_code) VALUES (?, ?, ?)", insertValues, function(err, result) {
			if(err)
				return res.sendError(ERROR_MYSQL_ERROR, "Database error!");
			res.sendObject({ id: result.insertId });
		});
	});
});

app.get("/api/house/:house_id/device/:device_id/:command", function(req, res) {
	var command = req.params.command;

	// Check, if :command is valid
	if(command != "enable" && command != "disable")
		return res.sendError(ERROR_INVALID_COMMAND, "Invalid command. Valid commands: enable | disable");

	var sqlArgs = [req.params.house_id, req.params.device_id];
	mysqlConnection.query("SELECT * FROM house INNER JOIN device WHERE house.id = ? AND device.id = ?", sqlArgs, function(err, rows) {
		if(err)
			return res.sendError(ERROR_MYSQL_ERROR, "Database error!");

		// Check if the requested device exists
		if(rows.length === 0)
			return res.sendError(ERROR_INVALID_DEVICE, "No device found!");

		// Convert the codes into FS20 codes, e.g. 1111 --> 0x00, 1112 --> 0x01
		var hc1 = fs20.convertCode(rows[0].house_code_1),
			hc2 = fs20.convertCode(rows[0].house_code_2),
			dc = fs20.convertCode(rows[0].device_code)
		;

		// Map the command to the fs20 appropriate BEF byte
		var commandByte = {
			"enable": 0x11,
			"disable": 0x00
		} [command];

		// Check if the codes are valid
		if(hc1 == -1 || hc2 == -1 || dc == -1)
			return res.sendError(ERROR_BAD_FS20_CODE, "Bad FS20 code!");

		// Execute the command on the FS20 module
		fs20([0x02, 0x06, 0xF1, hc1, hc2, dc, commandByte, 0x00], function(err, data) {
			/*
				The FS20's response should be 4 bytes long
				0: 0x2 (start opcode)
				1: 0x2 (length)
				2: exitcode (should be between 0x00 and 0x09)
				3: baudrate (should be between 0x00 and 0x04)
			*/
			// Check if an error occured and the response's length
			if(err || data.length != 4)
				return res.sendObject(ERROR_BAD_FS20_EXECUTION, "Bad FS20 command execution!");

			// Check if the exitcode is valid
			if(data[2] < 0x00 || data[2] > 0x09)
				return res.sendObject(ERROR_BAD_FS20_EXECUTION, "Bad FS20 command execution!");

			// Send the exitcode and its appropriate exitcode text
			res.sendObject({ exitCode: data[2], exitText: FS20_EXIT_CODES_TEXT[data[2]] });
		});
	});
});

app.get("/web/*", function(req, res) {
	res.sendFile(__dirname + "/web/" + (req.params["0"] || "index.html"), function(err) {
		if(err) {
			res.sendStatus(err.status);
		}
	});
});

try {
	app.listen(config.web.port);
	console.log("HTTP server has been successfully started!");
}
catch(e) {
	console.log("Error while starting HTTP server: " + e);
	process.exit(1);
}
<|endoftext|>


<commit_before>// create an new instance of a pixi stage
var stage = new PIXI.Stage(0x000000)

// create a renderer instance
var screenW = $(window).width(), screenH = $(window).height() - 10
//var screenW = 800, screenH = 600;
var renderer = PIXI.autoDetectRenderer(screenW, screenH)
$(window).resize(onResize)
// add the renderer view element to the DOM
document.body.appendChild(renderer.view)

function stagePosition(x, y) { //where x and y are absolute positions in the universe
//    stageX =

}

function ship(shipName) {
    this.name = shipName || ""
    this.renderReady = false
    this.lastAccelerating = false
}

ship.prototype.build = function() {
    //console.log(this.name)
    var url = "ships/" + this.name + '.json'
    var loader = new PIXI.JsonLoader(url);
    loader.on('loaded', _.bind(this.interpretShipJsonAndStartInterpretShipImageJson, this))
    loader.load()
}

ship.prototype.interpretShipJsonAndStartInterpretShipImageJson = function(evt) {
    //data is in evt.content.json
    this.meta = evt.content.json //generic ship infromation. Not Graphics.
    console.log(this.meta)	// DEBUG

    var url = "ships/" + this.meta.imageAssetsFile
    var loader = new PIXI.JsonLoader(url)
    console.log('loading ships/' + this.meta.imageAssetsFile) //DEBUG

    loader.on('loaded', _.bind(this.interpretShipImageJson, this))
    loader.load()
}
ship.prototype.interpretShipImageJson = function(evt) {
    this.shipImageInfo = evt.content.json
    console.log(this.shipImageInfo.meta.imagePurposes)

    var shipAssetsToLoad = ["ships/" + this.meta.imageAssetsFile]
    var shipLoader = new PIXI.AssetLoader(shipAssetsToLoad)

    shipLoader.onComplete = _.bind(this.onAssetsLoaded, this)
    shipLoader.load()
}

ship.prototype.onAssetsLoaded = function() {
    // Get a list of the textures for the ship.
    this.textures = _.map(_.keys(this.shipImageInfo.frames),
			 function(frame) { return(PIXI.Texture.fromFrame(frame)) })

    this.sprite = new PIXI.Sprite(this.textures[0])
    this.sprite.anchor.x = 0.5
    this.sprite.anchor.y = 0.5
    this.turnRate = this.meta.physics.turn_rate * 2*Math.PI/120 // 10 nova ship turn rate/sec ~= 30°/sec This turn rate is radians/sec
    stage.addChild(this.sprite)
    this.renderReady = true
    requestAnimFrame( animate ) // make a system for this where multiple ships are happy.
    console.log("loaded assets for " + this.name) //should happen when ship is finished loading
    return true
}

/*
render(time, turning):
  if turning different from last_turning:
    turning_start_time = time
    turning_start_direction = direction
    omega = omega[turning]
  direction = (time - turning_start_time) * omega
  set_my_picture_to(direction)
*/
ship.prototype.updateStats = function(time, turning, accelerating) {

    ship.prototype.render.call(this, time, turning, accelerating)
}

ship.prototype.render = function(time, turning, accelerating) {
    if (this.renderReady == true) {

	var frameStart = this.shipImageInfo.meta.imagePurposes.normal.start
	var frameCount = this.shipImageInfo.meta.imagePurposes.normal.length

	if (this.isPlayerShip == true) {
	    this.sprite.position.x = screenW/2
	    this.sprite.position.y = screenH/2
	}

	if (turning == 'back') {
	    var vvector = Math.acos((Math.pow(this.xvelocity, 2) - Math.pow(this.yvelocity, 2) - (Math.pow(this.xvelocity, 2) + Math.pow(this.yvelocity, 2)))/(-2*Math.pow(Math.pow(this.xvelocity, 2) + Math.pow(this.yvelocity, 2), 0.5) * this.yvelocity))
	    console.log(vvector)
	    var pointto = (vvector + Math.PI) % (2*Math.PI)
	    var pointDiff = pointto - this.pointing
	    if (pointDiff < 0) {
		pointDiff += 2*Math.PI
	    }
	    if (pointDiff < Math.PI) {
		turning = "right"
	    }
	    else if(pointDiff > Math.PI) {
		turning = "left"
	    }
	    else {
		turning = "nowhere"
	    }
	}

	// if the new direction does not equal the previous direction
	if ((typeof this.lastTurning == 'undefined') || (turning != this.lastTurning)) {
	    this.turnStartTime = time // the turn started at the average of the times
	    this.origionalPointing = this.pointing

	}
	if (turning == "left") {
	    this.pointing = this.origionalPointing - (this.turnRate * (time - this.turnStartTime) / 1000)
	    frameStart = this.shipImageInfo.meta.imagePurposes.left.start
	    frameCount = this.shipImageInfo.meta.imagePurposes.left.length
	}
	else if (turning == "right") {
	    this.pointing = this.origionalPointing + (this.turnRate * (time - this.turnStartTime) / 1000)
	    frameStart = this.shipImageInfo.meta.imagePurposes.right.start
	    frameCount = this.shipImageInfo.meta.imagePurposes.right.length
	}
	else if (turning == "back") { // turn backwards to velocity. Velocity not yet implemented.
	    //put some code here
	    console.log('turning = back')
	}
	else {
	    frameStart = this.shipImageInfo.meta.imagePurposes.normal.start
	    frameCount = this.shipImageInfo.meta.imagePurposes.normal.length
	}

	this.pointing = this.pointing % (2*Math.PI)  //makes sure ship.pointing is in the range [0, 2pi)
	if (this.pointing < 0) {
	    this.pointing += 2*Math.PI
	}

	// ship uses image 0 for [this.pointing - pi/frameCount, this.pointing + pi/frameCount) etc
	var useThisImage = Math.floor((this.pointing * frameCount/(2*Math.PI) + .5) % frameCount) + frameStart

	this.sprite.rotation = (this.pointing + (Math.PI / frameCount)) % (2*Math.PI/frameCount) - Math.PI/frameCount // how much to rotate the image

	this.sprite.setTexture(this.textures[useThisImage])

	// this.origionalPointing is the angle the ship was pointed towards before it was told a different direction to turn.
	this.lastTurning = turning // last turning value: left, right, or back

	//acceleration
	var xaccel = Math.sin(this.pointing) * this.meta.physics.acceleration
	var yaccel = Math.cos(this.pointing) * this.meta.physics.acceleration
	if (accelerating == true) {
	    if (typeof this.previousAccelTime != 'undefined') {
		this.xvelocity += xaccel * (time - this.previousAccelTime)/1000
		this.yvelocity += yaccel * (time - this.previousAccelTime)/1000
	    }

	    this.previousAccelTime = time
	}

	return true
    }
    else {
	return false // oh no. I'm not ready to render. better not try
    }
}

function playerShip(shipName) {
    this.pointing = Math.random()*2*Math.PI
    ship.call(this, shipName)
    this.xvelocity = 0
    this.yvelocity = 0
    this.isPlayerShip = true
}

playerShip.prototype = new ship

playerShip.prototype.onAssetsLoaded = function() {
    if (ship.prototype.onAssetsLoaded.call(this)) {
	console.log("and it's mine")

    }
}

var myShip = new playerShip("Starbridge A")
myShip.build()

function animate() {
    requestAnimFrame( animate )
    time = new Date().getTime()
    var keys = KeyboardJS.activeKeys()
    var turning
    var accelerating
    if (_.contains(keys, 'right') && !_.contains(keys, 'left')) {
	turning = 'right'
    }
    else if (_.contains(keys, 'left') && !_.contains(keys, 'right')) {
	turning = 'left'
    }
    else {
	turning = ''
    }
    if (_.contains(keys, 'down')) {
	turning = 'back'
    }
    if (_.contains(keys, 'up')) {
	accelerating = true
    }
    else {
	accelerating = false
    }
    myShip.updateStats(time, turning, accelerating)
    renderer.render(stage)
}
/*
var ship;
var shipTextures;
var shipTexture = 1;

var myShip = new playerShip("mother")
myShip.build()

var starbridgeAssetsToLoader = ["ships/Starbridge.json"];
starbridgeLoader = new PIXI.AssetLoader(starbridgeAssetsToLoader);
starbridgeLoader.onComplete = onAssetsLoaded;
starbridgeLoader.load();

function onAssetsLoaded() {

    shipTextures = [];
    for (var i=0; i<108; i++) {

	var texture = PIXI.Texture.fromFrame("Starbridge " + (i+1) + ".png");
	shipTextures.push(texture);

    };
    // create a texture from an image path
    //var test = PIXI.Texture.fromImage("ships/Starbridge.png");
    // create a new Sprite using the texture
    ship = new PIXI.Sprite(shipTextures[0]);

    //ship.setTexture(shipTextures[0]);
    // center the sprites anchor point
    ship.anchor.x = 0.5;
    ship.anchor.y = 0.5;
    ship.pointing = Math.random() * 2 * Math.PI
    ship.turnRate = 0.1

    // move the sprite t the center of the screen
    ship.position.x = screenW/2;
    ship.position.y = screenH/2;
    stage.addChild(ship);
    requestAnimFrame( animate );

}

function animate() {

    requestAnimFrame( animate );

    // just for fun, lets rotate mr rabbit a little
    //ship.rotation += 0.1;
    //ship.turnRate = 0.01
    var keys = KeyboardJS.activeKeys()
    ship.banking = 0
    if (_.contains(keys, 'right')) {
	ship.pointing += ship.turnRate
	ship.banking = 72
    }
    if (_.contains(keys, 'left')) {
	ship.pointing = ship.pointing - ship.turnRate
	ship.banking = 36
    }
    if (_.contains(keys, 'left') && _.contains(keys, 'right')) {
	ship.banking = 0
    }

    if (ship.pointing >= 2*Math.PI) {
	ship.pointing = 0
    }
    if (ship.pointing < 0) {
	ship.pointing += 2*Math.PI
    }

    var shipRange = ship.pointing * 36 / (2*Math.PI)
    shipTexture = Math.floor(shipRange) + ship.banking

    ship.rotation = ((shipRange % 1 - 0.5) * Math.PI / 18)

    ship.setTexture(shipTextures[shipTexture])

    //if (shipTexture < 35) {
	//shipTexture++
   // }
    //else {
	//shipTexture = 1
    //}

    ship.position.x = screenW/2;
    ship.position.y = screenH/2;
    // render the stage
    renderer.render(stage);
}
*/

function onResize() {
    screenW = $(window).width();
    screenH = $(window).height();
    renderer.resize(screenW,screenH);
}

<commit_msg>added pointing in the opposate direction as velocity vector by pressing down. It doesn't work perfectly yet.<commit_after>// create an new instance of a pixi stage
var stage = new PIXI.Stage(0x000000)

// create a renderer instance
var screenW = $(window).width(), screenH = $(window).height() - 10
//var screenW = 800, screenH = 600;
var renderer = PIXI.autoDetectRenderer(screenW, screenH)
$(window).resize(onResize)
// add the renderer view element to the DOM
document.body.appendChild(renderer.view)

function stagePosition(x, y) { //where x and y are absolute positions in the universe
//    stageX =

}

function ship(shipName) {
    this.name = shipName || ""
    this.renderReady = false
    this.lastAccelerating = false
}

ship.prototype.build = function() {
    //console.log(this.name)
    var url = "ships/" + this.name + '.json'
    var loader = new PIXI.JsonLoader(url);
    loader.on('loaded', _.bind(this.interpretShipJsonAndStartInterpretShipImageJson, this))
    loader.load()
}

ship.prototype.interpretShipJsonAndStartInterpretShipImageJson = function(evt) {
    //data is in evt.content.json
    this.meta = evt.content.json //generic ship infromation. Not Graphics.
    console.log(this.meta)	// DEBUG

    var url = "ships/" + this.meta.imageAssetsFile
    var loader = new PIXI.JsonLoader(url)
    console.log('loading ships/' + this.meta.imageAssetsFile) //DEBUG

    loader.on('loaded', _.bind(this.interpretShipImageJson, this))
    loader.load()
}
ship.prototype.interpretShipImageJson = function(evt) {
    this.shipImageInfo = evt.content.json
    console.log(this.shipImageInfo.meta.imagePurposes)

    var shipAssetsToLoad = ["ships/" + this.meta.imageAssetsFile]
    var shipLoader = new PIXI.AssetLoader(shipAssetsToLoad)

    shipLoader.onComplete = _.bind(this.onAssetsLoaded, this)
    shipLoader.load()
}

ship.prototype.onAssetsLoaded = function() {
    // Get a list of the textures for the ship.
    this.textures = _.map(_.keys(this.shipImageInfo.frames),
			 function(frame) { return(PIXI.Texture.fromFrame(frame)) })

    this.sprite = new PIXI.Sprite(this.textures[0])
    this.sprite.anchor.x = 0.5
    this.sprite.anchor.y = 0.5
    this.turnRate = this.meta.physics.turn_rate * 2*Math.PI/120 // 10 nova ship turn rate/sec ~= 30°/sec This turn rate is radians/sec
    stage.addChild(this.sprite)
    this.renderReady = true
    requestAnimFrame( animate ) // make a system for this where multiple ships are happy.
    console.log("loaded assets for " + this.name) //should happen when ship is finished loading
    return true
}

/*
render(time, turning):
  if turning different from last_turning:
    turning_start_time = time
    turning_start_direction = direction
    omega = omega[turning]
  direction = (time - turning_start_time) * omega
  set_my_picture_to(direction)
*/
ship.prototype.updateStats = function(time, turning, accelerating) {

    ship.prototype.render.call(this, time, turning, accelerating)
}

ship.prototype.render = function(time, turning, accelerating) {
    if (this.renderReady == true) {

	var frameStart = this.shipImageInfo.meta.imagePurposes.normal.start
	var frameCount = this.shipImageInfo.meta.imagePurposes.normal.length

	if (this.isPlayerShip == true) {
	    this.sprite.position.x = screenW/2
	    this.sprite.position.y = screenH/2
	}

	if (turning == 'back') {
	    var vAngle = Math.acos((Math.pow(this.xvelocity, 2) - Math.pow(this.yvelocity, 2) - (Math.pow(this.xvelocity, 2) + Math.pow(this.yvelocity, 2)))/(-2*Math.pow(Math.pow(this.xvelocity, 2) + Math.pow(this.yvelocity, 2), 0.5) * this.yvelocity))
	    if (this.xvelocity < 0) {
		vAngle = vAngle * -1
	    }
	    console.log(vAngle)
	    var pointto = (vAngle + Math.PI) % (2*Math.PI)
	    var pointDiff = pointto - this.pointing
	    if (pointDiff < 0) {
		pointDiff += 2*Math.PI
	    }
	    if (pointDiff < Math.PI) {
		turning = "right"
	    }
	    else if(pointDiff > Math.PI) {
		turning = "left"
	    }
	    else {
		turning = "nowhere"
	    }
	}

	// if the new direction does not equal the previous direction
	if ((typeof this.lastTurning == 'undefined') || (turning != this.lastTurning)) {
	    this.turnStartTime = time // the turn started at the average of the times
	    this.origionalPointing = this.pointing

	}
	if (turning == "left") {
	    this.pointing = this.origionalPointing - (this.turnRate * (time - this.turnStartTime) / 1000)
	    frameStart = this.shipImageInfo.meta.imagePurposes.left.start
	    frameCount = this.shipImageInfo.meta.imagePurposes.left.length
	}
	else if (turning == "right") {
	    this.pointing = this.origionalPointing + (this.turnRate * (time - this.turnStartTime) / 1000)
	    frameStart = this.shipImageInfo.meta.imagePurposes.right.start
	    frameCount = this.shipImageInfo.meta.imagePurposes.right.length
	}
	else if (turning == "back") { // turn backwards to velocity. Velocity not yet implemented.
	    //put some code here
	    console.log('turning = back')
	}
	else {
	    frameStart = this.shipImageInfo.meta.imagePurposes.normal.start
	    frameCount = this.shipImageInfo.meta.imagePurposes.normal.length
	}

	this.pointing = this.pointing % (2*Math.PI)  //makes sure ship.pointing is in the range [0, 2pi)
	if (this.pointing < 0) {
	    this.pointing += 2*Math.PI
	}

	// ship uses image 0 for [this.pointing - pi/frameCount, this.pointing + pi/frameCount) etc
	var useThisImage = Math.floor((this.pointing * frameCount/(2*Math.PI) + .5) % frameCount) + frameStart

	this.sprite.rotation = (this.pointing + (Math.PI / frameCount)) % (2*Math.PI/frameCount) - Math.PI/frameCount // how much to rotate the image

	this.sprite.setTexture(this.textures[useThisImage])

	// this.origionalPointing is the angle the ship was pointed towards before it was told a different direction to turn.
	this.lastTurning = turning // last turning value: left, right, or back

	//acceleration
	var xaccel = Math.sin(this.pointing) * this.meta.physics.acceleration
	var yaccel = Math.cos(this.pointing) * this.meta.physics.acceleration
	if (accelerating == true) {
	    if (typeof this.previousAccelTime != 'undefined') {
		this.xvelocity += xaccel * (time - this.previousAccelTime)/1000
		this.yvelocity += yaccel * (time - this.previousAccelTime)/1000
	    }

	    this.previousAccelTime = time
	}

	return true
    }
    else {
	return false // oh no. I'm not ready to render. better not try
    }
}

function playerShip(shipName) {
    this.pointing = Math.random()*2*Math.PI
    ship.call(this, shipName)
    this.xvelocity = 0
    this.yvelocity = 0
    this.isPlayerShip = true
}

playerShip.prototype = new ship

playerShip.prototype.onAssetsLoaded = function() {
    if (ship.prototype.onAssetsLoaded.call(this)) {
	console.log("and it's mine")

    }
}

var myShip = new playerShip("Starbridge A")
myShip.build()

function animate() {
    requestAnimFrame( animate )
    time = new Date().getTime()
    var keys = KeyboardJS.activeKeys()
    var turning
    var accelerating
    if (_.contains(keys, 'right') && !_.contains(keys, 'left')) {
	turning = 'right'
    }
    else if (_.contains(keys, 'left') && !_.contains(keys, 'right')) {
	turning = 'left'
    }
    else {
	turning = ''
    }
    if (_.contains(keys, 'down')) {
	turning = 'back'
    }
    if (_.contains(keys, 'up')) {
	accelerating = true
    }
    else {
	accelerating = false
    }
    myShip.updateStats(time, turning, accelerating)
    renderer.render(stage)
}
/*
var ship;
var shipTextures;
var shipTexture = 1;

var myShip = new playerShip("mother")
myShip.build()

var starbridgeAssetsToLoader = ["ships/Starbridge.json"];
starbridgeLoader = new PIXI.AssetLoader(starbridgeAssetsToLoader);
starbridgeLoader.onComplete = onAssetsLoaded;
starbridgeLoader.load();

function onAssetsLoaded() {

    shipTextures = [];
    for (var i=0; i<108; i++) {

	var texture = PIXI.Texture.fromFrame("Starbridge " + (i+1) + ".png");
	shipTextures.push(texture);

    };
    // create a texture from an image path
    //var test = PIXI.Texture.fromImage("ships/Starbridge.png");
    // create a new Sprite using the texture
    ship = new PIXI.Sprite(shipTextures[0]);

    //ship.setTexture(shipTextures[0]);
    // center the sprites anchor point
    ship.anchor.x = 0.5;
    ship.anchor.y = 0.5;
    ship.pointing = Math.random() * 2 * Math.PI
    ship.turnRate = 0.1

    // move the sprite t the center of the screen
    ship.position.x = screenW/2;
    ship.position.y = screenH/2;
    stage.addChild(ship);
    requestAnimFrame( animate );

}

function animate() {

    requestAnimFrame( animate );

    // just for fun, lets rotate mr rabbit a little
    //ship.rotation += 0.1;
    //ship.turnRate = 0.01
    var keys = KeyboardJS.activeKeys()
    ship.banking = 0
    if (_.contains(keys, 'right')) {
	ship.pointing += ship.turnRate
	ship.banking = 72
    }
    if (_.contains(keys, 'left')) {
	ship.pointing = ship.pointing - ship.turnRate
	ship.banking = 36
    }
    if (_.contains(keys, 'left') && _.contains(keys, 'right')) {
	ship.banking = 0
    }

    if (ship.pointing >= 2*Math.PI) {
	ship.pointing = 0
    }
    if (ship.pointing < 0) {
	ship.pointing += 2*Math.PI
    }

    var shipRange = ship.pointing * 36 / (2*Math.PI)
    shipTexture = Math.floor(shipRange) + ship.banking

    ship.rotation = ((shipRange % 1 - 0.5) * Math.PI / 18)

    ship.setTexture(shipTextures[shipTexture])

    //if (shipTexture < 35) {
	//shipTexture++
   // }
    //else {
	//shipTexture = 1
    //}

    ship.position.x = screenW/2;
    ship.position.y = screenH/2;
    // render the stage
    renderer.render(stage);
}
*/

function onResize() {
    screenW = $(window).width();
    screenH = $(window).height();
    renderer.resize(screenW,screenH);
}

<|endoftext|>


<commit_before>/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import * as zrUtil from 'zrender/src/core/util';
import * as graphic from '../../util/graphic';
import ChartView from '../../view/Chart';

/**
 * @param {module:echarts/model/Series} seriesModel
 * @param {boolean} hasAnimation
 * @inner
 */
function updateDataSelected(uid, seriesModel, hasAnimation, api) {
    var data = seriesModel.getData();
    var dataIndex = this.dataIndex;
    var name = data.getName(dataIndex);
    var selectedOffset = seriesModel.get('selectedOffset');

    api.dispatchAction({
        type: 'pieToggleSelect',
        from: uid,
        name: name,
        seriesId: seriesModel.id
    });

    data.each(function (idx) {
        toggleItemSelected(
            data.getItemGraphicEl(idx),
            data.getItemLayout(idx),
            seriesModel.isSelected(data.getName(idx)),
            selectedOffset,
            hasAnimation
        );
    });
}

/**
 * @param {module:zrender/graphic/Sector} el
 * @param {Object} layout
 * @param {boolean} isSelected
 * @param {number} selectedOffset
 * @param {boolean} hasAnimation
 * @inner
 */
function toggleItemSelected(el, layout, isSelected, selectedOffset, hasAnimation) {
    var midAngle = (layout.startAngle + layout.endAngle) / 2;

    var dx = Math.cos(midAngle);
    var dy = Math.sin(midAngle);

    var offset = isSelected ? selectedOffset : 0;
    var position = [dx * offset, dy * offset];

    hasAnimation
        // animateTo will stop revious animation like update transition
        ? el.animate()
            .when(200, {
                position: position
            })
            .start('bounceOut')
        : el.attr('position', position);
}

/**
 * Piece of pie including Sector, Label, LabelLine
 * @constructor
 * @extends {module:zrender/graphic/Group}
 */
function PiePiece(data, idx) {

    graphic.Group.call(this);

    var sector = new graphic.Sector({
        z2: 2
    });
    var polyline = new graphic.Polyline();
    var text = new graphic.Text();
    this.add(sector);
    this.add(polyline);
    this.add(text);

    this.updateData(data, idx, true);
}

var piePieceProto = PiePiece.prototype;

piePieceProto.updateData = function (data, idx, firstCreate) {

    var sector = this.childAt(0);
    var labelLine = this.childAt(1);
    var labelText = this.childAt(2);

    var seriesModel = data.hostModel;
    var itemModel = data.getItemModel(idx);
    var layout = data.getItemLayout(idx);
    var sectorShape = zrUtil.extend({}, layout);
    sectorShape.label = null;

    if (firstCreate) {
        sector.setShape(sectorShape);

        var animationType = seriesModel.getShallow('animationType');
        if (animationType === 'scale') {
            sector.shape.r = layout.r0;
            graphic.initProps(sector, {
                shape: {
                    r: layout.r
                }
            }, seriesModel, idx);
        }
        // Expansion
        else {
            sector.shape.endAngle = layout.startAngle;
            graphic.updateProps(sector, {
                shape: {
                    endAngle: layout.endAngle
                }
            }, seriesModel, idx);
        }

    }
    else {
        graphic.updateProps(sector, {
            shape: sectorShape
        }, seriesModel, idx);
    }

    // Update common style
    var visualColor = data.getItemVisual(idx, 'color');

    sector.useStyle(
        zrUtil.defaults(
            {
                lineJoin: 'bevel',
                fill: visualColor
            },
            itemModel.getModel('itemStyle').getItemStyle()
        )
    );
    sector.hoverStyle = itemModel.getModel('emphasis.itemStyle').getItemStyle();

    var cursorStyle = itemModel.getShallow('cursor');
    cursorStyle && sector.attr('cursor', cursorStyle);

    // Toggle selected
    toggleItemSelected(
        this,
        data.getItemLayout(idx),
        seriesModel.isSelected(null, idx),
        seriesModel.get('selectedOffset'),
        seriesModel.get('animation')
    );

    this._updateLabel(data, idx);

    this.highDownOnUpdate = (itemModel.get('hoverAnimation') && seriesModel.isAnimationEnabled())
        ? function (fromState, toState) {
            if (toState === 'emphasis') {
                labelLine.ignore = labelLine.hoverIgnore;
                labelText.ignore = labelText.hoverIgnore;

                // Sector may has animation of updating data. Force to move to the last frame
                // Or it may stopped on the wrong shape
                sector.stopAnimation(true);
                sector.animateTo({
                    shape: {
                        r: layout.r + seriesModel.get('hoverOffset')
                    }
                }, 300, 'elasticOut');
            }
            else {
                labelLine.ignore = labelLine.normalIgnore;
                labelText.ignore = labelText.normalIgnore;

                sector.stopAnimation(true);
                sector.animateTo({
                    shape: {
                        r: layout.r
                    }
                }, 300, 'elasticOut');
            }
        }
        : null;

    graphic.setHoverStyle(this);
};

piePieceProto._updateLabel = function (data, idx) {

    var labelLine = this.childAt(1);
    var labelText = this.childAt(2);

    var seriesModel = data.hostModel;
    var itemModel = data.getItemModel(idx);
    var layout = data.getItemLayout(idx);
    var labelLayout = layout.label;
    var visualColor = data.getItemVisual(idx, 'color');

    if (!labelLayout) {
        labelText.ignore = labelText.normalIgnore = labelText.hoverIgnore =
        labelLine.ignore = labelLine.normalIgnore = labelLine.hoverIgnore = true;
        return;
    }

    graphic.updateProps(labelLine, {
        shape: {
            points: labelLayout.linePoints || [
                [labelLayout.x, labelLayout.y], [labelLayout.x, labelLayout.y], [labelLayout.x, labelLayout.y]
            ]
        }
    }, seriesModel, idx);

    graphic.updateProps(labelText, {
        style: {
            x: labelLayout.x,
            y: labelLayout.y
        }
    }, seriesModel, idx);
    labelText.attr({
        rotation: labelLayout.rotation,
        origin: [labelLayout.x, labelLayout.y],
        z2: 10
    });

    var labelModel = itemModel.getModel('label');
    var labelHoverModel = itemModel.getModel('emphasis.label');
    var labelLineModel = itemModel.getModel('labelLine');
    var labelLineHoverModel = itemModel.getModel('emphasis.labelLine');
    var visualColor = data.getItemVisual(idx, 'color');

    graphic.setLabelStyle(
        labelText.style, labelText.hoverStyle = {}, labelModel, labelHoverModel,
        {
            labelFetcher: data.hostModel,
            labelDataIndex: idx,
            defaultText: data.getName(idx),
            autoColor: visualColor,
            useInsideStyle: !!labelLayout.inside
        },
        {
            textAlign: labelLayout.textAlign,
            textVerticalAlign: labelLayout.verticalAlign,
            opacity: data.getItemVisual(idx, 'opacity')
        }
    );

    labelText.ignore = labelText.normalIgnore = !labelModel.get('show');
    labelText.hoverIgnore = !labelHoverModel.get('show');

    labelLine.ignore = labelLine.normalIgnore = !labelLineModel.get('show');
    labelLine.hoverIgnore = !labelLineHoverModel.get('show');

    // Default use item visual color
    labelLine.setStyle({
        stroke: visualColor,
        opacity: data.getItemVisual(idx, 'opacity')
    });
    labelLine.setStyle(labelLineModel.getModel('lineStyle').getLineStyle());

    labelLine.hoverStyle = labelLineHoverModel.getModel('lineStyle').getLineStyle();

    var smooth = labelLineModel.get('smooth');
    if (smooth && smooth === true) {
        smooth = 0.4;
    }
    labelLine.setShape({
        smooth: smooth
    });
};

zrUtil.inherits(PiePiece, graphic.Group);

// Pie view
var PieView = ChartView.extend({

    type: 'pie',

    init: function () {
        var sectorGroup = new graphic.Group();
        this._sectorGroup = sectorGroup;
    },

    render: function (seriesModel, ecModel, api, payload) {
        if (payload && (payload.from === this.uid)) {
            return;
        }

        var data = seriesModel.getData();
        var oldData = this._data;
        var group = this.group;

        var hasAnimation = ecModel.get('animation');
        var isFirstRender = !oldData;
        var animationType = seriesModel.get('animationType');

        var onSectorClick = zrUtil.curry(
            updateDataSelected, this.uid, seriesModel, hasAnimation, api
        );

        var selectedMode = seriesModel.get('selectedMode');
        data.diff(oldData)
            .add(function (idx) {
                var piePiece = new PiePiece(data, idx);
                // Default expansion animation
                if (isFirstRender && animationType !== 'scale') {
                    piePiece.eachChild(function (child) {
                        child.stopAnimation(true);
                    });
                }

                selectedMode && piePiece.on('click', onSectorClick);

                data.setItemGraphicEl(idx, piePiece);

                group.add(piePiece);
            })
            .update(function (newIdx, oldIdx) {
                var piePiece = oldData.getItemGraphicEl(oldIdx);

                piePiece.updateData(data, newIdx);

                piePiece.off('click');
                selectedMode && piePiece.on('click', onSectorClick);
                group.add(piePiece);
                data.setItemGraphicEl(newIdx, piePiece);
            })
            .remove(function (idx) {
                var piePiece = oldData.getItemGraphicEl(idx);
                group.remove(piePiece);
            })
            .execute();

        if (
            hasAnimation && isFirstRender && data.count() > 0
            // Default expansion animation
            && animationType !== 'scale'
        ) {
            var shape = data.getItemLayout(0);
            for (let s = 1; isNaN(shape.endAngle) && s < data.count(); ++s) {
                shape = data.getItemLayout(s);
            }

            var r = Math.max(api.getWidth(), api.getHeight()) / 2;

            var removeClipPath = zrUtil.bind(group.removeClipPath, group);
            group.setClipPath(this._createClipPath(
                shape.cx, shape.cy, r, shape.startAngle, shape.clockwise, removeClipPath, seriesModel
            ));
        }
        else {
            // clipPath is used in first-time animation, so remove it when otherwise. See: #8994
            group.removeClipPath();
        }

        this._data = data;
    },

    dispose: function () {},

    _createClipPath: function (
        cx, cy, r, startAngle, clockwise, cb, seriesModel
    ) {
        var clipPath = new graphic.Sector({
            shape: {
                cx: cx,
                cy: cy,
                r0: 0,
                r: r,
                startAngle: startAngle,
                endAngle: startAngle,
                clockwise: clockwise
            }
        });

        graphic.initProps(clipPath, {
            shape: {
                endAngle: startAngle + (clockwise ? 1 : -1) * Math.PI * 2
            }
        }, seriesModel, cb);

        return clipPath;
    },

    /**
     * @implement
     */
    containPoint: function (point, seriesModel) {
        var data = seriesModel.getData();
        var itemLayout = data.getItemLayout(0);
        if (itemLayout) {
            var dx = point[0] - itemLayout.cx;
            var dy = point[1] - itemLayout.cy;
            var radius = Math.sqrt(dx * dx + dy * dy);
            return radius <= itemLayout.r && radius >= itemLayout.r0;
        }
    }

});

export default PieView;
<commit_msg>fix(pie): use startAngle instead of endAngle<commit_after>/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import * as zrUtil from 'zrender/src/core/util';
import * as graphic from '../../util/graphic';
import ChartView from '../../view/Chart';

/**
 * @param {module:echarts/model/Series} seriesModel
 * @param {boolean} hasAnimation
 * @inner
 */
function updateDataSelected(uid, seriesModel, hasAnimation, api) {
    var data = seriesModel.getData();
    var dataIndex = this.dataIndex;
    var name = data.getName(dataIndex);
    var selectedOffset = seriesModel.get('selectedOffset');

    api.dispatchAction({
        type: 'pieToggleSelect',
        from: uid,
        name: name,
        seriesId: seriesModel.id
    });

    data.each(function (idx) {
        toggleItemSelected(
            data.getItemGraphicEl(idx),
            data.getItemLayout(idx),
            seriesModel.isSelected(data.getName(idx)),
            selectedOffset,
            hasAnimation
        );
    });
}

/**
 * @param {module:zrender/graphic/Sector} el
 * @param {Object} layout
 * @param {boolean} isSelected
 * @param {number} selectedOffset
 * @param {boolean} hasAnimation
 * @inner
 */
function toggleItemSelected(el, layout, isSelected, selectedOffset, hasAnimation) {
    var midAngle = (layout.startAngle + layout.endAngle) / 2;

    var dx = Math.cos(midAngle);
    var dy = Math.sin(midAngle);

    var offset = isSelected ? selectedOffset : 0;
    var position = [dx * offset, dy * offset];

    hasAnimation
        // animateTo will stop revious animation like update transition
        ? el.animate()
            .when(200, {
                position: position
            })
            .start('bounceOut')
        : el.attr('position', position);
}

/**
 * Piece of pie including Sector, Label, LabelLine
 * @constructor
 * @extends {module:zrender/graphic/Group}
 */
function PiePiece(data, idx) {

    graphic.Group.call(this);

    var sector = new graphic.Sector({
        z2: 2
    });
    var polyline = new graphic.Polyline();
    var text = new graphic.Text();
    this.add(sector);
    this.add(polyline);
    this.add(text);

    this.updateData(data, idx, true);
}

var piePieceProto = PiePiece.prototype;

piePieceProto.updateData = function (data, idx, firstCreate) {

    var sector = this.childAt(0);
    var labelLine = this.childAt(1);
    var labelText = this.childAt(2);

    var seriesModel = data.hostModel;
    var itemModel = data.getItemModel(idx);
    var layout = data.getItemLayout(idx);
    var sectorShape = zrUtil.extend({}, layout);
    sectorShape.label = null;

    if (firstCreate) {
        sector.setShape(sectorShape);

        var animationType = seriesModel.getShallow('animationType');
        if (animationType === 'scale') {
            sector.shape.r = layout.r0;
            graphic.initProps(sector, {
                shape: {
                    r: layout.r
                }
            }, seriesModel, idx);
        }
        // Expansion
        else {
            sector.shape.endAngle = layout.startAngle;
            graphic.updateProps(sector, {
                shape: {
                    endAngle: layout.endAngle
                }
            }, seriesModel, idx);
        }

    }
    else {
        graphic.updateProps(sector, {
            shape: sectorShape
        }, seriesModel, idx);
    }

    // Update common style
    var visualColor = data.getItemVisual(idx, 'color');

    sector.useStyle(
        zrUtil.defaults(
            {
                lineJoin: 'bevel',
                fill: visualColor
            },
            itemModel.getModel('itemStyle').getItemStyle()
        )
    );
    sector.hoverStyle = itemModel.getModel('emphasis.itemStyle').getItemStyle();

    var cursorStyle = itemModel.getShallow('cursor');
    cursorStyle && sector.attr('cursor', cursorStyle);

    // Toggle selected
    toggleItemSelected(
        this,
        data.getItemLayout(idx),
        seriesModel.isSelected(null, idx),
        seriesModel.get('selectedOffset'),
        seriesModel.get('animation')
    );

    this._updateLabel(data, idx);

    this.highDownOnUpdate = (itemModel.get('hoverAnimation') && seriesModel.isAnimationEnabled())
        ? function (fromState, toState) {
            if (toState === 'emphasis') {
                labelLine.ignore = labelLine.hoverIgnore;
                labelText.ignore = labelText.hoverIgnore;

                // Sector may has animation of updating data. Force to move to the last frame
                // Or it may stopped on the wrong shape
                sector.stopAnimation(true);
                sector.animateTo({
                    shape: {
                        r: layout.r + seriesModel.get('hoverOffset')
                    }
                }, 300, 'elasticOut');
            }
            else {
                labelLine.ignore = labelLine.normalIgnore;
                labelText.ignore = labelText.normalIgnore;

                sector.stopAnimation(true);
                sector.animateTo({
                    shape: {
                        r: layout.r
                    }
                }, 300, 'elasticOut');
            }
        }
        : null;

    graphic.setHoverStyle(this);
};

piePieceProto._updateLabel = function (data, idx) {

    var labelLine = this.childAt(1);
    var labelText = this.childAt(2);

    var seriesModel = data.hostModel;
    var itemModel = data.getItemModel(idx);
    var layout = data.getItemLayout(idx);
    var labelLayout = layout.label;
    var visualColor = data.getItemVisual(idx, 'color');

    if (!labelLayout) {
        labelText.ignore = labelText.normalIgnore = labelText.hoverIgnore =
        labelLine.ignore = labelLine.normalIgnore = labelLine.hoverIgnore = true;
        return;
    }

    graphic.updateProps(labelLine, {
        shape: {
            points: labelLayout.linePoints || [
                [labelLayout.x, labelLayout.y], [labelLayout.x, labelLayout.y], [labelLayout.x, labelLayout.y]
            ]
        }
    }, seriesModel, idx);

    graphic.updateProps(labelText, {
        style: {
            x: labelLayout.x,
            y: labelLayout.y
        }
    }, seriesModel, idx);
    labelText.attr({
        rotation: labelLayout.rotation,
        origin: [labelLayout.x, labelLayout.y],
        z2: 10
    });

    var labelModel = itemModel.getModel('label');
    var labelHoverModel = itemModel.getModel('emphasis.label');
    var labelLineModel = itemModel.getModel('labelLine');
    var labelLineHoverModel = itemModel.getModel('emphasis.labelLine');
    var visualColor = data.getItemVisual(idx, 'color');

    graphic.setLabelStyle(
        labelText.style, labelText.hoverStyle = {}, labelModel, labelHoverModel,
        {
            labelFetcher: data.hostModel,
            labelDataIndex: idx,
            defaultText: data.getName(idx),
            autoColor: visualColor,
            useInsideStyle: !!labelLayout.inside
        },
        {
            textAlign: labelLayout.textAlign,
            textVerticalAlign: labelLayout.verticalAlign,
            opacity: data.getItemVisual(idx, 'opacity')
        }
    );

    labelText.ignore = labelText.normalIgnore = !labelModel.get('show');
    labelText.hoverIgnore = !labelHoverModel.get('show');

    labelLine.ignore = labelLine.normalIgnore = !labelLineModel.get('show');
    labelLine.hoverIgnore = !labelLineHoverModel.get('show');

    // Default use item visual color
    labelLine.setStyle({
        stroke: visualColor,
        opacity: data.getItemVisual(idx, 'opacity')
    });
    labelLine.setStyle(labelLineModel.getModel('lineStyle').getLineStyle());

    labelLine.hoverStyle = labelLineHoverModel.getModel('lineStyle').getLineStyle();

    var smooth = labelLineModel.get('smooth');
    if (smooth && smooth === true) {
        smooth = 0.4;
    }
    labelLine.setShape({
        smooth: smooth
    });
};

zrUtil.inherits(PiePiece, graphic.Group);

// Pie view
var PieView = ChartView.extend({

    type: 'pie',

    init: function () {
        var sectorGroup = new graphic.Group();
        this._sectorGroup = sectorGroup;
    },

    render: function (seriesModel, ecModel, api, payload) {
        if (payload && (payload.from === this.uid)) {
            return;
        }

        var data = seriesModel.getData();
        var oldData = this._data;
        var group = this.group;

        var hasAnimation = ecModel.get('animation');
        var isFirstRender = !oldData;
        var animationType = seriesModel.get('animationType');

        var onSectorClick = zrUtil.curry(
            updateDataSelected, this.uid, seriesModel, hasAnimation, api
        );

        var selectedMode = seriesModel.get('selectedMode');
        data.diff(oldData)
            .add(function (idx) {
                var piePiece = new PiePiece(data, idx);
                // Default expansion animation
                if (isFirstRender && animationType !== 'scale') {
                    piePiece.eachChild(function (child) {
                        child.stopAnimation(true);
                    });
                }

                selectedMode && piePiece.on('click', onSectorClick);

                data.setItemGraphicEl(idx, piePiece);

                group.add(piePiece);
            })
            .update(function (newIdx, oldIdx) {
                var piePiece = oldData.getItemGraphicEl(oldIdx);

                piePiece.updateData(data, newIdx);

                piePiece.off('click');
                selectedMode && piePiece.on('click', onSectorClick);
                group.add(piePiece);
                data.setItemGraphicEl(newIdx, piePiece);
            })
            .remove(function (idx) {
                var piePiece = oldData.getItemGraphicEl(idx);
                group.remove(piePiece);
            })
            .execute();

        if (
            hasAnimation && isFirstRender && data.count() > 0
            // Default expansion animation
            && animationType !== 'scale'
        ) {
            var shape = data.getItemLayout(0);
            for (var s = 1; isNaN(shape.startAngle) && s < data.count(); ++s) {
                shape = data.getItemLayout(s);
            }

            var r = Math.max(api.getWidth(), api.getHeight()) / 2;

            var removeClipPath = zrUtil.bind(group.removeClipPath, group);
            group.setClipPath(this._createClipPath(
                shape.cx, shape.cy, r, shape.startAngle, shape.clockwise, removeClipPath, seriesModel
            ));
        }
        else {
            // clipPath is used in first-time animation, so remove it when otherwise. See: #8994
            group.removeClipPath();
        }

        this._data = data;
    },

    dispose: function () {},

    _createClipPath: function (
        cx, cy, r, startAngle, clockwise, cb, seriesModel
    ) {
        var clipPath = new graphic.Sector({
            shape: {
                cx: cx,
                cy: cy,
                r0: 0,
                r: r,
                startAngle: startAngle,
                endAngle: startAngle,
                clockwise: clockwise
            }
        });

        graphic.initProps(clipPath, {
            shape: {
                endAngle: startAngle + (clockwise ? 1 : -1) * Math.PI * 2
            }
        }, seriesModel, cb);

        return clipPath;
    },

    /**
     * @implement
     */
    containPoint: function (point, seriesModel) {
        var data = seriesModel.getData();
        var itemLayout = data.getItemLayout(0);
        if (itemLayout) {
            var dx = point[0] - itemLayout.cx;
            var dy = point[1] - itemLayout.cy;
            var radius = Math.sqrt(dx * dx + dy * dy);
            return radius <= itemLayout.r && radius >= itemLayout.r0;
        }
    }

});

export default PieView;
<|endoftext|>


<commit_before>var pk = pk || {};

/**
Create a new checkbox control
@class pk.checkbox
@constructor
@param options {Object}
@param options.element {Object} DOM element to convert to control
@param [options.value=0] {String} Value of initially selected option, defaults to the attribute value set on the passed element, or `0`
@param [options.name=pk-checkbox-RandInt] {String} Name of underlying input control, defaults to the attribute value set on the passed element, or `pk-checkbox-RandInt`
@param [options.label=label] {String} String to use for the control label, defaults to the attribute value set on the passed element, or its `innerHTML`
@param [options.tabindex=0] {Number} Tabindex of control, defaults to the attribute value set on the passed element, or `0`
@param [options.disabled=false] {Boolean} Disabled state of control, defaults to the attribute value set on the passed element, or `false`
@param [options.listeners] {Object} Object array of event listeners to bind to underlying input(s)
@returns Object {Object} Consisting of original DOM element (item `0`) and class methods (see below)
@chainable
*/

@param [options.tabindex=] {Number} Tabindex of control, defaults to the attribute value set on the passed element, or `0`
@param [options.delay=4000] {Number} Delay in `ms` between item changes
@returns object {Object} Consisting of original DOM element and class methods
*/
(function(pk) {
    pk.checkbox = function(opt) {
        var el = opt.element,
            //    listeners = opt.listeners === undefined ? {} : opt.listeners,
            inputValue = opt.value || el.getAttribute('value') || 0,
            inputLabel = opt.label || el.getAttribute('label') || el.innerHTML,
            inputDisabled = (opt.disabled || el.getAttribute('disabled')) ? 'disabled' : '',
            inputName = opt.name || el.getAttribute('name') || 'pk-checkbox-' + pk.getRand(1, 999),
            inputTabIndex = opt.tabindex || el.getAttribute('tabindex') || 0;

        /*jshint multistr:true */
        var str = "\
		<input type = 'checkbox'  id = '" + inputName + "'  name = '" + inputName + "'  value = '" + inputValue + "'  tabindex = '" + inputTabIndex + "' / >\
             " + inputLabel + " \
		";
        el.innerHTML = '';
        el = pk.replaceEl(el, str);
/**
Gets or sets control value
@method val
@param [value] {String} Value to set
@return {String} Returns current value
*/

/**
Gets or sets control disabled state
@method disabled
@param [boolean] {Boolean} Disabled state
@return {Boolean} Returns disabled state
*/
        var obj = {
            0: el,
            val: function(val) {
                if (val === undefined) {
                    return inputValue;
                }
                pk.attribute(el.children[0], 'checked', Boolean(val));
            },
            disabled: function(val) {
                if (val !== undefined) {
                    pk.toggleClass(el, 'pk-disabled', Boolean(val));
                    pk.attribute(el.children[0], 'disabled', Boolean(val));
                }
                return pk.attribute(el.children[0], 'disabled');
            }
        };
        obj.val(inputValue);
        if (inputDisabled) {
            obj.disabled(true);
        }
		pk.bindListeners(listeners, el.children[0]);
        return obj;
    };
    return pk;
})(pk);
<commit_msg>Update checkbox.js<commit_after>var pk = pk || {};

/**
Create a new checkbox control
@class pk.checkbox
@constructor
@param options {Object}
@param options.element {Object} DOM element to convert to control
@param [options.value=0] {String} Value of initially selected option, defaults to the attribute value set on the passed element, or `0`
@param [options.name=pk-checkbox-RandInt] {String} Name of underlying input control, defaults to the attribute value set on the passed element, or `pk-checkbox-RandInt`
@param [options.label=label] {String} String to use for the control label, defaults to the attribute value set on the passed element, or its `innerHTML`
@param [options.tabindex=0] {Number} Tabindex of control, defaults to the attribute value set on the passed element, or `0`
@param [options.disabled=false] {Boolean} Disabled state of control, defaults to the attribute value set on the passed element, or `false`
@param [options.listeners] {Object} Object array of event listeners to bind to underlying input(s)
@returns Object {Object} Consisting of original DOM element (item `0`) and class methods (see below)
@chainable
*/
(function(pk) {
    pk.checkbox = function(opt) {
        var el = opt.element,
            //    listeners = opt.listeners === undefined ? {} : opt.listeners,
            inputValue = opt.value || el.getAttribute('value') || 0,
            inputLabel = opt.label || el.getAttribute('label') || el.innerHTML,
            inputDisabled = (opt.disabled || el.getAttribute('disabled')) ? 'disabled' : '',
            inputName = opt.name || el.getAttribute('name') || 'pk-checkbox-' + pk.getRand(1, 999),
            inputTabIndex = opt.tabindex || el.getAttribute('tabindex') || 0;

        /*jshint multistr:true */
        var str = "\
		<input type = 'checkbox'  id = '" + inputName + "'  name = '" + inputName + "'  value = '" + inputValue + "'  tabindex = '" + inputTabIndex + "' / >\
             " + inputLabel + " \
		";
        el.innerHTML = '';
        el = pk.replaceEl(el, str);
/**
Gets or sets control value
@method val
@param [value] {String} Value to set
@return {String} Returns current value
*/

/**
Gets or sets control disabled state
@method disabled
@param [boolean] {Boolean} Disabled state
@return {Boolean} Returns disabled state
*/
        var obj = {
            0: el,
            val: function(val) {
                if (val === undefined) {
                    return inputValue;
                }
                pk.attribute(el.children[0], 'checked', Boolean(val));
            },
            disabled: function(val) {
                if (val !== undefined) {
                    pk.toggleClass(el, 'pk-disabled', Boolean(val));
                    pk.attribute(el.children[0], 'disabled', Boolean(val));
                }
                return pk.attribute(el.children[0], 'disabled');
            }
        };
        obj.val(inputValue);
        if (inputDisabled) {
            obj.disabled(true);
        }
		pk.bindListeners(listeners, el.children[0]);
        return obj;
    };
    return pk;
})(pk);
<|endoftext|>


<commit_before>var ACTION_STATES = {
  active: {
    icons: {
      19: "images/browser-icon-active.png",
      38: "images/browser-icon-active@2x.png"
    },
    title: "Disable annotation"
  },
  sleeping: {
    icons: {
      19: "images/browser-icon-inactive.png",
      38: "images/browser-icon-inactive@2x.png"
    },
    title: "Enable annotation"
  }
}
var CRX_BASE_URL = chrome.extension.getURL('/')
var PDF_VIEWER_URL = chrome.extension.getURL('content/web/viewer.html')

function getPDFViewerURL(url) {
  return PDF_VIEWER_URL + '?file=' + encodeURIComponent(url)
}

function isPDFURL(url) {
  return url.toLowerCase().indexOf('.pdf') > 0
}

function isPDFViewerURL(url) {
  return url.indexOf(getPDFViewerURL('')) === 0
}

function isFileURL(url) {
  return url.indexOf("file://") === 0
}

function injectionFailed(tab) {
  setBrowserAction(tab.id, state(tab.id, 'sleeping'))
}

function injectIntoPDF(tab) {
  if (!isPDFViewerURL(tab.url)) {
    chrome.tabs.update(tab.id, {
      url: getPDFViewerURL(tab.url)
    })
  }
}

function inject(tab) {
  chrome.tabs.executeScript(tab.id, {
    code: [
      'var script = document.createElement("script");',
      'script.src = "' + CRX_BASE_URL + 'public/config.js' + '";',
      'document.body.appendChild(script);'
    ].join('\n')
  }, function () {
    if (isFileURL(tab.url)) {
      chrome.extension.isAllowedFileSchemeAccess(function (allowed) {
        if (allowed) {
          if (isPDFURL(tab.url)) {
            // This a local PDF, and we have permission - good to go.
            injectIntoPDF(tab)
          } else {
            // This a local HTML. Even with permission, this won't work.
            alert("Sorry, but as of now, this service can't be used on local HTMLs documents loaded via the file:/// protocol. (Local PDF documents are supported, though.)");
            injectionFailed(tab)
          }
        } else {
          // Access denied. Show an explanation about permissions.
          injectionFailed(tab)
          chrome.tabs.update(tab.id, {
            url: CRX_BASE_URL + "help/permissions.html"
          })
        }
      });
      return;
    }

    if (isPDFURL(tab.url)) {
      injectIntoPDF(tab)
    } else {
      chrome.tabs.executeScript(tab.id, {
        file: 'public/embed.js'
      }, function (result) {
        if (result !== undefined) {
          chrome.tabs.executeScript(tab.id, {
            code: 'window.annotator = true;'
          })
        } else {
          // Injection failed, so stop trying
          state(tab.id, 'sleeping')
        }
      })
    }
  })
}

function remove(tab) {
  if (isPDFViewerURL(tab.url)) {
    url = tab.url.slice(getPDFViewerURL('').length).split('#')[0];
    chrome.tabs.update(tab.id, {
      url: decodeURIComponent(url)
    })
  } else {
    chrome.tabs.executeScript(tab.id, {
      code: [
        'var script = document.createElement("script");',
        'script.src = "' + CRX_BASE_URL + 'public/destroy.js' + '";',
        'document.body.appendChild(script);',
        'delete window.annotator;',
      ].join('\n')
    })
  }
}

function state(tabId, value) {
  var stateMap = localStorage.getItem('state')
  stateMap = stateMap ? JSON.parse(stateMap) : {}

  if (value === undefined) {
    return stateMap[tabId]
  }

  if (value) {
    stateMap[tabId] = value
  } else {
    delete stateMap[tabId]
  }

  localStorage.setItem('state', JSON.stringify(stateMap))

  return value
}

function setBrowserAction(tabId, value) {
  chrome.browserAction.setIcon({
    tabId: tabId,
    path: ACTION_STATES[value].icons
  })
  chrome.browserAction.setTitle({
    tabId: tabId,
    title: ACTION_STATES[value].title
  })
}

function onInstalled(installDetails) {
  if (installDetails.reason === 'install') {
    chrome.tabs.create({url: 'https://hypothes.is/welcome'}, function (tab) {
      state(tab.id, 'active');
    });
  }

  /* We need this so that 3-rd party cookie blocking does not kill us.
     See https://github.com/hypothesis/h/issues/634 for more info.
     This is intended to be a temporary fix only.
  */
  var details = {
    primaryPattern: 'https://hypothes.is/*',
    setting: 'allow'
  }
  chrome.contentSettings.cookies.set(details)
  chrome.contentSettings.images.set(details)
  chrome.contentSettings.javascript.set(details)

  chrome.tabs.query({}, function (tabs) {
    for (var i in tabs) {
      var tabId = tabs[i].id
        , tabState = state(tabId) || 'sleeping'
      setBrowserAction(tabId, tabState)
    }
  })
}

function onUpdateAvailable() {
  chrome.runtime.reload()
}

function onBrowserAction(tab) {
  var newState

  if (state(tab.id) === 'active') {
    newState = state(tab.id, 'sleeping')
    remove(tab)
  } else {
    newState = state(tab.id, 'active')
    inject(tab)
  }

  setBrowserAction(tab.id, newState)
}

function onTabCreated(tab) {
  state(tab.id, 'sleeping')
}

function onTabRemoved(tab) {
  state(tab.id, null)
}

function onTabUpdated(tabId, info, tab) {
  var currentState = state(tabId) || 'sleeping'

  setBrowserAction(tabId, currentState)

  if (currentState === 'active' && info.status === 'complete') {
    inject(tab)
  }
}

chrome.runtime.onInstalled.addListener(onInstalled)
chrome.runtime.onUpdateAvailable.addListener(onUpdateAvailable)
chrome.browserAction.onClicked.addListener(onBrowserAction)
chrome.tabs.onCreated.addListener(onTabCreated)
chrome.tabs.onRemoved.addListener(onTabRemoved)
chrome.tabs.onUpdated.addListener(onTabUpdated)
<commit_msg>Refact long method.<commit_after>var ACTION_STATES = {
  active: {
    icons: {
      19: "images/browser-icon-active.png",
      38: "images/browser-icon-active@2x.png"
    },
    title: "Disable annotation"
  },
  sleeping: {
    icons: {
      19: "images/browser-icon-inactive.png",
      38: "images/browser-icon-inactive@2x.png"
    },
    title: "Enable annotation"
  }
}
var CRX_BASE_URL = chrome.extension.getURL('/')
var PDF_VIEWER_URL = chrome.extension.getURL('content/web/viewer.html')

function getPDFViewerURL(url) {
  return PDF_VIEWER_URL + '?file=' + encodeURIComponent(url)
}

function isPDFURL(url) {
  return url.toLowerCase().indexOf('.pdf') > 0
}

function isPDFViewerURL(url) {
  return url.indexOf(getPDFViewerURL('')) === 0
}

function isFileURL(url) {
  return url.indexOf("file://") === 0
}

function injectionFailed(tab) {
  setBrowserAction(tab.id, state(tab.id, 'sleeping'))
}

function injectIntoPDF(tab) {
  if (!isPDFViewerURL(tab.url)) {
    chrome.tabs.update(tab.id, {
      url: getPDFViewerURL(tab.url)
    })
  }
}

function injectIntoHTML(tab) {
  chrome.tabs.executeScript(tab.id, {
    file: 'public/embed.js'
  }, function (result) {
    if (result !== undefined) {
      chrome.tabs.executeScript(tab.id, {
        code: 'window.annotator = true;'
      })
    } else {
      injectionFailed(tab)
    }
  })
}

function handleFilePermissionProblem(tab) {
  // Access denied. We are lacking the proper permissions.
  injectionFailed(tab)
  chrome.tabs.update(tab.id, {
    url: CRX_BASE_URL + "help/permissions.html"
  })
}

function handleFileProtocolProblem(tab) {
  // This a local HTML. Even having the correct permissions,
  // this won't work.
  injectionFailed(tab)
  alert("Sorry, but as of now, this service can't be used on local HTMLs documents loaded via the file:/// protocol. (Local PDF documents are supported, though.)");
}

function injectConfig(tab, fn) {
  var src  = CRX_BASE_URL + 'public/config.js'
  var code = 'var script = document.createElement("script");' +
             'script.src = "{}";' +
             'document.body.appendChild(script);'

  chrome.tabs.executeScript(tab.id, {code: code.replace('{}', src)}, fn);
}

function inject(tab) {
  injectConfig(tab, function () {
    if (isFileURL(tab.url)) {
      chrome.extension.isAllowedFileSchemeAccess(function (allowed) {
        if (allowed) {
          if (isPDFURL(tab.url)) {
            injectIntoPDF(tab)
          } else {
            handleFileProtocolProblem(tab)
          }
        } else {
          handleFilePermissionProblem(tab)
        }
      });
    } else {
      if (isPDFURL(tab.url)) {
        injectIntoPDF(tab)
      } else {
        injectIntoHTML(tab)
      }
    }
  })
}

function remove(tab) {
  if (isPDFViewerURL(tab.url)) {
    url = tab.url.slice(getPDFViewerURL('').length).split('#')[0];
    chrome.tabs.update(tab.id, {
      url: decodeURIComponent(url)
    })
  } else {
    chrome.tabs.executeScript(tab.id, {
      code: [
        'var script = document.createElement("script");',
        'script.src = "' + CRX_BASE_URL + 'public/destroy.js' + '";',
        'document.body.appendChild(script);',
        'delete window.annotator;',
      ].join('\n')
    })
  }
}

function state(tabId, value) {
  var stateMap = localStorage.getItem('state')
  stateMap = stateMap ? JSON.parse(stateMap) : {}

  if (value === undefined) {
    return stateMap[tabId]
  }

  if (value) {
    stateMap[tabId] = value
  } else {
    delete stateMap[tabId]
  }

  localStorage.setItem('state', JSON.stringify(stateMap))

  return value
}

function setBrowserAction(tabId, value) {
  chrome.browserAction.setIcon({
    tabId: tabId,
    path: ACTION_STATES[value].icons
  })
  chrome.browserAction.setTitle({
    tabId: tabId,
    title: ACTION_STATES[value].title
  })
}

function onInstalled(installDetails) {
  if (installDetails.reason === 'install') {
    chrome.tabs.create({url: 'https://hypothes.is/welcome'}, function (tab) {
      state(tab.id, 'active');
    });
  }

  /* We need this so that 3-rd party cookie blocking does not kill us.
     See https://github.com/hypothesis/h/issues/634 for more info.
     This is intended to be a temporary fix only.
  */
  var details = {
    primaryPattern: 'https://hypothes.is/*',
    setting: 'allow'
  }
  chrome.contentSettings.cookies.set(details)
  chrome.contentSettings.images.set(details)
  chrome.contentSettings.javascript.set(details)

  chrome.tabs.query({}, function (tabs) {
    for (var i in tabs) {
      var tabId = tabs[i].id
        , tabState = state(tabId) || 'sleeping'
      setBrowserAction(tabId, tabState)
    }
  })
}

function onUpdateAvailable() {
  chrome.runtime.reload()
}

function onBrowserAction(tab) {
  var newState

  if (state(tab.id) === 'active') {
    newState = state(tab.id, 'sleeping')
    remove(tab)
  } else {
    newState = state(tab.id, 'active')
    inject(tab)
  }

  setBrowserAction(tab.id, newState)
}

function onTabCreated(tab) {
  state(tab.id, 'sleeping')
}

function onTabRemoved(tab) {
  state(tab.id, null)
}

function onTabUpdated(tabId, info, tab) {
  var currentState = state(tabId) || 'sleeping'

  setBrowserAction(tabId, currentState)

  if (currentState === 'active' && info.status === 'complete') {
    inject(tab)
  }
}

chrome.runtime.onInstalled.addListener(onInstalled)
chrome.runtime.onUpdateAvailable.addListener(onUpdateAvailable)
chrome.browserAction.onClicked.addListener(onBrowserAction)
chrome.tabs.onCreated.addListener(onTabCreated)
chrome.tabs.onRemoved.addListener(onTabRemoved)
chrome.tabs.onUpdated.addListener(onTabUpdated)
<|endoftext|>