<commit_before>/*
    ***** BEGIN LICENSE BLOCK *****

    Copyright © 2009 Center for History and New Media
                     George Mason University, Fairfax, Virginia, USA
                     http://zotero.org

    This file is part of Zotero.

    Zotero is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Zotero is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Zotero.  If not, see <http://www.gnu.org/licenses/>.

    ***** END LICENSE BLOCK *****
*/

Zotero.File = new function(){
	this.getExtension = getExtension;
	this.getClosestDirectory = getClosestDirectory;
	this.getSample = getSample;
	this.getContents = getContents;
	this.getContentsFromURL = getContentsFromURL;
	this.putContents = putContents;
	this.getValidFileName = getValidFileName;
	this.copyToUnique = this.copyToUnique;
	this.getCharsetFromFile = getCharsetFromFile;
	this.addCharsetListener = addCharsetListener;

	function getExtension(file){
		var pos = file.leafName.lastIndexOf('.');
		return pos==-1 ? '' : file.leafName.substr(pos+1);
	}

	/*
	 * Traverses up the filesystem from a file until it finds an existing
	 *  directory, or false if it hits the root
	 */
	function getClosestDirectory(file) {
		var dir = file.parent;

		while (dir && !dir.exists()) {
			var dir = dir.parent;
		}

		if (dir && dir.exists()) {
			return dir;
		}
		return false;
	}

	/*
	 * Get the first 128 bytes of the file as a string (multibyte-safe)
	 */
	function getSample(file) {
		return this.getContents(file, null, 128);
	}

	/**
	 * Get contents of a binary file
	 */
	this.getBinaryContents = function(file) {
		var iStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
					 .createInstance(Components.interfaces.nsIFileInputStream);
		iStream.init(file, 0x01, 0664, 0);
		var bStream = Components.classes["@mozilla.org/binaryinputstream;1"]
					 .createInstance(Components.interfaces.nsIBinaryInputStream);
		bStream.setInputStream(iStream);
		var string = bStream.readBytes(file.fileSize);
		iStream.close();
		return string;
	}

	function getContents(file, charset, maxLength){
		var fis = Components.classes["@mozilla.org/network/file-input-stream;1"].
			createInstance(Components.interfaces.nsIFileInputStream);
		fis.init(file, 0x01, 0664, 0);

		if (charset){
			charset = Zotero.CharacterSets.getName(charset);
		}

		if (!charset){
			charset = "UTF-8";
		}

		const replacementChar
			= Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
		var is = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
			.createInstance(Components.interfaces.nsIConverterInputStream);
		is.init(fis, charset, 4096, replacementChar);
		var chars = 0;

		var contents = [], str = {};
		while (is.readString(4096, str) != 0) {
			var strLen = str.value.length;

			if (maxLength) {
				if ((chars + strLen) > maxLength) {
					var remainder = maxLength - chars;
					contents.push(str.value.slice(0, remainder));
					break;
				}
				chars += strLen;
			}

			contents.push(str.value);
		}

		is.close();

		return contents.join('');
	}

	/**
	 * Return the contents of a file as a byte array
	 */
	this.getBinaryContents = function (bfile) {
		var istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
			.createInstance(Components.interfaces.nsIFileInputStream);
		istream.init(bfile, -1, -1, false);
		var bstream = Components.classes["@mozilla.org/binaryinputstream;1"]
			.createInstance(Components.interfaces.nsIBinaryInputStream);
		bstream.setInputStream(istream);
		return bstream.readBytes(bstream.available());
	}

	/*
	 * Return the contents of a URL as a string
	 *
	 * Runs synchronously, so should only be run on local (e.g. chrome) URLs
	 */
	function getContentsFromURL(url) {
		var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
						.createInstance();
		xmlhttp.open('GET', url, false);
		xmlhttp.send(null);
		return xmlhttp.responseText;
	}

	/*
	 * Write string to a file, overwriting existing file if necessary
	 */
	function putContents(file, str) {
		if (file.exists()) {
			file.remove(null);
		}
		var fos = Components.classes["@mozilla.org/network/file-output-stream;1"].
				createInstance(Components.interfaces.nsIFileOutputStream);
		fos.init(file, 0x02 | 0x08 | 0x20, 0664, 0);  // write, create, truncate

		var os = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
						   .createInstance(Components.interfaces.nsIConverterOutputStream);
		os.init(fos, "UTF-8", 4096, "?".charCodeAt(0));
		os.writeString(str);
		os.close();

		fos.close();
	}

	function copyToUnique(file, newFile) {
		newFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
		var newName = newFile.leafName;
		newFile.remove(null);

		// Copy file to unique name
		file.copyTo(newFile.parent, newName);
		return file;
	}

	/**
	 * Copies all files from dir into newDir
	 */
	this.copyDirectory = function (dir, newDir) {
		if (!dir.exists()) {
			throw ("Directory doesn't exist in Zotero.File.copyDirectory()");
		}
		var otherFiles = dir.directoryEntries;
		while (otherFiles.hasMoreElements()) {
			var file = otherFiles.getNext();
			file.QueryInterface(Components.interfaces.nsIFile);
			file.copyTo(newDir, null);
		}
	}

	this.createDirectoryIfMissing = function (dir) {
		if (!dir.exists() || !dir.isDirectory()) {
			if (dir.exists() && !dir.isDirectory()) {
				dir.remove(null);
			}
			dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
		}
	}

	/**
	 * Strip potentially invalid characters
	 *
	 * See http://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
	 *
	 * @param	{String}	fileName
	 * @param	{Boolean}	[skipXML=false]		Don't strip characters invalid in XML
	 */
	function getValidFileName(fileName, skipXML) {
		// TODO: use space instead, and figure out what's doing extra
		// URL encode when saving attachments that trigger this
		fileName = fileName.replace(/[\/\\\?%\*:|"<>]/g, '');
		// Replace newlines and tabs (which shouldn't be in the string in the first place) with spaces
		fileName = fileName.replace(/[\n\t]/g, ' ');
		if (!skipXML) {
			// Strip characters not valid in XML, since they won't sync and they're probably unwanted
			fileName = fileName.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, '');
		}
		// Don't allow blank filename
		if (!fileName) {
			fileName = '_';
		}
		return fileName;
	}

	/*
	 * Not implemented, but it'd sure be great if it were
	 */
	function getCharsetFromByteArray(arr) {

	}

	/*
	 * An extraordinarily inelegant way of getting the character set of a
	 * text file using a hidden browser
	 *
	 * I'm quite sure there's a better way
	 *
	 * Note: This is for text files -- don't run on other files
	 *
	 * 'callback' is the function to pass the charset (and, if provided, 'args')
	 * to after detection is complete
	 */
	function getCharsetFromFile(file, mimeType, callback, args){
		if (!file || !file.exists()){
			callback(false, args);
			return;
		}

		if (mimeType.substr(0, 5) != 'text/' ||
				!Zotero.MIME.hasInternalHandler(mimeType, this.getExtension(file))) {
			callback(false, args);
			return;
		}

		var browser = Zotero.Browser.createHiddenBrowser();

		var url = Components.classes["@mozilla.org/network/protocol;1?name=file"]
				.getService(Components.interfaces.nsIFileProtocolHandler)
				.getURLSpecFromFile(file);

		this.addCharsetListener(browser, function (charset, args) {
			callback(charset, args);
			Zotero.Browser.deleteHiddenBrowser(browser);
		}, args);

		browser.loadURI(url);
	}

	/*
	 * Attach a load listener to a browser object to perform charset detection
	 *
	 * We make sure the universal character set detector is set to the
	 * universal_charset_detector (temporarily changing it if not--shhhh)
	 *
	 * 'callback' is the function to pass the charset (and, if provided, 'args')
	 * to after detection is complete
	 */
	function addCharsetListener(browser, callback, args){
		var prefService = Components.classes["@mozilla.org/preferences-service;1"]
							.getService(Components.interfaces.nsIPrefBranch);
		var oldPref = prefService.getCharPref('intl.charset.detector');
		var newPref = 'universal_charset_detector';
		//Zotero.debug("Default character detector is " + (oldPref ? oldPref : '(none)'));

		if (oldPref != newPref){
			//Zotero.debug('Setting character detector to universal_charset_detector');
			prefService.setCharPref('intl.charset.detector', 'universal_charset_detector');
		}

		var onpageshow = function(){
			// ignore spurious about:blank loads
			if(browser.contentDocument.location.href == "about:blank") return;

			browser.removeEventListener("pageshow", onpageshow, false);

			var charset = browser.contentDocument.characterSet;
			Zotero.debug("Detected character set '" + charset + "'");

			//Zotero.debug('Resetting character detector to ' + (oldPref ? oldPref : '(none)'));
			prefService.setCharPref('intl.charset.detector', oldPref);

			callback(charset, args);
		};

		browser.addEventListener("pageshow", onpageshow, false);
	}

	// TODO: localize
	this.checkFileAccessError = function (e, file, operation) {
		if (file) {
			var str = "The file '" + file.path + "' ";
		}
		else {
			var str = "A file ";
		}

		switch (operation) {
			case 'create':
				var opWord = "created";
				break;

			case 'update':
				var opWord = "updated";
				break;

			case 'delete':
				var opWord = "deleted";
				break;

			default:
				var opWord = "updated";
		}

		if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED' || e.name == 'NS_ERROR_FILE_IS_LOCKED'
				// Shows up on some Windows systems
				|| e.name == 'NS_ERROR_FAILURE') {
			Zotero.debug(e);
			// TODO: localize
			str = str + "cannot be " + opWord + ".";
			var checkFileWindows = "Check that the file is not currently "
				+ "in use and that it is not marked as read-only. To check "
				+ "all files in your Zotero data directory, right-click on "
				+ "the 'zotero' directory, click Properties, and ensure that "
				+ "the Read-Only checkbox is empty.";
			var checkFileOther = "Check that the file is not currently "
				+ "in use and that its permissions allow write access.";
			var msg = str + " "
					+ (Zotero.isWin ? checkFileWindows : checkFileOther)
					+ "\n\n"
					+ "Restarting your computer or disabling security "
					+ "software may also help.";

			if (operation == 'create') {
				var e = new Zotero.Error(
					msg,
					0,
					{
						dialogButtonText: "Show Parent Directory",
						dialogButtonCallback: function () {
							try {
								file.parent.QueryInterface(Components.interfaces.nsILocalFile).reveal();
							}
							// Unsupported on some platforms
							catch (e2) {
								Zotero.debug(e2);
							}
						}
					}
				);
			}
			else {
				var e = new Zotero.Error(
					msg,
					0,
					{
						dialogButtonText: "Show File",
						dialogButtonCallback: function () {
							try {
								file.QueryInterface(Components.interfaces.nsILocalFile);
								file.reveal();
							}
							// Unsupported on some platforms
							catch (e2) {
								Zotero.debug(e2);
							}
						}
					}
				);
			}
		}

		throw (e);
	}
}
<commit_msg>Replace various space characters in filenames that break on Windows<commit_after>/*
    ***** BEGIN LICENSE BLOCK *****

    Copyright © 2009 Center for History and New Media
                     George Mason University, Fairfax, Virginia, USA
                     http://zotero.org

    This file is part of Zotero.

    Zotero is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Zotero is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Zotero.  If not, see <http://www.gnu.org/licenses/>.

    ***** END LICENSE BLOCK *****
*/

