<commit_before>/*
 * (c) Copyright Ascensio System SIA 2010-2019
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

'use strict';

const cluster = require('cluster');
const configCommon = require('config');
const config = configCommon.get('services.CoAuthoring');
//process.env.NODE_ENV = config.get('server.mode');
const logger = require('./../../Common/sources/logger');
const co = require('co');
const license = require('./../../Common/sources/license');

if (cluster.isMaster) {
	const fs = require('fs');

	//const numCPUs = require('os').cpus().length;

	//const cfgWorkerPerCpu = config.get('server.workerpercpu');
	let licenseInfo, workersCount = 0, updateTime;
	const readLicense = function*() {
		licenseInfo = yield* license.readLicense();
		workersCount = Math.min(1, licenseInfo.count/*, Math.ceil(numCPUs * cfgWorkerPerCpu)*/);
	};
	const updateLicenseWorker = (worker) => {
		worker.send({type: 1, data: licenseInfo});
	};
	const updateWorkers = () => {
		const arrKeyWorkers = Object.keys(cluster.workers);
		if (arrKeyWorkers.length < workersCount) {
			for (let i = arrKeyWorkers.length; i < workersCount; ++i) {
				const newWorker = cluster.fork();
				logger.warn('worker %s started.', newWorker.process.pid);
			}
		} else {
			for (let i = workersCount; i < arrKeyWorkers.length; ++i) {
				const killWorker = cluster.workers[arrKeyWorkers[i]];
				if (killWorker) {
					killWorker.kill();
				}
			}
		}
	};
	const updatePlugins = (eventType, filename) => {
		console.log('update Folder: %s ; %s', eventType, filename);
		if (updateTime && 1000 >= (new Date() - updateTime)) {
			return;
		}
		console.log('update Folder true: %s ; %s', eventType, filename);
		updateTime = new Date();
		for (let i in cluster.workers) {
			cluster.workers[i].send({type: 2});
		}
	};
	const updateLicense = () => {
		return co(function*() {
			try {
				yield* readLicense();
				logger.warn('update cluster with %s workers', workersCount);
				for (let i in cluster.workers) {
					updateLicenseWorker(cluster.workers[i]);
				}
				updateWorkers();
			} catch (err) {
				logger.error('updateLicense error:\r\n%s', err.stack);
			}
		});
	};

	cluster.on('fork', (worker) => {
		updateLicenseWorker(worker);
	});
	cluster.on('exit', (worker, code, signal) => {
		logger.warn('worker %s died (code = %s; signal = %s).', worker.process.pid, code, signal);
		updateWorkers();
	});

	updateLicense();

	try {
		fs.watch(config.get('plugins.path'), updatePlugins);
	} catch (e) {
		logger.warn('Plugins watch exception (https://nodejs.org/docs/latest/api/fs.html#fs_availability).');
	}
	fs.watchFile(configCommon.get('license').get('license_file'), updateLicense);
	setInterval(updateLicense, 86400000);
} else {
	logger.warn('Express server starting...');

	const express = require('express');
	const http = require('http');
	const urlModule = require('url');
	const path = require('path');
	const bodyParser = require("body-parser");
	const mime = require('mime');
	const docsCoServer = require('./DocsCoServer');
	const canvasService = require('./canvasservice');
	const converterService = require('./converterservice');
	const fileUploaderService = require('./fileuploaderservice');
	const constants = require('./../../Common/sources/constants');
	const utils = require('./../../Common/sources/utils');
	const commonDefines = require('./../../Common/sources/commondefines');
	const configStorage = configCommon.get('storage');
	const app = express();
	const server = http.createServer(app);

	let userPlugins = null, updatePlugins = true;

	if (config.has('server.static_content')) {
		const staticContent = config.get('server.static_content');
		for (let i in staticContent) {
			app.use(i, express.static(staticContent[i]['path'], staticContent[i]['options']));
		}
	}

	if (configStorage.has('fs.folderPath')) {
		const cfgBucketName = configStorage.get('bucketName');
		const cfgStorageFolderName = configStorage.get('storageFolderName');
		app.use('/' + cfgBucketName + '/' + cfgStorageFolderName, (req, res, next) => {
			const index = req.url.lastIndexOf('/');
			if ('GET' === req.method && -1 != index) {
				const contentDisposition = req.query['disposition'] || 'attachment';
				let sendFileOptions = {
					root: configStorage.get('fs.folderPath'), dotfiles: 'deny', headers: {
						'Content-Disposition': contentDisposition
					}
				};
				const urlParsed = urlModule.parse(req.url);
				if (urlParsed && urlParsed.pathname) {
					const filename = decodeURIComponent(path.basename(urlParsed.pathname));
					sendFileOptions.headers['Content-Type'] = mime.getType(filename);
				}
				const realUrl = req.url.substring(0, index);
				res.sendFile(realUrl, sendFileOptions, (err) => {
					if (err) {
						logger.error(err);
						res.status(400).end();
					}
				});
			} else {
				res.sendStatus(404)
			}
		});
	}

	// Если захочется использовать 'development' и 'production',
	// то с помощью app.settings.env (https://github.com/strongloop/express/issues/936)
	// Если нужна обработка ошибок, то теперь она такая https://github.com/expressjs/errorhandler
	docsCoServer.install(server, () => {
		server.listen(config.get('server.port'), () => {
			logger.warn("Express server listening on port %d in %s mode", config.get('server.port'), app.settings.env);
		});

		app.get('/index.html', (req, res) => {
			res.send('Server is functioning normally. Version: ' + commonDefines.buildVersion + '. Build: ' +
				commonDefines.buildNumber);
		});
		const rawFileParser = bodyParser.raw(
			{inflate: true, limit: config.get('server.limits_tempfile_upload'), type: '*/*'});

		app.get('/coauthoring/CommandService.ashx', utils.checkClientIp, rawFileParser, docsCoServer.commandFromServer);
		app.post('/coauthoring/CommandService.ashx', utils.checkClientIp, rawFileParser,
			docsCoServer.commandFromServer);

		app.get('/ConvertService.ashx', utils.checkClientIp, rawFileParser, converterService.convertXml);
		app.post('/ConvertService.ashx', utils.checkClientIp, rawFileParser, converterService.convertXml);
		app.post('/converter', utils.checkClientIp, rawFileParser, converterService.convertJson);

		app.get('/FileUploader.ashx', utils.checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);
		app.post('/FileUploader.ashx', utils.checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);

		const docIdRegExp = new RegExp("^[" + constants.DOC_ID_PATTERN + "]*$", 'i');
		app.param('docid', (req, res, next, val) => {
			if (docIdRegExp.test(val)) {
				next();
			} else {
				res.sendStatus(403);
			}
		});
		app.param('index', (req, res, next, val) => {
			if (!isNaN(parseInt(val))) {
				next();
			} else {
				res.sendStatus(403);
			}
		});
		app.post('/uploadold/:docid/:userid/:index', fileUploaderService.uploadImageFileOld);
		app.post('/upload/:docid/:userid/:index', rawFileParser, fileUploaderService.uploadImageFile);

		app.post('/downloadas/:docid', rawFileParser, canvasService.downloadAs);
		app.post('/savefile/:docid', rawFileParser, canvasService.saveFile);
		app.get('/healthcheck', utils.checkClientIp, docsCoServer.healthCheck);

		app.get('/baseurl', (req, res) => {
			res.send(utils.getBaseUrlByRequest(req));
		});

		app.get('/robots.txt', (req, res) => {
			res.setHeader('Content-Type', 'plain/text');
			res.send("User-agent: *\nDisallow: /");
		});

		app.post('/docbuilder', utils.checkClientIp, rawFileParser, (req, res) => {
			const licenseInfo = docsCoServer.getLicenseInfo();
			if (licenseInfo.type !== constants.LICENSE_RESULT.Success) {
				logger.error('License expired');
				res.sendStatus(402);
				return;
			}
			converterService.builder(req, res);
		});
		app.get('/info/info.json', utils.checkClientIp, docsCoServer.licenseInfo);

		const sendUserPlugins = (res, data) => {
			res.setHeader('Content-Type', 'application/json');
			res.send(JSON.stringify(data));
		};
		app.get('/plugins.json', (req, res) => {
			if (userPlugins && !updatePlugins) {
				sendUserPlugins(res, userPlugins);
				return;
			}

			if (!config.has('server.static_content') || !config.has('plugins.uri')) {
				res.sendStatus(404);
				return;
			}

			let staticContent = config.get('server.static_content');
			let pluginsUri = config.get('plugins.uri');
			let pluginsPath = undefined;
			let pluginsAutostart = config.get('plugins.autostart');

			if (staticContent[pluginsUri]) {
				pluginsPath = staticContent[pluginsUri].path;
			}

			let baseUrl = '../../../..';
			utils.listFolders(pluginsPath, true).then((values) => {
				return co(function*() {
					const configFile = 'config.json';
					let stats = null;
					let result = [];
					for (let i = 0; i < values.length; ++i) {
						try {
							stats = yield utils.fsStat(path.join(values[i], configFile));
						} catch (err) {
							stats = null;
						}

						if (stats && stats.isFile) {
							result.push( baseUrl + pluginsUri + '/' + path.basename(values[i]) + '/' + configFile);
						}
					}

					userPlugins = {'url': '', 'pluginsData': result, 'autostart': pluginsAutostart};
					sendUserPlugins(res, userPlugins);
				});
			});
		});
	});

	process.on('message', (msg) => {
		if (!docsCoServer) {
			return;
		}
		switch (msg.type) {
			case 1:
				docsCoServer.setLicenseInfo(msg.data);
				break;
			case 2:
				updatePlugins = true;
				break;
		}
	});
}

