<commit_before>/*$AMPERSAND_VERSION*/
var sync = require('ampersand-sync');
var assign = require('lodash.assign');

// Wrap an optional error callback with a fallback error event.
var wrapError = function(model, options) {
    var error = options.error;
    options.error = function(resp) {
        if (error) error(model, resp, options);
        model.trigger('error', model, resp, options);
    };
};

module.exports = {
    // Fetch the default set of models for this collection, resetting the
    // collection when they arrive. If `reset: true` is passed, the response
    // data will be passed through the `reset` method instead of `set`.
    fetch: function(options) {
        options = options ? assign({}, options) : {};
        if (options.parse === void 0) options.parse = true;
        var success = options.success;
        var collection = this;
        options.success = function(resp) {
            var method = options.reset ? 'reset' : 'set';
            if (options.set !== false) collection[method](resp, options);
            if (success) success(collection, resp, options);
            if (options.set !== false) collection.trigger('sync', collection, resp, options);
        };
        wrapError(this, options);
        var request = this.sync('read', this, options);
        // Make the request available on the options object so it can be accessed
        // further down the line by `parse`, sync listeners, etc
        // https://github.com/AmpersandJS/ampersand-collection-rest-mixin/commit/d32d788aaff912387eb1106f2d7ad183ec39e11a#diff-84c84703169bf5017b1bc323653acaa3R32
        options.xhr = request;
        return request;
    },

    // Create a new instance of a model in this collection. Add the model to the
    // collection immediately, unless `wait: true` is passed, in which case we
    // wait for the server to agree.
    create: function(model, options) {
        options = options ? assign({}, options) : {};
        if (!(model = this._prepareModel(model, options))) return false;
        if (!options.wait) this.add(model, options);
        var collection = this;
        var success = options.success;
        options.success = function(model, resp) {
            if (options.wait) collection.add(model, options);
            if (success) success(model, resp, options);
        };
        model.save(null, options);
        return model;
    },

    sync: function() {
        return sync.apply(this, arguments);
    },

    // Get or fetch a model by Id.
    getOrFetch: function (id, options, cb) {
        if (arguments.length !== 3) {
            cb = options;
            options = {};
        }
        var self = this;
        var model = this.get(id);
        if (model) {
            return window.setTimeout(function() {
                return cb(null, model);
            }, 0);
        }
        function done() {
            var model = self.get(id);
            if (model) {
                if (cb) cb(null, model);
            } else {
                cb(new Error('not found'));
            }
        }
        if (options.all) {
            options.success = done;
            options.error = done;
            return this.fetch(options);
        } else {
            return this.fetchById(id, cb);
        }
    },

    // fetchById: fetches a model and adds it to
    // collection when fetched.
    fetchById: function (id, cb) {
        var self = this;
        var idObj = {};
        idObj[this.mainIndex] = id;
        var model = new this.model(idObj, {collection: this});
        return model.fetch({
            success: function () {
                model = self.add(model);
                if (cb) cb(null, model);
            },
            error: function (collection, resp) {
                delete model.collection;
                if (cb) {
                    var error = new Error(resp.statusText);
                    error.status = resp.status;
                    cb(error);
                }
            }
        });
    }
};
<commit_msg>Add options argument to fetchById method; Use options in getOrFetch<commit_after>/*$AMPERSAND_VERSION*/
var sync = require('ampersand-sync');
var assign = require('lodash.assign');

// Wrap an optional error callback with a fallback error event.
var wrapError = function(model, options) {
    var error = options.error;
    options.error = function(resp) {
        if (error) error(model, resp, options);
        model.trigger('error', model, resp, options);
    };
};

module.exports = {
    // Fetch the default set of models for this collection, resetting the
    // collection when they arrive. If `reset: true` is passed, the response
    // data will be passed through the `reset` method instead of `set`.
    fetch: function(options) {
        options = options ? assign({}, options) : {};
        if (options.parse === void 0) options.parse = true;
        var success = options.success;
        var collection = this;
        options.success = function(resp) {
            var method = options.reset ? 'reset' : 'set';
            if (options.set !== false) collection[method](resp, options);
            if (success) success(collection, resp, options);
            if (options.set !== false) collection.trigger('sync', collection, resp, options);
        };
        wrapError(this, options);
        var request = this.sync('read', this, options);
        // Make the request available on the options object so it can be accessed
        // further down the line by `parse`, sync listeners, etc
        // https://github.com/AmpersandJS/ampersand-collection-rest-mixin/commit/d32d788aaff912387eb1106f2d7ad183ec39e11a#diff-84c84703169bf5017b1bc323653acaa3R32
        options.xhr = request;
        return request;
    },

    // Create a new instance of a model in this collection. Add the model to the
    // collection immediately, unless `wait: true` is passed, in which case we
    // wait for the server to agree.
    create: function(model, options) {
        options = options ? assign({}, options) : {};
        if (!(model = this._prepareModel(model, options))) return false;
        if (!options.wait) this.add(model, options);
        var collection = this;
        var success = options.success;
        options.success = function(model, resp) {
            if (options.wait) collection.add(model, options);
            if (success) success(model, resp, options);
        };
        model.save(null, options);
        return model;
    },

    sync: function() {
        return sync.apply(this, arguments);
    },

    // Get or fetch a model by Id.
    getOrFetch: function (id, options, cb) {
        if (arguments.length !== 3) {
            cb = options;
            options = {};
        }
        var self = this;
        var model = this.get(id);
        if (model) {
            return window.setTimeout(function() {
	            cb(null, model);
            }, 0);
        }
        if (options.all) {
            options.always = function() {
	            if (!cb) return;
            	var model = self.get(id);
	            if (model) {
	                cb(null, model);
	            } else {
	                cb(new Error('not found'));
	            }
	        };
            return this.fetch(options);
        } else {
            return this.fetchById(id, options, cb);
        }
    },

    // fetchById: fetches a model and adds it to
    // collection when fetched.
    fetchById: function (id, options, cb) {
	    if (arguments.length !== 3) {
            cb = options;
            options = {};
        }
        var self = this;
        var idObj = {};
        idObj[this.mainIndex] = id;
        var model = new this.model(idObj, {collection: this});

        options.success = function () {
            model = self.add(model);
            if (cb) cb(null, model);
        };

        options.error = function (collection, resp) {
            delete model.collection;
            if (cb) {
                var error = new Error(resp.statusText);
                error.status = resp.status;
                cb(error);
            }
        };

        return model.fetch(options);
    }
};
<|endoftext|>


<commit_before>var AarTransformer = require('appc-aar-transform');
var async = require('async');
var fs = require('fs');
var path = require('path');

exports.cliVersion = '>=3.2';

exports.init = function (logger, config, cli) {
	cli.on('build.module.pre.compile', {
		post: function (builder, callback) {
			var foundAndroidArchives = [];
			var aarOutputPath = path.join(builder.buildDir, 'intermediates/exploded-aar');
			var assetsDestinationPath = path.join(builder.buildDir, 'intermediates/assets');
			var libraryDestinationPath = path.join(builder.buildDir, 'intermediates/lib');
			var sharedLibraryDestinationPath = path.join(builder.buildGenJniLocalDir, 'jni');
			var transformer = new AarTransformer(logger);
			fs.readdirSync(builder.projLibDir).forEach(function(file) {
				if (/\.aar/.test(file)) {
					foundAndroidArchives.push(path.join(builder.projLibDir, file));
				}
			});
			logger.trace('Pre-compile hook: Transforming bundled .aar libraries');
			async.each(foundAndroidArchives, function(aarPathAndFilename, next) {
				transformer.transform({
					aarPathAndFilename: aarPathAndFilename,
					outputPath: aarOutputPath,
					assetsDestinationPath: assetsDestinationPath,
					libraryDestinationPath: libraryDestinationPath,
					sharedLibraryDestinationPath: sharedLibraryDestinationPath
				}, function(err, result) {
					if (err) {
						return next(err);
					}

					result.jars.forEach(function(jarPathAndFilename) {
						builder.classPaths[jarPathAndFilename] = 1;
					});
					builder.extraPackages[result.packageName] = 1;

					next();
				});
			}, callback);
		}
	});
};
<commit_msg>[TIMOB-18565] Improve .aar handling<commit_after>/* jshint node: true, esversion: 6 */
'use strict';

/*
 * aar-transform.js: Titanium Android hook to transform Android Archives
 *
 * Copyright (c) 2017, Appcelerator, Inc.  All Rights Reserved.
 * See the LICENSE file for more information.
 */

var AarTransformer = require('appc-aar-tools').AarTransformer;
var async = require('async');
var crypto = require('crypto');
var fs = require('fs');
var path = require('path');
var wrench = require('wrench');

/**
 * Version number to idenfity the data structure of transform results that are
 * passed to the builder instances. This needs to be changed every time the data
 * structure changes to make sure the cache does not pass outdated data.
 *
 * @type {String}
 * @see doTransform method
 */
const HOOK_DATA_VERSION = '1';

/*
 * Constants do identify the type of a builder instance.
 */
const BUILD_VARIANT_MODULE = 'Module';
const BUILD_VARIANT_APP = 'App';

/*
 * Constants to identify where an .aar file comes from. Currently only Titanium
 * modules and projects are able to provide Android Libraries.
 */
const LIBRARY_ORIGIN_CORE = 'Core';
const LIBRARY_ORIGIN_MODULE = 'Module';
const LIBRARY_ORIGIN_PORJECT = 'Project';

exports.cliVersion = '>=3.2';

exports.init = function (logger, config, cli) {
	cli.on('build.pre.compile', {
		post: function(builder, callback) {
			scanProjectAndStartTransform(builder, logger, callback);
		}
	});

	cli.on('build.module.pre.compile', {
		post: function(builder, callback) {
			scanModuleAndStartTransform(builder, logger, callback);
		}
	});
};

/**
 * Scans a project for all available Android Archives and transforms them so
 * they can be used in the build process.
 *
 * Iterates over every module in the project and looks for .aar files inside the
 * module's lib folder. Also checks the project's platform/android folder for
 * additional .aar files.
 *
 * @param {AndroidBuilder} builder Intance of the AndroidBuilder
 * @param {Object} logger Logger to use
 * @param {Function} callback Function to call once the transform is complete
 */
function scanProjectAndStartTransform(builder, logger, callback) {
	var projectAndroidLibraries = [];

	builder.nativeLibModules.forEach(function(moduleInfo) {
		var moduleLibrariesPath = path.join(moduleInfo.modulePath, 'lib');
		if (!fs.existsSync(moduleLibrariesPath)) {
			return;
		}

		fs.readdirSync(moduleLibrariesPath).forEach(function(file) {
			if (/\.aar$/.test(file)) {
				projectAndroidLibraries.push({
					aarPathAndFilename: path.join(moduleLibrariesPath, file),
					originType: LIBRARY_ORIGIN_MODULE,
					moduleInfo: moduleInfo
				});
			}
		});
	});

	var androidPlatformPath = path.join(builder.projectDir, 'platform', 'android');
	if (fs.existsSync(androidPlatformPath)) {
		fs.readdirSync(androidPlatformPath).forEach(function(file) {
			if (/\.aar$/.test(file)) {
				projectAndroidLibraries.push({
					aarPathAndFilename: path.join(androidPlatformPath, file),
					originType: LIBRARY_ORIGIN_PORJECT
				});
			}
		});
	}

	transformAndroidLibraries(projectAndroidLibraries, builder, BUILD_VARIANT_APP, logger, callback);
}

/**
 * Scans a module for all available Android Archives and transforms them so
 * they can be used in the build process.
 *
 * All .aar files inside a module's lib folder will be considered.
 *
 * @param {AndroidModuleBuilder} builder Intance of the AndroidModuleBuilder
 * @param {Object} logger Logger to use
 * @param {Function} callback Function to call once the transform is complete
 */