Zotero.File = new function(){
	this.getExtension = getExtension;
	this.getClosestDirectory = getClosestDirectory;
	this.getSample = getSample;
	this.getContents = getContents;
	this.getContentsFromURL = getContentsFromURL;
	this.putContents = putContents;
	this.getValidFileName = getValidFileName;
	this.copyToUnique = this.copyToUnique;
	this.getCharsetFromFile = getCharsetFromFile;
	this.addCharsetListener = addCharsetListener;

	function getExtension(file){
		var pos = file.leafName.lastIndexOf('.');
		return pos==-1 ? '' : file.leafName.substr(pos+1);
	}

	/*
	 * Traverses up the filesystem from a file until it finds an existing
	 *  directory, or false if it hits the root
	 */
	function getClosestDirectory(file) {
		var dir = file.parent;

		while (dir && !dir.exists()) {
			var dir = dir.parent;
		}

		if (dir && dir.exists()) {
			return dir;
		}
		return false;
	}

	/*
	 * Get the first 128 bytes of the file as a string (multibyte-safe)
	 */
	function getSample(file) {
		return this.getContents(file, null, 128);
	}

	/**
	 * Get contents of a binary file
	 */
	this.getBinaryContents = function(file) {
		var iStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
					 .createInstance(Components.interfaces.nsIFileInputStream);
		iStream.init(file, 0x01, 0664, 0);
		var bStream = Components.classes["@mozilla.org/binaryinputstream;1"]
					 .createInstance(Components.interfaces.nsIBinaryInputStream);
		bStream.setInputStream(iStream);
		var string = bStream.readBytes(file.fileSize);
		iStream.close();
		return string;
	}

	function getContents(file, charset, maxLength){
		var fis = Components.classes["@mozilla.org/network/file-input-stream;1"].
			createInstance(Components.interfaces.nsIFileInputStream);
		fis.init(file, 0x01, 0664, 0);

		if (charset){
			charset = Zotero.CharacterSets.getName(charset);
		}

		if (!charset){
			charset = "UTF-8";
		}

		const replacementChar
			= Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
		var is = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
			.createInstance(Components.interfaces.nsIConverterInputStream);
		is.init(fis, charset, 4096, replacementChar);
		var chars = 0;

		var contents = [], str = {};
		while (is.readString(4096, str) != 0) {
			var strLen = str.value.length;

			if (maxLength) {
				if ((chars + strLen) > maxLength) {
					var remainder = maxLength - chars;
					contents.push(str.value.slice(0, remainder));
					break;
				}
				chars += strLen;
			}

			contents.push(str.value);
		}

		is.close();

		return contents.join('');
	}

	/**
	 * Return the contents of a file as a byte array
	 */
	this.getBinaryContents = function (bfile) {
		var istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
			.createInstance(Components.interfaces.nsIFileInputStream);
		istream.init(bfile, -1, -1, false);
		var bstream = Components.classes["@mozilla.org/binaryinputstream;1"]
			.createInstance(Components.interfaces.nsIBinaryInputStream);
		bstream.setInputStream(istream);
		return bstream.readBytes(bstream.available());
	}

	/*
	 * Return the contents of a URL as a string
	 *
	 * Runs synchronously, so should only be run on local (e.g. chrome) URLs
	 */
	function getContentsFromURL(url) {
		var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
						.createInstance();
		xmlhttp.open('GET', url, false);
		xmlhttp.send(null);
		return xmlhttp.responseText;
	}

	/*
	 * Write string to a file, overwriting existing file if necessary
	 */
	function putContents(file, str) {
		if (file.exists()) {
			file.remove(null);
		}
		var fos = Components.classes["@mozilla.org/network/file-output-stream;1"].
				createInstance(Components.interfaces.nsIFileOutputStream);
		fos.init(file, 0x02 | 0x08 | 0x20, 0664, 0);  // write, create, truncate

		var os = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
						   .createInstance(Components.interfaces.nsIConverterOutputStream);
		os.init(fos, "UTF-8", 4096, "?".charCodeAt(0));
		os.writeString(str);
		os.close();

		fos.close();
	}

	function copyToUnique(file, newFile) {
		newFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
		var newName = newFile.leafName;
		newFile.remove(null);

		// Copy file to unique name
		file.copyTo(newFile.parent, newName);
		return file;
	}

	/**
	 * Copies all files from dir into newDir
	 */
	this.copyDirectory = function (dir, newDir) {
		if (!dir.exists()) {
			throw ("Directory doesn't exist in Zotero.File.copyDirectory()");
		}
		var otherFiles = dir.directoryEntries;
		while (otherFiles.hasMoreElements()) {
			var file = otherFiles.getNext();
			file.QueryInterface(Components.interfaces.nsIFile);
			file.copyTo(newDir, null);
		}
	}

	this.createDirectoryIfMissing = function (dir) {
		if (!dir.exists() || !dir.isDirectory()) {
			if (dir.exists() && !dir.isDirectory()) {
				dir.remove(null);
			}
			dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
		}
	}

	/**
	 * Strip potentially invalid characters
	 *
	 * See http://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
	 *
	 * @param	{String}	fileName
	 * @param	{Boolean}	[skipXML=false]		Don't strip characters invalid in XML
	 */
	function getValidFileName(fileName, skipXML) {
		// TODO: use space instead, and figure out what's doing extra
		// URL encode when saving attachments that trigger this
		fileName = fileName.replace(/[\/\\\?%\*:|"<>]/g, '');
		// Replace newlines and tabs (which shouldn't be in the string in the first place) with spaces
		fileName = fileName.replace(/[\n\t]/g, ' ');
		// Replace various thin spaces
		fileName = fileName.replace(/[\u2000-\u200A]/g, ' ');
		// Replace zero-width spaces
		fileName = fileName.replace(/[\u200B-\u200E]/g, '');
		if (!skipXML) {
			// Strip characters not valid in XML, since they won't sync and they're probably unwanted
			fileName = fileName.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, '');
		}
		// Don't allow blank filename
		if (!fileName) {
			fileName = '_';
		}
		return fileName;
	}

	/*
	 * Not implemented, but it'd sure be great if it were
	 */
	function getCharsetFromByteArray(arr) {

	}

	/*
	 * An extraordinarily inelegant way of getting the character set of a
	 * text file using a hidden browser
	 *
	 * I'm quite sure there's a better way
	 *
	 * Note: This is for text files -- don't run on other files
	 *
	 * 'callback' is the function to pass the charset (and, if provided, 'args')
	 * to after detection is complete
	 */
	function getCharsetFromFile(file, mimeType, callback, args){
		if (!file || !file.exists()){
			callback(false, args);
			return;
		}

		if (mimeType.substr(0, 5) != 'text/' ||
				!Zotero.MIME.hasInternalHandler(mimeType, this.getExtension(file))) {
			callback(false, args);
			return;
		}

		var browser = Zotero.Browser.createHiddenBrowser();

		var url = Components.classes["@mozilla.org/network/protocol;1?name=file"]
				.getService(Components.interfaces.nsIFileProtocolHandler)
				.getURLSpecFromFile(file);

		this.addCharsetListener(browser, function (charset, args) {
			callback(charset, args);
			Zotero.Browser.deleteHiddenBrowser(browser);
		}, args);

		browser.loadURI(url);
	}

	/*
	 * Attach a load listener to a browser object to perform charset detection
	 *
	 * We make sure the universal character set detector is set to the
	 * universal_charset_detector (temporarily changing it if not--shhhh)
	 *
	 * 'callback' is the function to pass the charset (and, if provided, 'args')
	 * to after detection is complete
	 */
	function addCharsetListener(browser, callback, args){
		var prefService = Components.classes["@mozilla.org/preferences-service;1"]
							.getService(Components.interfaces.nsIPrefBranch);
		var oldPref = prefService.getCharPref('intl.charset.detector');
		var newPref = 'universal_charset_detector';
		//Zotero.debug("Default character detector is " + (oldPref ? oldPref : '(none)'));

		if (oldPref != newPref){
			//Zotero.debug('Setting character detector to universal_charset_detector');
			prefService.setCharPref('intl.charset.detector', 'universal_charset_detector');
		}

		var onpageshow = function(){
			// ignore spurious about:blank loads
			if(browser.contentDocument.location.href == "about:blank") return;

			browser.removeEventListener("pageshow", onpageshow, false);

			var charset = browser.contentDocument.characterSet;
			Zotero.debug("Detected character set '" + charset + "'");

			//Zotero.debug('Resetting character detector to ' + (oldPref ? oldPref : '(none)'));
			prefService.setCharPref('intl.charset.detector', oldPref);

			callback(charset, args);
		};

		browser.addEventListener("pageshow", onpageshow, false);
	}

	// TODO: localize
	this.checkFileAccessError = function (e, file, operation) {
		if (file) {
			var str = "The file '" + file.path + "' ";
		}
		else {
			var str = "A file ";
		}

		switch (operation) {
			case 'create':
				var opWord = "created";
				break;

			case 'update':
				var opWord = "updated";
				break;

			case 'delete':
				var opWord = "deleted";
				break;

			default:
				var opWord = "updated";
		}

		if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED' || e.name == 'NS_ERROR_FILE_IS_LOCKED'
				// Shows up on some Windows systems
				|| e.name == 'NS_ERROR_FAILURE') {
			Zotero.debug(e);
			// TODO: localize
			str = str + "cannot be " + opWord + ".";
			var checkFileWindows = "Check that the file is not currently "
				+ "in use and that it is not marked as read-only. To check "
				+ "all files in your Zotero data directory, right-click on "
				+ "the 'zotero' directory, click Properties, and ensure that "
				+ "the Read-Only checkbox is empty.";
			var checkFileOther = "Check that the file is not currently "
				+ "in use and that its permissions allow write access.";
			var msg = str + " "
					+ (Zotero.isWin ? checkFileWindows : checkFileOther)
					+ "\n\n"
					+ "Restarting your computer or disabling security "
					+ "software may also help.";

			if (operation == 'create') {
				var e = new Zotero.Error(
					msg,
					0,
					{
						dialogButtonText: "Show Parent Directory",
						dialogButtonCallback: function () {
							try {
								file.parent.QueryInterface(Components.interfaces.nsILocalFile).reveal();
							}
							// Unsupported on some platforms
							catch (e2) {
								Zotero.debug(e2);
							}
						}
					}
				);
			}
			else {
				var e = new Zotero.Error(
					msg,
					0,
					{
						dialogButtonText: "Show File",
						dialogButtonCallback: function () {
							try {
								file.QueryInterface(Components.interfaces.nsILocalFile);
								file.reveal();
							}
							// Unsupported on some platforms
							catch (e2) {
								Zotero.debug(e2);
							}
						}
					}
				);
			}
		}

		throw (e);
	}
}
<|endoftext|>