process.on('uncaughtException', (err) => {
	logger.error((new Date).toUTCString() + ' uncaughtException:', err.message);
	logger.error(err.stack);
	logger.shutdown(() => {
		process.exit(1);
	});
});
<commit_msg>[builder] OpenSource<commit_after>/*
 * (c) Copyright Ascensio System SIA 2010-2019
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

'use strict';

const cluster = require('cluster');
const configCommon = require('config');
const config = configCommon.get('services.CoAuthoring');
//process.env.NODE_ENV = config.get('server.mode');
const logger = require('./../../Common/sources/logger');
const co = require('co');
const license = require('./../../Common/sources/license');

if (cluster.isMaster) {
	const fs = require('fs');

	//const numCPUs = require('os').cpus().length;

	//const cfgWorkerPerCpu = config.get('server.workerpercpu');
	let licenseInfo, workersCount = 0, updateTime;
	const readLicense = function*() {
		licenseInfo = yield* license.readLicense();
		workersCount = Math.min(1, licenseInfo.count/*, Math.ceil(numCPUs * cfgWorkerPerCpu)*/);
	};
	const updateLicenseWorker = (worker) => {
		worker.send({type: 1, data: licenseInfo});
	};
	const updateWorkers = () => {
		const arrKeyWorkers = Object.keys(cluster.workers);
		if (arrKeyWorkers.length < workersCount) {
			for (let i = arrKeyWorkers.length; i < workersCount; ++i) {
				const newWorker = cluster.fork();
				logger.warn('worker %s started.', newWorker.process.pid);
			}
		} else {
			for (let i = workersCount; i < arrKeyWorkers.length; ++i) {
				const killWorker = cluster.workers[arrKeyWorkers[i]];
				if (killWorker) {
					killWorker.kill();
				}
			}
		}
	};
	const updatePlugins = (eventType, filename) => {
		console.log('update Folder: %s ; %s', eventType, filename);
		if (updateTime && 1000 >= (new Date() - updateTime)) {
			return;
		}
		console.log('update Folder true: %s ; %s', eventType, filename);
		updateTime = new Date();
		for (let i in cluster.workers) {
			cluster.workers[i].send({type: 2});
		}
	};
	const updateLicense = () => {
		return co(function*() {
			try {
				yield* readLicense();
				logger.warn('update cluster with %s workers', workersCount);
				for (let i in cluster.workers) {
					updateLicenseWorker(cluster.workers[i]);
				}
				updateWorkers();
			} catch (err) {
				logger.error('updateLicense error:\r\n%s', err.stack);
			}
		});
	};

	cluster.on('fork', (worker) => {
		updateLicenseWorker(worker);
	});
	cluster.on('exit', (worker, code, signal) => {
		logger.warn('worker %s died (code = %s; signal = %s).', worker.process.pid, code, signal);
		updateWorkers();
	});

	updateLicense();

	try {
		fs.watch(config.get('plugins.path'), updatePlugins);
	} catch (e) {
		logger.warn('Plugins watch exception (https://nodejs.org/docs/latest/api/fs.html#fs_availability).');
	}
	fs.watchFile(configCommon.get('license').get('license_file'), updateLicense);
	setInterval(updateLicense, 86400000);
} else {
	logger.warn('Express server starting...');

	const express = require('express');
	const http = require('http');
	const urlModule = require('url');
	const path = require('path');
	const bodyParser = require("body-parser");
	const mime = require('mime');
	const docsCoServer = require('./DocsCoServer');
	const canvasService = require('./canvasservice');
	const converterService = require('./converterservice');
	const fileUploaderService = require('./fileuploaderservice');
	const constants = require('./../../Common/sources/constants');
	const utils = require('./../../Common/sources/utils');
	const commonDefines = require('./../../Common/sources/commondefines');
	const configStorage = configCommon.get('storage');
	const app = express();
	const server = http.createServer(app);

	let userPlugins = null, updatePlugins = true;

	if (config.has('server.static_content')) {
		const staticContent = config.get('server.static_content');
		for (let i in staticContent) {
			app.use(i, express.static(staticContent[i]['path'], staticContent[i]['options']));
		}
	}

	if (configStorage.has('fs.folderPath')) {
		const cfgBucketName = configStorage.get('bucketName');
		const cfgStorageFolderName = configStorage.get('storageFolderName');
		app.use('/' + cfgBucketName + '/' + cfgStorageFolderName, (req, res, next) => {
			const index = req.url.lastIndexOf('/');
			if ('GET' === req.method && -1 != index) {
				const contentDisposition = req.query['disposition'] || 'attachment';
				let sendFileOptions = {
					root: configStorage.get('fs.folderPath'), dotfiles: 'deny', headers: {
						'Content-Disposition': contentDisposition
					}
				};
				const urlParsed = urlModule.parse(req.url);
				if (urlParsed && urlParsed.pathname) {
					const filename = decodeURIComponent(path.basename(urlParsed.pathname));
					sendFileOptions.headers['Content-Type'] = mime.getType(filename);
				}
				const realUrl = req.url.substring(0, index);
				res.sendFile(realUrl, sendFileOptions, (err) => {
					if (err) {
						logger.error(err);
						res.status(400).end();
					}
				});
			} else {
				res.sendStatus(404)
			}
		});
	}

	// Если захочется использовать 'development' и 'production',
	// то с помощью app.settings.env (https://github.com/strongloop/express/issues/936)
	// Если нужна обработка ошибок, то теперь она такая https://github.com/expressjs/errorhandler
	docsCoServer.install(server, () => {
		server.listen(config.get('server.port'), () => {
			logger.warn("Express server listening on port %d in %s mode", config.get('server.port'), app.settings.env);
		});

		app.get('/index.html', (req, res) => {
			res.send('Server is functioning normally. Version: ' + commonDefines.buildVersion + '. Build: ' +
				commonDefines.buildNumber);
		});
		const rawFileParser = bodyParser.raw(
			{inflate: true, limit: config.get('server.limits_tempfile_upload'), type: '*/*'});

		app.get('/coauthoring/CommandService.ashx', utils.checkClientIp, rawFileParser, docsCoServer.commandFromServer);
		app.post('/coauthoring/CommandService.ashx', utils.checkClientIp, rawFileParser,
			docsCoServer.commandFromServer);

		app.get('/ConvertService.ashx', utils.checkClientIp, rawFileParser, converterService.convertXml);
		app.post('/ConvertService.ashx', utils.checkClientIp, rawFileParser, converterService.convertXml);
		app.post('/converter', utils.checkClientIp, rawFileParser, converterService.convertJson);

		app.get('/FileUploader.ashx', utils.checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);
		app.post('/FileUploader.ashx', utils.checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);

		const docIdRegExp = new RegExp("^[" + constants.DOC_ID_PATTERN + "]*$", 'i');
		app.param('docid', (req, res, next, val) => {
			if (docIdRegExp.test(val)) {
				next();
			} else {
				res.sendStatus(403);
			}
		});
		app.param('index', (req, res, next, val) => {
			if (!isNaN(parseInt(val))) {
				next();
			} else {
				res.sendStatus(403);
			}
		});
		app.post('/uploadold/:docid/:userid/:index', fileUploaderService.uploadImageFileOld);
		app.post('/upload/:docid/:userid/:index', rawFileParser, fileUploaderService.uploadImageFile);

		app.post('/downloadas/:docid', rawFileParser, canvasService.downloadAs);
		app.post('/savefile/:docid', rawFileParser, canvasService.saveFile);
		app.get('/healthcheck', utils.checkClientIp, docsCoServer.healthCheck);

		app.get('/baseurl', (req, res) => {
			res.send(utils.getBaseUrlByRequest(req));
		});

		app.get('/robots.txt', (req, res) => {
			res.setHeader('Content-Type', 'plain/text');
			res.send("User-agent: *\nDisallow: /");
		});

		app.post('/docbuilder', utils.checkClientIp, rawFileParser, (req, res) => {
			converterService.builder(req, res);
		});
		app.get('/info/info.json', utils.checkClientIp, docsCoServer.licenseInfo);

		const sendUserPlugins = (res, data) => {
			res.setHeader('Content-Type', 'application/json');
			res.send(JSON.stringify(data));
		};
		app.get('/plugins.json', (req, res) => {
			if (userPlugins && !updatePlugins) {
				sendUserPlugins(res, userPlugins);
				return;
			}

			if (!config.has('server.static_content') || !config.has('plugins.uri')) {
				res.sendStatus(404);
				return;
			}

			let staticContent = config.get('server.static_content');
			let pluginsUri = config.get('plugins.uri');
			let pluginsPath = undefined;
			let pluginsAutostart = config.get('plugins.autostart');

			if (staticContent[pluginsUri]) {
				pluginsPath = staticContent[pluginsUri].path;
			}

			let baseUrl = '../../../..';
			utils.listFolders(pluginsPath, true).then((values) => {
				return co(function*() {
					const configFile = 'config.json';
					let stats = null;
					let result = [];
					for (let i = 0; i < values.length; ++i) {
						try {
							stats = yield utils.fsStat(path.join(values[i], configFile));
						} catch (err) {
							stats = null;
						}

						if (stats && stats.isFile) {
							result.push( baseUrl + pluginsUri + '/' + path.basename(values[i]) + '/' + configFile);
						}
					}

					userPlugins = {'url': '', 'pluginsData': result, 'autostart': pluginsAutostart};
					sendUserPlugins(res, userPlugins);
				});
			});
		});
	});

	process.on('message', (msg) => {
		if (!docsCoServer) {
			return;
		}
		switch (msg.type) {
			case 1:
				docsCoServer.setLicenseInfo(msg.data);
				break;
			case 2:
				updatePlugins = true;
				break;
		}
	});
}

process.on('uncaughtException', (err) => {
	logger.error((new Date).toUTCString() + ' uncaughtException:', err.message);
	logger.error(err.stack);
	logger.shutdown(() => {
		process.exit(1);
	});
});
<|endoftext|>


<commit_before>/* eslint-disable no-bitwise, no-process-exit */
"use strict"