function scanModuleAndStartTransform(builder, logger, callback) {
	var moduleAndroidLibraries = [];
	fs.readdirSync(builder.projLibDir).forEach(function(file) {
		if (/\.aar/.test(file)) {
			moduleAndroidLibraries.push({
				aarPathAndFilename: path.join(builder.projLibDir, file),
				originType: LIBRARY_ORIGIN_PORJECT
			});
		}
	});
	transformAndroidLibraries(moduleAndroidLibraries, builder, BUILD_VARIANT_MODULE, logger, callback);
}

/**
 * Starts the actual transform process for all .aar files provided.
 *
 * @param {Array} transformTasks Array of task object, containing info about the file to transform
 * @param {AndroidBaseBuilder} builder The current builder instance
 * @param {String} buildVariant One of the BUILD_VARIANT_* constants
 * @param {Object} logger Logger to use
 * @param {Function} callback Function to call once all tasks are complete
 */
function transformAndroidLibraries(transformTasks, builder, buildVariant, logger, callback) {
	if (transformTasks.length === 0) {
		logger.trace('No .aar files to transform');
		return callback();
	}

	var aarOutputPath = path.join(builder.buildIntermediatesDir, 'exploded-aar');

	var cache = new SimpleFileCache(path.join(aarOutputPath, 'state.json'));
	if (cache.has('data-version')) {
		if (cache.get('data-version') !== HOOK_DATA_VERSION) {
			logger.trace('Cache data structure is out of date, flushing current cache data.');
			cache.flush();
		}
	}
	cache.set('data-version', HOOK_DATA_VERSION);

	var libraryHashMap = {};
	var packageNameMap = {};

	logger.trace('Pre-compile hook: Transforming bundled .aar libraries');
	async.eachSeries(transformTasks, function(transformTaskInfo, next) {
		var aarPathAndFilename = transformTaskInfo.aarPathAndFilename;
		async.waterfall([
			/**
			 * Create a hash from the AAR file we are about to transform.
			 *
			 * We use that hash to store the result of the transform in a cache so
			 * we can skip the whole transform process on subsequent builds.
			 *
			 * @param {Function} done Function to call once the hash has been computed
			 */
			function hashFile(done) {
				var hash = crypto.createHash('sha1');
				var fileReadStream = fs.createReadStream(aarPathAndFilename);
				fileReadStream.on('readable', function() {
					var data = fileReadStream.read();
					if (data)
						hash.update(data);
					else {
						var finalHash = hash.digest('hex');
						done(null, finalHash);
					}
				});
			},

			/**
			 * If there already is a library with the exact same SHA-256 hash we can safely
			 * skip all others with the same hash
			 *
			 * @param {String} hash SHA-256 hash of a .aar file
			 * @param {Function} done Function to call once the dupe check is complete
			 */
			function skipLibraryIfDuplicate(hash, done) {
				if (!libraryHashMap[hash]) {
					return done(null, hash);
				}

				logger.trace('Skipping ' + aarPathAndFilename.cyan + ' because it is a duplicate of ' + libraryHashMap[hash].aarPathAndFilename.cyan);
				done(new SkipLibraryError());
			},

			/**
			 * Starts the actual transform process.
			 *
			 * This first checks the cache if we have a transform result for the .aar
			 * hash stored from a previous built and if the exploded aar directory is
			 * still present. If that's the case we can skip the transform and use the
			 * existing data.
			 *
			 * Remember to update HOOK_DATA_VERSION constant if the data
			 * structure this method returns changes.
			 *
			 * @param {String} hash SHA-256 hash of the AAR file.
			 * @param {Function} done Function to call once the transform is complete
			 */
			function doTransform(hash, done) {
				if (cache.has(hash)) {
					var cacheData = cache.get(hash);
					if (cacheData.task.aarPathAndFilename === transformTaskInfo.aarPathAndFilename && fs.existsSync(cacheData.explodedPath)) {
						logger.trace(aarPathAndFilename.cyan + ' has not changed since last built, skipping transform task.');
						return done(null, cacheData);
					}
				}

				var transformer = new AarTransformer(logger);
				var transformOptions = {
					aarPathAndFilename: aarPathAndFilename,
					outputPath: aarOutputPath,
				};
				if (buildVariant === BUILD_VARIANT_APP) {
					transformOptions.assetsDestinationPath = builder.buildBinAssetsDir;
				} else if (buildVariant === BUILD_VARIANT_MODULE) {
					transformOptions.sharedLibraryDestinationPath = builder.localJniGenDir;
				}
				transformer.transform(transformOptions, function (err, result) {
					if (err) {
						return done(err);
					}

					var libraryInfo = {
						packageName: result.packageName,
						explodedPath: result.explodedPath,
						jars: result.jars,
						nativeLibraries: result.nativeLibraries,
						sha256: hash,
						task: transformTaskInfo
					};

					done(null, libraryInfo);
				});
			},

			/**
			 * Ensures all libraries use a unique package name.
			 *
			 * Errors out when two libraries have the same package name and prints
			 * a detailed error message with instructions how to resolve this issue.
			 * We have to do this due to the lack of Gradle and therefore no available
			 * dependency resolution.
			 *
			 * @param {Object} libraryInfo The result of the library transform task
			 * @param {Function} done Function to call once the unique pacakge name check is done
			 */
			function ensureUniquePackageName(libraryInfo, done) {
				function formatDupeInfo(dupeLibraryInfo) {
					var infoString = dupeLibraryInfo.task.aarPathAndFilename + ' (hash: ' + dupeLibraryInfo.sha256;
					if (dupeLibraryInfo.task.originType === LIBRARY_ORIGIN_MODULE) {
						infoString += ', origin: Module ' + dupeLibraryInfo.task.moduleInfo.id;
					} else if (dupeLibraryInfo.task.originType === LIBRARY_ORIGIN_PORJECT) {
						infoString += ', origin: Project';
					}
					infoString += ')';

					return infoString;
				}

				var existingLibrary = packageNameMap[libraryInfo.packageName];
				if (existingLibrary) {
					var errorMessage = 'Conflicting Android Libraries with package name "' + libraryInfo.packageName + '" detected:\n';
					errorMessage += '  ' + formatDupeInfo(existingLibrary) + '\n';
					errorMessage += '  ' + formatDupeInfo(libraryInfo) + '\n\n';
					if (existingLibrary.task.originType === LIBRARY_ORIGIN_MODULE && libraryInfo.task.originType === LIBRARY_ORIGIN_MODULE) {
						errorMessage += 'Please either select a version of these modules where the conflicting .aar file is the same or you can try removing the .aar file from one module\'s "lib" folder.';
					} else if (existingLibrary.task.originType === LIBRARY_ORIGIN_PORJECT && libraryInfo.task.originType === LIBRARY_ORIGIN_PORJECT) {
						errorMessage += 'Please either remove the duplicate .aar file or change the package name of one Android Library if possible.';
					} else if (existingLibrary.task.originType === LIBRARY_ORIGIN_PORJECT || libraryInfo.task.originType === LIBRARY_ORIGIN_PORJECT) {
						errorMessage += 'Please make sure the .aar files in your project and the module match or try removing either the one in your project or in the module.';
					}
					// @TODO: Add a link to docs where this issue is described more in detail.

					return done(new Error(errorMessage));
				}

				done(null, libraryInfo);
			},

			/**
			 * Updates the Builder instance with the result from our transform task.
			 *
			 * @param {Object} libraryInfo The result of the library transform task
			 * @param {Function} done Function to call once the builder was updated
			 */
			function updateBuilderWithTransformResult(libraryInfo, done) {
				if (buildVariant === BUILD_VARIANT_MODULE) {
					libraryInfo.jars.forEach(function(jarPathAndFilename) {
						builder.classPaths[jarPathAndFilename] = 1;
					});
				}

				libraryHashMap[libraryInfo.sha256] = libraryInfo;
				packageNameMap[libraryInfo.packageName] = libraryInfo;
				builder.androidLibraries.push(libraryInfo);

				cache.set(libraryInfo.sha256, libraryInfo);

				done();
			}
		], function (err) {
			if (!err || err instanceof SkipLibraryError) {
				return next();
			}

			next(err);
		});
	}, function(err) {
		if (err) {
			return callback(err);
		}

		// Clean up the cache if files were removed
		var hashes = Object.keys(libraryHashMap);
		var unusedKeys = cache.keys().filter((key) => {
			// exlcude our version meta data key from being removed
			if (key === 'data-version') {
				return false;
			}
			return hashes.indexOf(key) === -1;
		});
		unusedKeys.forEach((key) => {
			cache.remove(key);
		});

		cache.persist();

		callback();
	});
}

/**
 * Marker error class for skipping libraries
 */
class SkipLibraryError extends Error {

}

/**
 * A simple file cache that uses a JSON file as it's storage.
 *
 * This cache reads its data from the cache file once and then operates on a
 * in-memory basis. Changes can be persisted to disk by calling the persist()
 * method.
 */
class SimpleFileCache {
	/**
	 * Constructs a new cache and loads any date from the cache file.
	 *
	 * @param {String} cachePathAndFilename Absolute path and filename to the cache file
	 */
	constructor(cachePathAndFilename) {
		this.cachePathAndFilename = cachePathAndFilename;
		try {
			this.data = fs.existsSync(cachePathAndFilename) ? JSON.parse(fs.readFileSync(cachePathAndFilename)) : {};
		} catch (e) {
			fs.unlinkSync(cachePathAndFilename);
			this.data = {};
		}
	}

	/**
	 * Gets an entry from this cache identfied by key.
	 */
	get(key) {
		return this.has(key) ? this.data[key] : null;
	}

	/**
	 * Sets an entry in this cache, overwriting any existing data.
	 *
	 * This only happens in-memory, call the persist() method to make sure the
	 * changes will be persisted to disk.
	 *
	 * @param {String} key Key to idenfitify the data
	 * @param {Object} data The data to store
	 */
	set(key, data) {
		this.data[key] = data;
	}

	/**
	 * Checks if this cache contains an entry for the specified key.
	 *
	 * @param {String} key The key to check for
	 */
	has(key) {
		return this.data.hasOwnProperty(key);
	}

	/**
	 * Returns all keys that are currently in this cache.
	 */
	keys() {
		return Object.keys(this.data);
	}

	/**
	 * Removes the cache entry identified by key from this cache.
	 *
	 * This only happens in-memory, call the persist() method to make sure the
	 * changes will be persisted to disk.
	 *
	 * @param {String} key The key to remove
	 */
	remove(key) {
		if (this.has(key)) {
			delete this.data[key];
		}
	}

	/**
	 * Removes all data from this cache
	 *
	 * This only happens in-memory, call the persist() method to make sure the
	 * changes will be persisted to disk.
	 */
	flush() {
		this.data = {};
	}

	/**
	 * Persists the current state of this cache to disk.
	 */
	persist() {
		var dataToWrite = JSON.stringify(this.data);
		if (!fs.exists(path.dirname(this.cachePathAndFilename))) {
			wrench.mkdirSyncRecursive(path.dirname(this.cachePathAndFilename));
		}
		fs.writeFileSync(this.cachePathAndFilename, dataToWrite);
	}
}
<|endoftext|>


<commit_before>// Created: 03/01/2007
// Updated: 03/06/2007

// graphics class
// Implements graphics/drawing routines for the labeling tool.