<commit_before>import Promise from 'bluebird';
import contentTypeParser from 'content-type';
import os from 'os';
import fs from 'fs';
import fsExtra from 'fs-extra';
import ApifyClient from 'apify-client';
import psTree from 'ps-tree';
import pidusage from 'pidusage';
import requestPromise from 'request-promise';
import _ from 'underscore';
import XRegExp from 'xregexp';
import { delayPromise } from 'apify-shared/utilities';
import { ENV_VARS } from './constants';

export const PID_USAGE_NOT_FOUND_ERROR = 'No maching pid found';

export const URL_NO_COMMAS_REGEX = XRegExp('https?://(www\\.)?[-\\p{L}0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-\\p{L}0-9@:%_\\+.~#?&//=\\(\\)]*)', 'gi'); // eslint-disable-line
export const URL_WITH_COMMAS_REGEX = XRegExp('https?://(www\\.)?[-\\p{L}0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-\\p{L}0-9@:%_\\+.,~#?&//=\\(\\)]*)', 'gi'); // eslint-disable-line

const ensureDirPromised = Promise.promisify(fsExtra.ensureDir);
const psTreePromised = Promise.promisify(psTree);

/**
 * Creates an instance of ApifyClient using options as defined in the environment variables.
 * This function is exported to enable unit testing.
 *
 * @returns {*}
 * @ignore
 */
export const newClient = () => {
    const opts = {
        userId: process.env[ENV_VARS.USER_ID] || null,
        token: process.env[ENV_VARS.TOKEN] || null,
    };

    // Only set baseUrl if overridden by env var, so that 'https://api.apify.com' is used by default.
    // This simplifies local development, which should run against production unless user wants otherwise.
    const apiBaseUrl = process.env[ENV_VARS.API_BASE_URL];
    if (apiBaseUrl) opts.baseUrl = apiBaseUrl;

    return new ApifyClient(opts);
};

/**
 * A default instance of the `ApifyClient` class provided
 * by the apify-client NPM package.
 * The instance is created when the `apify` package is first imported
 * and it is configured using the `APIFY_API_BASE_URL`, `APIFY_USER_ID` and `APIFY_TOKEN`
 * environment variables.
 *
 * After that, the instance is used for all underlying calls to the Apify API
 * in functions such as Apify.getValue()
 * or Apify.call().
 * The settings of the client can be globally altered by calling the
 * Apify.client.setOptions() function.
 * Just be careful, it might have undesired effects on other functions provided by this package.
 *
 * @memberof module:Apify
 * @name client
 * @instance
 */
export const apifyClient = newClient();

/**
 * Returns a result of `Promise.resolve()`.
 *
 * @returns {*}
 *
 * @ignore
 */
export const newPromise = () => {
    return Promise.resolve();
};

/**
 * Adds charset=utf-8 to given content type if this parameter is missing.
 *
 * @param contentType
 * @returns {string}
 *
 * @ignore
 */
export const addCharsetToContentType = (contentType) => {
    if (!contentType) return contentType;

    const parsed = contentTypeParser.parse(contentType);

    if (parsed.parameters.charset) return contentType;

    parsed.parameters.charset = 'utf-8';

    return contentTypeParser.format(parsed);
};

let isDockerPromiseCache;
const createIsDockerPromise = () => {
    const promise1 = Promise
        .promisify(fs.stat)('/.dockerenv')
        .then(() => true)
        .catch(() => false);

    const promise2 = Promise
        .promisify(fs.readFile)('/proc/self/cgroup', 'utf8')
        .then(content => content.indexOf('docker') !== -1)
        .catch(() => false);

    return Promise
        .all([promise1, promise2])
        .then(([result1, result2]) => result1 || result2);
};

/**
 * Returns promise that resolves to true if the code is running in a Docker container.
 *
 * @return {Promise}
 *
 * @memberof module:Apify
 * @name isDocker
 * @instance
 * @function
 */
export const isDocker = (forceReset) => {
    // Parameter forceReset is just internal for unit tests.
    if (!isDockerPromiseCache || forceReset) isDockerPromiseCache = createIsDockerPromise();

    return isDockerPromiseCache;
};

/**
 * Sums an array of numbers.
 *
 * @param  {Array} arr An array of numbers.
 * @return {Number} Sum of the numbers.
 *
 * @ignore
 */
export const sum = arr => arr.reduce((total, c) => total + c, 0);

/**
 * Computes an average of an array of numbers.
 *
 * @param  {Array} arr An array of numbers.
 * @return {Number} Average value.
 *
 * @ignore
 */
export const avg = arr => sum(arr) / arr.length;

/**
 * Returns memory statistics of the container, which is an object with the following properties:
 *
 * ```javascript
 * {
 *   // Total memory available to the act
 *   totalBytes: Number,
 *
 *   // Amount of free memory
 *   freeBytes: Number,
 *
 *   // Amount of memory used (= totalBytes - freeBytes)
 *   usedBytes: Number,
 *   // Amount of memory used by main NodeJS process
 *   mainProcessBytes: Number,
 *   // Amount of memory used by child processes of main NodeJS process
 *   childProcessesBytes: Number,
 * }
 * ```
 *
 * @returns {Promise} Returns a promise.
 *
 * @memberof module:Apify
 * @name getMemoryInfo
 * @instance
 * @function
 */
export const getMemoryInfo = () => {
    // module.exports must be here so that we can mock it.
    const isDockerPromise = module.exports.isDocker();

    const childProcessesUsagePromise = psTreePromised(process.pid)
        .then((childProcesses) => {
            const pids = _.pluck(childProcesses, 'PID');

            const promises = pids.map((pid) => {
                return pidusage(pid)
                    .then(info => info.memory)
                    .catch((err) => {
                        if (err.message === PID_USAGE_NOT_FOUND_ERROR) return 0;

                        throw err;
                    });
            });

            return Promise.all(promises).then(infos => sum(infos));
        });

    return Promise
        .all([
            isDockerPromise,
            childProcessesUsagePromise,
        ])
        .then(([isDockerVar, childProcessesBytes]) => {
            if (!isDockerVar) {
                const freeBytes = os.freemem();
                const totalBytes = os.totalmem();
                const usedBytes = totalBytes - freeBytes;

                return Promise.resolve({
                    totalBytes,
                    freeBytes,
                    usedBytes,
                    mainProcessBytes: usedBytes - childProcessesBytes,
                    childProcessesBytes,
                });
            }

            // This must be promisified here so that we can Mock it.
            const readPromised = Promise.promisify(fs.readFile);

            return Promise
                .all([
                    readPromised('/sys/fs/cgroup/memory/memory.limit_in_bytes'),
                    readPromised('/sys/fs/cgroup/memory/memory.usage_in_bytes'),
                ])
                .then(([totalBytesStr, usedBytesStr]) => {
                    const totalBytes = parseInt(totalBytesStr, 10);
                    const usedBytes = parseInt(usedBytesStr, 10);

                    return {
                        totalBytes,
                        freeBytes: totalBytes - usedBytes,
                        usedBytes,
                        mainProcessBytes: usedBytes - childProcessesBytes,
                        childProcessesBytes,
                    };
                });
        });
};

/**
 * Helper function that detrermines if given parameter is an instance of Promise.
 *
 * @ignore
 */
export const isPromise = (maybePromise) => {
    return maybePromise && typeof maybePromise.then === 'function' && typeof maybePromise.catch === 'function';
};

/**
 * Returns true if node is in production environment and false otherwise.
 *
 * @ignore
 */
export const isProduction = () => process.env.NODE_ENV === 'production';

/**
 * Helper function used for local implementations. Creates dir.
 *
 * @ignore
 */
export const ensureDirExists = path => ensureDirPromised(path);

/**
 * Helper function that returns the first key from plan object.
 *
 * @ignore
 */
export const getFirstKey = (dict) => {
    for (const key in dict) { // eslint-disable-line guard-for-in, no-restricted-syntax
        return key;
    }
};

/**
 * Gets a typical path to Chrome executable, depending on the current operating system.
 *
 * @return {string}
 * @ignore
 */
export const getTypicalChromeExecutablePath = () => {
    switch (os.platform()) {
    case 'darwin': return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
    case 'win32': return 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe';
    default: return 'google-chrome';
    }
};

/**
 * Creates a promise that after given time gets rejected with given error.
 *
 * @return {Promise<Error>}
 * @ignore.
 */
export const createTimeoutPromise = (timeoutMillis, errorMessage) => {
    return delayPromise(timeoutMillis).then(() => {
        throw new Error(errorMessage);
    });
};

/**
 * Returns `true` when code is running on Apify platform and `false` otherwise (for example locally).
 *
 * @returns {Boolean}
 *
 * @memberof module:Apify
 * @name isAtHome
 * @instance
 * @function
 */
export const isAtHome = () => !!process.env[ENV_VARS.IS_AT_HOME];

/**
 * Returns a promise that resolves after a specific period of time. This is useful to implement waiting
 * in your code, e.g. to prevent overloading of target website or to avoid bot detection.
 *
 * Example usage:
 * ```javascript
 * const Apify = require('apify);
 *
 * ...
 *
 * // Sleep 1.5 seconds
 * await Apify.utils.sleep(1500);
 * ```
 * @param millis Period of time to sleep, in milliseconds. If not a positive number, the returned promise resolves immediately.
 * @memberof utils
 * @return {Promise}
 */
const sleep = (millis) => {
    return delayPromise(millis);
};

/**
 * A namespace that contains various utilities.
 *
 * Example usage:
 * ```javascript
 * const Apify = require('apify);
 *
 * ...
 *
 * // Sleep 1.5 seconds
 * await Apify.utils.sleep(1500);
 * ```
 * @namespace utils
 */