module.exports = new function init() {
	var spec = {}, subjects = [], results, only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty

	function o(subject, predicate) {
		if (predicate === undefined) {
			if (results == null) throw new Error("Assertions should not occur outside test definitions")
			return new Assert(subject)
		}
		else if (results == null) {
			ctx[unique(subject)] = predicate
		} else {
			throw new Error("Test definition shouldn't be nested. To group tests use `o.spec()`")
		}
	}
	o.before = hook("__before")
	o.after = hook("__after")
	o.beforeEach = hook("__beforeEach")
	o.afterEach = hook("__afterEach")
	o.new = init
	o.spec = function(subject, predicate) {
		var parent = ctx
		ctx = ctx[unique(subject)] = {}
		predicate()
		ctx = parent
	}
	o.only = function(subject, predicate, silent) {
		if (!silent) console.log(highlight("/!\\ WARNING /!\\ o.only() mode"))
		o(subject, only = predicate)
	}
	o.spy = function(fn) {
		var spy = function() {
			spy.this = this
			spy.args = [].slice.call(arguments)
			spy.callCount++

			if (fn) return fn.apply(this, arguments)
		}
		if (fn)
			Object.defineProperties(spy, {
				length: {value: fn.length},
				name: {value: fn.name}
			})
		spy.args = []
		spy.callCount = 0
		return spy
	}
	o.run = function() {
		results = []
		start = new Date
		test(spec, [], [], report)

		function test(spec, pre, post, finalize) {
			pre = [].concat(pre, spec["__beforeEach"] || [])
			post = [].concat(spec["__afterEach"] || [], post)
			series([].concat(spec["__before"] || [], Object.keys(spec).map(function(key) {
				return function(done, timeout) {
					timeout(Infinity)

					if (key.slice(0, 2) === "__") return done()
					if (only !== null && spec[key] !== only && typeof only === typeof spec[key]) return done()
					subjects.push(key)
					var type = typeof spec[key]
					if (type === "object") test(spec[key], pre, post, pop)
					if (type === "function") series([].concat(pre, spec[key], post, pop))

					function pop() {
						subjects.pop()
						done()
					}
				}
			}), spec["__after"] || [], finalize))
		}

		function series(fns) {
			var cursor = 0
			next()

			function next() {
				if (cursor === fns.length) return

				var fn = fns[cursor++]
				if (fn.length > 0) {
					var timeout = 0, delay = 200, s = new Date
					var isDone = false
					var body = fn.toString()
					var arg = (body.match(/\(([\w$]+)/) || body.match(/([\w$]+)\s*=>/) || []).pop()
					if (body.indexOf(arg) === body.lastIndexOf(arg)) throw new Error("`" + arg + "()` should be called at least once")
					try {
						fn(function done() {
							if (timeout !== undefined) {
								timeout = clearTimeout(timeout)
								if (delay !== Infinity) record(null)
								if (!isDone) next()
								else throw new Error("`" + arg + "()` should only be called once")
								isDone = true
							}
							else console.log("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms")
						}, function(t) {delay = t})
					}
					catch (e) {
						record(e.message, e)
						subjects.pop()
						next()
					}
					if (timeout === 0) {
						timeout = setTimeout(function() {
							timeout = undefined
							record("async test timed out")
							next()
						}, Math.min(delay, 2147483647))
					}
				}
				else {
					fn()
					nextTickish(next)
				}
			}
		}
	}
	function unique(subject) {
		if (hasOwn.call(ctx, subject)) {
			console.warn("A test or a spec named `" + subject + "` was already defined")
			while (hasOwn.call(ctx, subject)) subject += "*"
		}
		return subject
	}
	function hook(name) {
		return function(predicate) {
			if (ctx[name]) throw new Error("This hook should be defined outside of a loop or inside a nested test group:\n" + predicate)
			ctx[name] = predicate
		}
	}

	define("equals", "should equal", function(a, b) {return a === b})
	define("notEquals", "should not equal", function(a, b) {return a !== b})
	define("deepEquals", "should deep equal", deepEqual)
	define("notDeepEquals", "should not deep equal", function(a, b) {return !deepEqual(a, b)})

	function isArguments(a) {
		if ("callee" in a) {
			for (var i in a) if (i === "callee") return false
			return true
		}
	}
	function deepEqual(a, b) {
		if (a === b) return true
		if (a === null ^ b === null || a === undefined ^ b === undefined) return false
		if (typeof a === "object" && typeof b === "object") {
			var aIsArgs = isArguments(a), bIsArgs = isArguments(b)
			if (a.constructor === Object && b.constructor === Object && !aIsArgs && !bIsArgs) {
				for (var i in a) {
					if ((!(i in b)) || !deepEqual(a[i], b[i])) return false
				}
				for (var i in b) {
					if (!(i in a)) return false
				}
				return true
			}
			if (a.length === b.length && (a instanceof Array && b instanceof Array || aIsArgs && bIsArgs)) {
				var aKeys = Object.getOwnPropertyNames(a), bKeys = Object.getOwnPropertyNames(b)
				if (aKeys.length !== bKeys.length) return false
				for (var i = 0; i < aKeys.length; i++) {
					if (!hasOwn.call(b, aKeys[i]) || !deepEqual(a[aKeys[i]], b[aKeys[i]])) return false
				}
				return true
			}
			if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()
			if (typeof Buffer === "function" && a instanceof Buffer && b instanceof Buffer) {
				for (var i = 0; i < a.length; i++) {
					if (a[i] !== b[i]) return false
				}
				return true
			}
			if (a.valueOf() === b.valueOf()) return true
		}
		return false
	}

	function Assert(value) {this.value = value}
	function define(name, verb, compare) {
		Assert.prototype[name] = function assert(value) {
			if (compare(this.value, value)) record(null)
			else record(serialize(this.value) + "\n" + verb + "\n" + serialize(value))
			return function(message) {
				var result = results[results.length - 1]
				result.message = message + "\n\n" + result.message
			}
		}
	}
	function record(message, error) {
		var result = {pass: message === null}
		if (result.pass === false) {
			if (error == null || typeof error === "string") {
				error = new Error
				if (error.stack === undefined) new function() {try {throw error} catch (e) {error = e}}
			}
			result.context = subjects.join(" > ")
			result.message = message
			result.error = error.stack
		}
		results.push(result)
	}
	function serialize(value) {
		if (value === null || (typeof value === "object" && !(value instanceof Array)) || typeof value === "number") return String(value)
		else if (typeof value === "function") return value.name || "<anonymous function>"
		try {return JSON.stringify(value)} catch (e) {return String(value)}
	}
	function highlight(message) {
		return hasProcess ? "\x1b[31m" + message + "\x1b[0m" : "%c" + message + "%c "
	}

	function report() {
		var status = 0
		for (var i = 0, r; r = results[i]; i++) {
			if (!r.pass) {
				var stackTrace = r.error.match(/^(?:(?!Error|[\/\\]ospec[\/\\]ospec\.js).)*$/m)
				console.error(r.context + ":\n" + highlight(r.message) + (stackTrace ? "\n\n" + stackTrace + "\n\n" : ""), hasProcess ? "" : "color:red", hasProcess ? "" : "color:black")
				status = 1
			}
		}
		console.log(
			results.length + " assertions completed in " + Math.round(new Date - start) + "ms, " +
			"of which " + results.filter(function(result){return result.error}).length + " failed"
		)
		if (hasProcess && status === 1) process.exit(1)
	}

	if(hasProcess) {
		nextTickish = process.nextTick
	} else {
		nextTickish = function fakeFastNextTick(next) {
			if (stack++ < 5000) next()
			else setTimeout(next, stack = 0)
		}
	}

	return o
}
<commit_msg>A more general fix<commit_after>/* eslint-disable no-bitwise, no-process-exit */
"use strict"

module.exports = new function init() {
	var spec = {}, subjects = [], results, only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty

	function o(subject, predicate) {
		if (predicate === undefined) {
			if (results == null) throw new Error("Assertions should not occur outside test definitions")
			return new Assert(subject)
		}
		else if (results == null) {
			ctx[unique(subject)] = predicate
		} else {
			throw new Error("Test definition shouldn't be nested. To group tests use `o.spec()`")
		}
	}
	o.before = hook("__before")
	o.after = hook("__after")
	o.beforeEach = hook("__beforeEach")
	o.afterEach = hook("__afterEach")
	o.new = init
	o.spec = function(subject, predicate) {
		var parent = ctx
		ctx = ctx[unique(subject)] = {}
		predicate()
		ctx = parent
	}
	o.only = function(subject, predicate, silent) {
		if (!silent) console.log(highlight("/!\\ WARNING /!\\ o.only() mode"))
		o(subject, only = predicate)
	}
	o.spy = function(fn) {
		var spy = function() {
			spy.this = this
			spy.args = [].slice.call(arguments)
			spy.callCount++

			if (fn) return fn.apply(this, arguments)
		}
		if (fn)
			Object.defineProperties(spy, {
				length: {value: fn.length},
				name: {value: fn.name}
			})
		spy.args = []
		spy.callCount = 0
		return spy
	}
	o.run = function() {
		results = []
		start = new Date
		test(spec, [], [], report)

		function test(spec, pre, post, finalize) {
			pre = [].concat(pre, spec["__beforeEach"] || [])
			post = [].concat(spec["__afterEach"] || [], post)
			series([].concat(spec["__before"] || [], Object.keys(spec).map(function(key) {
				return function(done, timeout) {
					timeout(Infinity)

					if (key.slice(0, 2) === "__") return done()
					if (only !== null && spec[key] !== only && typeof only === typeof spec[key]) return done()
					subjects.push(key)
					var type = typeof spec[key]
					if (type === "object") test(spec[key], pre, post, pop)
					if (type === "function") series([].concat(pre, spec[key], post, pop))

					function pop() {
						subjects.pop()
						done()
					}
				}
			}), spec["__after"] || [], finalize))
		}

		function series(fns) {
			var cursor = 0
			next()

			function next() {
				if (cursor === fns.length) return

				var fn = fns[cursor++]
				if (fn.length > 0) {
					var timeout = 0, delay = 200, s = new Date
					var isDone = false
					var body = fn.toString()
					var arg = (body.match(/\(([\w$]+)/) || body.match(/([\w$]+)\s*=>/) || []).pop()
					if (body.indexOf(arg) === body.lastIndexOf(arg)) throw new Error("`" + arg + "()` should be called at least once")
					try {
						fn(function done() {
							if (timeout !== undefined) {
								timeout = clearTimeout(timeout)
								if (delay !== Infinity) record(null)
								if (!isDone) next()
								else throw new Error("`" + arg + "()` should only be called once")
								isDone = true
							}
							else console.log("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms")
						}, function(t) {delay = t})
					}
					catch (e) {
						if (e instanceof Error) record(e.message, e)
						else record(String(e))
						subjects.pop()
						next()
					}
					if (timeout === 0) {
						timeout = setTimeout(function() {
							timeout = undefined
							record("async test timed out")
							next()
						}, Math.min(delay, 2147483647))
					}
				}
				else {
					fn()
					nextTickish(next)
				}
			}
		}
	}
	function unique(subject) {
		if (hasOwn.call(ctx, subject)) {
			console.warn("A test or a spec named `" + subject + "` was already defined")
			while (hasOwn.call(ctx, subject)) subject += "*"
		}
		return subject
	}
	function hook(name) {
		return function(predicate) {
			if (ctx[name]) throw new Error("This hook should be defined outside of a loop or inside a nested test group:\n" + predicate)
			ctx[name] = predicate
		}
	}

	define("equals", "should equal", function(a, b) {return a === b})
	define("notEquals", "should not equal", function(a, b) {return a !== b})
	define("deepEquals", "should deep equal", deepEqual)
	define("notDeepEquals", "should not deep equal", function(a, b) {return !deepEqual(a, b)})

	function isArguments(a) {
		if ("callee" in a) {
			for (var i in a) if (i === "callee") return false
			return true
		}
	}
	function deepEqual(a, b) {
		if (a === b) return true
		if (a === null ^ b === null || a === undefined ^ b === undefined) return false
		if (typeof a === "object" && typeof b === "object") {
			var aIsArgs = isArguments(a), bIsArgs = isArguments(b)
			if (a.constructor === Object && b.constructor === Object && !aIsArgs && !bIsArgs) {
				for (var i in a) {
					if ((!(i in b)) || !deepEqual(a[i], b[i])) return false
				}
				for (var i in b) {
					if (!(i in a)) return false
				}
				return true
			}
			if (a.length === b.length && (a instanceof Array && b instanceof Array || aIsArgs && bIsArgs)) {
				var aKeys = Object.getOwnPropertyNames(a), bKeys = Object.getOwnPropertyNames(b)
				if (aKeys.length !== bKeys.length) return false
				for (var i = 0; i < aKeys.length; i++) {
					if (!hasOwn.call(b, aKeys[i]) || !deepEqual(a[aKeys[i]], b[aKeys[i]])) return false
				}
				return true
			}
			if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()
			if (typeof Buffer === "function" && a instanceof Buffer && b instanceof Buffer) {
				for (var i = 0; i < a.length; i++) {
					if (a[i] !== b[i]) return false
				}
				return true
			}
			if (a.valueOf() === b.valueOf()) return true
		}
		return false
	}

	function Assert(value) {this.value = value}
	function define(name, verb, compare) {
		Assert.prototype[name] = function assert(value) {
			if (compare(this.value, value)) record(null)
			else record(serialize(this.value) + "\n" + verb + "\n" + serialize(value))
			return function(message) {
				var result = results[results.length - 1]
				result.message = message + "\n\n" + result.message
			}
		}
	}
	function record(message, error) {
		var result = {pass: message === null}
		if (result.pass === false) {
			if (error == null) {
				error = new Error
				if (error.stack === undefined) new function() {try {throw error} catch (e) {error = e}}
			}
			result.context = subjects.join(" > ")
			result.message = message
			result.error = error.stack
		}
		results.push(result)
	}
	function serialize(value) {
		if (value === null || (typeof value === "object" && !(value instanceof Array)) || typeof value === "number") return String(value)
		else if (typeof value === "function") return value.name || "<anonymous function>"
		try {return JSON.stringify(value)} catch (e) {return String(value)}
	}
	function highlight(message) {
		return hasProcess ? "\x1b[31m" + message + "\x1b[0m" : "%c" + message + "%c "
	}

	function report() {
		var status = 0
		for (var i = 0, r; r = results[i]; i++) {
			if (!r.pass) {
				var stackTrace = r.error.match(/^(?:(?!Error|[\/\\]ospec[\/\\]ospec\.js).)*$/m)
				console.error(r.context + ":\n" + highlight(r.message) + (stackTrace ? "\n\n" + stackTrace + "\n\n" : ""), hasProcess ? "" : "color:red", hasProcess ? "" : "color:black")
				status = 1
			}
		}
		console.log(
			results.length + " assertions completed in " + Math.round(new Date - start) + "ms, " +
			"of which " + results.filter(function(result){return result.error}).length + " failed"
		)
		if (hasProcess && status === 1) process.exit(1)
	}

	if(hasProcess) {
		nextTickish = process.nextTick
	} else {
		nextTickish = function fakeFastNextTick(next) {
			if (stack++ < 5000) next()
			else setTimeout(next, stack = 0)
		}
	}

	return o
}
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies
 */
import * as mongo from 'mongodb';
import User from '../../../models/user';
import Post from '../../../models/post';

/**
 * Aggregate posts of a user
 *
 * @param {Object} params
 * @param {Object} reply
 * @return {void}
 */
module.exports = async (params, reply) =>
{
	// Init 'user' parameter
	const userId = params.user;
	if (userId === undefined || userId === null) {
		return reply(400, 'user is required');
	}

	// Init 'limit' parameter
	let limit = params.limit;
	if (limit !== undefined && limit !== null) {
		limit = parseInt(limit, 10);

		// 1 ~ 100 まで
		if (!(1 <= limit && limit <= 100)) {
			return reply(400, 'invalid limit range');
		}
	} else {
		limit = 10;
	}

	// Init 'offset' parameter
	let offset = params.offset;
	if (offset !== undefined && offset !== null) {
		offset = parseInt(offset, 10);
	} else {
		offset = 0;
	}

	// Init 'sort' parameter
	let sort = params.sort || 'desc';

	// Lookup user
	const user = await User.findOne({
		_id: new mongo.ObjectID(userId)
	});

	if (user === null) {
		return reply(404, 'user not found');
	}

	const posts = await Post
		.aggregate([
			{ $sort : { _id : sort == 'asc' ? 1 : -1 } },
			{ $match: { user: { $eq: user._id } } },
			{ $project:
				{ date: {
					day: { $subtract: [
						{ $dayOfYear: '$created_at' },
						{ $mod: [{ $dayOfYear: '$created_at' }, 1] }
					]}
				}, type: {
					$cond: {
						if: { $ne: ['$repost', null] },
						then: 'repost',
						else: {
							$cond: {
								if: { $ne: ['$reply_to', null] },
								then: 'reply',
								else: 'post'
							}
						}
					}
				}}
			},
			{ $group: { _id: {
				date: '$date',
				type: '$type'
			}, count: { '$sum': 1 } } },
			{ $group: {
				_id: '$_id.date',
				data: { $addToSet: {
					type: "$_id.type",
					count: "$count"
				}}
			} },
			{ $limit: limit },
			{ $skip: offset }
		])
		.toArray();

	posts.forEach(data => {
		data.day = data._id.day;
		delete data._id;

		data.posts = (data.data.filter(x => x.type == 'post')[0] || { count: 0 }).count;
		data.reposts = (data.data.filter(x => x.type == 'repost')[0] || { count: 0 }).count;
		data.replies = (data.data.filter(x => x.type == 'reply')[0] || { count: 0 }).count;

		delete data.data;
	});

	reply(posts);
};
<commit_msg>Maybe fix bug<commit_after>'use strict';

/**
 * Module dependencies
 */
import * as mongo from 'mongodb';
import User from '../../../models/user';
import Post from '../../../models/post';

/**
 * Aggregate posts of a user
 *
 * @param {Object} params
 * @param {Object} reply
 * @return {void}
 */
module.exports = async (params, reply) =>
{
	// Init 'user' parameter
	const userId = params.user;
	if (userId === undefined || userId === null) {
		return reply(400, 'user is required');
	}

	// Init 'limit' parameter
	let limit = params.limit;
	if (limit !== undefined && limit !== null) {
		limit = parseInt(limit, 10);

		// 1 ~ 100 まで
		if (!(1 <= limit && limit <= 100)) {
			return reply(400, 'invalid limit range');
		}
	} else {
		limit = 10;
	}

	// Init 'offset' parameter
	let offset = params.offset;
	if (offset !== undefined && offset !== null) {
		offset = parseInt(offset, 10);
	} else {
		offset = 0;
	}

	// Init 'sort' parameter
	let sort = params.sort || 'desc';

	// Lookup user
	const user = await User.findOne({
		_id: new mongo.ObjectID(userId)
	});

	if (user === null) {
		return reply(404, 'user not found');
	}

	const posts = await Post
		.aggregate([
			{ $match: { user: { $eq: user._id } } },
			{ $project:
				{ date: {
					day: { $subtract: [
						{ $dayOfYear: '$created_at' },
						{ $mod: [{ $dayOfYear: '$created_at' }, 1] }
					]}
				}, type: {
					$cond: {
						if: { $ne: ['$repost', null] },
						then: 'repost',
						else: {
							$cond: {
								if: { $ne: ['$reply_to', null] },
								then: 'reply',
								else: 'post'
							}
						}
					}
				}}
			},
			{ $group: { _id: {
				date: '$date',
				type: '$type'
			}, count: { '$sum': 1 } } },
			{ $group: {
				_id: '$_id.date',
				data: { $addToSet: {
					type: "$_id.type",
					count: "$count"
				}}
			} },
			{ $sort : { _id : sort == 'asc' ? 1 : -1 } },
			{ $limit: limit },
			{ $skip: offset }
		])
		.toArray();

	posts.forEach(data => {
		data.day = data._id.day;
		delete data._id;

		data.posts = (data.data.filter(x => x.type == 'post')[0] || { count: 0 }).count;
		data.reposts = (data.data.filter(x => x.type == 'repost')[0] || { count: 0 }).count;
		data.replies = (data.data.filter(x => x.type == 'reply')[0] || { count: 0 }).count;

		delete data.data;
	});

	reply(posts);
};
<|endoftext|>


<commit_before>import type { Action } from '../action'
import type { State } from './index'

// first layer of reducer, manipulate the selectedPostId
const reduceSelectedPostId = (state: State, action: Action): State => {
    switch (action.type) {
        case 'navigatorRead':
            return action.path[1]
                ? {
                      ...state,
                      selectedPostId: action.path[1],
                  }
                : state

        case 'goTo:post':
            return {
                ...state,
                selectedPostId: action.postId,
            }

        case 'goTo:home':
        case 'goTo:about':
        case 'selectTag':
            return {
                ...state,
                selectedPostId: null,
            }

        default:
            return state
    }
}

// second layer of reducer, populate the selectedPost
const _reduceSelectedPost = (state: State, action: Action): State => {
    // if the posts list have been populated, check if the selected one is in it now
    if (
        state.selectedPostId &&
        (!state.selectedPost || state.selectedPost.id !== state.selectedPostId)
    ) {
        const selectedPost = state.posts.find(
            ({ id }) => id === state.selectedPostId
        )
        return {
            ...state,
            selectedPost,
        }
    }

    if (!state.selectedPostId && state.selectedPost)
        return {
            ...state,
            selectedPost: null,
        }

    return state
}

export const reduceSelectedPost = (state: State, action: Action): State =>
    _reduceSelectedPost(reduceSelectedPostId(state, action), action)
<commit_msg>:hammer: fix case where the back action failed<commit_after>import type { Action } from '../action'
import type { State } from './index'

// first layer of reducer, manipulate the selectedPostId
const reduceSelectedPostId = (state: State, action: Action): State => {
    switch (action.type) {
        case 'navigatorRead':
            return {
                ...state,
                selectedPostId: action.path[1],
            }

        case 'goTo:post':
            return {
                ...state,
                selectedPostId: action.postId,
            }

        case 'goTo:home':
        case 'goTo:about':
        case 'selectTag':
            return {
                ...state,
                selectedPostId: null,
            }

        default:
            return state
    }
}

// second layer of reducer, populate the selectedPost
const _reduceSelectedPost = (state: State, action: Action): State => {
    // if the posts list have been populated, check if the selected one is in it now
    if (
        state.selectedPostId &&
        (!state.selectedPost || state.selectedPost.id !== state.selectedPostId)
    ) {
        const selectedPost = state.posts.find(
            ({ id }) => id === state.selectedPostId
        )
        return {
            ...state,
            selectedPost,
        }
    }

    if (!state.selectedPostId && state.selectedPost)
        return {
            ...state,
            selectedPost: null,
        }

    return state
}

export const reduceSelectedPost = (state: State, action: Action): State =>
    _reduceSelectedPost(reduceSelectedPostId(state, action), action)
<|endoftext|>


<commit_before>import * as types from './actionTypes';
import uuid from 'uuid';

import {AsyncStorage} from 'react-native';
export function appInitializedAction() {

  return async function(dispatch, getState) {
    const userId = await AsyncStorage.getItem('userId');
    if (userId) {
      dispatch({type: types.APP_INITIALIZED, userId: userId, isUserNew: false});
      getStore().isUserNew = false;
    }
    else {
      const newUuid = uuid.v4();
      await AsyncStorage.setItem('userId', newUuid);
      dispatch({type: types.APP_INITIALIZED, userId: newUuid, isUserNew: true});
    }
  };
}
<commit_msg>Remove unnecessary<commit_after>import * as types from './actionTypes';
import uuid from 'uuid';

import {AsyncStorage} from 'react-native';
export function appInitializedAction() {

  return async function(dispatch, getState) {
    const userId = await AsyncStorage.getItem('userId');
    if (userId) {
      dispatch({type: types.APP_INITIALIZED, userId: userId, isUserNew: false});
    }
    else {
      const newUuid = uuid.v4();
      await AsyncStorage.setItem('userId', newUuid);
      dispatch({type: types.APP_INITIALIZED, userId: newUuid, isUserNew: true});
    }
  };
}
<|endoftext|>


<commit_before>const initialState = {
  activeModal: null,
  tool: null,
  homeLocation: null,
  progress: [],
  location: null,
  expertMode: false,
  locate: false,
  selectingHomeLocation: false,
};

export default function main(state = initialState, action) {
  switch (action.type) {
    case 'MAIN_LOAD_STATE': {
      const s = { ...state };
      const { homeLocation, expertMode } = action.payload;
      if (homeLocation && typeof homeLocation.lat === 'number' && typeof homeLocation.lon === 'number') {
        s.homeLocation = { lat: homeLocation.lat, lon: homeLocation.lon };
      }
      s.expertMode = !!expertMode;
      return s;
    }
    case 'AUTH_SET_USER':
      return {
        ...state,
        homeLocation: action.payload && action.payload.lat && action.payload.lon ? { lat: action.payload.lat, lon: action.payload.lon } : null,
      };
    case 'AUTH_LOGOUT':
      return { ...state, homeLocation: null };
    case 'SET_ACTIVE_MODAL':
      return { ...state, activeModal: action.payload };
    case 'SET_TOOL':
      return { ...state, tool: action.payload };
    case 'SET_HOME_LOCATION':
      return { ...state, homeLocation: action.payload ? { ...action.payload } : null };
    case 'START_PROGRESS':
      return { ...state, progress: [...state.progress, action.payload] };
    case 'STOP_PROGRESS':
      return { ...state, progress: state.progress.filter(pid => pid !== action.payload) };
    case 'SET_LOCATION':
      return { ...state, location: { lat: action.payload.lat, lon: action.payload.lon, accuracy: action.payload.accuracy } };
    case 'SET_EXPERT_MODE':
      return { ...state, expertMode: action.payload };
    case 'LOCATE':
      return { ...state, locate: !state.locate };
    case 'SET_SELECTING_HOME_LOCATION':
      return { ...state, selectingHomeLocation: action.payload };
    default:
      return state;
  }
}
<commit_msg>prevented reseting homelocation on anonymous page refresh<commit_after>const initialState = {
  activeModal: null,
  tool: null,
  homeLocation: null,
  progress: [],
  location: null,
  expertMode: false,
  locate: false,
  selectingHomeLocation: false,
};

export default function main(state = initialState, action) {
  switch (action.type) {
    case 'MAIN_LOAD_STATE': {
      const s = { ...state };
      const { homeLocation, expertMode } = action.payload;
      if (homeLocation && typeof homeLocation.lat === 'number' && typeof homeLocation.lon === 'number') {
        s.homeLocation = { lat: homeLocation.lat, lon: homeLocation.lon };
      }
      s.expertMode = !!expertMode;
      return s;
    }
    case 'AUTH_SET_USER':
      return {
        ...state,
        homeLocation: !action.payload ? state.homeLocation
          : action.payload.lat && action.payload.lon ? { lat: action.payload.lat, lon: action.payload.lon }
            : null,
      };
    case 'AUTH_LOGOUT':
      return { ...state, homeLocation: null };
    case 'SET_ACTIVE_MODAL':
      return { ...state, activeModal: action.payload };
    case 'SET_TOOL':
      return { ...state, tool: action.payload };
    case 'SET_HOME_LOCATION':
      return { ...state, homeLocation: action.payload ? { ...action.payload } : null };
    case 'START_PROGRESS':
      return { ...state, progress: [...state.progress, action.payload] };
    case 'STOP_PROGRESS':
      return { ...state, progress: state.progress.filter(pid => pid !== action.payload) };
    case 'SET_LOCATION':
      return { ...state, location: { lat: action.payload.lat, lon: action.payload.lon, accuracy: action.payload.accuracy } };
    case 'SET_EXPERT_MODE':
      return { ...state, expertMode: action.payload };
    case 'LOCATE':
      return { ...state, locate: !state.locate };
    case 'SET_SELECTING_HOME_LOCATION':
      return { ...state, selectingHomeLocation: action.payload };
    default:
      return state;
  }
}
<|endoftext|>


<commit_before>/*
 * OrientationChange Event Shim
 * https://gist.github.com/2966043
 *
 * Copyright (c) 2012, Rich Tibbett
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * DESCRIPTION:
 * ------------
 *
 * JavaScript shim of iOS's window.orientation + orientationchange events
 * for other (typically mobile) browsers.
 *
 * Documentation URL @
 * http://developer.apple.com/library/IOS/#documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html
 *
 * Demo page @
 * http://people.opera.com/richt/release/demos/orientation/orientationchange_shim/test.html
 *
 * USAGE:
 * ------
 *
 * 1. Add this file to the <head> of your web page:
 *
 * <script type="text/javascript" src="orientationChange.js"></script>
 *
 * 2. Register for 'orientationchange' events in the usual way:
 *
 * window.addEventListener('orientationchange', function(e) {
 *   console.log( window.orientation );
 * }, true);
 *
 *   or:
 *
 * window.onorientationchange = function(e) {
 *   console.log( window.orientation );
 * }
 *
 *   or:
 *
 * <body onorientationchange="checkOrientation()">
 *
 * *********************************************************************
 * NOTE: Adding e.g. <body onorientationchange="checkOrientation()"> is
 * partially supported but is not recommended. Please consider using one
 * of the scripting approaches shown above instead.
 * *********************************************************************
 *
 * KNOWN ISSUES:
 * --------------
 *
 * 21-06-2012:
 * The native orientationchange implementation in Android's Native Browser
 * and Chrome Beta for Android is borked. Note that this shim is not loaded
 * in these browsers. This shims does not replace these broken native
 * implementations.
 *
 */

 !(function( window, undefined ) {

    var supportsOrientation = ( typeof window.orientation == 'number' && typeof window.onorientationchange == 'object' );

    // Do nothing if we don't need to shim
    if ( supportsOrientation ) return;

    /* START: requestAnimationFrame shim
     *
     * By: Eric Möller
     * URL: http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
     */
    (function() {
        var lastTime = 0;
        var vendors = ['ms', 'moz', 'webkit', 'o'];
        for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
            window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
            window.cancelRequestAnimationFrame = window[vendors[x] +
            'CancelRequestAnimationFrame'];
        }

        if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() {
                callback(currTime + timeToCall);
            },
            timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

        if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
    } ());

    /* END: requestAnimationFrame shim */

    var cOrientationChange = function() {

        this.currentOrientationAngle = -1;
        this.lastOrientationAngle = -2;

        this.currentOrientationState = this.lastOrientationState = ( window.innerWidth > window.innerHeight ) ? 'landscape': 'portrait';

        var self = this;

        // Setup deviceorientation listener (if it is available)
        window.addEventListener( 'deviceorientation', function( e ) {
            // Don't use unless we have a 3-axis implementation of device orientation
            if ( e.alpha === null || e.alpha === undefined ) {
                self.currentOrientationAngle = 0;
                return;
            }

            // set orientation angle to nearest 90 degrees
            self.currentOrientationAngle = Math.round( -e.gamma / 90) * 90;

            // correction for holding the device upside down
            if ( self.currentOrientationAngle == 0 && e.beta < 0 ) {
                self.currentOrientationAngle = 180;
            }

        }, true );

        this.manualOrientationChange = function() {

            // landscape when width is biggest, otherwise portrait
            self.currentOrientationState = ( window.innerWidth > window.innerHeight ) ? 'landscape': 'portrait';

            if (  ( self.currentOrientationState !== self.lastOrientationState ) ||
                    ( self.lastOrientationAngle === -2 && self.currentOrientationAngle !== -1 ) ||
                        ( self.currentOrientationAngle !== 0 && self.currentOrientationAngle === -self.lastOrientationAngle )  ) {

                if ( self.currentOrientationAngle === -1 ) self.currentOrientationAngle = 0;

                // Update static window.orientation value
                window.orientation = self.currentOrientationAngle;

                // Create and dispatch pseudo-orientationchange event
                var orientationEvent = window.document.createEvent( 'Event' );
                orientationEvent.initEvent( 'orientationchange', true, true );
                orientationEvent.orientation = this.currentOrientationAngle;
                orientationEvent.mode = this.currentOrientationState;
                window.dispatchEvent( orientationEvent );

                // Fire event to window.onorientationchange assigned handler (if any)
                if ( typeof window.onorientationchange == 'object' ) {
                    window.onorientationchange.call( this, orientationEvent );
                }

                // Check for onorientationevent handler on body element and execute
                var body = document.body;
                if( body && body.getAttribute('onorientationchange')) {
                    // Create new script and run function
                    var onorientationchange_script = document.createElement('script');
                    onorientationchange_script.type = "text/javascript";
                    onorientationchange_script.textContent = "//<![CDATA[\n" + body.getAttribute('onorientationchange') + "\n//]]>";
                    body.appendChild(onorientationchange_script);
                    onorientationchange_script.parentNode.removeChild( onorientationchange_script );
                }

                self.lastOrientationAngle = self.currentOrientationAngle;
                self.lastOrientationState = self.currentOrientationState;

            }

            window.requestAnimationFrame( self.manualOrientationChange.bind( self ) );

        };

        this.manualOrientationChange();

    };

    //*** WINDOW EVENTLISTENER SHIM
    var orientationHandler = new cOrientationChange();

    window.orientation = orientationHandler.currentOrientationAngle === -1 ? 0: orientationHandler.currentOrientationAngle;

    window.onorientationchange = function( e ) {};

})( window );<commit_msg>Clean unused file<commit_after><|endoftext|>


<commit_before>(function () {
    var cookieName = 'sidebar';
    var defaultState = {a0: true};
    var sidebar = function () {
        var json = Cookies.getJSON(cookieName);
        if (!_.isObject(json)) {
            Cookies.set(cookieName, defaultState);
            return defaultState;
        }
        return json;
    }();

    function cookieToCheckbox(checkbox) {
        checkbox.prop("checked", sidebar[checkbox.attr('id')]);
        console.log( "sidebar", sidebar );
        return checkbox;
    }

    function checkboxToCookie(checkbox) {
        var id = checkbox.attr('id');
        if (checkbox.is(':checked'))
            sidebar[id] = true;
        else
            delete sidebar[id];
        Cookies.set('sidebar', sidebar);
        console.log( "sidebar", sidebar );
    }

    function checkAllBoxes(selector) {
        $(selector).each(function (index, elem) {
            var checkbox = $(elem);
            cookieToCheckbox(checkbox).on("click", function () {
                checkboxToCookie(checkbox);
            });
        });
    }

    $(function () {
        checkAllBoxes("#sidebar input[type=checkbox]")
    });
})();<commit_msg>SITE-22: Removed logging.<commit_after>(function () {
    var cookieName = 'sidebar';
    var defaultState = {a0: true};
    var sidebar = function () {
        var json = Cookies.getJSON(cookieName);
        if (!_.isObject(json)) {
            Cookies.set(cookieName, defaultState);
            return defaultState;
        }
        return json;
    }();

    function cookieToCheckbox(checkbox) {
        checkbox.prop("checked", sidebar[checkbox.attr('id')]);
        return checkbox;
    }

    function checkboxToCookie(checkbox) {
        var id = checkbox.attr('id');
        if (checkbox.is(':checked'))
            sidebar[id] = true;
        else
            delete sidebar[id];
        Cookies.set('sidebar', sidebar);
    }

    function checkAllBoxes(selector) {
        $(selector).each(function (index, elem) {
            var checkbox = $(elem);
            cookieToCheckbox(checkbox).on("click", function () {
                checkboxToCookie(checkbox);
            });
        });
    }

    $(function () {
        checkAllBoxes("#sidebar input[type=checkbox]")
    });
})();<|endoftext|>


<commit_before>OBJS = ['deployment', 'image', 'networkTemplate', 'network', 'networkSliver', 'networkDeployment', 'node', 'service', 'site', 'slice', 'sliceDeployment', 'slicePrivilege', 'sliver', 'user', 'sliceRole', 'userDeployment'];
NAV_OBJS = ['deployment', 'site', 'slice', 'user'];

REWRITES = {"/admin/core/deployment/": "#deployments",
            "/admin/core/site/" : "#sites",
            "/admin/core/slice/" : "#slices",
            "/admin/core/user/" : "#users"};

XOSAdminApp = new XOSApplication({
    logTableId: "#logTable",
    statusMsgId: "#statusMsg",
    hideTabsByDefault: true
});

XOSAdminApp.addRegions({
    navigation: "#navigationPanel",

    detail: "#detail",
    linkedObjs1: "#linkedObjs1",
    linkedObjs2: "#linkedObjs2",
    linkedObjs3: "#linkedObjs3",
    linkedObjs4: "#linkedObjs4"
});

XOSAdminApp.navigateToModel = function(app, detailClass, detailNavLink, model) {
     XOSAdminApp.Router.navigate(detailNavLink + "/" + model.id, {trigger: true});
};

XOSAdminApp.navigate = function(what, modelName, modelId) {
    collection_name = modelName + "s";
    if (what=="list") {
        XOSAdminApp.Router.navigate(collection_name, {trigger: true})
    } else if (what=="detail") {
        XOSAdminApp.Router.navigate(collection_name + "/" + modelId, {trigger: true})
    } else if (what=="add") {
        XOSAdminApp.Router.navigate("add" + firstCharUpper(modelName), {trigger: true})
    }
}

ICON_CLASSES = {home: "icon-home", deployments: "icon-deployment", sites: "icon-site", slices: "icon-slice", users: "icon-user"};

XOSAdminApp.updateNavigationPanel = function() {
    buttonTemplate=$("#xos-navbutton").html();
    assert(buttonTemplate != undefined, "buttonTemplate is undefined");
    html="

";
    for (var index in NAV_OBJS) {
        name = NAV_OBJS[index];
        collection_name = name+"s";
        nav_url = "#" + collection_name;
        id = "nav-"+name;
        icon_class = ICON_CLASSES[collection_name] || "icon-cog";

        html = html + _.template(buttonTemplate, {name: collection_name, router: "XOSAdminApp.Router", routeUrl: nav_url, iconClass: icon_class});
    }

    html = html + "
";

    $("#navigationPanel").html(html);
};

XOSAdminApp.buildViews = function() {
     for (var index in OBJS) {
         name = OBJS[index];
         tr_template = '#xosAdmin-' + name + '-listitem-template';
         table_template = '#xosAdmin-' + name + '-list-template';
         detail_template = '#xosAdmin-' + name + '-detail-template';
         collection_name = name + "s";
         region_name = name + "List";
         detailNavLink = collection_name;

         detailClass = XOSDetailView.extend({
            template: detail_template,
            app: XOSAdminApp,
         });
         XOSAdminApp[collection_name + "DetailView"] = detailClass;

         itemViewClass = XOSItemView.extend({
             detailClass: detailClass,
             template: tr_template,
             app: XOSAdminApp,
             detailNavLink: detailNavLink,
         });

         listViewClass = XOSListView.extend({
             childView: itemViewClass,
             template: table_template,
             collection: xos[collection_name],
             title: name + "s",
             app: XOSAdminApp,
         });

         XOSAdminApp[collection_name + "ListView"] = listViewClass;

         xos[collection_name].fetch(); //startPolling();
     }
};

XOSAdminApp.initRouter = function() {
    router = XOSRouter;
    var api = {};
    var routes = {};

    for (var index in OBJS) {
        name = OBJS[index];
        collection_name = name + "s";
        nav_url = collection_name;
        api_command = "list" + firstCharUpper(collection_name);
        listViewName = collection_name + "ListView";
        detailViewName = collection_name + "DetailView";

        api[api_command] = XOSAdminApp.listViewShower(listViewName, collection_name, "detail", collection_name);
        routes[nav_url] = api_command;

        nav_url = collection_name + "/:id";
        api_command = "detail" + firstCharUpper(collection_name);

        api[api_command] = XOSAdminApp.detailShower(detailViewName, collection_name, "detail", name);
        routes[nav_url] = api_command;

        nav_url = "add" + firstCharUpper(name);
        api_command = "add" + firstCharUpper(name);
        api[api_command] = XOSAdminApp.addShower(detailViewName, collection_name, "detail", name);
        routes[nav_url] = api_command;

        nav_url = "delete" + firstCharUpper(name) + "/:id";
        api_command = "delete" + firstCharUpper(name);
        api[api_command] = XOSAdminApp.deleteShower(collection_name, name);
        routes[nav_url] = api_command;
    };

    XOSAdminApp.Router = new router({ appRoutes: routes, controller: api });
};

/* rewriteLinks

   Rewrite the links in the suit navbar from django-links to marionette
   links. This let's us intercept the navbar and make it function within
   this view rather than jumping back out to a django view.
*/

XOSAdminApp.rewriteLinks = function () {
    $("a").each(function() {
        href=$(this).attr("href");
        rewrite_href=REWRITES[href];
        if (rewrite_href) {
            $(this).attr("href", rewrite_href);
        }
    });
};

XOSAdminApp.startNavigation = function() {
    Backbone.history.start();
    XOSAdminApp.navigationStarted = true;
}

XOSAdminApp.collectionLoadChange = function() {
    stats = xos.getCollectionStatus();

    if (!XOSAdminApp.navigationStarted) {
        if (stats["isLoaded"] + stats["failedLoad"] >= stats["startedLoad"]) {
            XOSAdminApp.startNavigation();
        } else {
            $("#detail").html("
Loading...

");
            $("#xos-startup-progress").progressbar({value: stats["completedLoad"], max: stats["startedLoad"]});
        }
    }
};

XOSAdminApp.on("start", function() {
     XOSAdminApp.buildViews();

     XOSAdminApp.initRouter();

     XOSAdminApp.updateNavigationPanel();

     XOSAdminApp.rewriteLinks();

     // fire it once to initially show the progress bar
     XOSAdminApp.collectionLoadChange();

     // fire it each time the collection load status is updated
     Backbone.on("xoslib:collectionLoadChange", XOSAdminApp.collectionLoadChange);
});

$(document).ready(function(){
    XOSAdminApp.start();
});

<commit_msg>rename shower functions to createHandler functions, region for addChild<commit_after>OBJS = ['deployment', 'image', 'networkTemplate', 'network', 'networkSliver', 'networkDeployment', 'node', 'service', 'site', 'slice', 'sliceDeployment', 'slicePrivilege', 'sliver', 'user', 'sliceRole', 'userDeployment'];
NAV_OBJS = ['deployment', 'site', 'slice', 'user'];

REWRITES = {"/admin/core/deployment/": "#deployments",
            "/admin/core/site/" : "#sites",
            "/admin/core/slice/" : "#slices",
            "/admin/core/user/" : "#users"};

XOSAdminApp = new XOSApplication({
    logTableId: "#logTable",
    statusMsgId: "#statusMsg",
    hideTabsByDefault: true
});

XOSAdminApp.addRegions({
    navigation: "#navigationPanel",

    detail: "#detail",
    linkedObjs1: "#linkedObjs1",
    linkedObjs2: "#linkedObjs2",
    linkedObjs3: "#linkedObjs3",
    linkedObjs4: "#linkedObjs4",

    addChildDetail: "#xos-addchild-detail"
});

XOSAdminApp.navigateToModel = function(app, detailClass, detailNavLink, model) {
     XOSAdminApp.Router.navigate(detailNavLink + "/" + model.id, {trigger: true});
};

XOSAdminApp.navigate = function(what, modelName, modelId) {
    collection_name = modelName + "s";
    if (what=="list") {
        XOSAdminApp.Router.navigate(collection_name, {trigger: true})
    } else if (what=="detail") {
        XOSAdminApp.Router.navigate(collection_name + "/" + modelId, {trigger: true})
    } else if (what=="add") {
        XOSAdminApp.Router.navigate("add" + firstCharUpper(modelName), {trigger: true})
    }
}

ICON_CLASSES = {home: "icon-home", deployments: "icon-deployment", sites: "icon-site", slices: "icon-slice", users: "icon-user"};

XOSAdminApp.updateNavigationPanel = function() {
    buttonTemplate=$("#xos-navbutton").html();
    assert(buttonTemplate != undefined, "buttonTemplate is undefined");
    html="

";
    for (var index in NAV_OBJS) {
        name = NAV_OBJS[index];
        collection_name = name+"s";
        nav_url = "#" + collection_name;
        id = "nav-"+name;
        icon_class = ICON_CLASSES[collection_name] || "icon-cog";

        html = html + _.template(buttonTemplate, {name: collection_name, router: "XOSAdminApp.Router", routeUrl: nav_url, iconClass: icon_class});
    }

    html = html + "
";

    $("#navigationPanel").html(html);
};

XOSAdminApp.buildViews = function() {
     for (var index in OBJS) {
         name = OBJS[index];
         tr_template = '#xosAdmin-' + name + '-listitem-template';
         table_template = '#xosAdmin-' + name + '-list-template';
         detail_template = '#xosAdmin-' + name + '-detail-template';
         collection_name = name + "s";
         region_name = name + "List";
         detailNavLink = collection_name;

         detailClass = XOSDetailView.extend({
            template: detail_template,
            app: XOSAdminApp,
         });
         XOSAdminApp[collection_name + "DetailView"] = detailClass;

         itemViewClass = XOSItemView.extend({
             detailClass: detailClass,
             template: tr_template,
             app: XOSAdminApp,
             detailNavLink: detailNavLink,
         });

         listViewClass = XOSListView.extend({
             childView: itemViewClass,
             template: table_template,
             collection: xos[collection_name],
             title: name + "s",
             app: XOSAdminApp,
         });

         XOSAdminApp[collection_name + "ListView"] = listViewClass;

         xos[collection_name].fetch(); //startPolling();
     }
};

XOSAdminApp.initRouter = function() {
    router = XOSRouter;
    var api = {};
    var routes = {};

    for (var index in OBJS) {
        name = OBJS[index];
        collection_name = name + "s";
        nav_url = collection_name;
        api_command = "list" + firstCharUpper(collection_name);
        listViewName = collection_name + "ListView";
        detailViewName = collection_name + "DetailView";

        api[api_command] = XOSAdminApp.createListHandler(listViewName, collection_name, "detail", collection_name);
        routes[nav_url] = api_command;

        nav_url = collection_name + "/:id";
        api_command = "detail" + firstCharUpper(collection_name);

        api[api_command] = XOSAdminApp.createDetailHandler(detailViewName, collection_name, "detail", name);
        routes[nav_url] = api_command;

        nav_url = "add" + firstCharUpper(name);
        api_command = "add" + firstCharUpper(name);
        api[api_command] = XOSAdminApp.createAddHandler(detailViewName, collection_name, "detail", name);
        routes[nav_url] = api_command;

        nav_url = "addChild" + firstCharUpper(name) + "/:parentModel/:parentField/:parentId";
        api_command = "addChild" + firstCharUpper(name);
        api[api_command] = XOSAdminApp.createAddChildHandler(detailViewName, collection_name);
        routes[nav_url] = api_command;

        nav_url = "delete" + firstCharUpper(name) + "/:id";
        api_command = "delete" + firstCharUpper(name);
        api[api_command] = XOSAdminApp.createDeleteHandler(collection_name, name);
        routes[nav_url] = api_command;
    };

    XOSAdminApp.Router = new router({ appRoutes: routes, controller: api });
};

/* rewriteLinks

   Rewrite the links in the suit navbar from django-links to marionette
   links. This let's us intercept the navbar and make it function within
   this view rather than jumping back out to a django view.
*/

XOSAdminApp.rewriteLinks = function () {
    $("a").each(function() {
        href=$(this).attr("href");
        rewrite_href=REWRITES[href];
        if (rewrite_href) {
            $(this).attr("href", rewrite_href);
        }
    });
};

XOSAdminApp.startNavigation = function() {
    Backbone.history.start();
    XOSAdminApp.navigationStarted = true;
}

XOSAdminApp.collectionLoadChange = function() {
    stats = xos.getCollectionStatus();

    if (!XOSAdminApp.navigationStarted) {
        if (stats["isLoaded"] + stats["failedLoad"] >= stats["startedLoad"]) {
            XOSAdminApp.startNavigation();
        } else {
            $("#detail").html("
Loading...

");
            $("#xos-startup-progress").progressbar({value: stats["completedLoad"], max: stats["startedLoad"]});
        }
    }
};

XOSAdminApp.on("start", function() {
     XOSAdminApp.buildViews();

     XOSAdminApp.initRouter();

     XOSAdminApp.updateNavigationPanel();

     XOSAdminApp.rewriteLinks();

     // fire it once to initially show the progress bar
     XOSAdminApp.collectionLoadChange();

     // fire it each time the collection load status is updated
     Backbone.on("xoslib:collectionLoadChange", XOSAdminApp.collectionLoadChange);
});

$(document).ready(function(){
    XOSAdminApp.start();
});

<|endoftext|>


<commit_before>import React from 'react';
import SPELLS from 'common/SPELLS/index';
import ITEMS from 'common/ITEMS/index';
import ItemLink from 'common/ItemLink';
import { formatNumber, formatPercentage } from 'common/format';

import Analyzer, { SELECTED_PLAYER } from 'parser/core/Analyzer';
import Events from 'parser/core/Events';
import ItemHealingDone from 'interface/others/ItemHealingDone';
import Abilities from 'parser/core/modules/Abilities';

const MAX_ALLIES_HIT = 5;
const ACTIVATION_COOLDOWN = 120; // seconds

/**
 * Use: Envelop up to 5 allies in the targeted area in shared shields for 10 sec, preventing up to [x] damage taken by any of them. Damage prevented is increased when affecting multiple allies. (2 Min Cooldown)
 *
 * Allies affected have the Enveloping Protection buff, lasts 10 seconds or until all damage is absorbed.
 * Hitting 1 ally gives the base value, each additional ally hit adds 7.5% of the base value. Maximum of 5 people hit for 30% extra on top of the base value.
 */
class WardOfEnvelopment extends Analyzer {
  static dependencies = {
    abilities: Abilities,
  }

  absorbUsed = 0;
  absorbWasted = 0;
  targetsHit = 0;
  firstExpiration = false;
  uses = 0;

  constructor(...args) {
    super(...args);
    this.active = this.selectedCombatant.hasTrinket(ITEMS.WARD_OF_ENVELOPMENT.id);
    this.addEventListener(Events.cast.by(SELECTED_PLAYER).spell([SPELLS.ENVELOPING_PROTECTION]), this.onCast);
    this.addEventListener(Events.absorbed.by(SELECTED_PLAYER).spell([SPELLS.ENVELOPING_PROTECTION]), this.onAbsorb);
    this.addEventListener(Events.applybuff.by(SELECTED_PLAYER).spell([SPELLS.ENVELOPING_PROTECTION]), this.onBuff);
    this.addEventListener(Events.removebuff.by(SELECTED_PLAYER).spell([SPELLS.ENVELOPING_PROTECTION]), this.onRemoveBuff);

    if (this.active) {
      this.abilities.add({
        spell: SPELLS.ENVELOPING_PROTECTION,
        name: ITEMS.WARD_OF_ENVELOPMENT.name,
        category: Abilities.SPELL_CATEGORIES.ITEMS,
        cooldown: ACTIVATION_COOLDOWN,
        castEfficiency: {
          suggestion: true,
        },
      });
    }
  }

  onCast(event) {
    const spellId = event.ability.guid;
    if(spellId === SPELLS.ENVELOPING_PROTECTION.id){
      this.uses += 1;
      this.firstExpiration = true; // Reset it back to the first expiration
    }
  }

  onAbsorb(event) {
    const spellId = event.ability.guid;
    if(spellId === SPELLS.ENVELOPING_PROTECTION.id){
      this.absorbUsed += (event.amount || 0) + (event.absorbed || 0);
    }
  }

  onBuff(event) {
    const spellId = event.ability.guid;
    if(spellId === SPELLS.ENVELOPING_PROTECTION.id){
      this.targetsHit += 1;
    }
  }

  onRemoveBuff(event) {
    const spellId = event.ability.guid;
    if(spellId === SPELLS.ENVELOPING_PROTECTION.id){
      if (this.firstExpiration === true) { // If this is the first buff expiration for this cast, add it to the total wasted
        this.absorbWasted += event.absorb;
        this.firstExpiration = false; // Set it to false so it doesn't add the absorb for every other buff
      }
    }
  }

  get possibleUseCount() {
    return 1 + Math.floor(this.owner.fightDuration / (ACTIVATION_COOLDOWN * 1000));
  }

  // Average amount of damage absorbed on each cast
  get averageAbsorbPerCast() {
    return this.absorbUsed / this.uses;
  }

  // Total amount of absorbs put out
  get totalAbsorb() {
    return this.absorbUsed + this.absorbWasted;
  }

  // Percentage of absorbs put out that was wasted by expiring
  get wastedPercentage() {
    return this.absorbWasted / (this.absorbUsed + this.absorbWasted);
  }

  // Average number of allies hit (max of 5)
  get averageTargetsHit() {
    return this.targetsHit / this.uses;
  }

  // Extra shield value gained by hitting multiple allies (up to 5, 7.5% for each after base)
  get gainedShieldValue() {
    return Math.ceil((this.targetsHit / this.uses) - 1) * 0.075;
  }

  item() {
    return {
      item: ITEMS.WARD_OF_ENVELOPMENT,
      result: (
        <dfn data-tip={`
          You activated your Ward of Envelopment ${this.uses} of ${this.possibleUseCount} possible time${this.uses === 1 ? '' : 's'} with an average of ${formatNumber(this.averageAbsorbPerCast)} absorption per use.
          It absorbed ${formatNumber(this.absorbUsed)} out of ${formatNumber(this.totalAbsorb)} damage and ${formatNumber(this.absorbWasted)} (${formatPercentage(this.wastedPercentage)}%) was unused.

          On average you hit ${formatNumber(this.averageTargetsHit)} out of 5 allies and gained ${formatPercentage(this.gainedShieldValue)}% extra absorption value.

          `}>
          <ItemHealingDone amount={this.absorbUsed} />
        </dfn>
      ),
    };
  }

  get suggestedShieldValue() {
    return {
      actual: this.averageTargetsHit,
      isLessThan: {
        minor: 5,
        average: 4,
        major: 3,
      },
      style: 'percentage',
    };
  }
  suggestions(when) {
    when(this.suggestedShieldValue).addSuggestion((suggest, actual, recommended) => {
      return suggest(
        <>
          Your usage of <ItemLink id={ITEMS.WARD_OF_ENVELOPMENT.id} /> can be improved. Try to place the it in an area with more allies to increase the overall abosorption it provides.
        </>
      )
        .icon(ITEMS.WARD_OF_ENVELOPMENT.icon)
        .actual(`Average ${formatNumber(actual)} out of ${MAX_ALLIES_HIT} allies hit.`)
        .recommended(`5 is recommended`);
    });
  }
}

export default WardOfEnvelopment;
<commit_msg>Added referance to log which uses the item.<commit_after>import React from 'react';
import SPELLS from 'common/SPELLS/index';
import ITEMS from 'common/ITEMS/index';
import ItemLink from 'common/ItemLink';
import { formatNumber, formatPercentage } from 'common/format';

import Analyzer, { SELECTED_PLAYER } from 'parser/core/Analyzer';
import Events from 'parser/core/Events';
import ItemHealingDone from 'interface/others/ItemHealingDone';
import Abilities from 'parser/core/modules/Abilities';

const MAX_ALLIES_HIT = 5;
const ACTIVATION_COOLDOWN = 120; // seconds

/**
 * Use: Envelop up to 5 allies in the targeted area in shared shields for 10 sec, preventing up to [x] damage taken by any of them. Damage prevented is increased when affecting multiple allies. (2 Min Cooldown)
 *
 * Allies affected have the Enveloping Protection buff, lasts 10 seconds or until all damage is absorbed.
 * Hitting 1 ally gives the base value, each additional ally hit adds 7.5% of the base value. Maximum of 5 people hit for 30% extra on top of the base value.
 *
 * Tested with log:
 * https://wowanalyzer.com/report/MBqH3rCN7wa2fvjR/13-Normal+Conclave+of+the+Chosen+-+Kill+(7:45)/18-Holyschmidt
 */
class WardOfEnvelopment extends Analyzer {
  static dependencies = {
    abilities: Abilities,
  }

  absorbUsed = 0;
  absorbWasted = 0;
  targetsHit = 0;
  firstExpiration = false;
  uses = 0;

  constructor(...args) {
    super(...args);
    this.active = this.selectedCombatant.hasTrinket(ITEMS.WARD_OF_ENVELOPMENT.id);
    this.addEventListener(Events.cast.by(SELECTED_PLAYER).spell([SPELLS.ENVELOPING_PROTECTION]), this.onCast);
    this.addEventListener(Events.absorbed.by(SELECTED_PLAYER).spell([SPELLS.ENVELOPING_PROTECTION]), this.onAbsorb);
    this.addEventListener(Events.applybuff.by(SELECTED_PLAYER).spell([SPELLS.ENVELOPING_PROTECTION]), this.onBuff);
    this.addEventListener(Events.removebuff.by(SELECTED_PLAYER).spell([SPELLS.ENVELOPING_PROTECTION]), this.onRemoveBuff);

    if (this.active) {
      this.abilities.add({
        spell: SPELLS.ENVELOPING_PROTECTION,
        name: ITEMS.WARD_OF_ENVELOPMENT.name,
        category: Abilities.SPELL_CATEGORIES.ITEMS,
        cooldown: ACTIVATION_COOLDOWN,
        castEfficiency: {
          suggestion: true,
        },
      });
    }
  }

  onCast(event) {
    const spellId = event.ability.guid;
    if(spellId === SPELLS.ENVELOPING_PROTECTION.id){
      this.uses += 1;
      this.firstExpiration = true; // Reset it back to the first expiration
    }
  }

  onAbsorb(event) {
    const spellId = event.ability.guid;
    if(spellId === SPELLS.ENVELOPING_PROTECTION.id){
      this.absorbUsed += (event.amount || 0) + (event.absorbed || 0);
    }
  }

  onBuff(event) {
    const spellId = event.ability.guid;
    if(spellId === SPELLS.ENVELOPING_PROTECTION.id){
      this.targetsHit += 1;
    }
  }

  onRemoveBuff(event) {
    const spellId = event.ability.guid;
    if(spellId === SPELLS.ENVELOPING_PROTECTION.id){
      if (this.firstExpiration === true) { // If this is the first buff expiration for this cast, add it to the total wasted
        this.absorbWasted += event.absorb;
        this.firstExpiration = false; // Set it to false so it doesn't add the absorb for every other buff
      }
    }
  }

  get possibleUseCount() {
    return 1 + Math.floor(this.owner.fightDuration / (ACTIVATION_COOLDOWN * 1000));
  }

  // Average amount of damage absorbed on each cast
  get averageAbsorbPerCast() {
    return this.absorbUsed / this.uses;
  }

  // Total amount of absorbs put out
  get totalAbsorb() {
    return this.absorbUsed + this.absorbWasted;
  }

  // Percentage of absorbs put out that was wasted by expiring
  get wastedPercentage() {
    return this.absorbWasted / (this.absorbUsed + this.absorbWasted);
  }

  // Average number of allies hit (max of 5)
  get averageTargetsHit() {
    return this.targetsHit / this.uses;
  }

  // Extra shield value gained by hitting multiple allies (up to 5, 7.5% for each after base)
  get gainedShieldValue() {
    return Math.ceil((this.targetsHit / this.uses) - 1) * 0.075;
  }

  item() {
    return {
      item: ITEMS.WARD_OF_ENVELOPMENT,
      result: (
        <dfn data-tip={`
          You activated your Ward of Envelopment ${this.uses} of ${this.possibleUseCount} possible time${this.uses === 1 ? '' : 's'} with an average of ${formatNumber(this.averageAbsorbPerCast)} absorption per use.
          It absorbed ${formatNumber(this.absorbUsed)} out of ${formatNumber(this.totalAbsorb)} damage and ${formatNumber(this.absorbWasted)} (${formatPercentage(this.wastedPercentage)}%) was unused.

          On average you hit ${formatNumber(this.averageTargetsHit)} out of 5 allies and gained ${formatPercentage(this.gainedShieldValue)}% extra absorption value.

          `}>
          <ItemHealingDone amount={this.absorbUsed} />
        </dfn>
      ),
    };
  }

  get suggestedShieldValue() {
    return {
      actual: this.averageTargetsHit,
      isLessThan: {
        minor: 5,
        average: 4,
        major: 3,
      },
      style: 'percentage',
    };
  }
  suggestions(when) {
    when(this.suggestedShieldValue).addSuggestion((suggest, actual, recommended) => {
      return suggest(
        <>
          Your usage of <ItemLink id={ITEMS.WARD_OF_ENVELOPMENT.id} /> can be improved. Try to place the it in an area with more allies to increase the overall abosorption it provides.
        </>
      )
        .icon(ITEMS.WARD_OF_ENVELOPMENT.icon)
        .actual(`Average ${formatNumber(actual)} out of ${MAX_ALLIES_HIT} allies hit.`)
        .recommended(`5 is recommended`);
    });
  }
}

export default WardOfEnvelopment;
<|endoftext|>