function graphics(div_attach,name) {

  // *******************************************
  // Private variables:
  // *******************************************
  this.name = name;

  //the way the template is set up in line1.svg, this input needs to be
  //"MyCanvas"
  this.div_attach = div_attach;

  this.drawn_obj;
  this.svgNS = "http://www.w3.org/2000/svg";
  this.xlinkNS = "http://www.w3.org/1999/xlink";

  // *******************************************
  // Public methods:
  // *******************************************

  // Draw a polygon given an array of control points X and Y.
  // Returns the polygon element
  this.DrawPolygon = function(X,Y,color,scale,obj_name) {
    var poly_points = "";

    for(i = 0; i < X.length; i++) {
      poly_points = poly_points + (scale*X[i]) + "," +
	(scale*Y[i]) + " ";
    }

    var anchor_obj = document.createElementNS(this.svgNS,"a");
    anchor_obj.setAttributeNS(this.xlinkNS,"title",obj_name);
    document.getElementById(this.div_attach).appendChild(anchor_obj);

    this.drawn_obj = document.createElementNS(this.svgNS,"polygon");
    this.drawn_obj.setAttributeNS(null,"id",this.name);
//     this.drawn_obj.setAttributeNS(null,"title",obj_name);
    this.drawn_obj.setAttributeNS(null,"points",poly_points);
    this.drawn_obj.setAttributeNS(null,"fill","none");
    this.drawn_obj.setAttributeNS(null,"stroke",color);
    this.drawn_obj.setAttributeNS(null,"stroke-width","4");
    anchor_obj.appendChild(this.drawn_obj);
//     document.getElementById(this.div_attach).appendChild(this.drawn_obj);
  };

  // Draw a dashed polygon given an array of control points X and Y.
  // Returns the polygon element
  this.DrawDashedPolygon = function(X,Y,color,scale,obj_name) {
    var poly_points = "";

    for(i = 0; i < X.length; i++) {
      poly_points = poly_points + (scale*X[i]) + "," +
	(scale*Y[i]) + " ";
    }

    var anchor_obj = document.createElementNS(this.svgNS,"a");
    anchor_obj.setAttributeNS(this.xlinkNS,"title",obj_name);
    document.getElementById(this.div_attach).appendChild(anchor_obj);

    this.drawn_obj = document.createElementNS(this.svgNS,"polygon");
    this.drawn_obj.setAttributeNS(null,"id",this.name);
//     this.drawn_obj.setAttributeNS(null,"title",obj_name);
    this.drawn_obj.setAttributeNS(null,"points",poly_points);
    this.drawn_obj.setAttributeNS(null,"fill","none");
    this.drawn_obj.setAttributeNS(null,"stroke",color);
    this.drawn_obj.setAttributeNS(null,"stroke-dasharray","9, 5");
    this.drawn_obj.setAttributeNS(null,"stroke-width","4");
    anchor_obj.appendChild(this.drawn_obj);
//     document.getElementById(this.div_attach).appendChild(this.drawn_obj);
  };

  // Fill the drawn object.
  this.FillPolygon = function () {
    this.drawn_obj.setAttributeNS(null,"fill","red");
    this.drawn_obj.setAttributeNS(null,"fill-opacity","0.5");
  };

  // Unfill the drawn object.
  this.UnfillPolygon = function () {
    this.drawn_obj.setAttributeNS(null,"fill","none");
  };

  // Draw a line given starting coordinates and ending coordinates.
  // Returns the line element
  this.DrawLineSegment = function(x1,y1,x2,y2,scale,color) {
    this.drawn_obj = document.createElementNS(this.svgNS,"line");
    this.drawn_obj.setAttributeNS(null,"id",this.name);
    this.drawn_obj.setAttributeNS(null,"x1",x1*scale);
    this.drawn_obj.setAttributeNS(null,"x2",x2*scale);
    this.drawn_obj.setAttributeNS(null,"y1",y1*scale);
    this.drawn_obj.setAttributeNS(null,"y2",y2*scale);
    this.drawn_obj.setAttributeNS(null,"stroke",color);
    this.drawn_obj.setAttributeNS(null,"stroke-width","4");
    document.getElementById(this.div_attach).appendChild(this.drawn_obj);
  };

  // Draw a point given coordinates.  Returns the point element
  this.DrawPoint = function(x,y,color,width) {
    this.drawn_obj = document.createElementNS(this.svgNS,"circle");
    this.drawn_obj.setAttributeNS(null,"id",this.name);
    this.drawn_obj.setAttributeNS(null,"cx",x);
    this.drawn_obj.setAttributeNS(null,"cy",y);
    this.drawn_obj.setAttributeNS(null,"r",width);
    this.drawn_obj.setAttributeNS(null,"fill",color);
    this.drawn_obj.setAttributeNS(null,"stroke","#ffffff");
    this.drawn_obj.setAttributeNS(null,"stroke-width",width/2);
    document.getElementById(this.div_attach).appendChild(this.drawn_obj);
  };

  // Draw a point given coordinates.  Returns the point element
  this.DrawFlag = function(x,y) {
//     color = '#00ff00';
//     width = 6;
//     this.drawn_obj = document.createElementNS(this.svgNS,"circle");
//     this.drawn_obj.setAttributeNS(null,"id",this.name);
//     this.drawn_obj.setAttributeNS(null,"cx",x);
//     this.drawn_obj.setAttributeNS(null,"cy",y);
//     this.drawn_obj.setAttributeNS(null,"r",width);
//     this.drawn_obj.setAttributeNS(null,"fill",color);
//     this.drawn_obj.setAttributeNS(null,"stroke","#ffffff");
//     this.drawn_obj.setAttributeNS(null,"stroke-width",width/2);
//     document.getElementById(this.div_attach).appendChild(this.drawn_obj);

    x -= 12;
    y -= 38;
    this.drawn_obj = document.createElementNS(this.svgNS,'image');
    this.drawn_obj.setAttributeNS(null,"id",this.name);
    this.drawn_obj.setAttributeNS(null,"width",36);
    this.drawn_obj.setAttributeNS(null,"height",43);
    this.drawn_obj.setAttributeNS(null,"x",x);
    this.drawn_obj.setAttributeNS(null,"y",y);
    this.drawn_obj.setAttributeNS('http://www.w3.org/1999/xlink',"href","Icons/flag.png");
    document.getElementById(this.div_attach).appendChild(this.drawn_obj);
  };

  // Clear all drawings related to this object.
  this.ClearDrawing = function () {
    var q = document.getElementById(this.name);
    if(q) q.parentNode.removeChild(q);
    this.drawn_obj = null;
  };

  // Sets an attribute for the drawn object.
  this.SetAttribute = function(field,value) {
    var v = this.drawn_obj.getAttributeNS(null,field);
    if(v != null) value = value + v;
    this.drawn_obj.setAttributeNS(null,field,value);
  };

  // Move this drawn element to the top in the depth ordering.
  this.MoveToTop = function () {
    var q = document.getElementById(this.name);
    if(q) q.parentNode.appendChild(q);
  };

  // Change the canvas.
  this.ChangeCanvas = function (div_attach) {
    this.div_attach = div_attach;
  };

  // Draw a polyline given an array of control points X and Y.
  // Returns the polygon element
  this.DrawPolyLine = function(X,Y, color, scale) {
    var poly_points = "";
    for(i = 0; i < X.length; i++) {
      poly_points = poly_points + (scale*X[i]) + "," + (scale*Y[i]) + " ";
    }

    this.drawn_obj = document.createElementNS(this.svgNS,"polyline");
    this.drawn_obj.setAttributeNS(null,"id",name);
    this.drawn_obj.setAttributeNS(null,"points",poly_points);
    this.drawn_obj.setAttributeNS(null,"fill","none");
    this.drawn_obj.setAttributeNS(null,"stroke",color);
    this.drawn_obj.setAttributeNS(null,"stroke-width","4");
    document.getElementById(this.div_attach).appendChild(this.drawn_obj);
  };

  // *******************************************
  // Private methods:
  // *******************************************

}
<commit_msg>Antonio changes<commit_after>// Created: 03/01/2007
// Updated: 03/06/2007

// graphics class
// Implements graphics/drawing routines for the labeling tool.