export const publicUtils = {
    sleep,
};

/**
 * Returns a promise that resolves to an array of urls parsed from the resource
 * available at the provided url.
 *
 * @param {String} url
 * @param {String} encoding
 * @param {RegExp} urlRegExp
 * @returns {Promise<Array>}
 */
export const downloadListOfUrls = ({ url, encoding, urlRegExp }) => {
    if (!url) return Promise.reject(new Error('Resource URL must be provided.'));
    encoding = encoding || 'utf8';
    return requestPromise.get({ url, encoding })
        .then(string => extractUrls({ string, urlRegExp }));
};

/**
 * Collects all URLs in an arbitrary string to an array.
 * @param string
 * @param urlRegExp
 * @param keepCommas
 * @returns {Array}
 */
export const extractUrls = ({ string, urlRegExp = URL_NO_COMMAS_REGEX }) => {
    return string.match(urlRegExp) || [];
};
<commit_msg>Fix default value when null in extractUrls()<commit_after>import Promise from 'bluebird';
import contentTypeParser from 'content-type';
import os from 'os';
import fs from 'fs';
import fsExtra from 'fs-extra';
import ApifyClient from 'apify-client';
import psTree from 'ps-tree';
import pidusage from 'pidusage';
import requestPromise from 'request-promise';
import _ from 'underscore';
import XRegExp from 'xregexp';
import { delayPromise } from 'apify-shared/utilities';
import { ENV_VARS } from './constants';

export const PID_USAGE_NOT_FOUND_ERROR = 'No maching pid found';

export const URL_NO_COMMAS_REGEX = XRegExp('https?://(www\\.)?[-\\p{L}0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-\\p{L}0-9@:%_\\+.~#?&//=\\(\\)]*)', 'gi'); // eslint-disable-line
export const URL_WITH_COMMAS_REGEX = XRegExp('https?://(www\\.)?[-\\p{L}0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-\\p{L}0-9@:%_\\+.,~#?&//=\\(\\)]*)', 'gi'); // eslint-disable-line

const ensureDirPromised = Promise.promisify(fsExtra.ensureDir);
const psTreePromised = Promise.promisify(psTree);

/**
 * Creates an instance of ApifyClient using options as defined in the environment variables.
 * This function is exported to enable unit testing.
 *
 * @returns {*}
 * @ignore
 */
export const newClient = () => {
    const opts = {
        userId: process.env[ENV_VARS.USER_ID] || null,
        token: process.env[ENV_VARS.TOKEN] || null,
    };

    // Only set baseUrl if overridden by env var, so that 'https://api.apify.com' is used by default.
    // This simplifies local development, which should run against production unless user wants otherwise.
    const apiBaseUrl = process.env[ENV_VARS.API_BASE_URL];
    if (apiBaseUrl) opts.baseUrl = apiBaseUrl;

    return new ApifyClient(opts);
};

/**
 * A default instance of the `ApifyClient` class provided
 * by the apify-client NPM package.
 * The instance is created when the `apify` package is first imported
 * and it is configured using the `APIFY_API_BASE_URL`, `APIFY_USER_ID` and `APIFY_TOKEN`
 * environment variables.
 *
 * After that, the instance is used for all underlying calls to the Apify API
 * in functions such as Apify.getValue()
 * or Apify.call().
 * The settings of the client can be globally altered by calling the
 * Apify.client.setOptions() function.
 * Just be careful, it might have undesired effects on other functions provided by this package.
 *
 * @memberof module:Apify
 * @name client
 * @instance
 */
export const apifyClient = newClient();

/**
 * Returns a result of `Promise.resolve()`.
 *
 * @returns {*}
 *
 * @ignore
 */
export const newPromise = () => {
    return Promise.resolve();
};

/**
 * Adds charset=utf-8 to given content type if this parameter is missing.
 *
 * @param contentType
 * @returns {string}
 *
 * @ignore
 */
export const addCharsetToContentType = (contentType) => {
    if (!contentType) return contentType;

    const parsed = contentTypeParser.parse(contentType);

    if (parsed.parameters.charset) return contentType;

    parsed.parameters.charset = 'utf-8';

    return contentTypeParser.format(parsed);
};

let isDockerPromiseCache;
const createIsDockerPromise = () => {
    const promise1 = Promise
        .promisify(fs.stat)('/.dockerenv')
        .then(() => true)
        .catch(() => false);

    const promise2 = Promise
        .promisify(fs.readFile)('/proc/self/cgroup', 'utf8')
        .then(content => content.indexOf('docker') !== -1)
        .catch(() => false);

    return Promise
        .all([promise1, promise2])
        .then(([result1, result2]) => result1 || result2);
};

/**
 * Returns promise that resolves to true if the code is running in a Docker container.
 *
 * @return {Promise}
 *
 * @memberof module:Apify
 * @name isDocker
 * @instance
 * @function
 */
export const isDocker = (forceReset) => {
    // Parameter forceReset is just internal for unit tests.
    if (!isDockerPromiseCache || forceReset) isDockerPromiseCache = createIsDockerPromise();

    return isDockerPromiseCache;
};

/**
 * Sums an array of numbers.
 *
 * @param  {Array} arr An array of numbers.
 * @return {Number} Sum of the numbers.
 *
 * @ignore
 */
export const sum = arr => arr.reduce((total, c) => total + c, 0);

/**
 * Computes an average of an array of numbers.
 *
 * @param  {Array} arr An array of numbers.
 * @return {Number} Average value.
 *
 * @ignore
 */
export const avg = arr => sum(arr) / arr.length;

/**
 * Returns memory statistics of the container, which is an object with the following properties:
 *
 * ```javascript
 * {
 *   // Total memory available to the act
 *   totalBytes: Number,
 *
 *   // Amount of free memory
 *   freeBytes: Number,
 *
 *   // Amount of memory used (= totalBytes - freeBytes)
 *   usedBytes: Number,
 *   // Amount of memory used by main NodeJS process
 *   mainProcessBytes: Number,
 *   // Amount of memory used by child processes of main NodeJS process
 *   childProcessesBytes: Number,
 * }
 * ```
 *
 * @returns {Promise} Returns a promise.
 *
 * @memberof module:Apify
 * @name getMemoryInfo
 * @instance
 * @function
 */
export const getMemoryInfo = () => {
    // module.exports must be here so that we can mock it.
    const isDockerPromise = module.exports.isDocker();

    const childProcessesUsagePromise = psTreePromised(process.pid)
        .then((childProcesses) => {
            const pids = _.pluck(childProcesses, 'PID');

            const promises = pids.map((pid) => {
                return pidusage(pid)
                    .then(info => info.memory)
                    .catch((err) => {
                        if (err.message === PID_USAGE_NOT_FOUND_ERROR) return 0;

                        throw err;
                    });
            });

            return Promise.all(promises).then(infos => sum(infos));
        });

    return Promise
        .all([
            isDockerPromise,
            childProcessesUsagePromise,
        ])
        .then(([isDockerVar, childProcessesBytes]) => {
            if (!isDockerVar) {
                const freeBytes = os.freemem();
                const totalBytes = os.totalmem();
                const usedBytes = totalBytes - freeBytes;

                return Promise.resolve({
                    totalBytes,
                    freeBytes,
                    usedBytes,
                    mainProcessBytes: usedBytes - childProcessesBytes,
                    childProcessesBytes,
                });
            }

            // This must be promisified here so that we can Mock it.
            const readPromised = Promise.promisify(fs.readFile);

            return Promise
                .all([
                    readPromised('/sys/fs/cgroup/memory/memory.limit_in_bytes'),
                    readPromised('/sys/fs/cgroup/memory/memory.usage_in_bytes'),
                ])
                .then(([totalBytesStr, usedBytesStr]) => {
                    const totalBytes = parseInt(totalBytesStr, 10);
                    const usedBytes = parseInt(usedBytesStr, 10);

                    return {
                        totalBytes,
                        freeBytes: totalBytes - usedBytes,
                        usedBytes,
                        mainProcessBytes: usedBytes - childProcessesBytes,
                        childProcessesBytes,
                    };
                });
        });
};

/**
 * Helper function that detrermines if given parameter is an instance of Promise.
 *
 * @ignore
 */
export const isPromise = (maybePromise) => {
    return maybePromise && typeof maybePromise.then === 'function' && typeof maybePromise.catch === 'function';
};

/**
 * Returns true if node is in production environment and false otherwise.
 *
 * @ignore
 */
export const isProduction = () => process.env.NODE_ENV === 'production';

/**
 * Helper function used for local implementations. Creates dir.
 *
 * @ignore
 */
export const ensureDirExists = path => ensureDirPromised(path);

/**
 * Helper function that returns the first key from plan object.
 *
 * @ignore
 */
export const getFirstKey = (dict) => {
    for (const key in dict) { // eslint-disable-line guard-for-in, no-restricted-syntax
        return key;
    }
};

/**
 * Gets a typical path to Chrome executable, depending on the current operating system.
 *
 * @return {string}
 * @ignore
 */
export const getTypicalChromeExecutablePath = () => {
    switch (os.platform()) {
    case 'darwin': return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
    case 'win32': return 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe';
    default: return 'google-chrome';
    }
};

/**
 * Creates a promise that after given time gets rejected with given error.
 *
 * @return {Promise<Error>}
 * @ignore.
 */
export const createTimeoutPromise = (timeoutMillis, errorMessage) => {
    return delayPromise(timeoutMillis).then(() => {
        throw new Error(errorMessage);
    });
};

/**
 * Returns `true` when code is running on Apify platform and `false` otherwise (for example locally).
 *
 * @returns {Boolean}
 *
 * @memberof module:Apify
 * @name isAtHome
 * @instance
 * @function
 */
export const isAtHome = () => !!process.env[ENV_VARS.IS_AT_HOME];

/**
 * Returns a promise that resolves after a specific period of time. This is useful to implement waiting
 * in your code, e.g. to prevent overloading of target website or to avoid bot detection.
 *
 * Example usage:
 * ```javascript
 * const Apify = require('apify);
 *
 * ...
 *
 * // Sleep 1.5 seconds
 * await Apify.utils.sleep(1500);
 * ```
 * @param millis Period of time to sleep, in milliseconds. If not a positive number, the returned promise resolves immediately.
 * @memberof utils
 * @return {Promise}
 */
const sleep = (millis) => {
    return delayPromise(millis);
};

/**
 * A namespace that contains various utilities.
 *
 * Example usage:
 * ```javascript
 * const Apify = require('apify);
 *
 * ...
 *
 * // Sleep 1.5 seconds
 * await Apify.utils.sleep(1500);
 * ```
 * @namespace utils
 */
export const publicUtils = {
    sleep,
};

/**
 * Returns a promise that resolves to an array of urls parsed from the resource
 * available at the provided url.
 *
 * @param {String} url
 * @param {String} encoding
 * @param {RegExp} urlRegExp
 * @returns {Promise<Array>}
 */
export const downloadListOfUrls = ({ url, encoding, urlRegExp }) => {
    if (!url) return Promise.reject(new Error('Resource URL must be provided.'));
    encoding = encoding || 'utf8';
    return requestPromise.get({ url, encoding })
        .then(string => extractUrls({ string, urlRegExp }));
};

/**
 * Collects all URLs in an arbitrary string to an array.
 * @param string
 * @param urlRegExp
 * @param keepCommas
 * @returns {Array}
 */
export const extractUrls = ({ string, urlRegExp }) => {
    urlRegExp = urlRegExp || URL_NO_COMMAS_REGEX;
    return string.match(urlRegExp) || [];
};
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2016 Bruce Schubert.
 * The MIT License
 * http://www.opensource.org/licenses/mit-license
 */

/*global define, $, WorldWind */

define(['knockout'],
    function (ko) {
        "use strict";
        /**
         * The PickController operates on picked objects containing the Selectable,
         * Movable, Openable and/or ContextSensitive capabilites.
         * @constructor
         * @param {WorldWindow} worldWindow
         * @returns {PickController}
         */
        var PickController = function (worldWindow) {
            var self = this;

            this.wwd = worldWindow;
            // Flag to signal that dragging/moving has been initiated.
            // When dragging, the mouse event is consumed, i.e., not propagated.
            this.isDragging = false;
            // Flag to signal if a touch tap has occurred.
            // Used to determine single or double tap.
            this.tapped = false;
            // The time in ms to wait for a double tap
            this.DOUBLE_TAP_INTERVAL = 250;
            // The currently selected item.
            this.lastSelectedItem = ko.observable(null);
            // The top item in the pick list
            this.pickedItem = null;
            // Caches the clicked item for dblclick to process
            this.clickedItem = null;
            // The list of highlighted items
            this.highlightedItems = [];

            // Register listeners on the event target.
            function eventListener(event) {
                self.handlePick(event);
            }

            // Mirror WorldWind's behavior and use the PointerEvent interface if it's available.
            if (window.PointerEvent) {
                // If the WorldWind LookAtNavigator is using these events, then we must also in order
                // to consume these events to prevent pan/drag operations when dragging objects.
                this.wwd.addEventListener("pointerdown", eventListener);
                this.wwd.addEventListener("pointermove", eventListener);
                this.wwd.addEventListener("pointerup", eventListener);
                this.wwd.addEventListener("pointerout", eventListener);
            }
            // Listen for mouse
            this.wwd.addEventListener("mousedown", eventListener);  // Listen for mouse down to select an item
            this.wwd.addEventListener("mousemove", eventListener);  // Listen for mouse moves and tap gestures to move an item
            this.wwd.addEventListener("mouseup", eventListener);    // Listen for mouse up to release an item
            this.wwd.addEventListener("mouseout", eventListener);
            // Listen for touch
            this.wwd.addEventListener("touchstart", eventListener);
            this.wwd.addEventListener("touchmove", eventListener);
            this.wwd.addEventListener("touchend", eventListener);
            // Listen for mouse clicks
            this.wwd.addEventListener("click", eventListener);          // Listen for single clicks to select an item
            this.wwd.addEventListener("dblclick", eventListener);       // Listen for double clicks to open an item
            this.wwd.addEventListener("contextmenu", eventListener);    // Listen for right clicks to open menu

        };

        /**
         * Performs the pick apply the appropriate action on the selected item.
         * @param {Event or TapRecognizer} o The input argument is either an Event or a TapRecognizer. Both have the
         *  same properties for determining the mouse or tap location.
         */
        PickController.prototype.handlePick = function (o) {
            // The input argument is either an Event or a TapRecognizer. Both have the same properties for determining
            // the mouse or tap location.
            var eventType,
                x, y,
                h, len,
                redrawRequired,
                pickList,
                button = o.button,
                isTouchDevice = false;

            // De-highlight any previously highlighted shapes.
            len = this.highlightedItems.length;
            redrawRequired = len > 0;
            for (h = 0; h < len; h++) {
                this.highlightedItems[h].highlighted = false;
            }
            this.highlightedItems = [];

            // Alias PointerEvent event types to mouse and touch event types
            if (o.type === "pointerdown" && o.pointerType === "mouse") {
                eventType = "mousedown";
            } else if (o.type === "pointermove" && o.pointerType === "mouse") {
                eventType = "mousemove";
            } else if (o.type === "pointerout" && o.pointerType === "mouse") {
                eventType = "mouseout";
            } else if (o.type === "pointerup" && o.pointerType === "mouse") {
                eventType = "mouseup";
            } else if (o.type === "pointerdown" && o.pointerType === "touch") {
                eventType = "touchstart";
            } else if (o.type === "pointermove" && o.pointerType === "touch") {
                eventType = "touchmove";
            } else if (o.type === "pointercancel" && o.pointerType === "touch") {
                eventType = "touchcancel";
            } else if (o.type === "pointerup" && o.pointerType === "touch") {
                eventType = "touchend";
            } else {
                eventType = o.type;
            }

            // Get our X,Y values from the event;
            // determine if this is a touch device.
            if (eventType.substring(0, 5) === 'touch') {
                isTouchDevice = true;
                // Use the first touches entry
                // Note: x, y remain undefined for touchend
                if (o.touches && o.touches.length > 0) {
                    x = o.touches[0].clientX;
                    y = o.touches[0].clientY;
                } else {
                    x = o.clientX;
                    y = o.clientY;
                }
            } else {  // Mouse events...
                // Prevent handling of simulated mouse events on touch devices.
                if (isTouchDevice) {
                    return;
                }
                x = o.clientX;
                y = o.clientY;
            }

            // Perform the pick. Must first convert from window coordinates to canvas coordinates, which are
            // relative to the upper left corner of the canvas rather than the upper left corner of the page.
            pickList = this.wwd.pick(this.wwd.canvasCoordinates(x, y));

            switch (eventType) {
                case "mousedown":
                case "touchstart":
                    this.handleMouseDown(pickList, x, y);
                    break;
                case "mousemove":
                case "touchmove":
                    this.handleMouseMove(pickList, x, y, eventType, button);
                    break;
                case "mouseup":
                case "mouseout":
                case "touchend":
                case "touchcancel":
                    this.handleMouseUp(eventType);
                    break;
                case "click":
                    this.handleClick();
                    break;
                case "dblclick":
                    this.handleDoubleClick();
                    break;
                case "contextmenu":
                    this.handleContextMenu();
                    break;
            }
            // Prevent pan/drag operations on the globe when we're dragging an object.
            if (this.isDragging) {
                o.preventDefault();
            }
            // Update the window if we changed anything.
            if (redrawRequired) {
                this.wwd.redraw(); // redraw to make the highlighting changes take effect on the screen
            }
        };

        PickController.prototype.handleMouseDown = function (pickList, x, y) {

            if (pickList.hasNonTerrainObjects()) {
                // Establish the picked item - may be used by
                // mouse, select, and open actions
                this.pickedItem = pickList.topPickedObject();
                if (this.pickedItem) {
                    // Capture the initial mouse/touch points for comparison in subsequent mousemove/touchmove
                    // events to determine if whether to initiate dragging of the picked item.
                    this.startX = x;
                    this.startY = y;
                }
            } else {
                this.pickedItem = null;
            }
        };

        PickController.prototype.handleMouseMove = function (pickList, x, y, eventType, button) {
            var p, len,
                terrainObject;

            // Highlight the picked items by simply setting their highlight flag to true.
            len = pickList.objects.length;
            if (len > 0 && button === -1) {
                for (p = 0; p < len; p++) {
                    if (!pickList.objects[p].isTerrain) {
                        // Set the highlighted flag
                        pickList.objects[p].userObject.highlighted = true;
                        // Keep track of highlighted items in order to de-highlight them later.
                        this.highlightedItems.push(pickList.objects[p].userObject);
                    }
                }
            }
            // Move the selected item if left-button down or touch device
            if (this.pickedItem && (button === 0 || eventType === "touchmove")) {
                if (this.isMovable(this.pickedItem.userObject)) {
                    // To prevent confusion with clicks and taps,
                    // start dragging only if the mouse or touch
                    // point has moved a few pixels.
                    if (!this.isDragging &&
                        (Math.abs(this.startX - x) > 2 || Math.abs(this.startY - y) > 2)) {
                        this.isDragging = true;
                        this.startMove(this.pickedItem.userObject);
                    }
                    // Perform the actual move of the picked object
                    if (this.isDragging) {
                        // Get the new terrain coords at the pick point
                        terrainObject = pickList.terrainObject();
                        if (terrainObject) {
                            this.doMove(this.pickedItem.userObject, terrainObject);
                        }
                    }
                }
            }
        };

        PickController.prototype.handleMouseUp = function (eventType) {
            var self = this;

            if (this.pickedItem) {
                // The end of a touch can signal either the end of a
                // drag/move operation or a tap/double-tap.
                // If our isDragging flag is set, then it"s a given
                // that the touch/mouse event signals a move finished.
                if (this.isDragging) {
                    this.finishMove(this.pickedItem.userObject);
                    this.pickedItem = null;
                } else if (eventType === "touchend") {
                    // Determine if touch event is a single tap or a double tap:
                    // Capture the first tap, and if another tap doesn"t come in
                    // within the alloted time, then perform single tap action.
                    if (!this.tapped) {
                        // Wait for another tap, if if doesn"t happen,
                        // then perform the select action
                        this.clickedItem = this.pickedItem;
                        this.tapped = setTimeout(function () {
                            self.tapped = null;
                            self.doSelect(self.clickedItem.userObject);
                        }, this.DOUBLE_TAP_INTERVAL);
                    } else {
                        // A double tap has occured. Clear the pending
                        // single tap action and perform the open action
                        clearTimeout(this.tapped);
                        this.tapped = null;
                        this.doOpen(this.pickedItem.userObject);
                    }
                    this.pickedItem = null;
                }
            }
            this.isDragging = false;
        };

        PickController.prototype.handleClick = function () {
            // Remember the clicked item for dblclick processing
            this.clickedItem = this.pickedItem;
            if (this.clickedItem) {
                this.doClick(this.clickedItem.userObject);
                this.doSelect(this.clickedItem.userObject);
            }
            // Release the picked item so mousemove doesn"t act on it
            this.pickedItem = null;
        };

        PickController.prototype.handleDoubleClick = function () {
            if (this.clickedItem) {
                this.doOpen(this.clickedItem.userObject);
            }
            // Release the picked item so mousemove doesn"t act on it
            this.pickedItem = null;
        };

        PickController.prototype.handleContextMenu = function () {
            this.isDragging = false;
            if (this.pickedItem) {
                this.doContextSensitive(this.pickedItem.userObject);
                // Release the picked item so mousemove doesn't act on it
                this.pickedItem = null;
            }
        };

        PickController.prototype.doContextSensitive = function (userObject) {
            if (ko.isObservable(userObject.isContextSensitive) && userObject.isContextSensitive()) {
                if (userObject.showContextMenu) {
                    userObject.showContextMenu();
                } else {
                    // Otherwise, build a context menu from standard capabilities
                    //              $('#globeContextMenu-popup').puimenu('show');
                }
            }
        };

        PickController.prototype.isMovable = function (userObject) {
            if (ko.isObservable(userObject.isMovable)) {
                return userObject.isMovable();
            } else {
                return userObject.isMovable;
            }
        };

        PickController.prototype.startMove = function (userObject) {
            if (userObject.moveStarted) {
                // Fires EVENT_OBJECT_MOVE_STARTED
                userObject.moveStarted();
            }
        };

        PickController.prototype.doMove = function (userObject, terrainObject) {
            if (userObject.moveToLatLon) {
                // Fires EVENT_OBJECT_MOVED
                userObject.moveToLatLon(
                    terrainObject.position.latitude,
                    terrainObject.position.longitude);
            }
// Uncomment to allow ordinary renderables to be moved.
//                            // Or, move the object (a Renderable) if it has a position object
//                            else if (this.pickedItem.userObject.position) {
//                                this.pickedItem.userObject.position =
//                                    new WorldWind.Position(
//                                        terrainObject.position.latitude,
//                                        terrainObject.position.longitude,
//                                        this.pickedItem.userObject.position.elevation);
//                                redrawRequired = true;
//                            }
        };

        PickController.prototype.finishMove = function (userObject) {
            // Test for a "Movable" capability
            if (userObject.moveFinished) {
                // Fires EVENT_OBJECT_MOVE_FINISHED
                userObject.moveFinished();
            }
        };

        PickController.prototype.doClick = function (userObject) {
            if (ko.isObservable(userObject.isClickable) && userObject.isClickable()) {
                if (userObject.click) {
                    userObject.click();
                }
            }
        };

        PickController.prototype.doSelect = function (userObject) {
            if (ko.isObservable(userObject.isSelectable) && userObject.isSelectable()) {
                if (this.lastSelectedItem() === userObject) {
                    return;
                }
                if (this.lastSelectedItem() !== null) {
                    this.lastSelectedItem().select({selected: false});
                    this.lastSelectedItem(null);
                }
                if (userObject.select) {
                    userObject.select({selected: true});
                    this.lastSelectedItem(userObject);
                }
            }
        };

        PickController.prototype.doDeselect = function (userObject) {
            if (this.lastSelectedItem() === userObject) {
                this.lastSelectedItem().select({selected: false});
                this.lastSelectedItem(null);
            }
        };

        PickController.prototype.doOpen = function (userObject) {
            if (ko.isObservable(userObject.isOpenable) && userObject.isOpenable()) {
                if (userObject.open) {
                    userObject.open();
                }
            }
        };

        return PickController;
    }
);<commit_msg>Updated PickController with call to stopImmediatePropagation. - Prevents panning globe when dragging a marker. - Required for Safari. - Fixes #83<commit_after>/*
 * Copyright (c) 2016 Bruce Schubert.
 * The MIT License
 * http://www.opensource.org/licenses/mit-license
 */