function graphics(div_attach,name) {

  // *******************************************
  // Private variables:
  // *******************************************
  this.name = name;

  //the way the template is set up in line1.svg, this input needs to be
  //"MyCanvas"
  this.div_attach = div_attach;

  this.drawn_obj;
  this.svgNS = "http://www.w3.org/2000/svg";
  this.xlinkNS = "http://www.w3.org/1999/xlink";

  // *******************************************
  // Public methods:
  // *******************************************

  // Draw a polygon given an array of control points X and Y.
  // Returns the polygon element
  this.DrawPolygon = function(X,Y,color,scale,obj_name) {
    var poly_points = "";

    for(i = 0; i < X.length; i++) {
      poly_points = poly_points + (scale*X[i]) + "," +
	(scale*Y[i]) + " ";
    }

    var anchor_obj = document.createElementNS(this.svgNS,"a");
    anchor_obj.setAttributeNS(this.xlinkNS,"title",obj_name);
    document.getElementById(this.div_attach).appendChild(anchor_obj);

    this.drawn_obj = document.createElementNS(this.svgNS,"polygon");
    this.drawn_obj.setAttributeNS(null,"id",this.name);
//     this.drawn_obj.setAttributeNS(null,"title",obj_name);
    this.drawn_obj.setAttributeNS(null,"points",poly_points);
    this.drawn_obj.setAttributeNS(null,"fill","none");
    this.drawn_obj.setAttributeNS(null,"stroke",color);
    this.drawn_obj.setAttributeNS(null,"stroke-width","4");
    anchor_obj.appendChild(this.drawn_obj);
//     document.getElementById(this.div_attach).appendChild(this.drawn_obj);
  };

  // Draw a dashed polygon given an array of control points X and Y.
  // Returns the polygon element
  this.DrawDashedPolygon = function(X,Y,color,scale,obj_name) {
    var poly_points = "";

    for(i = 0; i < X.length; i++) {
      poly_points = poly_points + (scale*X[i]) + "," +
	(scale*Y[i]) + " ";
    }

    var anchor_obj = document.createElementNS(this.svgNS,"a");
    anchor_obj.setAttributeNS(this.xlinkNS,"title",obj_name);
    document.getElementById(this.div_attach).appendChild(anchor_obj);

    this.drawn_obj = document.createElementNS(this.svgNS,"polygon");
    this.drawn_obj.setAttributeNS(null,"id",this.name);
//     this.drawn_obj.setAttributeNS(null,"title",obj_name);
    this.drawn_obj.setAttributeNS(null,"points",poly_points);
    this.drawn_obj.setAttributeNS(null,"fill","none");
    this.drawn_obj.setAttributeNS(null,"stroke",color);
    this.drawn_obj.setAttributeNS(null,"stroke-dasharray","9, 5");
    this.drawn_obj.setAttributeNS(null,"stroke-width","4");
    anchor_obj.appendChild(this.drawn_obj);
//     document.getElementById(this.div_attach).appendChild(this.drawn_obj);
  };

  // Fill the drawn object.
  this.FillPolygon = function () {
      color = this.drawn_obj.getAttributeNS(null, "stroke");
      this.drawn_obj.setAttributeNS(null,"fill",color);
      // this.drawn_obj.setAttributeNS(null,"fill","yellow");
    this.drawn_obj.setAttributeNS(null,"fill-opacity","0.5");
  };

  // Unfill the drawn object.
  this.UnfillPolygon = function () {
    this.drawn_obj.setAttributeNS(null,"fill","none");
  };

  // Draw a line given starting coordinates and ending coordinates.
  // Returns the line element
  this.DrawLineSegment = function(x1,y1,x2,y2,scale,color) {
    this.drawn_obj = document.createElementNS(this.svgNS,"line");
    this.drawn_obj.setAttributeNS(null,"id",this.name);
    this.drawn_obj.setAttributeNS(null,"x1",x1*scale);
    this.drawn_obj.setAttributeNS(null,"x2",x2*scale);
    this.drawn_obj.setAttributeNS(null,"y1",y1*scale);
    this.drawn_obj.setAttributeNS(null,"y2",y2*scale);
    this.drawn_obj.setAttributeNS(null,"stroke",color);
    this.drawn_obj.setAttributeNS(null,"stroke-width","4");
    document.getElementById(this.div_attach).appendChild(this.drawn_obj);
  };

  // Draw a point given coordinates.  Returns the point element
  this.DrawPoint = function(x,y,color,width) {
    this.drawn_obj = document.createElementNS(this.svgNS,"circle");
    this.drawn_obj.setAttributeNS(null,"id",this.name);
    this.drawn_obj.setAttributeNS(null,"cx",x);
    this.drawn_obj.setAttributeNS(null,"cy",y);
    this.drawn_obj.setAttributeNS(null,"r",width);
    this.drawn_obj.setAttributeNS(null,"fill",color);
    this.drawn_obj.setAttributeNS(null,"stroke","#ffffff");
    this.drawn_obj.setAttributeNS(null,"stroke-width",width/2);
    document.getElementById(this.div_attach).appendChild(this.drawn_obj);
  };

  // Draw a point given coordinates.  Returns the point element
  this.DrawFlag = function(x,y) {
//     color = '#00ff00';
//     width = 6;
//     this.drawn_obj = document.createElementNS(this.svgNS,"circle");
//     this.drawn_obj.setAttributeNS(null,"id",this.name);
//     this.drawn_obj.setAttributeNS(null,"cx",x);
//     this.drawn_obj.setAttributeNS(null,"cy",y);
//     this.drawn_obj.setAttributeNS(null,"r",width);
//     this.drawn_obj.setAttributeNS(null,"fill",color);
//     this.drawn_obj.setAttributeNS(null,"stroke","#ffffff");
//     this.drawn_obj.setAttributeNS(null,"stroke-width",width/2);
//     document.getElementById(this.div_attach).appendChild(this.drawn_obj);

    x -= 12;
    y -= 38;
    this.drawn_obj = document.createElementNS(this.svgNS,'image');
    this.drawn_obj.setAttributeNS(null,"id",this.name);
    this.drawn_obj.setAttributeNS(null,"width",36);
    this.drawn_obj.setAttributeNS(null,"height",43);
    this.drawn_obj.setAttributeNS(null,"x",x);
    this.drawn_obj.setAttributeNS(null,"y",y);
    this.drawn_obj.setAttributeNS('http://www.w3.org/1999/xlink',"href","Icons/flag.png");
    document.getElementById(this.div_attach).appendChild(this.drawn_obj);
  };

  // Clear all drawings related to this object.
  this.ClearDrawing = function () {
    var q = document.getElementById(this.name);
    if(q) q.parentNode.removeChild(q);
    this.drawn_obj = null;
  };

  // Sets an attribute for the drawn object.
  this.SetAttribute = function(field,value) {
    var v = this.drawn_obj.getAttributeNS(null,field);
    if(v != null) value = value + v;
    this.drawn_obj.setAttributeNS(null,field,value);
  };

  // Move this drawn element to the top in the depth ordering.
  this.MoveToTop = function () {
    var q = document.getElementById(this.name);
    if(q) q.parentNode.appendChild(q);
  };

  // Change the canvas.
  this.ChangeCanvas = function (div_attach) {
    this.div_attach = div_attach;
  };

  // Draw a polyline given an array of control points X and Y.
  // Returns the polygon element
  this.DrawPolyLine = function(X,Y, color, scale) {
    var poly_points = "";
    for(i = 0; i < X.length; i++) {
      poly_points = poly_points + (scale*X[i]) + "," + (scale*Y[i]) + " ";
    }

    this.drawn_obj = document.createElementNS(this.svgNS,"polyline");
    this.drawn_obj.setAttributeNS(null,"id",name);
    this.drawn_obj.setAttributeNS(null,"points",poly_points);
    this.drawn_obj.setAttributeNS(null,"fill","none");
    this.drawn_obj.setAttributeNS(null,"stroke",color);
    this.drawn_obj.setAttributeNS(null,"stroke-width","4");
    document.getElementById(this.div_attach).appendChild(this.drawn_obj);
  };

  // *******************************************
  // Private methods:
  // *******************************************

}
<|endoftext|>


<commit_before>import Actions from "./Actions"

const keymaps = {
  2: 50,
  3: 51,
  4: 52
}

const addEvent = (obj, event, method) => {
  if (obj.addEventListener) {
    obj.addEventListener(event, method, false)
  } else if (obj.attachEvent) {
    obj.attachEvent(`on${event}`, () => { method(window.event) })
  }
}

const triggers = []
const addTrigger = (keyCode, callback) => {
  triggers.push({ key: keyCode, func: callback })
}

export default {
  register() {
    addEvent(document, "keyup", (event) => {
      const pressedKey = event.keyCode || event.which || event.charCode

      triggers
        .filter(({ key }) => (key === pressedKey))
        .forEach(({ func }) => {
          func(event)
        })
    })

    addTrigger(keymaps["2"], () => {
      Actions.toggleGlobalOption("isImageAutoOpen")
    })
    addTrigger(keymaps["3"], () => {
      Actions.toggleGlobalOption("isLiveStreaming")
    })
    addTrigger(keymaps["4"], () => {
      Actions.focusForm()
    })
  }
}
<commit_msg>Skip Hotkey trigger if focus is input kinda el<commit_after>import Actions from "./Actions"

const keymaps = {
  2: 50,
  3: 51,
  4: 52
}

const addEvent = (obj, event, method) => {
  if (obj.addEventListener) {
    obj.addEventListener(event, method, false)
  } else if (obj.attachEvent) {
    obj.attachEvent(`on${event}`, () => { method(window.event) })
  }
}

const triggers = []
const addTrigger = (keyCode, callback) => {
  triggers.push({ key: keyCode, func: callback })
}

const inputTags = ["INPUT", "SELECT", "TEXTAREA"]
const isTriggeredInput = (event) => {
  const tagName = (event.target || event.srcElement).tagName
  return inputTags.indexOf(tagName) > -1
}

const startMonitoring = () => {
  addEvent(document, "keyup", (event) => {
    const pressedKey = event.keyCode || event.which || event.charCode

    if (isTriggeredInput(event)) { return }

    triggers
      .filter(({ key }) => (key === pressedKey))
      .forEach(({ func }) => {
        func(event)
      })
  })
}

export default {
  register() {
    startMonitoring()

    addTrigger(keymaps["2"], () => {
      Actions.toggleGlobalOption("isImageAutoOpen")
    })
    addTrigger(keymaps["3"], () => {
      Actions.toggleGlobalOption("isLiveStreaming")
    })
    addTrigger(keymaps["4"], () => {
      Actions.focusForm()
    })
  }
}
<|endoftext|>


<commit_before>'use strict';