/*global define, $, WorldWind */

define(['knockout'],
    function (ko) {
        "use strict";
        /**
         * The PickController operates on picked objects containing the Selectable,
         * Movable, Openable and/or ContextSensitive capabilites.
         * @constructor
         * @param {WorldWindow} worldWindow
         * @returns {PickController}
         */
        var PickController = function (worldWindow) {
            var self = this;

            this.wwd = worldWindow;
            // Flag to signal that dragging/moving has been initiated.
            // When dragging, the mouse event is consumed, i.e., not propagated.
            this.isDragging = false;
            // Flag to signal if a touch tap has occurred.
            // Used to determine single or double tap.
            this.tapped = false;
            // The time in ms to wait for a double tap
            this.DOUBLE_TAP_INTERVAL = 250;
            // The currently selected item.
            this.lastSelectedItem = ko.observable(null);
            // The top item in the pick list
            this.pickedItem = null;
            // Caches the clicked item for dblclick to process
            this.clickedItem = null;
            // The list of highlighted items
            this.highlightedItems = [];

            // Register listeners on the event target.
            function eventListener(event) {
                self.handlePick(event);
            }

            // Mirror WorldWind's behavior and use the PointerEvent interface if it's available.
            if (window.PointerEvent) {
                // If the WorldWind LookAtNavigator is using these events, then we must also in order
                // to consume these events to prevent pan/drag operations when dragging objects.
                this.wwd.addEventListener("pointerdown", eventListener);
                this.wwd.addEventListener("pointermove", eventListener);
                this.wwd.addEventListener("pointerup", eventListener);
                this.wwd.addEventListener("pointerout", eventListener);
            }
            // Listen for mouse
            this.wwd.addEventListener("mousedown", eventListener);  // Listen for mouse down to select an item
            this.wwd.addEventListener("mousemove", eventListener);  // Listen for mouse moves and tap gestures to move an item
            this.wwd.addEventListener("mouseup", eventListener);    // Listen for mouse up to release an item
            this.wwd.addEventListener("mouseout", eventListener);
            // Listen for touch
            this.wwd.addEventListener("touchstart", eventListener);
            this.wwd.addEventListener("touchmove", eventListener);
            this.wwd.addEventListener("touchend", eventListener);
            // Listen for mouse clicks
            this.wwd.addEventListener("click", eventListener);          // Listen for single clicks to select an item
            this.wwd.addEventListener("dblclick", eventListener);       // Listen for double clicks to open an item
            this.wwd.addEventListener("contextmenu", eventListener);    // Listen for right clicks to open menu

        };

        /**
         * Performs the pick apply the appropriate action on the selected item.
         * @param {Event or TapRecognizer} o The input argument is either an Event or a TapRecognizer. Both have the
         *  same properties for determining the mouse or tap location.
         */
        PickController.prototype.handlePick = function (o) {
            // The input argument is either an Event or a TapRecognizer. Both have the same properties for determining
            // the mouse or tap location.
            var eventType,
                x, y,
                h, len,
                redrawRequired,
                pickList,
                button = o.button,
                isTouchDevice = false;

            // De-highlight any previously highlighted shapes.
            len = this.highlightedItems.length;
            redrawRequired = len > 0;
            for (h = 0; h < len; h++) {
                this.highlightedItems[h].highlighted = false;
            }
            this.highlightedItems = [];

            // Alias PointerEvent event types to mouse and touch event types
            if (o.type === "pointerdown" && o.pointerType === "mouse") {
                eventType = "mousedown";
            } else if (o.type === "pointermove" && o.pointerType === "mouse") {
                eventType = "mousemove";
            } else if (o.type === "pointerout" && o.pointerType === "mouse") {
                eventType = "mouseout";
            } else if (o.type === "pointerup" && o.pointerType === "mouse") {
                eventType = "mouseup";
            } else if (o.type === "pointerdown" && o.pointerType === "touch") {
                eventType = "touchstart";
            } else if (o.type === "pointermove" && o.pointerType === "touch") {
                eventType = "touchmove";
            } else if (o.type === "pointercancel" && o.pointerType === "touch") {
                eventType = "touchcancel";
            } else if (o.type === "pointerup" && o.pointerType === "touch") {
                eventType = "touchend";
            } else {
                eventType = o.type;
            }

            // Get our X,Y values from the event;
            // determine if this is a touch device.
            if (eventType.substring(0, 5) === 'touch') {
                isTouchDevice = true;
                // Use the first touches entry
                // Note: x, y remain undefined for touchend
                if (o.touches && o.touches.length > 0) {
                    x = o.touches[0].clientX;
                    y = o.touches[0].clientY;
                } else {
                    x = o.clientX;
                    y = o.clientY;
                }
            } else {  // Mouse events...
                // Prevent handling of simulated mouse events on touch devices.
                if (isTouchDevice) {
                    return;
                }
                x = o.clientX;
                y = o.clientY;
            }

            // Perform the pick. Must first convert from window coordinates to canvas coordinates, which are
            // relative to the upper left corner of the canvas rather than the upper left corner of the page.
            pickList = this.wwd.pick(this.wwd.canvasCoordinates(x, y));

            switch (eventType) {
                case "mousedown":
                case "touchstart":
                    this.handleMouseDown(pickList, x, y);
                    break;
                case "mousemove":
                case "touchmove":
                    this.handleMouseMove(pickList, x, y, eventType, button);
                    break;
                case "mouseup":
                case "mouseout":
                case "touchend":
                case "touchcancel":
                    this.handleMouseUp(eventType);
                    break;
                case "click":
                    this.handleClick();
                    break;
                case "dblclick":
                    this.handleDoubleClick();
                    break;
                case "contextmenu":
                    this.handleContextMenu();
                    break;
            }
            // Prevent pan/drag operations on the globe when we're dragging an object.
            if (this.isDragging) {
                o.preventDefault();
                o.stopImmediatePropagation();
            }
            // Update the window if we changed anything.
            if (redrawRequired) {
                this.wwd.redraw(); // redraw to make the highlighting changes take effect on the screen
            }
        };

        PickController.prototype.handleMouseDown = function (pickList, x, y) {

            if (pickList.hasNonTerrainObjects()) {
                // Establish the picked item - may be used by
                // mouse, select, and open actions
                this.pickedItem = pickList.topPickedObject();
                if (this.pickedItem) {
                    // Capture the initial mouse/touch points for comparison in subsequent mousemove/touchmove
                    // events to determine if whether to initiate dragging of the picked item.
                    this.startX = x;
                    this.startY = y;
                }
            } else {
                this.pickedItem = null;
            }
        };

        PickController.prototype.handleMouseMove = function (pickList, x, y, eventType, button) {
            var p, len,
                terrainObject;

            // Highlight the picked items by simply setting their highlight flag to true.
            len = pickList.objects.length;
            if (len > 0 && button === -1) {
                for (p = 0; p < len; p++) {
                    if (!pickList.objects[p].isTerrain) {
                        // Set the highlighted flag
                        pickList.objects[p].userObject.highlighted = true;
                        // Keep track of highlighted items in order to de-highlight them later.
                        this.highlightedItems.push(pickList.objects[p].userObject);
                    }
                }
            }
            // Move the selected item if left-button down or touch device
            if (this.pickedItem && (button === 0 || eventType === "touchmove")) {
                if (this.isMovable(this.pickedItem.userObject)) {
                    // To prevent confusion with clicks and taps,
                    // start dragging only if the mouse or touch
                    // point has moved a few pixels.
                    if (!this.isDragging &&
                        (Math.abs(this.startX - x) > 2 || Math.abs(this.startY - y) > 2)) {
                        this.isDragging = true;
                        this.startMove(this.pickedItem.userObject);
                    }
                    // Perform the actual move of the picked object
                    if (this.isDragging) {
                        // Get the new terrain coords at the pick point
                        terrainObject = pickList.terrainObject();
                        if (terrainObject) {
                            this.doMove(this.pickedItem.userObject, terrainObject);
                        }
                    }
                }
            }
        };

        PickController.prototype.handleMouseUp = function (eventType) {
            var self = this;

            if (this.pickedItem) {
                // The end of a touch can signal either the end of a
                // drag/move operation or a tap/double-tap.
                // If our isDragging flag is set, then it"s a given
                // that the touch/mouse event signals a move finished.
                if (this.isDragging) {
                    this.finishMove(this.pickedItem.userObject);
                    this.pickedItem = null;
                } else if (eventType === "touchend") {
                    // Determine if touch event is a single tap or a double tap:
                    // Capture the first tap, and if another tap doesn"t come in
                    // within the alloted time, then perform single tap action.
                    if (!this.tapped) {
                        // Wait for another tap, if if doesn"t happen,
                        // then perform the select action
                        this.clickedItem = this.pickedItem;
                        this.tapped = setTimeout(function () {
                            self.tapped = null;
                            self.doSelect(self.clickedItem.userObject);
                        }, this.DOUBLE_TAP_INTERVAL);
                    } else {
                        // A double tap has occured. Clear the pending
                        // single tap action and perform the open action
                        clearTimeout(this.tapped);
                        this.tapped = null;
                        this.doOpen(this.pickedItem.userObject);
                    }
                    this.pickedItem = null;
                }
            }
            this.isDragging = false;
        };

        PickController.prototype.handleClick = function () {
            // Remember the clicked item for dblclick processing
            this.clickedItem = this.pickedItem;
            if (this.clickedItem) {
                this.doClick(this.clickedItem.userObject);
                this.doSelect(this.clickedItem.userObject);
            }
            // Release the picked item so mousemove doesn"t act on it
            this.pickedItem = null;
        };

        PickController.prototype.handleDoubleClick = function () {
            if (this.clickedItem) {
                this.doOpen(this.clickedItem.userObject);
            }
            // Release the picked item so mousemove doesn"t act on it
            this.pickedItem = null;
        };

        PickController.prototype.handleContextMenu = function () {
            this.isDragging = false;
            if (this.pickedItem) {
                this.doContextSensitive(this.pickedItem.userObject);
                // Release the picked item so mousemove doesn't act on it
                this.pickedItem = null;
            }
        };

        PickController.prototype.doContextSensitive = function (userObject) {
            if (ko.isObservable(userObject.isContextSensitive) && userObject.isContextSensitive()) {
                if (userObject.showContextMenu) {
                    userObject.showContextMenu();
                } else {
                    // Otherwise, build a context menu from standard capabilities
                    //              $('#globeContextMenu-popup').puimenu('show');
                }
            }
        };

        PickController.prototype.isMovable = function (userObject) {
            if (ko.isObservable(userObject.isMovable)) {
                return userObject.isMovable();
            } else {
                return userObject.isMovable;
            }
        };

        PickController.prototype.startMove = function (userObject) {
            if (userObject.moveStarted) {
                // Fires EVENT_OBJECT_MOVE_STARTED
                userObject.moveStarted();
            }
        };

        PickController.prototype.doMove = function (userObject, terrainObject) {
            if (userObject.moveToLatLon) {
                // Fires EVENT_OBJECT_MOVED
                userObject.moveToLatLon(
                    terrainObject.position.latitude,
                    terrainObject.position.longitude);
            }
// Uncomment to allow ordinary renderables to be moved.
//                            // Or, move the object (a Renderable) if it has a position object
//                            else if (this.pickedItem.userObject.position) {
//                                this.pickedItem.userObject.position =
//                                    new WorldWind.Position(
//                                        terrainObject.position.latitude,
//                                        terrainObject.position.longitude,
//                                        this.pickedItem.userObject.position.elevation);
//                                redrawRequired = true;
//                            }
        };

        PickController.prototype.finishMove = function (userObject) {
            // Test for a "Movable" capability
            if (userObject.moveFinished) {
                // Fires EVENT_OBJECT_MOVE_FINISHED
                userObject.moveFinished();
            }
        };

        PickController.prototype.doClick = function (userObject) {
            if (ko.isObservable(userObject.isClickable) && userObject.isClickable()) {
                if (userObject.click) {
                    userObject.click();
                }
            }
        };

        PickController.prototype.doSelect = function (userObject) {
            if (ko.isObservable(userObject.isSelectable) && userObject.isSelectable()) {
                if (this.lastSelectedItem() === userObject) {
                    return;
                }
                if (this.lastSelectedItem() !== null) {
                    this.lastSelectedItem().select({selected: false});
                    this.lastSelectedItem(null);
                }
                if (userObject.select) {
                    userObject.select({selected: true});
                    this.lastSelectedItem(userObject);
                }
            }
        };

        PickController.prototype.doDeselect = function (userObject) {
            if (this.lastSelectedItem() === userObject) {
                this.lastSelectedItem().select({selected: false});
                this.lastSelectedItem(null);
            }
        };

        PickController.prototype.doOpen = function (userObject) {
            if (ko.isObservable(userObject.isOpenable) && userObject.isOpenable()) {
                if (userObject.open) {
                    userObject.open();
                }
            }
        };

        return PickController;
    }
);<|endoftext|>