if (!window.console) console = {log: function() {}};

    // When the DOM is loaded, do the following:
    $(document).ready(function(){

        $('.tabs').show();

        $( window ).resize(function() {
            setMapHeight();
        });

        // When minority changes, redraw the circles with appropriate styles
        $('#category-selector').on('change', function(e) {
            var val = $('#category-selector').val();
            layerUpdate(val);
        });

        // Check to see if we have any parameters for category-selector
        if( typeof loadParams.category != 'undefined'){
            $('#category-selector').val( loadParams.category.values );
            layerUpdate( loadParams.category.values );
        } else {
            addParam( 'category', 'inv_non_hisp_white_only_perc' );
            layerUpdate( 'inv_non_hisp_white_only_perc' );
        }

        // Check to see if we have any parameters for action-taken
        if( typeof loadParams.action != 'undefined'){
            $('#action-taken-selector').val( loadParams.action.values );
        } else {
            addParam( 'action', 'all-apps-5' );
        }

        if( typeof loadParams.lh !== 'undefined'){
            var status = (loadParams.lh.values == "true");
            console.log('load params status: ', status);
            $('#superSelect').prop('checked', status );
            toggleSuper(status);
        }

        $('#superSelect').change( function(){
            var el = $('#superSelect');
            var status = el.prop('checked');
            toggleSuper(status);
            addParam('lh', status );
            init();
        });

        // When the user changes the action taken data selector, re-initialize
        $('#action-taken-selector').on('change', function(){
            addParam( 'action', $('#action-taken-selector option:selected').val() );
            init();
        });

        //Let the application do its thing
        init();

    });

    // Go get the tract centroids and supporting data, THEN build a data object (uses jQuery Deferreds)
    function init(){
        $.when( getTractsInBounds( getBoundParams() ), getTractData( getBoundParams(), getActionTaken( $('#action-taken-selector option:selected').val() ))).done( function(data1, data2){
            rawGeo = data1[0];
            rawData = data2[0];
            createTractDataObj();
            redrawCircles(dataStore.tracts);
            $('#bubbles_loading').hide();
        });
    }

    // Supporting function to adjust the leaflet map to the height of the window
    function setMapHeight() {
        /* Set the map div to the height of the browser window minus the header. */
        var viewportHeight = $(window).height();
        var warningBannerHeight = $('#warning-banner').outerHeight();
        var headerHeight = $('#header').outerHeight();
        var mapHeaderHeight = $('#map-header').outerHeight();
        var mapHeight = (viewportHeight - (warningBannerHeight + headerHeight + mapHeaderHeight));
        $('#map-aside').css('height', mapHeight);
        $('#map').css('height', mapHeight);
    }

    function toggleSuper( status ){
        var url = $('#download_data').data('hierarchy_download_url'),
            origUrl = $('#download_data').data('download_url');

        if( !status ){
            $('#download_data').attr('href', url);
        } else {
            $('#download_data').attr('href', origUrl);
        }
    }

    /*
        ---- GET DATA SCRIPTS ----
    */

    var rawGeo, rawLar, rawMinority, rawData,
    dataStore = {};
    dataStore.tracts = {};

    function getTractsInBounds( bounds, callback ){
        //TODO: Modify parameters for this endpoint to take param hooks instead of forward slash

        $('#bubbles_loading').show();

        // Create the appropriate URL path to return values
        var endpoint = '/api/tractCentroids/',
            params = { neLat: bounds.neLat,
                       neLon: bounds.neLon,
                       swLat: bounds.swLat,
                       swLon: bounds.swLon };
        return $.ajax({
            url: endpoint, data: params, traditional: true,
            success: console.log('tract Get successful')
        }).fail( function( status ){
            console.log( 'no data was available at' + endpoint + '. status: ' + status );
        });

        if( typeof callback === 'function' && callback() ){
            callback;
        }

    }

    function getTractData( bounds, actionTakenVal, callback ){
        $('#bubbles_loading').show();
        var endpoint = '/api/all/',
            params = { year: 2013,
                        'lh': $('#superSelect').prop('checked'),
                        'neLat': bounds.neLat,
                        'neLon': bounds.neLon,
                        'swLat': bounds.swLat,
                        'swLon': bounds.swLon };

        // Check to see if another year has been requested other than the default
        if ( urlParam('year') ){
            params.year = urlParam('year');
        }

        // Set the lender parameter based on the current URL param
        if ( urlParam('lender') ){
            params['lender'] = urlParam('lender');
        } else {
            console.log(' Lender parameter is required.');
            return false;
        }

        // If actionTaken, go get data, otherwise
        // let the user know about the default value
        if ( actionTakenVal ) {
            params['action_taken'] = actionTakenVal;
        } else {
            console.log('No action taken value - default (1-5) will be used.');
        }

        return $.ajax({
            url: endpoint, data: params, traditional: true,
            success: console.log('get API All Data request successful')
        }).fail( function( status ){
            console.log( 'no data was available at' + endpoint + '. status: ' + status );
        });;

        if( typeof callback === 'function' && callback() ){
            callback;
        }
    }

    function createTractDataObj( callback ){
        dataStore.tracts = {};

        // For each top-level data element returned (minority, loanVolume)
        _.each( rawGeo.features, function(feature, key){
            // Loop through each tract and merge the dataset (this could be done server side as well if faster)
            // Make sure the tracts object exists before writing to it.
            var geoid = feature.properties.geoid;
            dataStore.tracts[geoid] = feature.properties;
            _.extend( dataStore.tracts[geoid], rawData.minority[geoid] );

            if( typeof rawData.loanVolume[geoid] != 'undefined'){
                _.extend( dataStore.tracts[geoid], rawData.loanVolume[geoid] );
            } else {
                dataStore.tracts[geoid].volume = 0;
            }

        });

        if( typeof callback === 'function' && callback() ){
            callback;
        }
    }

    /*
        END GET DATA SECTION
    */

    /*
        ---- DRAW CIRCLES ----
    */

    function redrawCircles( geoData ){
        // Remove circles currently on the page (TODO: Add as LayerGroup and transition)
        $('#bubbles_loading').show();
        layers.Centroids.clearLayers();
        _.each(geoData, function(geo) {
            var bubble = drawCircle(geo);
        });
    }

    function updateCircles(){
        //TODO: Figure out best way to update colors of existing, not redraw to reduce lag
        layers.Centroids.eachLayer( function(layer){
            layer.setStyle({fillColor: updateMinorityCircleFill(layer.geoid) });
        });
        console.log("color update complete.");
    }

    function drawCircle(geo){
        var data = geo,
            style = minorityContinuousStyle(
               geo, baseStyle),
            circle = L.circle([geo.centlat, geo.centlon],
                              hmdaStat(data), style );
        //  We will use the geoid when redrawing
        circle.geoid = geo.geoid;
        circle.on('mouseover mousemove', function(e){
            new L.Rrose({ offset: new L.Point(0,0), closeButton: false, autoPan: false })
              .setContent(data['volume'] + ' records<br />' + data['num_households'] + ' households')
              .setLatLng(e.latlng)
              .openOn(map);
        });
        circle.on('mouseout', function(){
            map.closePopup();
        });
        layers.Centroids.addLayer(circle);

    }

    /*
        END DRAW CIRCLES SECTION
    */

    /*
        ---- STYLE THE CIRCLES BASED ON MINORITY ----
    */

    var baseStyle = { fillOpacity: 0.9, weight: 0.5, className: 'lar-circle', fillColor: '#333' };

    //  population-less tracts
    var noStyle = {stroke: false, weight: 0, fill: false};

    function minorityContinuousStyle(geoProps, baseStyle) {
        return minorityStyle(
            geoProps,
            function(minorityPercent, bucket) {
                return (minorityPercent - bucket.lowerBound) / bucket.span;
            },
            baseStyle
        );
    }

    //  Shared function for minority styling; called by the two previous fns
    function minorityStyle(geoProps, percentFn, baseStyle) {
        var geoid = geoProps.geoid,
            tract = dataStore.tracts[geoid];
        // Different styles for when the tract has zero pop, or
        // we have percentages of minorities
        if (tract['total_pop'] === 0 || tract.volume === 0 ) {
            return noStyle;
        } else {
            var perc = minorityPercent(tract),
                bucket = toBucket(perc),
                // convert given percentage to percents within bucket's bounds
                bucketPercent = percentFn(perc, bucket);
            return $.extend({}, baseStyle, {
                fillColor: colorFromPercent(bucketPercent,
                                           bucket.colors)
            });
        }
    }

    // This function returns only the fill color after a minority is changed.
    function updateMinorityCircleFill(geoid){
        var tract = dataStore.tracts[geoid];
        // Different styles for when the tract has zero pop, or
        // we have percentages of minorities
        if (tract['total_pop'] === 0 || tract.volume === 0 ){
            return noStyle;
        } else {
            var perc = minorityPercent(tract),
                bucket = toBucket(perc),
                // convert given percentage to percents within bucket's bounds
                bucketPercent = percentFn(perc, bucket);
            return colorFromPercent(bucketPercent, bucket.colors);
        }
    }

    function percentFn(minorityPercent, bucket) {
                return (minorityPercent - bucket.lowerBound) / bucket.span;
    }

    //  Using the selector, determine which statistic to display.
    function minorityPercent(tractData) {
        var fieldName = $('#category-selector option:selected').val();
        if (fieldName.substring(0, 4) === 'inv_') {
            return 1 - tractData[fieldName.substr(4)];
        } else {
            return tractData[fieldName];
        }
    }

    var colorRanges = [
        {
            span: 0.5,
            lowerBound: 0,
            colors: {
                lowR: 107,
                lowG: 40,
                lowB: 10,
                highR: 250,
                highG: 186,
                highB: 106
            }
        },
        {
            span: 0.5,
            lowerBound: 0.5,
            colors: {
                lowR: 124,
                lowG: 198,
                lowB: 186,
                highR: 12,
                highG: 48,
                highB: 97
            }
        }
    ]

    function toBucket(percent) {
        var i,
            len = colorRanges.length;
        for (i = 0; i < len - 1; i++) {
            //  Next bucket is too far
            if (colorRanges[i + 1].lowerBound > percent) {
                return colorRanges[i];
            }
        }
        return colorRanges[len - 1];  //  last color
    };

    /* Given low and high colors and a percent, figure out the RGB of said
     * percent in that scale */
    function colorFromPercent(percent, c) {
        var diffR = (c.highR - c.lowR) * percent,
            diffG = (c.highG - c.lowG) * percent,
            diffB = (c.highB - c.lowB) * percent;
        return 'rgb(' + (c.lowR + diffR).toFixed() + ', ' +
               (c.lowG + diffG).toFixed() + ', ' +
               (c.lowB + diffB).toFixed() + ')';
    }

    /*
        END STYLE SECTION
    */

    /*
        ---- UTILITY FUNCTIONS ----

    */

    //Scales statistical data to the appropriate level
    function hmdaStat(tractData) {
        var $selected = $('#action-taken-selector option:selected'),
            fieldName = $selected.val(),
            scale = $selected.data('scale'),
            area = scale * tractData['volume'];
        //  As Pi is just a constant scalar, we can ignore it in this
        //  calculation: a = pi*r*r   or r = sqrt(a/pi)
            return Math.sqrt(area);
    }

    // Helper that ensures when a new layer is selected, all others are hidden and primaries stay up front
    function layerUpdate( layer ){
        if ( !layer ){
            console.log('The layer you\'ve requested does not exist.');
        }
        for (var i = minorityLayers.length - 1; i >= 0; i--) {
            map.removeLayer(minorityLayers[i]);
        };
        switch( layer ){
            case 'inv_non_hisp_white_only_perc':
                layer = layers.PctMinority;
                break;
            case 'hispanic_perc':
                layer = layers.PctHispanic;
                break;
            case 'non_hisp_black_only_perc':
                layer = layers.PctBlack;
                break;
            case 'non_hisp_asian_only_perc':
                layer = layers.PctAsian;
                break;
            case 'non_hisp_white_only_perc':
                layer = layers.PctNonWhite;
                break;
        }
        map.addLayer( layer );
        layers.Water.bringToFront();
        layers.Boundaries.bringToFront();
        layers.CountyLabels.bringToFront();
        addParam( 'category', $('#category-selector option:selected').val() );
        updateCircles();
    }

    // Gets non-hash URL parameters
    function urlParam(field) {
        var url = window.location.search.replace('?', ''),
            keyValueStrs = url.split('&'),
            pairs = _.map(keyValueStrs, function(keyValueStr) {
                return keyValueStr.split('=');
            }),
            params = _.reduce(pairs, function(soFar, pair) {
                if (pair.length === 2) {
                    soFar[pair[0]] = pair[1];
                }
                return soFar;
            }, {});
        return params[field];
    }

    // Parameter helper function that filters the query according to dropdown values
    function getActionTaken( value ){
        var actionTaken;

        switch (value) {
            case 'all-apps-5':
                actionTaken = '1,2,3,4,5';
                break;
            case 'all-apps-6':
                actionTaken = '1,2,3,4,5,6';
                break;
            case 'originations-1':
                actionTaken = '1';
                break;
        }
        return actionTaken;
    }

    // Simple function to return bounds consistently with our padding / fixed #
    function getBoundParams(){
        var bounds = map.getBounds(),
        padding = .00;
        console.log(bounds);
        return { neLat: (bounds._northEast.lat + padding).toFixed(6),
                neLon: (bounds._northEast.lng + padding).toFixed(6),
                swLat: (bounds._southWest.lat - padding).toFixed(6),
                swLon: (bounds._southWest.lng - padding).toFixed(6)
            }
    }

    function getUniques( arr ){
        return _.uniq( arr );
    }

    /*
        END UTILITY FUNCTIONS
    */<commit_msg>updated DOM points, names<commit_after>'use strict';

if (!window.console) console = {log: function() {}};

    // When the DOM is loaded, do the following:
    $(document).ready(function(){

        $('.tabs').show();

        $( window ).resize(function() {
            setMapHeight();
        });

        // When minority changes, redraw the circles with appropriate styles
        $('#category-selector').on('change', function(e) {
            var val = $('#category-selector').val();
            layerUpdate(val);
        });

        // Check to see if we have any parameters for category-selector
        if( typeof loadParams.category != 'undefined'){
            $('#category-selector').val( loadParams.category.values );
            layerUpdate( loadParams.category.values );
        } else {
            addParam( 'category', 'inv_non_hisp_white_only_perc' );
            layerUpdate( 'inv_non_hisp_white_only_perc' );
        }

        // Check to see if we have any parameters for action-taken
        if( typeof loadParams.action != 'undefined'){
            $('#action-taken-selector').val( loadParams.action.values );
        } else {
            addParam( 'action', 'all-apps-5' );
        }

        if( typeof loadParams.lh !== 'undefined'){
            var status = (loadParams.lh.values == "true");
            console.log('load params status: ', status);
            $('#superSelect').prop('checked', status );
            toggleSuper(status);
        }

        $('#superSelect').change( function(){
            var el = $('#superSelect');
            var status = el.prop('checked');
            addParam('lh', status );
            toggleSuper(status);
            init();
        });

        // When the user changes the action taken data selector, re-initialize
        $('#action-taken-selector').on('change', function(){
            addParam( 'action', $('#action-taken-selector option:selected').val() );
            init();
        });

        //Let the application do its thing
        init();

    });

    // Go get the tract centroids and supporting data, THEN build a data object (uses jQuery Deferreds)
    function init(){
        $.when( getTractsInBounds( getBoundParams() ), getTractData( getBoundParams(), getActionTaken( $('#action-taken-selector option:selected').val() ))).done( function(data1, data2){
            rawGeo = data1[0];
            rawData = data2[0];
            createTractDataObj();
            redrawCircles(dataStore.tracts);
            $('#bubbles_loading').hide();
        });
    }

    // Supporting function to adjust the leaflet map to the height of the window
    function setMapHeight() {
        /* Set the map div to the height of the browser window minus the header. */
        var viewportHeight = $(window).height();
        var warningBannerHeight = $('#warning-banner').outerHeight();
        var headerHeight = $('#header').outerHeight();
        var mapHeaderHeight = $('#map-header').outerHeight();
        var mapHeight = (viewportHeight - (warningBannerHeight + headerHeight + mapHeaderHeight));
        $('#map-aside').css('height', mapHeight);
        $('#map').css('height', mapHeight);
    }

    function toggleSuper( status ){
        var url = $('#download-data').data('super-download'),
            origUrl = $('#download-data').data('download');
        if( !status ){
            $('#download-data').attr('href', origUrl);
        } else {
            $('#download-data').attr('href', url);
        }
    }

    /*
        ---- GET DATA SCRIPTS ----
    */

    var rawGeo, rawLar, rawMinority, rawData,
    dataStore = {};
    dataStore.tracts = {};

    function getTractsInBounds( bounds, callback ){
        //TODO: Modify parameters for this endpoint to take param hooks instead of forward slash

        $('#bubbles_loading').show();

        // Create the appropriate URL path to return values
        var endpoint = '/api/tractCentroids/',
            params = { neLat: bounds.neLat,
                       neLon: bounds.neLon,
                       swLat: bounds.swLat,
                       swLon: bounds.swLon };
        return $.ajax({
            url: endpoint, data: params, traditional: true,
            success: console.log('tract Get successful')
        }).fail( function( status ){
            console.log( 'no data was available at' + endpoint + '. status: ' + status );
        });

        if( typeof callback === 'function' && callback() ){
            callback;
        }

    }

    function getTractData( bounds, actionTakenVal, callback ){
        $('#bubbles_loading').show();
        var endpoint = '/api/all/',
            params = { year: 2013,
                        'lh': $('#superSelect').prop('checked'),
                        'neLat': bounds.neLat,
                        'neLon': bounds.neLon,
                        'swLat': bounds.swLat,
                        'swLon': bounds.swLon };

        // Check to see if another year has been requested other than the default
        if ( urlParam('year') ){
            params.year = urlParam('year');
        }

        // Set the lender parameter based on the current URL param
        if ( urlParam('lender') ){
            params['lender'] = urlParam('lender');
        } else {
            console.log(' Lender parameter is required.');
            return false;
        }

        // If actionTaken, go get data, otherwise
        // let the user know about the default value
        if ( actionTakenVal ) {
            params['action_taken'] = actionTakenVal;
        } else {
            console.log('No action taken value - default (1-5) will be used.');
        }

        return $.ajax({
            url: endpoint, data: params, traditional: true,
            success: console.log('get API All Data request successful')
        }).fail( function( status ){
            console.log( 'no data was available at' + endpoint + '. status: ' + status );
        });;

        if( typeof callback === 'function' && callback() ){
            callback;
        }
    }

    function createTractDataObj( callback ){
        dataStore.tracts = {};

        // For each top-level data element returned (minority, loanVolume)
        _.each( rawGeo.features, function(feature, key){
            // Loop through each tract and merge the dataset (this could be done server side as well if faster)
            // Make sure the tracts object exists before writing to it.
            var geoid = feature.properties.geoid;
            dataStore.tracts[geoid] = feature.properties;
            _.extend( dataStore.tracts[geoid], rawData.minority[geoid] );

            if( typeof rawData.loanVolume[geoid] != 'undefined'){
                _.extend( dataStore.tracts[geoid], rawData.loanVolume[geoid] );
            } else {
                dataStore.tracts[geoid].volume = 0;
            }

        });

        if( typeof callback === 'function' && callback() ){
            callback;
        }
    }

    /*
        END GET DATA SECTION
    */

    /*
        ---- DRAW CIRCLES ----
    */

    function redrawCircles( geoData ){
        // Remove circles currently on the page (TODO: Add as LayerGroup and transition)
        $('#bubbles_loading').show();
        layers.Centroids.clearLayers();
        _.each(geoData, function(geo) {
            var bubble = drawCircle(geo);
        });
    }

    function updateCircles(){
        //TODO: Figure out best way to update colors of existing, not redraw to reduce lag
        layers.Centroids.eachLayer( function(layer){
            layer.setStyle({fillColor: updateMinorityCircleFill(layer.geoid) });
        });
        console.log("color update complete.");
    }

    function drawCircle(geo){
        var data = geo,
            style = minorityContinuousStyle(
               geo, baseStyle),
            circle = L.circle([geo.centlat, geo.centlon],
                              hmdaStat(data), style );
        //  We will use the geoid when redrawing
        circle.geoid = geo.geoid;
        circle.on('mouseover mousemove', function(e){
            new L.Rrose({ offset: new L.Point(0,0), closeButton: false, autoPan: false })
              .setContent(data['volume'] + ' records<br />' + data['num_households'] + ' households')
              .setLatLng(e.latlng)
              .openOn(map);
        });
        circle.on('mouseout', function(){
            map.closePopup();
        });
        layers.Centroids.addLayer(circle);

    }

    /*
        END DRAW CIRCLES SECTION
    */

    /*
        ---- STYLE THE CIRCLES BASED ON MINORITY ----
    */

    var baseStyle = { fillOpacity: 0.9, weight: 0.5, className: 'lar-circle', fillColor: '#333' };

    //  population-less tracts
    var noStyle = {stroke: false, weight: 0, fill: false};

    function minorityContinuousStyle(geoProps, baseStyle) {
        return minorityStyle(
            geoProps,
            function(minorityPercent, bucket) {
                return (minorityPercent - bucket.lowerBound) / bucket.span;
            },
            baseStyle
        );
    }

    //  Shared function for minority styling; called by the two previous fns
    function minorityStyle(geoProps, percentFn, baseStyle) {
        var geoid = geoProps.geoid,
            tract = dataStore.tracts[geoid];
        // Different styles for when the tract has zero pop, or
        // we have percentages of minorities
        if (tract['total_pop'] === 0 || tract.volume === 0 ) {
            return noStyle;
        } else {
            var perc = minorityPercent(tract),
                bucket = toBucket(perc),
                // convert given percentage to percents within bucket's bounds
                bucketPercent = percentFn(perc, bucket);
            return $.extend({}, baseStyle, {
                fillColor: colorFromPercent(bucketPercent,
                                           bucket.colors)
            });
        }
    }

    // This function returns only the fill color after a minority is changed.
    function updateMinorityCircleFill(geoid){
        var tract = dataStore.tracts[geoid];
        // Different styles for when the tract has zero pop, or
        // we have percentages of minorities
        if (tract['total_pop'] === 0 || tract.volume === 0 ){
            return noStyle;
        } else {
            var perc = minorityPercent(tract),
                bucket = toBucket(perc),
                // convert given percentage to percents within bucket's bounds
                bucketPercent = percentFn(perc, bucket);
            return colorFromPercent(bucketPercent, bucket.colors);
        }
    }

    function percentFn(minorityPercent, bucket) {
                return (minorityPercent - bucket.lowerBound) / bucket.span;
    }

    //  Using the selector, determine which statistic to display.
    function minorityPercent(tractData) {
        var fieldName = $('#category-selector option:selected').val();
        if (fieldName.substring(0, 4) === 'inv_') {
            return 1 - tractData[fieldName.substr(4)];
        } else {
            return tractData[fieldName];
        }
    }

    var colorRanges = [
        {
            span: 0.5,
            lowerBound: 0,
            colors: {
                lowR: 107,
                lowG: 40,
                lowB: 10,
                highR: 250,
                highG: 186,
                highB: 106
            }
        },
        {
            span: 0.5,
            lowerBound: 0.5,
            colors: {
                lowR: 124,
                lowG: 198,
                lowB: 186,
                highR: 12,
                highG: 48,
                highB: 97
            }
        }
    ]

    function toBucket(percent) {
        var i,
            len = colorRanges.length;
        for (i = 0; i < len - 1; i++) {
            //  Next bucket is too far
            if (colorRanges[i + 1].lowerBound > percent) {
                return colorRanges[i];
            }
        }
        return colorRanges[len - 1];  //  last color
    };

    /* Given low and high colors and a percent, figure out the RGB of said
     * percent in that scale */
    function colorFromPercent(percent, c) {
        var diffR = (c.highR - c.lowR) * percent,
            diffG = (c.highG - c.lowG) * percent,
            diffB = (c.highB - c.lowB) * percent;
        return 'rgb(' + (c.lowR + diffR).toFixed() + ', ' +
               (c.lowG + diffG).toFixed() + ', ' +
               (c.lowB + diffB).toFixed() + ')';
    }

    /*
        END STYLE SECTION
    */

    /*
        ---- UTILITY FUNCTIONS ----

    */

    //Scales statistical data to the appropriate level
    function hmdaStat(tractData) {
        var $selected = $('#action-taken-selector option:selected'),
            fieldName = $selected.val(),
            scale = $selected.data('scale'),
            area = scale * tractData['volume'];
        //  As Pi is just a constant scalar, we can ignore it in this
        //  calculation: a = pi*r*r   or r = sqrt(a/pi)
            return Math.sqrt(area);
    }

    // Helper that ensures when a new layer is selected, all others are hidden and primaries stay up front
    function layerUpdate( layer ){
        if ( !layer ){
            console.log('The layer you\'ve requested does not exist.');
        }
        for (var i = minorityLayers.length - 1; i >= 0; i--) {
            map.removeLayer(minorityLayers[i]);
        };
        switch( layer ){
            case 'inv_non_hisp_white_only_perc':
                layer = layers.PctMinority;
                break;
            case 'hispanic_perc':
                layer = layers.PctHispanic;
                break;
            case 'non_hisp_black_only_perc':
                layer = layers.PctBlack;
                break;
            case 'non_hisp_asian_only_perc':
                layer = layers.PctAsian;
                break;
            case 'non_hisp_white_only_perc':
                layer = layers.PctNonWhite;
                break;
        }
        map.addLayer( layer );
        layers.Water.bringToFront();
        layers.Boundaries.bringToFront();
        layers.CountyLabels.bringToFront();
        addParam( 'category', $('#category-selector option:selected').val() );
        updateCircles();
    }

    // Gets non-hash URL parameters
    function urlParam(field) {
        var url = window.location.search.replace('?', ''),
            keyValueStrs = url.split('&'),
            pairs = _.map(keyValueStrs, function(keyValueStr) {
                return keyValueStr.split('=');
            }),
            params = _.reduce(pairs, function(soFar, pair) {
                if (pair.length === 2) {
                    soFar[pair[0]] = pair[1];
                }
                return soFar;
            }, {});
        return params[field];
    }

    // Parameter helper function that filters the query according to dropdown values
    function getActionTaken( value ){
        var actionTaken;

        switch (value) {
            case 'all-apps-5':
                actionTaken = '1,2,3,4,5';
                break;
            case 'all-apps-6':
                actionTaken = '1,2,3,4,5,6';
                break;
            case 'originations-1':
                actionTaken = '1';
                break;
        }
        return actionTaken;
    }

    // Simple function to return bounds consistently with our padding / fixed #
    function getBoundParams(){
        var bounds = map.getBounds(),
        padding = .00;
        console.log(bounds);
        return { neLat: (bounds._northEast.lat + padding).toFixed(6),
                neLon: (bounds._northEast.lng + padding).toFixed(6),
                swLat: (bounds._southWest.lat - padding).toFixed(6),
                swLon: (bounds._southWest.lng - padding).toFixed(6)
            }
    }

    function getUniques( arr ){
        return _.uniq( arr );
    }

    /*
        END UTILITY FUNCTIONS
    */<|endoftext|>


<commit_before>const FileSender = require('./fileSender');
const { notify, gcmCompliant } = require('./utils');
const $ = require('jquery');
require('jquery-circle-progress');

const Raven = window.Raven;