<commit_before>function Shatter (img, numPolys) {
    this.img = img;
    this.numPolys = numPolys;
    this.images = [];

    // scale polygon coordinates
    var adjustedCoordinates = function (polygon) {
            polygon.points = [];
            var scale = 0.9;
            var xCenter = (polygon.maxX + polygon.minX) / 2;
            var yCenter = (polygon.maxY + polygon.minY) / 2;
            polygon.forEach(function (coordinatePair) {
                var x = coordinatePair[0] - polygon.minX;
                var y = coordinatePair[1] - polygon.minY;
                // scale points in for collision bounds
                x = Math.round(scale * (x - xCenter) + xCenter);
                y = Math.round(scale * (y - yCenter) + yCenter);
                polygon.points.push(x, y);
            });
    }

    // Splits the given image into separate segments based on
    // a list of polygons (or Voronoi cells)
    var spliceImage = function (polygonList, img) {
        imageList = [];
        var tempCanvas = document.createElement('canvas');
        tempCanvas.width = img.width;
        tempCanvas.height = img.height;
        var tempCtx = tempCanvas.getContext("2d");
        tempCtx.save();
        polygonList.forEach(function (polygon, index, polygonList) {
            polygon.forEach(function (coordinatePair, index, polygon) {
                if (index === 0) {
                    tempCtx.beginPath();
                    tempCtx.moveTo(coordinatePair[0], coordinatePair[1]);
                    return
                }
                tempCtx.lineTo(coordinatePair[0], coordinatePair[1]);
                if (index === polygon.length - 1) {
                    // last coordinate, close polygon
                    tempCtx.lineTo(polygon[0][0], polygon[0][1]);
                    // create clipped canvas with polygon
                    tempCtx.clip();
                    // draw the original image onto the canvas
                    tempCtx.drawImage(img, 0, 0);
                    // save clipped image
                    var tempBigImage = new Image();
                    tempBigImage.src = tempCanvas.toDataURL("image/png");
                    // now crop the image by drawing on a new canvas and saving
                    // that canvas
                    var imgHeight = polygon.maxY - polygon.minY,
                        imgWidth = polygon.maxX - polygon.minX;
                    var cropCanvas = document.createElement('canvas');
                    cropCanvas.width = imgWidth;
                    cropCanvas.height = imgHeight;
                    cropCtx = cropCanvas.getContext("2d");
                    cropCtx.drawImage(tempBigImage, -polygon.minX, -polygon.minY);
                    var saveImage = new Image();
                    saveImage.src = cropCanvas.toDataURL("image/png");

                    imageList.push([saveImage, [polygon.minX, polygon.minY], polygon.points]);
                    tempBigImage = null, saveImage = null, cropCanvas = null; // clean up
                    tempCtx.restore();
                    tempCtx.clearRect(0,0,250,250);
                    tempCtx.save();
                    return;
                }
            });
        });
        canvas = null;
        return imageList;
    }

    var polygonList = this.getPolys(img.width, img.height, numPolys);
    this.roundVertices(polygonList);
    this.calcBoundaries(polygonList, this.img);
    polygonList.forEach(adjustedCoordinates);
    this.images = spliceImage(polygonList, img);
}

// Divides a rectangular area into the specified number
// of Voronoi cells, rounds all vertices
Shatter.prototype.getPolys = function (width, height, numPolys) {
    var vertices = d3.range(numPolys).map(function (d) {
      return [Math.random() * width, Math.random() * height];
    });
    var voronoi = d3.geom.voronoi()
        .clipExtent([[0, 0], [width, height]]);
    var polygons = voronoi(vertices);
    return polygons;
}

// Round all vertices in a polygon
Shatter.prototype.roundVertices = function (polygonList) {
    polygonList.forEach(function (polygon) {
        polygon.forEach(function (coordinatePair) {
            coordinatePair[0] = Math.round(coordinatePair[0]);
            coordinatePair[1] = Math.round(coordinatePair[1]);
        });
    });
}