$(document).ready(function() {
  gcmCompliant().catch(err => {
    $('#page-one').attr('hidden', true);
    $('#unsupported-browser').removeAttr('hidden');
  });

  $('#file-upload').change(onUpload);
  $('body').on('dragover', allowDrop).on('drop', onUpload);
  // reset copy button
  const $copyBtn = $('#copy-btn');
  $copyBtn.attr('disabled', false);
  $('#link').attr('disabled', false);
  $copyBtn.attr('data-l10n-id', 'copyUrlFormButton');

  if (localStorage.length === 0) {
    toggleHeader();
  } else {
    for (let i = 0; i < localStorage.length; i++) {
      const id = localStorage.key(i);
      //check if file exists before adding to list
      checkExistence(id, true);
    }
  }

  // copy link to clipboard
  $copyBtn.click(() => {
    const aux = document.createElement('input');
    aux.setAttribute('value', $('#link').attr('value'));
    document.body.appendChild(aux);
    aux.select();
    document.execCommand('copy');
    document.body.removeChild(aux);
    //disable button for 3s
    $copyBtn.attr('disabled', true);
    $('#link').attr('disabled', true);
    $copyBtn.html('');
    window.setTimeout(() => {
      $copyBtn.attr('disabled', false);
      $('#link').attr('disabled', false);
      $copyBtn.attr('data-l10n-id', 'copyUrlFormButton');
    }, 3000);
  });

  $('.upload-window').on('dragover', () => {
    $('.upload-window').addClass('ondrag');
  });
  $('.upload-window').on('dragleave', () => {
    $('.upload-window').removeClass('ondrag');
  });
  //initiate progress bar
  $('#ul-progress').circleProgress({
    value: 0.0,
    startAngle: -Math.PI / 2,
    fill: '#3B9DFF',
    size: 158,
    animation: { duration: 300 }
  });

  //link back to homepage
  $('.send-new').attr('href', window.location);

  // on file upload by browse or drag & drop
  function onUpload(event) {
    event.preventDefault();
    let file = '';
    if (event.type === 'drop') {
      if (event.originalEvent.dataTransfer.files.length > 1){
        document.l10n.formatValue('uploadPageMultipleFilesAlert')
                     .then(str => {
                       alert(str);
                     });
        return;
      }
      file = event.originalEvent.dataTransfer.files[0];
    } else {
      file = event.target.files[0];
    }

    $('#page-one').attr('hidden', true);
    $('#upload-error').attr('hidden', true);
    $('#upload-progress').removeAttr('hidden');
    //don't allow drag and drop when not on page-one
    $('body').off('drop', onUpload);
    const expiration = 24 * 60 * 60 * 1000; //will eventually come from a field

    const fileSender = new FileSender(file);
    $('#cancel-upload').click(() => {
      fileSender.cancel();
      location.reload();
      document.l10n.formatValue('uploadCancelNotification')
                   .then(str => {
                     notify(str);
                   });
    });

    fileSender.on('progress', progress => {
      const percent = progress[0] / progress[1];
      // update progress bar
      $('#ul-progress').circleProgress('value', percent);
      $('#ul-progress').circleProgress().on('circle-animation-end', function() {
        $('.percent-number').html(`${Math.floor(percent * 100)}`);
      });
      if (progress[1] < 1000000) {
        $('.progress-text').text(
          `${file.name} (${(progress[0] / 1000).toFixed(1)}KB of ${(progress[1] / 1000).toFixed(1)}KB)`
        );
      } else if (progress[1] < 1000000000) {
        $('.progress-text').text(
          `${file.name} (${(progress[0] / 1000000).toFixed(1)}MB of ${(progress[1] / 1000000).toFixed(1)}MB)`
        );
      } else {
        $('.progress-text').text(
          `${file.name} (${(progress[0] / 1000000).toFixed(1)}MB of ${(progress[1] / 1000000000).toFixed(1)}GB)`
        );
      }
    });

    fileSender.on('loading', isStillLoading => {
      // The file is loading into Firefox at this stage
      if (isStillLoading) {
        console.log('Processing');
      } else {
        console.log('Finished processing');
      }
    });

    fileSender.on('hashing', isStillHashing => {
      // The file is being hashed
      if (isStillHashing) {
        console.log('Hashing');
      } else {
        console.log('Finished hashing');
      }
    });

    fileSender.on('encrypting', isStillEncrypting => {
      // The file is being encrypted
      if (isStillEncrypting) {
        console.log('Encrypting');
      } else {
        console.log('Finished encrypting');
      }
    });
    let t = '';
    fileSender
      .upload()
      .then(info => {
        const fileData = {
          name: file.name,
          fileId: info.fileId,
          url: info.url,
          secretKey: info.secretKey,
          deleteToken: info.deleteToken,
          creationDate: new Date(),
          expiry: expiration
        };
        localStorage.setItem(info.fileId, JSON.stringify(fileData));
        $('#upload-filename').attr('data-l10n-id', 'uploadSuccessConfirmHeader');
        t = window.setTimeout(() => {
          $('#page-one').attr('hidden', true);
          $('#upload-progress').attr('hidden', true);
          $('#upload-error').attr('hidden', true);
          $('#share-link').removeAttr('hidden');
        }, 2000);

        populateFileList(JSON.stringify(fileData));
        document.l10n.formatValue('notifyUploadDone')
                     .then(str => {
                       notify(str);
                     });
      })
      .catch(err => {
        Raven.captureException(err);
        console.log(err);
        $('#page-one').attr('hidden', true);
        $('#upload-progress').attr('hidden', true);
        $('#upload-error').removeAttr('hidden');
        window.clearTimeout(t);
      });
  }

  function allowDrop(ev) {
    ev.preventDefault();
  }

  function checkExistence(id, populate) {
    const xhr = new XMLHttpRequest();
    xhr.onreadystatechange = () => {
      if (xhr.readyState === XMLHttpRequest.DONE) {
        if (xhr.status === 200) {
          if (populate) {
            populateFileList(localStorage.getItem(id));
          }
        } else if (xhr.status === 404) {
          localStorage.removeItem(id);
        }
      }
    };
    xhr.open('get', '/exists/' + id, true);
    xhr.send();
  }

  //update file table with current files in localStorage
  function populateFileList(file) {
    try {
      file = JSON.parse(file);
    } catch (e) {
      return;
    }

    const row = document.createElement('tr');
    const name = document.createElement('td');
    const link = document.createElement('td');
    const $copyIcon = $('', { src: '/resources/copy-16.svg', class: 'icon-copy', 'data-l10n-id': 'copyUrlHover'});
    const expiry = document.createElement('td');
    const del = document.createElement('td');
    const $delIcon = $('', { src: '/resources/close-16.svg', class: 'icon-delete', 'data-l10n-id': 'deleteButtonHover' });
    const popupDiv = document.createElement('div');
    const $popupText = $('
', { class: 'popuptext' });
    const cellText = document.createTextNode(file.name);

    const url = file.url.trim() + `#${file.secretKey}`.trim();

    $('#link').attr('value', url);
    $('#copy-text').attr(
      'data-l10n-args',
      '{"filename": "' + file.name + '"}'
    );
    $('#copy-text').attr(
      'data-l10n-id',
      'copyUrlFormLabelWithName'
    );
    $popupText.attr('tabindex', '-1');

    name.appendChild(cellText);

    // create delete button

    const delSpan = document.createElement('span');
    $(delSpan).addClass('icon-cancel-1');
    $(delSpan).attr('data-l10n-id', 'deleteButtonHover');
    del.appendChild(delSpan);

    const linkSpan = document.createElement('span');
    $(linkSpan).addClass('icon-docs');
    $(linkSpan).attr('data-l10n-id', 'copyUrlHover');
    link.appendChild(linkSpan);

    link.style.color = '#0A8DFF';

    //copy link to clipboard when icon clicked
    $copyIcon.click(function() {
      const aux = document.createElement('input');
      aux.setAttribute('value', url);
      document.body.appendChild(aux);
      aux.select();
      document.execCommand('copy');
      document.body.removeChild(aux);
      document.l10n.formatValue('copiedUrl')
                   .then(translated => {
                     link.innerHTML = translated;
                   });
      window.setTimeout(() => {
        const linkImg = document.createElement('img');
        $(linkImg).addClass('icon-copy');
        $(linkImg).attr('data-l10n-id', 'copyUrlHover');
        $(linkImg).attr('src', '/resources/copy-16.svg');
        $(link).html(linkImg);
      }, 500);
    });

    file.creationDate = new Date(file.creationDate);

    const future = new Date();
    future.setTime(file.creationDate.getTime() + file.expiry);

    let countdown = 0;
    countdown = future.getTime() - new Date().getTime();
    let minutes = Math.floor(countdown / 1000 / 60);
    let hours = Math.floor(minutes / 60);
    let seconds = Math.floor(countdown / 1000 % 60);

    poll();

    function poll() {
      countdown = future.getTime() - new Date().getTime();
      minutes = Math.floor(countdown / 1000 / 60);
      hours = Math.floor(minutes / 60);
      seconds = Math.floor(countdown / 1000 % 60);
      let t;

      if (hours >= 1) {
        expiry.innerHTML = hours + 'h ' + minutes % 60 + 'm';
        t = window.setTimeout(() => {
          poll();
        }, 60000);
      } else if (hours === 0) {
        expiry.innerHTML = minutes + 'm ' + seconds + 's';
        t = window.setTimeout(() => {
          poll();
        }, 1000);
      }
      //remove from list when expired
      if (countdown <= 0) {
        localStorage.removeItem(file.fileId);
        $(expiry).parents('tr').remove();
        window.clearTimeout(t);
        toggleHeader();
      }
    }

    // create popup
    popupDiv.classList.add('popup');
    const popupDelSpan = document.createElement('span');
    $(popupDelSpan).addClass('del-file');
    $(popupDelSpan).attr('data-l10n-id', 'sentFilesTitle4');

    const popupNvmSpan = document.createElement('span');
    $(popupNvmSpan).addClass('nvm');
    $(popupNvmSpan).attr('data-l10n-id', 'nevermindButton');

    $popupText.html([
      popupDelSpan,
      ' ',
      ' ',
      popupNvmSpan
    ]);

    // add data cells to table row
    row.appendChild(name);
    $(link).append($copyIcon);
    row.appendChild(link);
    row.appendChild(expiry);
    $(popupDiv).append($popupText);
    $(del).append($delIcon);
    del.appendChild(popupDiv);
    row.appendChild(del);
    $('tbody').append(row); //add row to table

    // delete file
    $popupText.find('.del-file').click(e => {
      FileSender.delete(file.fileId, file.deleteToken).then(() => {
        $(e.target).parents('tr').remove();
        localStorage.removeItem(file.fileId);
        toggleHeader();
      });
    });
    document.getElementById('delete-file').onclick = () => {
      FileSender.delete(file.fileId, file.deleteToken).then(() => {
        localStorage.removeItem(file.fileId);
        location.reload();
      });
    };
    // show popup
    $delIcon.click(function() {
      $popupText.addClass('show');
      $popupText.focus();
    });
    // hide popup
    $popupText.find('.nvm').click(function(e) {
      e.stopPropagation();
      $popupText.removeClass('show');
    });
    $popupText.click(function(e) {
      e.stopPropagation();
    });
    //close when popup loses focus
    $popupText.blur(() => {
      $popupText.removeClass('show');
    });

    toggleHeader();
  }
  function toggleHeader() {
    //hide table header if empty list
    if (document.querySelector('tbody').childNodes.length === 1) {
      $('#file-list').attr('hidden', true);
    } else {
      $('#file-list').removeAttr('hidden');
    }
  }
});
<commit_msg>make alert show when uploading a folder<commit_after>const FileSender = require('./fileSender');
const { notify, gcmCompliant } = require('./utils');
const $ = require('jquery');
require('jquery-circle-progress');

const Raven = window.Raven;

$(document).ready(function() {
  gcmCompliant().catch(err => {
    $('#page-one').attr('hidden', true);
    $('#unsupported-browser').removeAttr('hidden');
  });

  $('#file-upload').change(onUpload);
  $('body').on('dragover', allowDrop).on('drop', onUpload);
  // reset copy button
  const $copyBtn = $('#copy-btn');
  $copyBtn.attr('disabled', false);
  $('#link').attr('disabled', false);
  $copyBtn.attr('data-l10n-id', 'copyUrlFormButton');

  if (localStorage.length === 0) {
    toggleHeader();
  } else {
    for (let i = 0; i < localStorage.length; i++) {
      const id = localStorage.key(i);
      //check if file exists before adding to list
      checkExistence(id, true);
    }
  }

  // copy link to clipboard
  $copyBtn.click(() => {
    const aux = document.createElement('input');
    aux.setAttribute('value', $('#link').attr('value'));
    document.body.appendChild(aux);
    aux.select();
    document.execCommand('copy');
    document.body.removeChild(aux);
    //disable button for 3s
    $copyBtn.attr('disabled', true);
    $('#link').attr('disabled', true);
    $copyBtn.html('');
    window.setTimeout(() => {
      $copyBtn.attr('disabled', false);
      $('#link').attr('disabled', false);
      $copyBtn.attr('data-l10n-id', 'copyUrlFormButton');
    }, 3000);
  });

  $('.upload-window').on('dragover', () => {
    $('.upload-window').addClass('ondrag');
  });
  $('.upload-window').on('dragleave', () => {
    $('.upload-window').removeClass('ondrag');
  });
  //initiate progress bar
  $('#ul-progress').circleProgress({
    value: 0.0,
    startAngle: -Math.PI / 2,
    fill: '#3B9DFF',
    size: 158,
    animation: { duration: 300 }
  });

  //link back to homepage
  $('.send-new').attr('href', window.location);

  // on file upload by browse or drag & drop
  function onUpload(event) {
    event.preventDefault();
    let file = '';
    if (event.type === 'drop') {
      if (event.originalEvent.dataTransfer.files.length > 1 || event.originalEvent.dataTransfer.files[0].size === 0){
        $('.upload-window').removeClass('ondrag');
        document.l10n.formatValue('uploadPageMultipleFilesAlert')
                     .then(str => {
                       alert(str);
                     });
        return;
      }
      file = event.originalEvent.dataTransfer.files[0];
    } else {
      file = event.target.files[0];
    }

    $('#page-one').attr('hidden', true);
    $('#upload-error').attr('hidden', true);
    $('#upload-progress').removeAttr('hidden');
    //don't allow drag and drop when not on page-one
    $('body').off('drop', onUpload);
    const expiration = 24 * 60 * 60 * 1000; //will eventually come from a field

    const fileSender = new FileSender(file);
    $('#cancel-upload').click(() => {
      fileSender.cancel();
      location.reload();
      document.l10n.formatValue('uploadCancelNotification')
                   .then(str => {
                     notify(str);
                   });
    });

    fileSender.on('progress', progress => {
      const percent = progress[0] / progress[1];
      // update progress bar
      $('#ul-progress').circleProgress('value', percent);
      $('#ul-progress').circleProgress().on('circle-animation-end', function() {
        $('.percent-number').html(`${Math.floor(percent * 100)}`);
      });
      if (progress[1] < 1000000) {
        $('.progress-text').text(
          `${file.name} (${(progress[0] / 1000).toFixed(1)}KB of ${(progress[1] / 1000).toFixed(1)}KB)`
        );
      } else if (progress[1] < 1000000000) {
        $('.progress-text').text(
          `${file.name} (${(progress[0] / 1000000).toFixed(1)}MB of ${(progress[1] / 1000000).toFixed(1)}MB)`
        );
      } else {
        $('.progress-text').text(
          `${file.name} (${(progress[0] / 1000000).toFixed(1)}MB of ${(progress[1] / 1000000000).toFixed(1)}GB)`
        );
      }
    });

    fileSender.on('loading', isStillLoading => {
      // The file is loading into Firefox at this stage
      if (isStillLoading) {
        console.log('Processing');
      } else {
        console.log('Finished processing');
      }
    });

    fileSender.on('hashing', isStillHashing => {
      // The file is being hashed
      if (isStillHashing) {
        console.log('Hashing');
      } else {
        console.log('Finished hashing');
      }
    });

    fileSender.on('encrypting', isStillEncrypting => {
      // The file is being encrypted
      if (isStillEncrypting) {
        console.log('Encrypting');
      } else {
        console.log('Finished encrypting');
      }
    });
    let t = '';
    fileSender
      .upload()
      .then(info => {
        const fileData = {
          name: file.name,
          fileId: info.fileId,
          url: info.url,
          secretKey: info.secretKey,
          deleteToken: info.deleteToken,
          creationDate: new Date(),
          expiry: expiration
        };
        localStorage.setItem(info.fileId, JSON.stringify(fileData));
        $('#upload-filename').attr('data-l10n-id', 'uploadSuccessConfirmHeader');
        t = window.setTimeout(() => {
          $('#page-one').attr('hidden', true);
          $('#upload-progress').attr('hidden', true);
          $('#upload-error').attr('hidden', true);
          $('#share-link').removeAttr('hidden');
        }, 1000);

        populateFileList(JSON.stringify(fileData));
        document.l10n.formatValue('notifyUploadDone')
                     .then(str => {
                       notify(str);
                     });
      })
      .catch(err => {
        Raven.captureException(err);
        console.log(err);
        $('#page-one').attr('hidden', true);
        $('#upload-progress').attr('hidden', true);
        $('#upload-error').removeAttr('hidden');
        window.clearTimeout(t);
      });
  }

  function allowDrop(ev) {
    ev.preventDefault();
  }

  function checkExistence(id, populate) {
    const xhr = new XMLHttpRequest();
    xhr.onreadystatechange = () => {
      if (xhr.readyState === XMLHttpRequest.DONE) {
        if (xhr.status === 200) {
          if (populate) {
            populateFileList(localStorage.getItem(id));
          }
        } else if (xhr.status === 404) {
          localStorage.removeItem(id);
        }
      }
    };
    xhr.open('get', '/exists/' + id, true);
    xhr.send();
  }

  //update file table with current files in localStorage
  function populateFileList(file) {
    try {
      file = JSON.parse(file);
    } catch (e) {
      return;
    }

    const row = document.createElement('tr');
    const name = document.createElement('td');
    const link = document.createElement('td');
    const $copyIcon = $('', { src: '/resources/copy-16.svg', class: 'icon-copy', 'data-l10n-id': 'copyUrlHover'});
    const expiry = document.createElement('td');
    const del = document.createElement('td');
    const $delIcon = $('', { src: '/resources/close-16.svg', class: 'icon-delete', 'data-l10n-id': 'deleteButtonHover' });
    const popupDiv = document.createElement('div');
    const $popupText = $('
', { class: 'popuptext' });
    const cellText = document.createTextNode(file.name);

    const url = file.url.trim() + `#${file.secretKey}`.trim();

    $('#link').attr('value', url);
    $('#copy-text').attr(
      'data-l10n-args',
      '{"filename": "' + file.name + '"}'
    );
    $('#copy-text').attr(
      'data-l10n-id',
      'copyUrlFormLabelWithName'
    );
    $popupText.attr('tabindex', '-1');

    name.appendChild(cellText);

    // create delete button

    const delSpan = document.createElement('span');
    $(delSpan).addClass('icon-cancel-1');
    $(delSpan).attr('data-l10n-id', 'deleteButtonHover');
    del.appendChild(delSpan);

    const linkSpan = document.createElement('span');
    $(linkSpan).addClass('icon-docs');
    $(linkSpan).attr('data-l10n-id', 'copyUrlHover');
    link.appendChild(linkSpan);

    link.style.color = '#0A8DFF';

    //copy link to clipboard when icon clicked
    $copyIcon.click(function() {
      const aux = document.createElement('input');
      aux.setAttribute('value', url);
      document.body.appendChild(aux);
      aux.select();
      document.execCommand('copy');
      document.body.removeChild(aux);
      document.l10n.formatValue('copiedUrl')
                   .then(translated => {
                     link.innerHTML = translated;
                   });
      window.setTimeout(() => {
        const linkImg = document.createElement('img');
        $(linkImg).addClass('icon-copy');
        $(linkImg).attr('data-l10n-id', 'copyUrlHover');
        $(linkImg).attr('src', '/resources/copy-16.svg');
        $(link).html(linkImg);
      }, 500);
    });

    file.creationDate = new Date(file.creationDate);

    const future = new Date();
    future.setTime(file.creationDate.getTime() + file.expiry);

    let countdown = 0;
    countdown = future.getTime() - new Date().getTime();
    let minutes = Math.floor(countdown / 1000 / 60);
    let hours = Math.floor(minutes / 60);
    let seconds = Math.floor(countdown / 1000 % 60);

    poll();

    function poll() {
      countdown = future.getTime() - new Date().getTime();
      minutes = Math.floor(countdown / 1000 / 60);
      hours = Math.floor(minutes / 60);
      seconds = Math.floor(countdown / 1000 % 60);
      let t;

      if (hours >= 1) {
        expiry.innerHTML = hours + 'h ' + minutes % 60 + 'm';
        t = window.setTimeout(() => {
          poll();
        }, 60000);
      } else if (hours === 0) {
        expiry.innerHTML = minutes + 'm ' + seconds + 's';
        t = window.setTimeout(() => {
          poll();
        }, 1000);
      }
      //remove from list when expired
      if (countdown <= 0) {
        localStorage.removeItem(file.fileId);
        $(expiry).parents('tr').remove();
        window.clearTimeout(t);
        toggleHeader();
      }
    }

    // create popup
    popupDiv.classList.add('popup');
    const popupDelSpan = document.createElement('span');
    $(popupDelSpan).addClass('del-file');
    $(popupDelSpan).attr('data-l10n-id', 'sentFilesTitle4');

    const popupNvmSpan = document.createElement('span');
    $(popupNvmSpan).addClass('nvm');
    $(popupNvmSpan).attr('data-l10n-id', 'nevermindButton');

    $popupText.html([
      popupDelSpan,
      ' ',
      ' ',
      popupNvmSpan
    ]);

    // add data cells to table row
    row.appendChild(name);
    $(link).append($copyIcon);
    row.appendChild(link);
    row.appendChild(expiry);
    $(popupDiv).append($popupText);
    $(del).append($delIcon);
    del.appendChild(popupDiv);
    row.appendChild(del);
    $('tbody').append(row); //add row to table

    // delete file
    $popupText.find('.del-file').click(e => {
      FileSender.delete(file.fileId, file.deleteToken).then(() => {
        $(e.target).parents('tr').remove();
        localStorage.removeItem(file.fileId);
        toggleHeader();
      });
    });
    document.getElementById('delete-file').onclick = () => {
      FileSender.delete(file.fileId, file.deleteToken).then(() => {
        localStorage.removeItem(file.fileId);
        location.reload();
      });
    };
    // show popup
    $delIcon.click(function() {
      $popupText.addClass('show');
      $popupText.focus();
    });
    // hide popup
    $popupText.find('.nvm').click(function(e) {
      e.stopPropagation();
      $popupText.removeClass('show');
    });
    $popupText.click(function(e) {
      e.stopPropagation();
    });
    //close when popup loses focus
    $popupText.blur(() => {
      $popupText.removeClass('show');
    });

    toggleHeader();
  }
  function toggleHeader() {
    //hide table header if empty list
    if (document.querySelector('tbody').childNodes.length === 1) {
      $('#file-list').attr('hidden', true);
    } else {
      $('#file-list').removeAttr('hidden');
    }
  }
});
<|endoftext|>