// Calculate and store minimum and maximum X, Y coords in a polygon
Shatter.prototype.calcBoundaries = function (polygonList, img) {
    polygonList.forEach(function (polygon) {
        polygon.minX = img.width;
        polygon.minY = img.height;
        polygon.maxX = 0;
        polygon.maxY = 0;
        polygon.forEach(function (coordinatePair) {
            polygon.minX = coordinatePair[0] < polygon.minX ? coordinatePair[0] : polygon.minX;
            polygon.minY = coordinatePair[1] < polygon.minY ? coordinatePair[1] : polygon.minY;
            polygon.maxX = coordinatePair[0] > polygon.maxX ? coordinatePair[0] : polygon.maxX;
            polygon.maxY = coordinatePair[1] > polygon.maxY ? coordinatePair[1] : polygon.maxY;
        });
    });
}
<commit_msg>rename variables<commit_after>function Shatter (img, numPolys) {
    this.img = img;
    this.numPolys = numPolys;
    this.images = [];
    var polygons;

    // scale polygon coordinates
    var adjustedCoordinates = function (polygon) {
            polygon.points = [];
            var scale = 0.9;
            var xCenter = (polygon.maxX + polygon.minX) / 2;
            var yCenter = (polygon.maxY + polygon.minY) / 2;
            polygon.forEach(function (coordinatePair) {
                var x = coordinatePair[0] - polygon.minX;
                var y = coordinatePair[1] - polygon.minY;
                // scale points in for collision bounds
                x = Math.round(scale * (x - xCenter) + xCenter);
                y = Math.round(scale * (y - yCenter) + yCenter);
                polygon.points.push(x, y);
            });
    }

    // Splits the given image into separate segments based on
    // a list of polygons (or Voronoi cells)
    var spliceImage = function (polygons, img) {
        imageList = [];
        var tempCanvas = document.createElement('canvas');
        tempCanvas.width = img.width;
        tempCanvas.height = img.height;
        var tempCtx = tempCanvas.getContext("2d");
        tempCtx.save();
        polygons.forEach(function (polygon, index, polygons) {
            polygon.forEach(function (coordinatePair, index, polygon) {
                if (index === 0) {
                    tempCtx.beginPath();
                    tempCtx.moveTo(coordinatePair[0], coordinatePair[1]);
                    return
                }
                tempCtx.lineTo(coordinatePair[0], coordinatePair[1]);
                if (index === polygon.length - 1) {
                    // last coordinate, close polygon
                    tempCtx.lineTo(polygon[0][0], polygon[0][1]);
                    // create clipped canvas with polygon
                    tempCtx.clip();
                    // draw the original image onto the canvas
                    tempCtx.drawImage(img, 0, 0);
                    // save clipped image
                    var tempBigImage = new Image();
                    tempBigImage.src = tempCanvas.toDataURL("image/png");
                    // now crop the image by drawing on a new canvas and saving
                    // that canvas
                    var imgHeight = polygon.maxY - polygon.minY,
                        imgWidth = polygon.maxX - polygon.minX;
                    var cropCanvas = document.createElement('canvas');
                    cropCanvas.width = imgWidth;
                    cropCanvas.height = imgHeight;
                    cropCtx = cropCanvas.getContext("2d");
                    cropCtx.drawImage(tempBigImage, -polygon.minX, -polygon.minY);
                    var saveImage = new Image();
                    saveImage.src = cropCanvas.toDataURL("image/png");

                    imageList.push([saveImage, [polygon.minX, polygon.minY], polygon.points]);
                    tempBigImage = null, saveImage = null, cropCanvas = null; // clean up
                    tempCtx.restore();
                    tempCtx.clearRect(0,0,250,250);
                    tempCtx.save();
                    return;
                }
            });
        });
        canvas = null;
        return imageList;
    }

    // Init shattered image
    polygons = this.getPolys(img.width, img.height, numPolys);
    this.roundVertices(polygons);
    this.calcBoundaries(polygons, this.img);
    polygons.forEach(adjustedCoordinates);
    this.images = spliceImage(polygons, img);
}

// Divides a rectangular area into the specified number
// of Voronoi cells, rounds all vertices
Shatter.prototype.getPolys = function (width, height, numPolys) {
    var vertices = d3.range(numPolys).map(function (d) {
      return [Math.random() * width, Math.random() * height];
    });
    var voronoi = d3.geom.voronoi()
        .clipExtent([[0, 0], [width, height]]);
    var polygons = voronoi(vertices);
    return polygons;
}

// Round all vertices in a polygon
Shatter.prototype.roundVertices = function (polygons) {
    polygons.forEach(function (polygon) {
        polygon.forEach(function (coordinatePair) {
            coordinatePair[0] = Math.round(coordinatePair[0]);
            coordinatePair[1] = Math.round(coordinatePair[1]);
        });
    });
}

// Calculate and store minimum and maximum X, Y coords in a polygon
Shatter.prototype.calcBoundaries = function (polygons, img) {
    polygons.forEach(function (polygon) {
        polygon.minX = img.width;
        polygon.minY = img.height;
        polygon.maxX = 0;
        polygon.maxY = 0;
        polygon.forEach(function (coordinatePair) {
            polygon.minX = coordinatePair[0] < polygon.minX ? coordinatePair[0] : polygon.minX;
            polygon.minY = coordinatePair[1] < polygon.minY ? coordinatePair[1] : polygon.minY;
            polygon.maxX = coordinatePair[0] > polygon.maxX ? coordinatePair[0] : polygon.maxX;
            polygon.maxY = coordinatePair[1] > polygon.maxY ? coordinatePair[1] : polygon.maxY;
        });
    });
}
<|endoftext|>


<commit_before>import { struct } from 'brisky-struct'
import parent from '../render/dom/parent'
import delegate from './delegate'
import listen from './listener'

const emitterProperty = struct.props.on.struct.props.default
const cache = {}
const injectable = {}
const isTouch = typeof window !== 'undefined' && (
  ('ontouchstart' in global ||
    global.DocumentTouch &&
    document instanceof global.DocumentTouch) ||
  navigator.msMaxTouchPoints ||
  false)

const clear = () => { block = null }
const blockMouse = () => {
  if (block) clearTimeout(block)
  block = setTimeout(clear, 300)
}
var block

export default injectable

injectable.on = {
  props: {
    error: {},
    remove: {},
    default: (t, val, key) => {
      if (!cache[key]) {
        cache[key] = true
        listen(key, e => delegate(key, e))
      }
      t._p.set({ hasEvents: true }, false)
      emitterProperty(t, val, key)
<<<<<<< HEAD
=======
    },
    move: (t, val) => {
      t.set({
        mousemove: val,
        touchmove: val
      })
    },
    down: (t, val, key) => {
      if (!cache[key]) {
        cache[key] = true
        listen('mousedown', e => {
          !block && delegate(key, e)
        })
        listen('touchstart', e => {
          blockMouse()
          delegate(key, e)
        })
      }
      t._p.set({ hasEvents: true }, false)
      emitterProperty(t, val, key)
    },
    up: (t, val, key) => {
      if (!cache[key]) {
        cache[key] = true
        listen('mouseup', e => {
          !block && delegate(key, e)
        })
        listen('touchend', e => {
          blockMouse()
          delegate(key, e)
        })
      }
      t._p.set({ hasEvents: true }, false)
      emitterProperty(t, val, key)
>>>>>>> d618946e5db8211cdd5b20482c25ce5e6e088bbd
    }
  }
}

if (isTouch) {
  injectable.on.props.move = (t, val) => t.set({ touchmove: val })
  injectable.on.props.down = (t, val) => t.set({ touchstart: val })
  injectable.on.props.up = (t, val) => t.set({ touchend: val })
} else {
  injectable.on.props.move = (t, val) => t.set({ mousemove: val })
  injectable.on.props.down = (t, val) => t.set({ mousedown: val })
  injectable.on.props.up = (t, val) => t.set({ mouseup: val })
}

injectable.props = {
  hasEvents: {
    type: 'property',
    // sync: false,
    // this is the time for sync: false....
    subscriptionType: 'switch',
    $: true,
    render: {
      state (target, s, type, subs, tree, id, pid) {
        const node = parent(tree, pid)
        if (node) {
          if (s) {
            node._sc = s.storeContext()
            node._s = s
          }
          if (!('_' in node)) {
            node._ = target.parent()
          }
        }
      }
    }
  }
}
<commit_msg>Removed dem artifacts<commit_after>import { struct } from 'brisky-struct'
import parent from '../render/dom/parent'
import delegate from './delegate'
import listen from './listener'

const emitterProperty = struct.props.on.struct.props.default
const cache = {}
const injectable = {}
const isTouch = typeof window !== 'undefined' && (
  ('ontouchstart' in global ||
    global.DocumentTouch &&
    document instanceof global.DocumentTouch) ||
  navigator.msMaxTouchPoints ||
  false)

const clear = () => { block = null }
const blockMouse = () => {
  if (block) clearTimeout(block)
  block = setTimeout(clear, 300)
}
var block

export default injectable

injectable.on = {
  props: {
    error: {},
    remove: {},
    default: (t, val, key) => {
      if (!cache[key]) {
        cache[key] = true
        listen(key, e => delegate(key, e))
      }
      t._p.set({ hasEvents: true }, false)
      emitterProperty(t, val, key)
    },
    move: (t, val) => {
      t.set({
        mousemove: val,
        touchmove: val
      })
    },
    down: (t, val, key) => {
      if (!cache[key]) {
        cache[key] = true
        listen('mousedown', e => {
          !block && delegate(key, e)
        })
        listen('touchstart', e => {
          blockMouse()
          delegate(key, e)
        })
      }
      t._p.set({ hasEvents: true }, false)
      emitterProperty(t, val, key)
    },
    up: (t, val, key) => {
      if (!cache[key]) {
        cache[key] = true
        listen('mouseup', e => {
          !block && delegate(key, e)
        })
        listen('touchend', e => {
          blockMouse()
          delegate(key, e)
        })
      }
      t._p.set({ hasEvents: true }, false)
      emitterProperty(t, val, key)
    }
  }
}

if (isTouch) {
  injectable.on.props.move = (t, val) => t.set({ touchmove: val })
  injectable.on.props.down = (t, val) => t.set({ touchstart: val })
  injectable.on.props.up = (t, val) => t.set({ touchend: val })
} else {
  injectable.on.props.move = (t, val) => t.set({ mousemove: val })
  injectable.on.props.down = (t, val) => t.set({ mousedown: val })
  injectable.on.props.up = (t, val) => t.set({ mouseup: val })
}

injectable.props = {
  hasEvents: {
    type: 'property',
    // sync: false,
    // this is the time for sync: false....
    subscriptionType: 'switch',
    $: true,
    render: {
      state (target, s, type, subs, tree, id, pid) {
        const node = parent(tree, pid)
        if (node) {
          if (s) {
            node._sc = s.storeContext()
            node._s = s
          }
          if (!('_' in node)) {
            node._ = target.parent()
          }
        }
      }
    }
  }
}
<|endoftext|>


<commit_before><commit_msg>fix invocation<commit_after><|endoftext|>