<commit_before>//////////////////////////////////////////////////////////////////////////
// Spawner - spawns new projects!
//////////////////////////////////////////////////////////////////////////
//
// Spawner is a 'puppet master' controller for project generation. It
// takes a template procedure, and makes calls to Maker, Changer, and
// other modules as necessary.
/* ----------------------------------------------------------------------
													Object Structures
-------------------------------------------------------------------------
	var options = {
		dest: "...path where we'll be dumping files...",
	}
*/
//////////////////////////////////////////////////////////////////////////
// Requires
var ares = require("ares").ares,
	async = require("async"),
	maker = require("maker").createMaker(),
	mover = require("mover").createMover(),
	changer = require("changer").createChanger(),
	gitOps = require("./gitOperations"),
	ares = require("ares").ares,
	fs = require("fs-extra");

//////////////////////////////////////////////////////////////////////////
// Namespace (lol)
var SHOW_DEBUG_PRINTS = true;
var log = function( a ) { if(SHOW_DEBUG_PRINTS) console.log(a); };	// A log function we can turn off

//////////////////////////////////////////////////////////////////////////
// Constructor
function spawner() {
	var _this = this;

	this.tempRepoDest = "/_leto_temp/";

} // end spawner()

//////////////////////////////////////////////////////////////////////////
// Spawns a project
spawner.prototype.spawn = function( dest, leto_setup, options, contents, shouldCloneRepo, callback ) {
	if( contents === undefined ) {
		console.log( "!!! Spawning with undefined contents !!!" );
		contents = {};
	}

	var _this = this;

	var asyncCallQueue = [];

	// Make shouldCloneRepo optional
	if( callback === undefined ) {
		callback = shouldCloneRepo;
		shouldCloneRepo = false;
	}

	function requireFromString(src, filename) {
		try {
			var m = new module.constructor();
		    m._compile(src, filename);
		    return m.exports;
		} catch( err ) {
			console.log( "Failed to load module at: " + filename );
			console.log( err );
		}
	}

	function templatizeAndLoadModule( src ) {
		// Load the ruleset into a string and templatize it
		var strContents = fs.readFileSync( src, 'utf8' );
		strContents = maker.renderTemplateToString( maker.template(strContents, contents) );
		return requireFromString( strContents );
	}

	leto_setup.tempRepoDest = dest + this.tempRepoDest;

	// If we have a functions file defined, try to load it
	// and override necessary parameters
	if( leto_setup.functions != undefined ) {
		if( leto_setup.functions != undefined ) {
			try {
				var functionContents = templatizeAndLoadModule( leto_setup.__source + "/" + leto_setup.functions );
			} catch( err ) {
				console.log( "Error, failed to load functions at location " + leto_setup.functions );
				console.log( err );
				callback();
			}
		}

		// Load all exports of the functions into our contents blob so that it's loaded later
		for( var iFunction in functionContents ) {
			contents[iFunction] = functionContents[iFunction];
		}
	}

	// If we have any defaults defined, fill in missing values in the contents
	if( leto_setup.defaults != undefined ) {
		for( var iDefault in leto_setup.defaults ) {
			if( contents[iDefault] === undefined ) {
				contents[iDefault] = leto_setup.defaults[iDefault];
			}
		}
	}

	var getBatchReplace = function( step ) {
		return function( cb ) {
			var source = step.sourcedir || options.source || leto_setup.__source || process.cwd();

			// Construct our filepath map by grabbing the end values for each
			// template from the contents we're given
			var filePathMap = {};
			for( var iKey in step.keywords ) {
				var keyword = step.keywords[iKey];

				// If this item is a function, call it to find our string key
				var keyToFind = typeof(contents[iKey]) == "function" ? contents[iKey]() : contents[iKey];

				filePathMap[keyToFind] = keyword;
			}

			maker.makeTemplatesFromDir( source, dest + "/", step.keywords, filePathMap, step.extensions || [], contents, cb );
		}
	}

	var getTemplateFiles = function( step ) {
		return function( cb ) {
			maker.loadTemplateDir( leto_setup.__source + "/" + step.sourcedir, function( loadedTemplates ) {
				var makeFileCallQueue = [];

				for( var iTemplate=0; iTemplate<step.templates.length; ++iTemplate ) {
					// Create a closure so that we can preserve variables while we loop
					(function(){

						// Preserve this template for closure
						var thisTemplate = step.templates[iTemplate];

						// Templatize the path
						thisTemplate.dest = maker.renderTemplateToString( maker.template(thisTemplate.dest, contents) );

						var thisTemplateFileFn = function( callb ) {
							// Retreive the empty template object
							var templateObj = maker.getTemplate( thisTemplate.name ),
								templateFiles = [];

							if( templateObj === undefined ) {
								console.log( "Error retrieving template " + thisTemplate.name );
								cb( "Error retrieving template " + thisTemplate.name );
							}

							// Fill the template with our contents
							templateObj = maker.fillTemplate( templateObj, contents );
							templateFiles.push( templateObj );

							// Write the file out to disk
							maker.makeFile( thisTemplate.dest, templateFiles, callb );
						}

						makeFileCallQueue.push( thisTemplateFileFn );
					}()); // end closure
				}

				// Run our async call queue
				async.series( makeFileCallQueue, cb );
			});
		}
	}

	var getMoveFiles = function( step ) {
		return function( cb ) {
			var movingPlanPath = leto_setup.__source + "/" + step.plan;

			try {
				var movingPlan = require( movingPlanPath );
			} catch( err ) {
				console.log( "Failed to load moving plan " + movingPlanPath );
				cb( err );
			}

			mover.setPlan( movingPlan );
			mover.setDest( dest );
			//mover.setSrc( require("path").dirname(movingPlanPath) );
			mover.move( cb );
		}
	}

	var getExecuteCommand = function( step ) {
		return function( cb ) {
			// Change the current working directory if that setting is used
			if( step.workingdir != undefined ) {
				var oldCWD = process.cwd(),
					isRelative = step.isRelative || true,
					newCWD = isRelative ? leto_setup.__source + "/" + step.workingdir : step.workingdir;

				process.chdir( newCWD );
			}

			ares( step.command, true, cb );

			// Change the working directory back to what it was
			if( step.workingdir != undefined ) {
				process.chdir( oldCWD );
			}
		}
	}

	var getChangeCommand = function( step ) {
		return function( cb ) {
			var changeCallQueue = [];

			// We need to loop and do all of our changes asynchronously, and
			// then call back to resume running the spawn procedure.
			for( var iChange=0; iChange<step.changes.length; ++iChange ) {

				// Create a closure so that we can preserve variables while we loop
				(function(){
					// Load our rules inside of the closure, so that we can ensure that
					// variables in rules aren't as their applied between files
					if( step.ruleset != undefined ) {
						var loadedRules = templatizeAndLoadModule( leto_setup.__source + "/" + step.ruleset );
					}

					var rules = loadedRules === undefined ? {} : loadedRules;

					// Load in our default rules (as long as no user-set rule relaces it)
					for( var iRule in changer.defaultRules )
						rules[iRule] = rules[iRule] || changer.defaultRules[iRule];

					var thisChange = step.changes[iChange];

					// Templatize the filepath that we're about to change
					thisChange.file = maker.renderTemplateToString( maker.template(thisChange.file, contents) );

					// Templatize all of the arguments that we have
					if( typeof(thisChange.args) == "string" ) {
						thisChange.args = maker.renderTemplateToString( maker.template(thisChange.args, contents) );
					} else if( thisChange.args instanceof Array ) {
						for( var iArg in thisChange.args ) {
							thisChange.args[iArg] = maker.renderTemplateToString( maker.template(thisChange.args[iArg], contents) );
						}
					}

					var thisChangeFileFn = function( callb ) {

						var args = thisChange.args;

						if( rules[thisChange.rule] === undefined ) {
							var err = "Rule '" + thisChange.rule + "' not found";
							console.log( err );
							return callb( err );
						}

						// Allow users to use rule.start or rule.line interchangably, so that
						// the API is nicer. Prefer rule.start when both are present
						var startLine = thisChange.start || thisChange.line,
							endLine = thisChange.end;

						if( endLine === undefined ) {
							endLine = thisChange.line;
						}

						// Build the arguments for our call to changer.change()
						var changeArgs = [];
						changeArgs.push( rules[thisChange.rule] );
						changeArgs.push( thisChange.file );
						changeArgs.push( startLine );
						changeArgs.push( endLine );

						if( typeof(thisChange.args) == "string" ) {
							changeArgs.push( thisChange.args );
						} else if( typeof(thisChange.args) == "object" ) {
							for( var iArg in thisChange.args )
								changeArgs.push( thisChange.args[iArg] );
						}

						changeArgs.push( callb );

						// Change a thing!
						changer.change.apply( changer, changeArgs );
					}

					changeCallQueue.push( thisChangeFileFn );
				}()); // end closure
			} // end for each change

			// Run our series of change commands
			async.series( changeCallQueue, cb );
		}
	}

	function runSpawnSeries() {

		// Grab our setup procedure from the recently cloned repo
		var procedure = leto_setup.procedure;

		for( var iStep=0; iStep<procedure.length; ++iStep ) {
			switch( procedure[iStep].type ) {
			case "replace":
				asyncCallQueue.push( getBatchReplace(procedure[iStep]) );
			  	break;
		  	case "template":
				asyncCallQueue.push( getTemplateFiles(procedure[iStep]) );
			  	break;
		  	case "move":
				asyncCallQueue.push( getMoveFiles(procedure[iStep]) );
			  	break;
		  	case "execute":
				asyncCallQueue.push( getExecuteCommand(procedure[iStep]) );
			  	break;
		  	case "change":
				asyncCallQueue.push( getChangeCommand(procedure[iStep]) );
			  	break;

			default:
			  	console.log( "Step type " + procedure[iStep].type + " not recognized" );
			}
		}

		// Run our async call queue
		async.series( asyncCallQueue, function() {
			callback();
		});
	}

	// -------------------------------------------------- //
	// RUN THE SPAWN THE THING!
	// -------------------------------------------------- //
	if( shouldCloneRepo ) {
		gitOps.cloneRepo( leto_setup, function() {
			runSpawnSeries();
		});
	} else {
		runSpawnSeries();
	}

} // end spawn()

exports.spawner = spawner;<commit_msg>Introduce 'externals', a way to load and templatize files<commit_after>//////////////////////////////////////////////////////////////////////////
// Spawner - spawns new projects!
//////////////////////////////////////////////////////////////////////////
//
// Spawner is a 'puppet master' controller for project generation. It
// takes a template procedure, and makes calls to Maker, Changer, and
// other modules as necessary.
/* ----------------------------------------------------------------------
													Object Structures
-------------------------------------------------------------------------
	var options = {
		dest: "...path where we'll be dumping files...",
	}
*/
//////////////////////////////////////////////////////////////////////////
// Requires
var ares = require("ares").ares,
	async = require("async"),
	maker = require("maker").createMaker(),
	mover = require("mover").createMover(),
	changer = require("changer").createChanger(),
	gitOps = require("./gitOperations"),
	ares = require("ares").ares,
	fs = require("fs-extra");

//////////////////////////////////////////////////////////////////////////
// Namespace (lol)
var SHOW_DEBUG_PRINTS = true;
var log = function( a ) { if(SHOW_DEBUG_PRINTS) console.log(a); };	// A log function we can turn off

//////////////////////////////////////////////////////////////////////////
// Constructor
function spawner() {
	var _this = this;

	this.tempRepoDest = "/_leto_temp/";

} // end spawner()

//////////////////////////////////////////////////////////////////////////
// Spawns a project
spawner.prototype.spawn = function( dest, leto_setup, options, contents, shouldCloneRepo, callback ) {
	if( contents === undefined ) {
		console.log( "!!! Spawning with undefined contents !!!" );
		contents = {};
	}

	var _this = this;

	var asyncCallQueue = [];

	// Make shouldCloneRepo optional
	if( callback === undefined ) {
		callback = shouldCloneRepo;
		shouldCloneRepo = false;
	}

	function requireFromString(src, filename) {
		try {
			var m = new module.constructor();
		    m._compile(src, filename);
		    return m.exports;
		} catch( err ) {
			console.log( "Failed to load module at: " + filename );
			console.log( err );
		}
	}

	function templatizeAndLoadModule( src ) {
		// Load the ruleset into a string and templatize it
		var strContents = fs.readFileSync( src, 'utf8' );
		strContents = maker.renderTemplateToString( maker.template(strContents, contents) );
		return requireFromString( strContents );
	}

	function templatizeAndLoadFile( src ) {
		// Load the ruleset into a string and templatize it
		var strContents = fs.readFileSync( src, 'utf8' );
		strContents = maker.renderTemplateToString( maker.template(strContents, contents) );
		return strContents;
	}

	leto_setup.tempRepoDest = dest + this.tempRepoDest;

	// If we have a functions file defined, try to load it
	// and override necessary parameters
	if( leto_setup.functions != undefined ) {
		try {
			var functionContents = templatizeAndLoadModule( leto_setup.__source + "/" + leto_setup.functions );
		} catch( err ) {
			console.log( "Error, failed to load functions at location " + leto_setup.functions );
			console.log( err );
			callback();
		}

		// Load all exports of the functions into our contents blob so that it's loaded later
		for( var iFunction in functionContents ) {
			contents[iFunction] = functionContents[iFunction];
		}
	}

	// If we have external text files that we need to laod, read them
	// all in and templatize them
	if( leto_setup.externals != undefined ) {
		for( var iExternal in leto_setup.externals ) {
			contents[iExternal] = templatizeAndLoadFile( leto_setup.__source + "/" + leto_setup.externals[iExternal] );
		}
	}

	// If we have any defaults defined, fill in missing values in the contents
	if( leto_setup.defaults != undefined ) {
		for( var iDefault in leto_setup.defaults ) {
			if( contents[iDefault] === undefined ) {
				contents[iDefault] = leto_setup.defaults[iDefault];
			}
		}
	}

	var getBatchReplace = function( step ) {
		return function( cb ) {
			var source = step.sourcedir || options.source || leto_setup.__source || process.cwd();

			// Construct our filepath map by grabbing the end values for each
			// template from the contents we're given
			var filePathMap = {};
			for( var iKey in step.keywords ) {
				var keyword = step.keywords[iKey];

				// If this item is a function, call it to find our string key
				var keyToFind = typeof(contents[iKey]) == "function" ? contents[iKey]() : contents[iKey];

				filePathMap[keyToFind] = keyword;
			}

			maker.makeTemplatesFromDir( source, dest + "/", step.keywords, filePathMap, step.extensions || [], contents, cb );
		}
	}

	var getTemplateFiles = function( step ) {
		return function( cb ) {
			maker.loadTemplateDir( leto_setup.__source + "/" + step.sourcedir, function( loadedTemplates ) {
				var makeFileCallQueue = [];

				for( var iTemplate=0; iTemplate<step.templates.length; ++iTemplate ) {
					// Create a closure so that we can preserve variables while we loop
					(function(){

						// Preserve this template for closure
						var thisTemplate = step.templates[iTemplate];

						// Templatize the path
						thisTemplate.dest = maker.renderTemplateToString( maker.template(thisTemplate.dest, contents) );

						var thisTemplateFileFn = function( callb ) {
							// Retreive the empty template object
							var templateObj = maker.getTemplate( thisTemplate.name ),
								templateFiles = [];

							if( templateObj === undefined ) {
								console.log( "Error retrieving template " + thisTemplate.name );
								cb( "Error retrieving template " + thisTemplate.name );
							}

							// Fill the template with our contents
							templateObj = maker.fillTemplate( templateObj, contents );
							templateFiles.push( templateObj );

							// Write the file out to disk
							maker.makeFile( thisTemplate.dest, templateFiles, callb );
						}

						makeFileCallQueue.push( thisTemplateFileFn );
					}()); // end closure
				}

				// Run our async call queue
				async.series( makeFileCallQueue, cb );
			});
		}
	}

	var getMoveFiles = function( step ) {
		return function( cb ) {
			var movingPlanPath = leto_setup.__source + "/" + step.plan;

			try {
				var movingPlan = require( movingPlanPath );
			} catch( err ) {
				console.log( "Failed to load moving plan " + movingPlanPath );
				cb( err );
			}

			mover.setPlan( movingPlan );
			mover.setDest( dest );
			//mover.setSrc( require("path").dirname(movingPlanPath) );
			mover.move( cb );
		}
	}

	var getExecuteCommand = function( step ) {
		return function( cb ) {
			// Change the current working directory if that setting is used
			if( step.workingdir != undefined ) {
				var oldCWD = process.cwd(),
					isRelative = step.isRelative || true,
					newCWD = isRelative ? leto_setup.__source + "/" + step.workingdir : step.workingdir;

				process.chdir( newCWD );
			}

			ares( step.command, true, cb );

			// Change the working directory back to what it was
			if( step.workingdir != undefined ) {
				process.chdir( oldCWD );
			}
		}
	}

	var getChangeCommand = function( step ) {
		return function( cb ) {
			var changeCallQueue = [];

			// We need to loop and do all of our changes asynchronously, and
			// then call back to resume running the spawn procedure.
			for( var iChange=0; iChange<step.changes.length; ++iChange ) {

				// Create a closure so that we can preserve variables while we loop
				(function(){
					// Load our rules inside of the closure, so that we can ensure that
					// variables in rules aren't as their applied between files
					if( step.ruleset != undefined ) {
						var loadedRules = templatizeAndLoadModule( leto_setup.__source + "/" + step.ruleset );
					}

					var rules = loadedRules === undefined ? {} : loadedRules;

					// Load in our default rules (as long as no user-set rule relaces it)
					for( var iRule in changer.defaultRules )
						rules[iRule] = rules[iRule] || changer.defaultRules[iRule];

					var thisChange = step.changes[iChange];

					// Templatize the filepath that we're about to change
					thisChange.file = maker.renderTemplateToString( maker.template(thisChange.file, contents) );

					// Templatize all of the arguments that we have
					if( typeof(thisChange.args) == "string" ) {
						thisChange.args = maker.renderTemplateToString( maker.template(thisChange.args, contents) );
					} else if( thisChange.args instanceof Array ) {
						for( var iArg in thisChange.args ) {
							thisChange.args[iArg] = maker.renderTemplateToString( maker.template(thisChange.args[iArg], contents) );
						}
					}

					var thisChangeFileFn = function( callb ) {

						var args = thisChange.args;

						if( rules[thisChange.rule] === undefined ) {
							var err = "Rule '" + thisChange.rule + "' not found";
							console.log( err );
							return callb( err );
						}

						// Allow users to use rule.start or rule.line interchangably, so that
						// the API is nicer. Prefer rule.start when both are present
						var startLine = thisChange.start || thisChange.line,
							endLine = thisChange.end;

						if( endLine === undefined ) {
							endLine = thisChange.line;
						}

						// Build the arguments for our call to changer.change()
						var changeArgs = [];
						changeArgs.push( rules[thisChange.rule] );
						changeArgs.push( thisChange.file );
						changeArgs.push( startLine );
						changeArgs.push( endLine );

						if( typeof(thisChange.args) == "string" ) {
							changeArgs.push( thisChange.args );
						} else if( typeof(thisChange.args) == "object" ) {
							for( var iArg in thisChange.args )
								changeArgs.push( thisChange.args[iArg] );
						}

						changeArgs.push( callb );

						// Change a thing!
						changer.change.apply( changer, changeArgs );
					}

					changeCallQueue.push( thisChangeFileFn );
				}()); // end closure
			} // end for each change

			// Run our series of change commands
			async.series( changeCallQueue, cb );
		}
	}

	function runSpawnSeries() {

		// Grab our setup procedure from the recently cloned repo
		var procedure = leto_setup.procedure;

		for( var iStep=0; iStep<procedure.length; ++iStep ) {
			switch( procedure[iStep].type ) {
			case "replace":
				asyncCallQueue.push( getBatchReplace(procedure[iStep]) );
			  	break;
		  	case "template":
				asyncCallQueue.push( getTemplateFiles(procedure[iStep]) );
			  	break;
		  	case "move":
				asyncCallQueue.push( getMoveFiles(procedure[iStep]) );
			  	break;
		  	case "execute":
				asyncCallQueue.push( getExecuteCommand(procedure[iStep]) );
			  	break;
		  	case "change":
				asyncCallQueue.push( getChangeCommand(procedure[iStep]) );
			  	break;

			default:
			  	console.log( "Step type " + procedure[iStep].type + " not recognized" );
			}
		}

		// Run our async call queue
		async.series( asyncCallQueue, function() {
			callback();
		});
	}

	// -------------------------------------------------- //
	// RUN THE SPAWN THE THING!
	// -------------------------------------------------- //
	if( shouldCloneRepo ) {
		gitOps.cloneRepo( leto_setup, function() {
			runSpawnSeries();
		});
	} else {
		runSpawnSeries();
	}

} // end spawn()

exports.spawner = spawner;<|endoftext|>


<commit_before>qq.Identify = function(fileOrBlob, log) {
    var PREVIEWABLE_MAGIC_BYTES = {
            "image/jpeg": "ffd8ff",
            "image/gif": "474946",
            "image/png": "89504e",
            "image/bmp": "424d",
            "image/tiff": ["49492a00", "4d4d002a"]
        };

    function isIdentifiable(magicBytes, questionableBytes) {
        var identifiable = false,
            magicBytesEntries = [].concat(magicBytes);

        qq.each(magicBytesEntries, function(idx, magicBytesArrayEntry) {
            if (questionableBytes.indexOf(magicBytesArrayEntry) === 0) {
                identifiable = true;
                return false;
            }
        });

        return identifiable;
    }

    return {
        isPreviewable: function() {
            var idenitifer = new qq.Promise(),
                previewable = false,
                name = fileOrBlob.name === undefined ? "blob" : fileOrBlob.name;

            log(qq.format("Attempting to determine if {} can be rendered in this browser", name));

            qq.readBlobToHex(fileOrBlob, 0, 4).then(function(hex) {
                qq.each(PREVIEWABLE_MAGIC_BYTES, function(mime, bytes) {
                    if (isIdentifiable(bytes, hex)) {
                        // Safari is the only supported browser that can deal with TIFFs natively,
                        // so, if this is a TIFF and the UA isn't Safari, declare this file "non-previewable".
                        if (mime !== "image/tiff" || qq.safari()) {
                            previewable = true;
                            idenitifer.success(mime);
                        }

                        return false;
                    }
                });

                log(qq.format("'{}' is {} able to be rendered in this browser", name, previewable ? "" : "NOT"));

                if (!previewable) {
                    idenitifer.failure();
                }
            });

            return idenitifer;
        }
    }
};
<commit_msg>chore($identify): #890 - Lint<commit_after>/*globals qq */
qq.Identify = function(fileOrBlob, log) {
    "use strict";

    var PREVIEWABLE_MAGIC_BYTES = {
            "image/jpeg": "ffd8ff",
            "image/gif": "474946",
            "image/png": "89504e",
            "image/bmp": "424d",
            "image/tiff": ["49492a00", "4d4d002a"]
        };

    function isIdentifiable(magicBytes, questionableBytes) {
        var identifiable = false,
            magicBytesEntries = [].concat(magicBytes);

        qq.each(magicBytesEntries, function(idx, magicBytesArrayEntry) {
            if (questionableBytes.indexOf(magicBytesArrayEntry) === 0) {
                identifiable = true;
                return false;
            }
        });

        return identifiable;
    }

    return {
        isPreviewable: function() {
            var idenitifer = new qq.Promise(),
                previewable = false,
                name = fileOrBlob.name === undefined ? "blob" : fileOrBlob.name;

            log(qq.format("Attempting to determine if {} can be rendered in this browser", name));

            qq.readBlobToHex(fileOrBlob, 0, 4).then(function(hex) {
                qq.each(PREVIEWABLE_MAGIC_BYTES, function(mime, bytes) {
                    if (isIdentifiable(bytes, hex)) {
                        // Safari is the only supported browser that can deal with TIFFs natively,
                        // so, if this is a TIFF and the UA isn't Safari, declare this file "non-previewable".
                        if (mime !== "image/tiff" || qq.safari()) {
                            previewable = true;
                            idenitifer.success(mime);
                        }

                        return false;
                    }
                });

                log(qq.format("'{}' is {} able to be rendered in this browser", name, previewable ? "" : "NOT"));

                if (!previewable) {
                    idenitifer.failure();
                }
            });

            return idenitifer;
        }
    };
};
<|endoftext|>


<commit_before>(function () {
  Polymer({
    is: 'uql-chat-proactive',
    properties: {
      /**
       * Whether Chat is online
       */
      _chatOnline: {
        type: Boolean,
        value: false,
        observer: '_handleChangedChatStatus'
      },

      /* all three display tabs are hidden until we ascertain the Chat Status */
      _showChatOnlineTab: {
        type: Boolean,
        value: false
      },
      _showChatOfflineTab: {
        type: Boolean,
        value: false
      },
      _showPopupChatBlock: {
        type: Boolean,
        value: false
      },

      /* stops flash of offline before online tab */
      _chatStatusUpdated: {
        type: Boolean,
        value: false
      },

      numberMillisecondsBeforeChatTabAppears: {
        type: Number,
        value: 3000
      },

      numberMillsecondsBeforePopup: {
        type: Number,
        value: 60000
      },

      cookieNameNoPopup: {
        type: String,
        value: 'noChatPopup'
      },

      /**
       * get the contacts.json details so we launch the same size popup as the regular ask us button
       */
      chatLinkItems: {
        type: Object,
        value: null
      },

      filterButtonDivMarginBottom: {
        type: Number,
        value: 0
      }
},

    attached: function () {
      var self = this;

      if (this._isPrimoPage(window.location.hostname)) {
        this._watchForPrimoFiltersButton();
      }

      // get chat status after a number of seconds
      // this avoids the initial call which always seems to be offline
      // so we dont briefly load the offline tab
      // the delay also draws the user's attention to the tab
      this.async(function () {
        this.$.chatStatusApi.addEventListener('uqlibrary-api-chat-status-loaded', function(e) {
          if(e.detail && e.detail.hasOwnProperty('online')) {
            self._chatOnline = e.detail.online;
            self._chatStatusUpdated = true;
            self._handleChangedChatStatus();
          }
        });
        this.$.chatStatusApi.get();
      }, this.numberMillisecondsBeforeChatTabAppears);

      // get contact data - it holds popup details for chat

      // DO NOT REMOVE!! gulp vulcanize task will replace 'null' with json data and thus avoid a live api call
      var contactsJsonFileData = null;
      // we are using this data to get the same size popup as the other chat methods
      var contactsJson = contactsJsonFileData;

      if (contactsJson !== null) {
        this._setLinks(contactsJson);
      } else {
        this.$.contactsApi.addEventListener('uqlibrary-api-contacts-loaded', function(e) {
          self._setLinks(e.detail);
        });
        this.$.contactsApi.get();
      }

      if (!this._isCookieSetNoPopup()) {
        // set a timer for the tab to expand to a window
        // logic - we only do this on page load (ie attached function), not whenever chat comes online.
        // we could do it when chat comes online, but that is liable to give uneven chat loads
        // particularly in the unusual event that chat is going up and down a lot
        // and it might annoy users, going up and down, if they are on the page for a while
        // the tab is always there - that is sufficient
        this.async(function () {
          if (this._chatOnline) {
            this._showPopupChatBlock = true;
            this._setPrimoFilterButtonPositioning(125);
            this._makeRoomForSidebarBottomElements(70);
            this._showChatOnlineTab = false;
          }
        }, this.numberMillsecondsBeforePopup);
      }
    },

    /**
     * Called when the chat status has changed, eg uqlibrary-api-chat-status-loaded has fired. Updates display status
     */
    _handleChangedChatStatus: function() {
      if (this._chatOnline) {
        this._showChatOnlineTab = true;
        this._showChatOfflineTab = false;
      } else if (typeof this._chatStatusUpdated !== 'undefined') {
        this._showChatOnlineTab = false;
        this._showPopupChatBlock = false;
        this._showChatOfflineTab = true;
      }
      this._setPrimoFilterButtonPositioningForTab();
      this._makeRoomForSidebarBottomElements(0);
    },

    /**
     * while we have used css to stop the facet sidebar going 'over' the proactive chat widget,
     * static css isnt sufficient for the primo 'apply filters' widget
     * as its 'bottom edge' position must vary depending on whether the tab or the popup shows, or nothing
     * and we cant just set this on load, because the 'filter' popup isnt available to the dom unless
     * (and until) the user checks a checkbox in the sidebar
     * Details:
     * https://jcubic.wordpress.com/2017/04/28/how-to-detect-if-element-is-added-or-removed-from-dom/
     * https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
     * mutation observer browser support: https://caniuse.com/#feat=mutationobserver
     * @private
     */
    _watchForPrimoFiltersButton: function() {
console.log('_watchForPrimoFiltersButton');
      var checkEvery2Seconds = 1000;
      this.async(function () {
        var filterButtonDivs = document.getElementsByClassName('multiselect-submit-inner');
        if (filterButtonDivs && filterButtonDivs.length) {
          var filterButtonDiv = filterButtonDivs[0];
          if (filterButtonDiv) {
            // move the block with the filter button so it doesnt slide under the proactive chat widget
console.log('change the bottom margin to ' + this.filterButtonDivMarginBottom + 'px');
            filterButtonDiv.style.marginBottom = this.filterButtonDivMarginBottom + 'px';
          }
        }

        // check again
        this._watchForPrimoFiltersButton();
      }, checkEvery2Seconds);
    },

    _watchForPrimoFiltersButtonMUTATION: function() {
      var facetsSidebar = document.querySelector('prm-facet div');
console.log(facetsSidebar);
//      var filterButtonDivs = document.getElementsByClassName('multiselect-submit-inner');
      var filterButtonDivs = document.querySelector('.multiselect-submit-inner');
      var in_dom;
      // if (filterButtonDivs && filterButtonDivs.length) {
      //   in_dom = document.body.contains(filterButtonDivs);
      // } else {
      //   in_dom = false;
      // }

      in_dom = document.body.contains(filterButtonDivs);
      var observer = new MutationObserver(function(mutations) {
//        filterButtonDivs = document.getElementsByClassName('multiselect-submit-inner'); // do we need to set it again?
        if (document.body.contains(filterButtonDivs)) {
          if (!in_dom) {
            // element inserted
console.log('filter button added');
            var filterButtonDiv = filterButtonDivs[0];
            if (filterButtonDiv) {
              // move the block with the filter button so it doesnt slide under the proactive chat widget
              filterButtonDiv.style.marginBottom = this.filterButtonDivMarginBottom + 'px';
            }
          }
          in_dom = true;
        } else if (in_dom) {
console.log('filter button removed');
          // element removed
          in_dom = false;
        }

      });
console.log('_watchForPrimoFiltersButton observing');
      // observer.observe(document.body, {
      //   childList: true, // required field
      //   subtree: true // the field to be observed is a descendant, not a child
      // });
      observer.observe(facetsSidebar, {childList: true});
    },

    /*
     * force a gap at the bottom of the facets sidebar on primo
     * so proactive chat doesnt cover any options
     */
    _makeRoomForSidebarBottomElements: function(sidebarDivMarginBottom) {
console.log('_makeRoomForSidebarBottomElements');
      if (this._isPrimoPage(window.location.hostname)) {
        var sidebarDivs = document.getElementsByClassName('sidebar-inner-wrapper');
        if (sidebarDivs && sidebarDivs.length) {
          var sidebarDiv = sidebarDivs[0];
          if (sidebarDiv) {
            // move the bottom of the sidebar so it doesnt slide under the filter button block
console.log('_makeRoomForSidebarBottomElements setting ' + sidebarDivMarginBottom + 'px');
            sidebarDiv.style.marginBottom = sidebarDivMarginBottom + 'px';
          }
        }
      }
    },

    /*
     * the amount of space needed to allow the 'apply filters' button to appear
     */
    _setPrimoFilterButtonPositioning: function(bottomMargin) {
console.log('_setPrimoFilterButtonPositioning setting bottommargin variable to ' + bottomMargin);
      // put a 125px margin at the bottom
      this.filterButtonDivMarginBottom = bottomMargin;
    },

    /*
     * the amount of space needed to allow the 'apply filters' button to appear above the tab
     */
    _setPrimoFilterButtonPositioningForTab: function() {
      // put a 45px margin at the bottom
      this._setPrimoFilterButtonPositioning(45);
    },

    /*
     * called when the uses clicks the 'x' button or 'maybe later'
     */
    _closeDialog: function() {
      this._setCookieNoPopup();
      this._showPopupChatBlock = false;
      this._showChatOnlineTab = true;
      this._setPrimoFilterButtonPositioningForTab();
    },

    /**
     * Returns the relevant link for this item
     * @param item
     * @returns {*}
     * @private
     */
    _getLink: function (item) {
      if (item.linkMobile && this._isMobile(navigator.userAgent)) {
        return item.linkMobile;
      } else {
        return item.link;
      }
    },

    /**
     * open a link for any Contact object
     * @param item
     * @private
     */
    _openWindow: function (item) {
      // Check if this item has a custom "target" attribute
      if (item.target) {
        if (this._isMobile(navigator.userAgent)) {
          // On mobile we ignore the targetOptions
          window.open(this._getLink(item), item.target);
        } else {
          window.open(this._getLink(item), item.target, item.targetOptions || "");
        }
      } else {
        window.location = this._getLink(item);
      }
    },

    /**
     * Called when 'Chat Now' is clicked.
     */
    openChat: function () {
      this._openWindow(this.chatLinkItems);
      this._showPopupChatBlock = false;
      this._showChatOnlineTab = true;
      this._setPrimoFilterButtonPositioningForTab();
    },

    /**
     *
     * called when 'leave a question' is clicked
     */
    openContactForm: function() {
      this._openWindow(this.contactLinkItems);
    },

    /**
     * Returns whether the user is on a mobile device
     * @returns {boolean}
     * @private
     */
    _isMobile: function (userAgent) {
      return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
    },

    /**
     * extracts the relevant display items from the contacts array
     * @param data
     * @private
     */
    _setLinks: function (data) {
      this.chatLinkItems = this._setLink(data, 'Chat');
      this.contactLinkItems = this._setLink(data, 'Contact form');
    },

    /**
     * get one chunk of the Contact data, per the label
     * @param data Object
     * @param label string
     * @returns object
     * @private
     */
    _setLink: function (data, label) {
      tempitem = data.items.filter(function(item) {
        return (item.label === label);
      });
      return tempitem[0];
    },

    /**
     * Gets a cookie by name
     * @param name the name of the cookie to return
     * @returns {String}
     * @private
     */
    _getCookie: function (name) {
      var parts = document.cookie.split(";");
      for (var i = 0, l = parts.length; i < l; i++) {
        var cookieParts = parts[i].trim().split('=');
        if (cookieParts[0] === name) {
          return cookieParts[1];
        }
      }
    },

    /**
     * get the date string for the cookie expiry
     * @param numDaysExpiry integer
     * @returns string
     */
    _getCookieExpiryDate: function (numDaysExpiry) {
      var date = new Date();
      date.setTime(date.getTime() + (numDaysExpiry * 24 * 60 * 60 * 1000));
      return date.toGMTString();
    },

    /**
     * set a cookie so the user doesnt see the popup for a while
     * (was originally session cookie, but Chrome doesnt delete those!)
     * @private
     */
    _setCookieNoPopup: function() {
      var cookieString = this.cookieNameNoPopup + "=true;";
      cookieString += " expires=" + this._getCookieExpiryDate(1) + ";";
      cookieString += " path=/;";
      cookieString += " domain=" + this.getDomain(window.location.hostname);
      document.cookie = cookieString;
    },

    /**
     * check if the nopop cookie has been set
     * @returns {boolean}
     * @private
     */
    _isCookieSetNoPopup: function() {
      cookie = this._getCookie(this.cookieNameNoPopup);
      return (typeof cookie !== "undefined");
    },

    /**
     * get the domain that should be written to the cookie
     * we cant hit all the possible domains at the same time,
     * but we can at least set a generic library one, rather than only the subdomain
     * @param hostname
     */
    getDomain: function(hostname) {
      var libraryRegExp = /(.*).library.uq.edu.au/i;
      if ('localhost' == hostname)  {
        return 'localhost';
      } else if (libraryRegExp.test(hostname))  {
        // If we are on a library subdomain, use library root domain.
        return '.library.uq.edu.au';
      } else {
        // Otherwise, eg studenthub.uq.edu.au, use that domain, without any www
        var otherRegExp = /www.(.*)/i;
        if (otherRegExp.test(hostname)) {
          return hostname.substring(3);
        } else {
          return '.' + hostname;
        }
      }
    },

    _isPrimoPage: function(hostname) {
      // return (
      //   'search.library.uq.edu.au' === hostname || // primo prod
      //   hostname.endsWith('exlibrisgroup.com') // primo sandbox
      // );
      if (
        'search.library.uq.edu.au' === hostname || // primo prod
        hostname.endsWith('exlibrisgroup.com') // primo sandbox
      ) {
console.log('is primo page');
        return true;
      } else {
        return false;
      }
    }

  });
})();<commit_msg>adjust placement of filter button above tab [PT #153837535]<commit_after>(function () {
  Polymer({
    is: 'uql-chat-proactive',
    properties: {
      /**
       * Whether Chat is online
       */
      _chatOnline: {
        type: Boolean,
        value: false,
        observer: '_handleChangedChatStatus'
      },

      /* all three display tabs are hidden until we ascertain the Chat Status */
      _showChatOnlineTab: {
        type: Boolean,
        value: false
      },
      _showChatOfflineTab: {
        type: Boolean,
        value: false
      },
      _showPopupChatBlock: {
        type: Boolean,
        value: false
      },

      /* stops flash of offline before online tab */
      _chatStatusUpdated: {
        type: Boolean,
        value: false
      },

      numberMillisecondsBeforeChatTabAppears: {
        type: Number,
        value: 3000
      },

      numberMillsecondsBeforePopup: {
        type: Number,
        value: 60000
      },

      cookieNameNoPopup: {
        type: String,
        value: 'noChatPopup'
      },

      /**
       * get the contacts.json details so we launch the same size popup as the regular ask us button
       */
      chatLinkItems: {
        type: Object,
        value: null
      },

      filterButtonDivMarginBottom: {
        type: Number,
        value: 0
      }
},

    attached: function () {
      var self = this;

      if (this._isPrimoPage(window.location.hostname)) {
        this._watchForPrimoFiltersButton();
      }

      // get chat status after a number of seconds
      // this avoids the initial call which always seems to be offline
      // so we dont briefly load the offline tab
      // the delay also draws the user's attention to the tab
      this.async(function () {
        this.$.chatStatusApi.addEventListener('uqlibrary-api-chat-status-loaded', function(e) {
          if(e.detail && e.detail.hasOwnProperty('online')) {
            self._chatOnline = e.detail.online;
            self._chatStatusUpdated = true;
            self._handleChangedChatStatus();
          }
        });
        this.$.chatStatusApi.get();
      }, this.numberMillisecondsBeforeChatTabAppears);

      // get contact data - it holds popup details for chat

      // DO NOT REMOVE!! gulp vulcanize task will replace 'null' with json data and thus avoid a live api call
      var contactsJsonFileData = null;
      // we are using this data to get the same size popup as the other chat methods
      var contactsJson = contactsJsonFileData;

      if (contactsJson !== null) {
        this._setLinks(contactsJson);
      } else {
        this.$.contactsApi.addEventListener('uqlibrary-api-contacts-loaded', function(e) {
          self._setLinks(e.detail);
        });
        this.$.contactsApi.get();
      }

      if (!this._isCookieSetNoPopup()) {
        // set a timer for the tab to expand to a window
        // logic - we only do this on page load (ie attached function), not whenever chat comes online.
        // we could do it when chat comes online, but that is liable to give uneven chat loads
        // particularly in the unusual event that chat is going up and down a lot
        // and it might annoy users, going up and down, if they are on the page for a while
        // the tab is always there - that is sufficient
        this.async(function () {
          if (this._chatOnline) {
            this._showPopupChatBlock = true;
            this._setPrimoFilterButtonPositioning(125);
            this._makeRoomForSidebarBottomElements(70);
            this._showChatOnlineTab = false;
          }
        }, this.numberMillsecondsBeforePopup);
      }
    },

    /**
     * Called when the chat status has changed, eg uqlibrary-api-chat-status-loaded has fired. Updates display status
     */
    _handleChangedChatStatus: function() {
      if (this._chatOnline) {
        this._showChatOnlineTab = true;
        this._showChatOfflineTab = false;
      } else if (typeof this._chatStatusUpdated !== 'undefined') {
        this._showChatOnlineTab = false;
        this._showPopupChatBlock = false;
        this._showChatOfflineTab = true;
      }
      this._setPrimoFilterButtonPositioningForTab();
      this._makeRoomForSidebarBottomElements(0);
    },

    /**
     * while we have used css to stop the facet sidebar going 'over' the proactive chat widget,
     * static css isnt sufficient for the primo 'apply filters' widget
     * as its 'bottom edge' position must vary depending on whether the tab or the popup shows, or nothing
     * and we cant just set this on load, because the 'filter' popup isnt available to the dom unless
     * (and until) the user checks a checkbox in the sidebar
     * Details:
     * https://jcubic.wordpress.com/2017/04/28/how-to-detect-if-element-is-added-or-removed-from-dom/
     * https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
     * mutation observer browser support: https://caniuse.com/#feat=mutationobserver
     * @private
     */
    _watchForPrimoFiltersButton: function() {
console.log('_watchForPrimoFiltersButton');
      var checkEvery2Seconds = 1000;
      this.async(function () {
        var filterButtonDivs = document.getElementsByClassName('multiselect-submit-inner');
        if (filterButtonDivs && filterButtonDivs.length) {
          var filterButtonDiv = filterButtonDivs[0];
          if (filterButtonDiv) {
            // move the block with the filter button so it doesnt slide under the proactive chat widget
console.log('change the bottom margin to ' + this.filterButtonDivMarginBottom + 'px');
            filterButtonDiv.style.marginBottom = this.filterButtonDivMarginBottom + 'px';
          }
        }

        // check again
        this._watchForPrimoFiltersButton();
      }, checkEvery2Seconds);
    },

    _watchForPrimoFiltersButtonMUTATION: function() {
      var facetsSidebar = document.querySelector('prm-facet div');
console.log(facetsSidebar);
//      var filterButtonDivs = document.getElementsByClassName('multiselect-submit-inner');
      var filterButtonDivs = document.querySelector('.multiselect-submit-inner');
      var in_dom;
      // if (filterButtonDivs && filterButtonDivs.length) {
      //   in_dom = document.body.contains(filterButtonDivs);
      // } else {
      //   in_dom = false;
      // }

      in_dom = document.body.contains(filterButtonDivs);
      var observer = new MutationObserver(function(mutations) {
//        filterButtonDivs = document.getElementsByClassName('multiselect-submit-inner'); // do we need to set it again?
        if (document.body.contains(filterButtonDivs)) {
          if (!in_dom) {
            // element inserted
console.log('filter button added');
            var filterButtonDiv = filterButtonDivs[0];
            if (filterButtonDiv) {
              // move the block with the filter button so it doesnt slide under the proactive chat widget
              filterButtonDiv.style.marginBottom = this.filterButtonDivMarginBottom + 'px';
            }
          }
          in_dom = true;
        } else if (in_dom) {
console.log('filter button removed');
          // element removed
          in_dom = false;
        }

      });
console.log('_watchForPrimoFiltersButton observing');
      // observer.observe(document.body, {
      //   childList: true, // required field
      //   subtree: true // the field to be observed is a descendant, not a child
      // });
      observer.observe(facetsSidebar, {childList: true});
    },

    /*
     * force a gap at the bottom of the facets sidebar on primo
     * so proactive chat doesnt cover any options
     */
    _makeRoomForSidebarBottomElements: function(sidebarDivMarginBottom) {
console.log('_makeRoomForSidebarBottomElements');
      if (this._isPrimoPage(window.location.hostname)) {
        var sidebarDivs = document.getElementsByClassName('sidebar-inner-wrapper');
        if (sidebarDivs && sidebarDivs.length) {
          var sidebarDiv = sidebarDivs[0];
          if (sidebarDiv) {
            // move the bottom of the sidebar so it doesnt slide under the filter button block
console.log('_makeRoomForSidebarBottomElements setting ' + sidebarDivMarginBottom + 'px');
            sidebarDiv.style.marginBottom = sidebarDivMarginBottom + 'px';
          }
        }
      }
    },

    /*
     * the amount of space needed to allow the 'apply filters' button to appear
     */
    _setPrimoFilterButtonPositioning: function(bottomMargin) {
console.log('_setPrimoFilterButtonPositioning setting bottommargin variable to ' + bottomMargin);
      // put a 125px margin at the bottom
      this.filterButtonDivMarginBottom = bottomMargin;
    },

    /*
     * the amount of space needed to allow the 'apply filters' button to appear above the tab
     */
    _setPrimoFilterButtonPositioningForTab: function() {
      // put a 45px margin at the bottom
      this._setPrimoFilterButtonPositioning(25);
    },

    /*
     * called when the uses clicks the 'x' button or 'maybe later'
     */
    _closeDialog: function() {
      this._setCookieNoPopup();
      this._showPopupChatBlock = false;
      this._showChatOnlineTab = true;
      this._setPrimoFilterButtonPositioningForTab();
    },

    /**
     * Returns the relevant link for this item
     * @param item
     * @returns {*}
     * @private
     */
    _getLink: function (item) {
      if (item.linkMobile && this._isMobile(navigator.userAgent)) {
        return item.linkMobile;
      } else {
        return item.link;
      }
    },

    /**
     * open a link for any Contact object
     * @param item
     * @private
     */
    _openWindow: function (item) {
      // Check if this item has a custom "target" attribute
      if (item.target) {
        if (this._isMobile(navigator.userAgent)) {
          // On mobile we ignore the targetOptions
          window.open(this._getLink(item), item.target);
        } else {
          window.open(this._getLink(item), item.target, item.targetOptions || "");
        }
      } else {
        window.location = this._getLink(item);
      }
    },

    /**
     * Called when 'Chat Now' is clicked.
     */
    openChat: function () {
      this._openWindow(this.chatLinkItems);
      this._showPopupChatBlock = false;
      this._showChatOnlineTab = true;
      this._setPrimoFilterButtonPositioningForTab();
    },

    /**
     *
     * called when 'leave a question' is clicked
     */
    openContactForm: function() {
      this._openWindow(this.contactLinkItems);
    },

    /**
     * Returns whether the user is on a mobile device
     * @returns {boolean}
     * @private
     */
    _isMobile: function (userAgent) {
      return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
    },

    /**
     * extracts the relevant display items from the contacts array
     * @param data
     * @private
     */
    _setLinks: function (data) {
      this.chatLinkItems = this._setLink(data, 'Chat');
      this.contactLinkItems = this._setLink(data, 'Contact form');
    },

    /**
     * get one chunk of the Contact data, per the label
     * @param data Object
     * @param label string
     * @returns object
     * @private
     */
    _setLink: function (data, label) {
      tempitem = data.items.filter(function(item) {
        return (item.label === label);
      });
      return tempitem[0];
    },

    /**
     * Gets a cookie by name
     * @param name the name of the cookie to return
     * @returns {String}
     * @private
     */
    _getCookie: function (name) {
      var parts = document.cookie.split(";");
      for (var i = 0, l = parts.length; i < l; i++) {
        var cookieParts = parts[i].trim().split('=');
        if (cookieParts[0] === name) {
          return cookieParts[1];
        }
      }
    },

    /**
     * get the date string for the cookie expiry
     * @param numDaysExpiry integer
     * @returns string
     */
    _getCookieExpiryDate: function (numDaysExpiry) {
      var date = new Date();
      date.setTime(date.getTime() + (numDaysExpiry * 24 * 60 * 60 * 1000));
      return date.toGMTString();
    },

    /**
     * set a cookie so the user doesnt see the popup for a while
     * (was originally session cookie, but Chrome doesnt delete those!)
     * @private
     */
    _setCookieNoPopup: function() {
      var cookieString = this.cookieNameNoPopup + "=true;";
      cookieString += " expires=" + this._getCookieExpiryDate(1) + ";";
      cookieString += " path=/;";
      cookieString += " domain=" + this.getDomain(window.location.hostname);
      document.cookie = cookieString;
    },

    /**
     * check if the nopop cookie has been set
     * @returns {boolean}
     * @private
     */
    _isCookieSetNoPopup: function() {
      cookie = this._getCookie(this.cookieNameNoPopup);
      return (typeof cookie !== "undefined");
    },

    /**
     * get the domain that should be written to the cookie
     * we cant hit all the possible domains at the same time,
     * but we can at least set a generic library one, rather than only the subdomain
     * @param hostname
     */
    getDomain: function(hostname) {
      var libraryRegExp = /(.*).library.uq.edu.au/i;
      if ('localhost' == hostname)  {
        return 'localhost';
      } else if (libraryRegExp.test(hostname))  {
        // If we are on a library subdomain, use library root domain.
        return '.library.uq.edu.au';
      } else {
        // Otherwise, eg studenthub.uq.edu.au, use that domain, without any www
        var otherRegExp = /www.(.*)/i;
        if (otherRegExp.test(hostname)) {
          return hostname.substring(3);
        } else {
          return '.' + hostname;
        }
      }
    },

    _isPrimoPage: function(hostname) {
      // return (
      //   'search.library.uq.edu.au' === hostname || // primo prod
      //   hostname.endsWith('exlibrisgroup.com') // primo sandbox
      // );
      if (
        'search.library.uq.edu.au' === hostname || // primo prod
        hostname.endsWith('exlibrisgroup.com') // primo sandbox
      ) {
console.log('is primo page');
        return true;
      } else {
        return false;
      }
    }

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


<commit_before>import React from 'react';
import {StyleSheet, ScrollView, Text, View, Platform, TouchableHighlight} from 'react-native';
import {NavigationContext} from 'navigation-react';
import {RightBarIOS, BarButtonIOS, SharedElementAndroid} from 'navigation-react-native';

export default ({color}) => (
  <NavigationContext.Consumer>
    {({stateNavigator}) => (
      <ScrollView
        contentInsetAdjustmentBehavior="automatic"
        contentContainerStyle={{flex:1}}>
          <RightBarIOS>
            <BarButtonIOS systemItem="cancel" onPress={() => {
              stateNavigator.navigateBack(1);
            }} />
          </RightBarIOS>
          {Platform.OS === 'android' && <TouchableHighlight
            underlayColor="#fff"
            onPress={() => {
              stateNavigator.navigateBack(1);
            }}>
            <Text style={styles.back}>X</Text>
          </TouchableHighlight>}
          <SharedElementAndroid name={color} style={styles.color}>
            <View style={{backgroundColor: color, flex: 1}} />
          </SharedElementAndroid>
          <Text style={styles.text}>{color}</Text>
      </ScrollView>
    )}
  </NavigationContext.Consumer>
);

const styles = StyleSheet.create({
  back: {
    fontSize: 20,
    color: '#000',
    fontWeight: 'bold',
    paddingLeft: 20,
    paddingTop: 10,
  },
  color: {
    height: 300,
    marginTop: 10,
    marginLeft: 15,
    marginRight: 15,
  },
  text:{
    fontSize: 80,
    color: '#000',
    textAlign:'center',
    fontWeight: 'bold',
  },
});<commit_msg>Styled the row of sub-colors<commit_after>import React from 'react';
import {StyleSheet, ScrollView, Text, View, Platform, TouchableHighlight} from 'react-native';
import {NavigationContext} from 'navigation-react';
import {RightBarIOS, BarButtonIOS, SharedElementAndroid} from 'navigation-react-native';

export default ({color}) => (
  <NavigationContext.Consumer>
    {({stateNavigator}) => (
      <ScrollView
        contentInsetAdjustmentBehavior="automatic"
        contentContainerStyle={{flex:1}}>
          <RightBarIOS>
            <BarButtonIOS systemItem="cancel" onPress={() => {
              stateNavigator.navigateBack(1);
            }} />
          </RightBarIOS>
          {Platform.OS === 'android' && <TouchableHighlight
            underlayColor="#fff"
            onPress={() => {
              stateNavigator.navigateBack(1);
            }}>
            <Text style={styles.back}>X</Text>
          </TouchableHighlight>}
          <SharedElementAndroid name={color} style={styles.color}>
            <View style={{backgroundColor: color, flex: 1}} />
          </SharedElementAndroid>
          <Text style={styles.text}>{color}</Text>
          <View style={styles.colors}>
            {['red', 'blue', 'green'].map(subcolor => (<TouchableHighlight
              key={subcolor}
              style={[
                {backgroundColor: subcolor},
                styles.subcolor
              ]}
              underlayColor={subcolor}
              onPress={() => {
                stateNavigator.navigateBack(1);
              }}>
                <View />
            </TouchableHighlight>))}
          </View>
        </ScrollView>
    )}
  </NavigationContext.Consumer>
);

const styles = StyleSheet.create({
  back: {
    fontSize: 20,
    color: '#000',
    fontWeight: 'bold',
    paddingLeft: 20,
    paddingTop: 10,
  },
  color: {
    height: 300,
    marginTop: 10,
    marginLeft: 15,
    marginRight: 15,
  },
  text:{
    fontSize: 80,
    color: '#000',
    textAlign:'center',
    fontWeight: 'bold',
  },
  colors: {
    flexDirection: 'row',
    justifyContent: 'space-evenly',
    marginTop: 20,
    marginLeft: 40,
    marginRight: 40,
  },
  subcolor: {
    width: 100,
    height: 50,
  },
});<|endoftext|>


<commit_before><commit_msg>Delete Multicon-traittest.js<commit_after><|endoftext|>


<commit_before>(function() {

	// A multiple bar chart

	// The Model
	// The model abstraction is a matrix of categories: the main dimansion will define the groups,
	// and the secondary will define the single bars.
	// Optional dimension is on the bar chart color (to be defined).

	var model = raw.model();

	// Group dimension.
	// It can accept both numbers and strings
	var groups = model.dimension()
		.title('Groups')
		.types(Number, String)

	// Categories dimension. each category will define a bar
	// It can accept both numbers and strings
	var categories = model.dimension()
		.title('Categories')
		.types(Number, String)
		.required(1)

	// Values dimension. It will define the height of the bars
	var sizes = model.dimension()
		.title('Size')
		.types(Number)

	// Colors dimension. It will define the color of the bars
	var colorsDimesion = model.dimension()
		.title('Colors')
		.types(String)

	// Mapping function
	// For each record in the data returns the values
	// for the X and Y dimensions and casts them as numbers
	model.map(function(data) {

		var results = d3.nest()
						.key(groups)
						.key(categories)
						.rollup(function(g){
							//get all the variables from the first item
							var result = g.map(function(d){
														return {
															group: groups(d),
															category: categories(d),
															color: colorsDimesion(d)
														}
													})[0];
							//If the size is defined, sum the size, otherwise count items
							result.size = sizes() != null ? d3.sum(g, function(d) {return sizes(d) }) : g.length;

							return result;
						})
						.entries(data);

		//for each group, flatten the second level of nest
		results.forEach(function(group){
			temp_values = [];
			//get the values, flatten them
			group.values.forEach(function(category){
				temp_values.push(category.values);
			})
			group.values = temp_values;
		});

		console.log(results);

		return results;
	})

	// The Chart

	var chart = raw.chart()
		.title("Bar chart")
		.description("A bar chart or bar graph is a chart or graph that presents grouped data with rectangular bars with heights proportional to the values that they represent.
Chart based on https://bl.ocks.org/mbostock/3310560")
		.thumbnail("imgs/barChart.png")
		.model(model)

	// visualiziation options
	// Width
	var width = chart.number()
		.title('Width')
		.defaultValue(900)

	// Height
	var height = chart.number()
		.title('Height')
		.defaultValue(600)

	// Space between barcharts
	var padding = chart.number()
		.title('Vertical padding')
		.defaultValue(10);

	// Padding between bars
	var xPadding = chart.number()
		.title('Horizontal padding')
		.defaultValue(0.1);

	// Use or not the same scale across all the bar charts
	var sameScale = chart.checkbox()
        .title("Use same scale")
        .defaultValue(false)

	// Chart colors
	var colors = chart.color()
        .title("Color scale")

	// Drawing function
	// selection represents the d3 selection (svg)
	// data is not the original set of records
	// but the result of the model map function
	chart.draw(function(selection, data) {

		// Define margins
		var margin = {top: 0, right: 0, bottom: 50, left: 50};

		// Define common variables.
    	// Find the overall maximum value
    	var maxValue

    	if(sameScale()) {
    		maxValue = d3.max(data, function(item) {
    			return d3.max(item.values, function(d) {
    				return d.size; }); })
    		console.log(maxValue);
    	}

    	// Check consistency among categories and colors, save them all
    	var allCategories = [];
    	var allColors = [];
    	data.forEach(function(item){

    		var temp_categories = item.values.map(function(val){
    			return val.category;
    		})
    		allCategories = allCategories.concat(temp_categories);

    		// Same for color
    		var temp_colors = item.values.map(function(val){
    			return val.color;
    		})
    		allColors = allColors.concat(temp_colors);
    	})
    	//keep uniques
    	allCategories = d3.set(allCategories).values();
    	allColors = d3.set(allColors).values();

		// svg size
		selection
			.attr("width", width())
			.attr("height", height())

		// define single barchart height,
		// depending on the number of bar charts
		var w = +width() - margin.left,
			h = (+height() - margin.bottom - (+padding() * (data.length - 1))) / data.length;

		// Define scales
		var xScale = d3.scale.ordinal()
			.rangeRoundBands([0, w], +xPadding(), 0);

		var yScale = d3.scale.linear()
			.range([h, 0]);

		// Define color scale domain
		colors.domain(allColors);

		// Draw each bar chart
		data.forEach(function(item, index) {

			// Define x domain
			xScale.domain(allCategories);
			// Define y domain
			if(sameScale()) {
				yScale.domain([0, maxValue]);
			} else {
				yScale.domain([0, d3.max(item.values, function(d) {
					return d.size;
				})]);
			}

			// Append a grupo containing axis and bars,
			// move it according the index
			barchart = selection.append("g")
				.attr("transform", "translate(" + margin.left + "," + index * (h + padding()) + ")");

			// Draw y axis
			barchart.append("g")
				.attr("class", "y axis")
				.call(d3.svg.axis().scale(yScale).orient("left").ticks(h/20));

			// Draw the bars
			barchart.selectAll(".bar")
				.data(item.values)
				.enter().append("rect")
				.attr("class", "bar")
				.attr("x", function(d) {
					return xScale(d.category);
				})
				.attr("width", xScale.rangeBand())
				.attr("y", function(d) {
					return yScale(d.size);
				})
				.attr("height", function(d) {
					return h - yScale(d.size);
				})
				.style("fill", function(d){
					return colors()(d.color);
				});

		})

		// After all the charts, draw x axis
		selection.append("g")
			.attr("class", "x axis")
			.attr("transform", "translate(" + margin.bottom + "," + ((h + padding()) * data.length - padding()) + ")")
			.call(d3.svg.axis().scale(xScale).orient("bottom"));

		// Set styles

		d3.selectAll(".axis line, .axis path")
			.style("shape-rendering", "crispEdges")
			.style("fill", "none")
			.style("stroke", "#ccc")

	})
})();
<commit_msg>Added titles to bar chart<commit_after>(function() {

	// A multiple bar chart

	// The Model
	// The model abstraction is a matrix of categories: the main dimansion will define the groups,
	// and the secondary will define the single bars.
	// Optional dimension is on the bar chart color (to be defined).

	var model = raw.model();

	// Group dimension.
	// It can accept both numbers and strings
	var groups = model.dimension()
		.title('Groups')
		.types(Number, String)

	// Categories dimension. each category will define a bar
	// It can accept both numbers and strings
	var categories = model.dimension()
		.title('Categories')
		.types(Number, String)
		.required(1)

	// Values dimension. It will define the height of the bars
	var sizes = model.dimension()
		.title('Size')
		.types(Number)

	// Colors dimension. It will define the color of the bars
	var colorsDimesion = model.dimension()
		.title('Colors')
		.types(String)

	// Mapping function
	// For each record in the data returns the values
	// for the X and Y dimensions and casts them as numbers
	model.map(function(data) {

		var results = d3.nest()
						.key(groups)
						.key(categories)
						.rollup(function(g){
							//get all the variables from the first item
							var result = g.map(function(d){
														return {
															group: groups(d),
															category: categories(d),
															color: colorsDimesion(d)
														}
													})[0];
							//If the size is defined, sum the size, otherwise count items
							result.size = sizes() != null ? d3.sum(g, function(d) {return sizes(d) }) : g.length;

							return result;
						})
						.entries(data);

		//for each group, flatten the second level of nest
		results.forEach(function(group){
			temp_values = [];
			//get the values, flatten them
			group.values.forEach(function(category){
				temp_values.push(category.values);
			})
			group.values = temp_values;
		});

		return results;
	})

	// The Chart

	var chart = raw.chart()
		.title("Bar chart")
		.description("A bar chart or bar graph is a chart or graph that presents grouped data with rectangular bars with heights proportional to the values that they represent.
 Chart based on https://bl.ocks.org/mbostock/3310560")
		.thumbnail("imgs/barChart.png")
		.model(model)

	// visualiziation options
	// Width
	var width = chart.number()
		.title('Width')
		.defaultValue(1000)

	// Height
	var height = chart.number()
		.title('Height')
		.defaultValue(1000)

	// Space between barcharts
	var padding = chart.number()
		.title('Vertical padding')
		.defaultValue(0);

	// Padding between bars
	var xPadding = chart.number()
		.title('Horizontal padding')
		.defaultValue(0.1);

	// Use or not the same scale across all the bar charts
	var sameScale = chart.checkbox()
        .title("Use same scale")
        .defaultValue(false)

	// Chart colors
	var colors = chart.color()
        .title("Color scale")

	// Drawing function
	// selection represents the d3 selection (svg)
	// data is not the original set of records
	// but the result of the model map function
	chart.draw(function(selection, data) {

		// Define margins
		var margin = {top: 0, right: 0, bottom: 50, left: 50};
		//define title space
		var titleSpace = groups() == null ? 0 : 30;

		// Define common variables.
    	// Find the overall maximum value
    	var maxValue

    	if(sameScale()) {
    		maxValue = d3.max(data, function(item) {
    			return d3.max(item.values, function(d) {
    				return d.size; }); })
    	}

    	// Check consistency among categories and colors, save them all
    	var allCategories = [];
    	var allColors = [];
    	data.forEach(function(item){

    		var temp_categories = item.values.map(function(val){
    			return val.category;
    		})
    		allCategories = allCategories.concat(temp_categories);

    		// Same for color
    		var temp_colors = item.values.map(function(val){
    			return val.color;
    		})
    		allColors = allColors.concat(temp_colors);
    	})
    	//keep uniques
    	allCategories = d3.set(allCategories).values();
    	allColors = d3.set(allColors).values();

		// svg size
		selection
			.attr("width", width())
			.attr("height", height())

		// define single barchart height,
		// depending on the number of bar charts
		var w = +width() - margin.left,
			h = (+height() - margin.bottom - ((titleSpace+padding()) * (data.length - 1))) / data.length;

		// Define scales
		var xScale = d3.scale.ordinal()
			.rangeRoundBands([0, w], +xPadding(), 0);

		var yScale = d3.scale.linear()
			.range([h, 0]);

		// Define color scale domain
		colors.domain(allColors);

		// Draw each bar chart
		data.forEach(function(item, index) {

			// Define x domain
			xScale.domain(allCategories);
			// Define y domain
			if(sameScale()) {
				yScale.domain([0, maxValue]);
			} else {
				yScale.domain([0, d3.max(item.values, function(d) {
					return d.size;
				})]);
			}

			// Append a grupo containing axis and bars,
			// move it according the index
			barchart = selection.append("g")
				.attr("transform", "translate(" + margin.left + "," + index * (h + padding() + titleSpace) + ")");

			console.log(item);
			// Draw title
			barchart.append("text")
	            .attr("x", -margin.left)
	            .attr("y", titleSpace - 7)
	            .style("font-size","10px")
	            .style("font-family","Arial, Helvetica")
	            .text(item.key);

			// Draw y axis
			barchart.append("g")
				.attr("class", "y axis")
				.style("font-size","10px")
				.style("font-family","Arial, Helvetica")
				.attr("transform", "translate(0," + titleSpace + ")")
				.call(d3.svg.axis().scale(yScale).orient("left").ticks(h/15));

			// Draw the bars
			barchart.selectAll(".bar")
				.data(item.values)
				.enter().append("rect")
				.attr("transform", "translate(0," + titleSpace + ")")
				.attr("class", "bar")
				.attr("x", function(d) {
					return xScale(d.category);
				})
				.attr("width", xScale.rangeBand())
				.attr("y", function(d) {
					return yScale(d.size);
				})
				.attr("height", function(d) {
					return h - yScale(d.size);
				})
				.style("fill", function(d){
					return colors()(d.color);
				});

		})

		// After all the charts, draw x axis
		selection.append("g")
			.attr("class", "x axis")
			.style("font-size","10px")
			.style("font-family","Arial, Helvetica")
			.attr("transform", "translate(" + margin.left + "," + ((h + padding() + titleSpace) * data.length - padding()) + ")")
			.call(d3.svg.axis().scale(xScale).orient("bottom"));

		// Set styles

		d3.selectAll(".axis line, .axis path")
			.style("shape-rendering", "crispEdges")
			.style("fill", "none")
			.style("stroke", "#ccc");

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


<commit_before>'use strict';

// modules > native
const p = require('path');

// modules > passport
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

const config = require(p.join(process.cwd(), 'server/config/membership'));

const User = require('../services/users/model');

function localCallback(email, password, done) {
  User.findOne({ email: email.toLowerCase() }, (err, user) => {
    if (err) {
      return done(err);
    }

    let message;

    if (user) {
      if (!user.password) {
        message = config.messages.login.notLocal;
      } else if (!user.isVerified) {
        message = config.messages.login.unverified;
      } else if (user.isBlocked) {
        message = config.messages.login.blocked;
      } else if (user.isBanned) {
        message = config.messages.login.banned;
      } else if (!user.authenticate(password)) {
        message = config.messages.login.wrongPassword;
      }
    } else {
      message = config.messages.login.noLocalUser;
    }

    done(null, user, message);
  });
}

passport.use('local', new LocalStrategy(config.passport.local, localCallback));
<commit_msg>Missed one isVerified<commit_after>'use strict';

// modules > native
const p = require('path');

// modules > passport
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

const config = require(p.join(process.cwd(), 'server/config/membership'));

const User = require('../services/users/model');

function localCallback(email, password, done) {
  User.findOne({ email: email.toLowerCase() }, (err, user) => {
    if (err) {
      return done(err);
    }

    let message;

    if (user) {
      if (!user.password) {
        message = config.messages.login.notLocal;
      } else if (!user.isEmailVerified) {
        message = config.messages.login.unverified;
      } else if (user.isBlocked) {
        message = config.messages.login.blocked;
      } else if (user.isBanned) {
        message = config.messages.login.banned;
      } else if (!user.authenticate(password)) {
        message = config.messages.login.wrongPassword;
      }
    } else {
      message = config.messages.login.noLocalUser;
    }

    done(null, user, message);
  });
}

passport.use('local', new LocalStrategy(config.passport.local, localCallback));
<|endoftext|>


<commit_before>/*global $:false */
(function () {
  'use strict';

  angular
    .module('companies')
    .controller('CompaniesTimeController', CompaniesTimeController);

  CompaniesTimeController.$inject = ['CompaniesService', '$scope', 'ApplicationsService'];

  function CompaniesTimeController(CompaniesService, $scope, ApplicationsService) {
    var vm = this;

    $scope.chosenStudents = [];

    // Get Companies
    vm.companies = CompaniesService.query(function(data) {
      function sortName(c1, c2){ return c1.name > c2.name ? 1 : -1; }
      vm.companies = data.sort(sortName);
      angular.forEach(vm.companies, function(company, key) {
        company.weOffer = company.weOffer || '';
        company.language = company.language || '';
      });
    });
    // Get Applications
    vm.applications = ApplicationsService.query(function(data){
      vm.applications = data;
    });
    function prettify(str){
      return str.replace(/[ÅÄÖåäöøé\/ÉüÜ ]/g, '');
    }

    // Set Chosen Students
    function getChosenStudents(company){
      function hasSelectedCompany(a){
        return a.companies.filter(function(c){
          return prettify(c.name) === prettify(company.name);
        }).length > 0;
      }
      function setIsChosen(a){
        a.isChosen = company.chosenStudents.filter(function (c){ return c === a._id; }).length > 0;
      }
      var applications = vm.applications.filter(hasSelectedCompany);
      applications.forEach(setIsChosen);
      function byChosenAndName(s1, s2){ return (s2.isChosen - s1.isChosen) || (s1.name > s2.name ? 1 : -1); }
      applications.sort(byChosenAndName);
      applications.forEach(splitTimesToDayPeriodLists);
      return applications;
    }

    // Generate meetings
    function generateMeetings(company){
      var wed = company.wednesday;
      var thur = company.thursday;

      if(wed.hasMeetings){
        addWedMeetings(company, wed);
      }
      if(thur.hasMeetings){
        addThurMeetings(company, thur);
      }
    }
    function addWedMeetings(company, wed){
      var start = timeStrToInt(wed.starttime);
      company.wedMeetings = [];
      addMeetingIterator(start, wed, company, company.wedMeetings, 'wed');
    }
    function addThurMeetings(company, thur){
      var start = timeStrToInt(thur.starttime);
      company.thurMeetings = [];
      addMeetingIterator(start, thur, company, company.thurMeetings, 'thur');
    }
    function addMeetingIterator(start, day, company, list, dayStr){
      var dayEnd = timeStrToInt(day.endtime);
      var lunchstart = timeStrToInt(day.lunchstart);
      var lunchend = timeStrToInt(day.lunchend);
      if(start >= dayEnd){
        return;
      }
      var end = start + day.meetingLength;
      if(start >= lunchstart && start < lunchend && end >= lunchstart && end < lunchend){
        start = lunchend;
        end = start + day.meetingLength;
      }

      var m = company.meetings.filter(sameStartnDay);
      function sameStartnDay(m){ return m.startTime === timeIntToStr(start) && m.day === dayStr; }
      var hasMeeting = m.length > 0;
      if(!hasMeeting){
        list.push({ startTime: timeIntToStr(start), endTime: timeIntToStr(end), day: dayStr });
      } else {
        list.push(m[0]);
      }
      addMeetingIterator(end, day, company, list, dayStr);
    }

    // View Company
    $scope.viewCompany = function(company) {
      // Select only this company
      $scope.chosenStudents.forEach(unselect);
      unSelectAllMeetings(company);
      $scope.meeting = undefined;
      vm.companies.forEach(unselect);
      function unselect(c){ c.selected = false; }
      company.selected = true;

      $scope.company = company; // Company is the selected one.
      $scope.chosenStudents = getChosenStudents(company);
      generateMeetings(company);
      $scope.message = undefined;
    };

    // Update Company
    vm.updateCompany = function(){
      // Update DB.
      function saveCompany(company){
        var comp = CompaniesService.get({ companyId: company._id }, function() {
          comp = company;
          comp.$save();
          $scope.message = 'Saved company';
        });
      }
      saveCompany($scope.company);
    };

    function unselect(s){ s.selected = false; }
    // Select Student
    $scope.selectStudent = function(student){
      // Select only this company
      $scope.chosenStudents.forEach(unselect);
      student.selected = true;

      $scope.company.meetings.forEach(isSameStudent);
      function isSameStudent(m){ m.isSelectedStudent = (m.student.id === student._id); }

      student.wedPeriodList = student.times.filter(isTimesWed).map(timesToPeriodList)[0];
      student.thurPeriodList = student.times.filter(isTimesThur).map(timesToPeriodList)[0];
      // Merge periods that collide. (ex: 540-600 & 600-660 minutes);
      student.wedPeriodList = mergePeriodList(student.wedPeriodList);
      student.thurPeriodList = mergePeriodList(student.thurPeriodList);

      if($scope.company.wednesday.hasMeetings){
        $scope.company.wedMeetings.forEach(isStudentAvailableWed);
      }
      function isStudentAvailableWed(m){
        m.available = isAvailable(student.wedPeriodList, timeStrToInt(m.startTime), timeStrToInt(m.endTime));
      }
      if($scope.company.thursday.hasMeetings){
        $scope.company.thurMeetings.forEach(isStudentAvailableThur);
      }
      function isStudentAvailableThur(m){
        m.available = isAvailable(student.thurPeriodList, timeStrToInt(m.startTime), timeStrToInt(m.endTime));
      }
    };

    function unSelectAllMeetings(company){
      if(company.wednesday.hasMeetings && company.wedMeetings){
        company.wedMeetings.forEach(unselect);
      }
      if(company.thursday.hasMeetings && company.thurMeetings){
        company.thurMeetings.forEach(unselect);
      }
    }

    // Select Meeting
    $scope.selectMeeting = function(company, meeting){
      // Select only this company
      unSelectAllMeetings(company);
      function unselect(s){ s.selected = false; }
      meeting.selected = true;

      if(meeting.student){
        var stuList = $scope.chosenStudents.filter(function(s){ return s._id === meeting.student.id; });
        if(stuList.length > 0){
          vm.studentToAdd = stuList[0];
        }
      }
      $scope.meeting = meeting;
    };

    // Update Meeting
    $scope.updateMeeting = function(){
      alert('not yet implemented');
    };

    // Time int/string conversion
    function timeIntToStr(time){
      var hour = Math.floor(time / 60);
      if(hour < 10){
        hour = '0' + hour;
      }
      var minute = time % 60;
      if(minute < 10){
        minute = '0' + minute;
      }
      var t = hour + ':' + minute;
      return t;
    }
    function timeStrToInt(time){
      var time_hm = time.split(':');
      var timeStr = (+time_hm[0]) * 60 + (+time_hm[1]);
      return timeStr;
    }

    // PeriodList Code
    // For handling everything with periods on a application. A period is a
    // time span with a start and end, both as int's. The int refer to minute
    // in the day. 540 is for example 09:00. (9 * 60 minutes)
    // =====================================================
    function splitTimesToDayPeriodLists(application){
      application.wedPeriodList = application.times.filter(isTimesWed).map(timesToPeriodList)[0];
      application.thurPeriodList = application.times.filter(isTimesThur).map(timesToPeriodList)[0];

      // Merge periods that collide. (ex: 540-600 & 600-660 minutes);
      application.wedPeriodList = mergePeriodList(application.wedPeriodList);
      application.thurPeriodList = mergePeriodList(application.thurPeriodList);
    }

    // Create periodList on each Application.
    // Sort PeriodList timewise
    function sortByStart(period1, period2){ return period1.start > period2.start; }

    // Merge colliding periods in list
    function mergePeriodList(periodList){
      periodList = periodList.sort(sortByStart);
      var newList = [];
      // Check if endtime <= any other starttime
      function endTimeCollidesWithStartTime(period1, index1){
        // Get all periods that collide
        function filterStartBeforeTime(period){ return period.start <= period1.end; }
        var collidingPeriods = periodList.filter(filterStartBeforeTime);
        // Nbr colliding periods
        var nbrCollidingPeriods = collidingPeriods.length;
        if(nbrCollidingPeriods > 0){ // Merge periods in a new one
          newList.push({ start: period1.start, end: collidingPeriods[nbrCollidingPeriods-1].end });
        } else { // No collision, just push.
          newList.push(period1);
        }
      }
      periodList.forEach(endTimeCollidesWithStartTime);
      // Update periodlist with merged.
      return newList;
    }
    // Create a period (in minutes) from a time-object.
    function timesToPeriodList(times) {
      var start = times.hour * 60;
      function toInt(h){
        var obj = { start: h * 60, end: h * 60 + 60 };
        return obj;
      }
      return times.hour.map(toInt);
    }
    function isTimesWed(t) { return t.day === 'wed'; }
    function isTimesThur(t) { return t.day === 'thur'; }

    // Calculate time left the student is available
    // from currentTime and forward.
    function timeLeftToday(application, currentTime, day){
      // Reduce function, checks against currentTime.
      function sumTimeLeft(previousValue, period){
        var diff = period.end - Math.max(period.start, currentTime);
        return previousValue + Math.max(0, diff);
      }
      if(day === 'wed'){
        return application.wedPeriodList.reduce(sumTimeLeft, 0);
      } else {
        return application.thurPeriodList.reduce(sumTimeLeft, 0);
      }
    }
    function timeLeftAvailableWednesday(application, currentTime){
      return timeLeftToday(application, currentTime, 'wed');
    }
    function timeLeftAvailableThursday(application, currentTime){
      return timeLeftToday(application, currentTime, 'thur');
    }

    // Check if periodList contains given period.
    function isAvailable(periodList, start, end){
      function isStartEndWithinPeriod(period){
        return period.start <= start && period.end >= end;
      }
      return periodList.filter(isStartEndWithinPeriod).length > 0;
    }

    // Iterate periodList and add pause to colliding period
    function addPause(periodList, start, end){
      function addPauseToPeriod(period){
        if(period.start <= start && period.end >= end){
          var newPeriod1 = { start: period.start, end: start };
          var newPeriod2 = { start: end, end: period.end };
          if(newPeriod1.start !== newPeriod1.end){
            period = newPeriod1;
            if(newPeriod2.start !== newPeriod2.end){
              periodList.push(newPeriod2);
              periodList = periodList.sort(sortByStart);
            }
          } else {
            period.start = end;
          }
        }
      }
      periodList.forEach(addPauseToPeriod);
    }

  }
})();
<commit_msg>rm lines<commit_after>/*global $:false */
(function () {
  'use strict';

  angular
    .module('companies')
    .controller('CompaniesTimeController', CompaniesTimeController);

  CompaniesTimeController.$inject = ['CompaniesService', '$scope', 'ApplicationsService'];

  function CompaniesTimeController(CompaniesService, $scope, ApplicationsService) {
    var vm = this;

    $scope.chosenStudents = [];

    // Get Companies
    vm.companies = CompaniesService.query(function(data) {
      function sortName(c1, c2){ return c1.name > c2.name ? 1 : -1; }
      vm.companies = data.sort(sortName);
      angular.forEach(vm.companies, function(company, key) {
        company.weOffer = company.weOffer || '';
        company.language = company.language || '';
      });
    });
    // Get Applications
    vm.applications = ApplicationsService.query(function(data){
      vm.applications = data;
    });
    function prettify(str){
      return str.replace(/[ÅÄÖåäöøé\/ÉüÜ ]/g, '');
    }

    // Set Chosen Students
    function getChosenStudents(company){
      function hasSelectedCompany(a){
        return a.companies.filter(function(c){
          return prettify(c.name) === prettify(company.name);
        }).length > 0;
      }
      function setIsChosen(a){
        a.isChosen = company.chosenStudents.filter(function (c){ return c === a._id; }).length > 0;
      }
      var applications = vm.applications.filter(hasSelectedCompany);
      applications.forEach(setIsChosen);
      function byChosenAndName(s1, s2){ return (s2.isChosen - s1.isChosen) || (s1.name > s2.name ? 1 : -1); }
      applications.sort(byChosenAndName);
      applications.forEach(splitTimesToDayPeriodLists);
      return applications;
    }

    // Generate meetings
    function generateMeetings(company){
      var wed = company.wednesday;
      var thur = company.thursday;

      if(wed.hasMeetings){
        addWedMeetings(company, wed);
      }
      if(thur.hasMeetings){
        addThurMeetings(company, thur);
      }
    }
    function addWedMeetings(company, wed){
      var start = timeStrToInt(wed.starttime);
      company.wedMeetings = [];
      addMeetingIterator(start, wed, company, company.wedMeetings, 'wed');
    }
    function addThurMeetings(company, thur){
      var start = timeStrToInt(thur.starttime);
      company.thurMeetings = [];
      addMeetingIterator(start, thur, company, company.thurMeetings, 'thur');
    }
    function addMeetingIterator(start, day, company, list, dayStr){
      var dayEnd = timeStrToInt(day.endtime);
      var lunchstart = timeStrToInt(day.lunchstart);
      var lunchend = timeStrToInt(day.lunchend);
      if(start >= dayEnd){
        return;
      }
      var end = start + day.meetingLength;
      if(start >= lunchstart && start < lunchend && end >= lunchstart && end < lunchend){
        start = lunchend;
        end = start + day.meetingLength;
      }

      var m = company.meetings.filter(sameStartnDay);
      function sameStartnDay(m){ return m.startTime === timeIntToStr(start) && m.day === dayStr; }
      var hasMeeting = m.length > 0;
      if(!hasMeeting){
        list.push({ startTime: timeIntToStr(start), endTime: timeIntToStr(end), day: dayStr });
      } else {
        list.push(m[0]);
      }
      addMeetingIterator(end, day, company, list, dayStr);
    }

    // View Company
    $scope.viewCompany = function(company) {
      // Select only this company
      $scope.chosenStudents.forEach(unselect);
      unSelectAllMeetings(company);
      $scope.meeting = undefined;
      vm.companies.forEach(unselect);
      function unselect(c){ c.selected = false; }
      company.selected = true;

      $scope.company = company; // Company is the selected one.
      $scope.chosenStudents = getChosenStudents(company);
      generateMeetings(company);
      $scope.message = undefined;
    };

    // Update Company
    vm.updateCompany = function(){
      // Update DB.
      function saveCompany(company){
        var comp = CompaniesService.get({ companyId: company._id }, function() {
          comp = company;
          comp.$save();
          $scope.message = 'Saved company';
        });
      }
      saveCompany($scope.company);
    };

    function unselect(s){ s.selected = false; }
    // Select Student
    $scope.selectStudent = function(student){
      // Select only this company
      $scope.chosenStudents.forEach(unselect);
      student.selected = true;

      $scope.company.meetings.forEach(isSameStudent);
      function isSameStudent(m){ m.isSelectedStudent = (m.student.id === student._id); }

      student.wedPeriodList = student.times.filter(isTimesWed).map(timesToPeriodList)[0];
      student.thurPeriodList = student.times.filter(isTimesThur).map(timesToPeriodList)[0];
      // Merge periods that collide. (ex: 540-600 & 600-660 minutes);
      student.wedPeriodList = mergePeriodList(student.wedPeriodList);
      student.thurPeriodList = mergePeriodList(student.thurPeriodList);

      if($scope.company.wednesday.hasMeetings){
        $scope.company.wedMeetings.forEach(isStudentAvailableWed);
      }
      function isStudentAvailableWed(m){
        m.available = isAvailable(student.wedPeriodList, timeStrToInt(m.startTime), timeStrToInt(m.endTime));
      }
      if($scope.company.thursday.hasMeetings){
        $scope.company.thurMeetings.forEach(isStudentAvailableThur);
      }
      function isStudentAvailableThur(m){
        m.available = isAvailable(student.thurPeriodList, timeStrToInt(m.startTime), timeStrToInt(m.endTime));
      }
    };

    function unSelectAllMeetings(company){
      if(company.wednesday.hasMeetings && company.wedMeetings){
        company.wedMeetings.forEach(unselect);
      }
      if(company.thursday.hasMeetings && company.thurMeetings){
        company.thurMeetings.forEach(unselect);
      }
    }

    // Select Meeting
    $scope.selectMeeting = function(company, meeting){
      // Select only this company
      unSelectAllMeetings(company);
      function unselect(s){ s.selected = false; }
      meeting.selected = true;

      if(meeting.student){
        var stuList = $scope.chosenStudents.filter(function(s){ return s._id === meeting.student.id; });
        if(stuList.length > 0){
          vm.studentToAdd = stuList[0];
        }
      }
      $scope.meeting = meeting;
    };

    // Update Meeting
    $scope.updateMeeting = function(){
      alert('not yet implemented');
    };

    // Time int/string conversion
    function timeIntToStr(time){
      var hour = Math.floor(time / 60);
      if(hour < 10){
        hour = '0' + hour;
      }
      var minute = time % 60;
      if(minute < 10){
        minute = '0' + minute;
      }
      var t = hour + ':' + minute;
      return t;
    }
    function timeStrToInt(time){
      var time_hm = time.split(':');
      var timeStr = (+time_hm[0]) * 60 + (+time_hm[1]);
      return timeStr;
    }

    // PeriodList Code
    // For handling everything with periods on a application. A period is a
    // time span with a start and end, both as int's. The int refer to minute
    // in the day. 540 is for example 09:00. (9 * 60 minutes)
    // =====================================================
    function splitTimesToDayPeriodLists(application){
      application.wedPeriodList = application.times.filter(isTimesWed).map(timesToPeriodList)[0];
      application.thurPeriodList = application.times.filter(isTimesThur).map(timesToPeriodList)[0];

      // Merge periods that collide. (ex: 540-600 & 600-660 minutes);
      application.wedPeriodList = mergePeriodList(application.wedPeriodList);
      application.thurPeriodList = mergePeriodList(application.thurPeriodList);
    }

    // Create periodList on each Application.
    // Sort PeriodList timewise
    function sortByStart(period1, period2){ return period1.start > period2.start; }

    // Merge colliding periods in list
    function mergePeriodList(periodList){
      periodList = periodList.sort(sortByStart);
      var newList = [];
      // Check if endtime <= any other starttime
      function endTimeCollidesWithStartTime(period1, index1){
        // Get all periods that collide
        function filterStartBeforeTime(period){ return period.start <= period1.end; }
        var collidingPeriods = periodList.filter(filterStartBeforeTime);
        // Nbr colliding periods
        var nbrCollidingPeriods = collidingPeriods.length;
        if(nbrCollidingPeriods > 0){ // Merge periods in a new one
          newList.push({ start: period1.start, end: collidingPeriods[nbrCollidingPeriods-1].end });
        } else { // No collision, just push.
          newList.push(period1);
        }
      }
      periodList.forEach(endTimeCollidesWithStartTime);
      // Update periodlist with merged.
      return newList;
    }
    // Create a period (in minutes) from a time-object.
    function timesToPeriodList(times) {
      var start = times.hour * 60;
      function toInt(h){
        var obj = { start: h * 60, end: h * 60 + 60 };
        return obj;
      }
      return times.hour.map(toInt);
    }
    function isTimesWed(t) { return t.day === 'wed'; }
    function isTimesThur(t) { return t.day === 'thur'; }

    // Calculate time left the student is available
    // from currentTime and forward.
    function timeLeftToday(application, currentTime, day){
      // Reduce function, checks against currentTime.
      function sumTimeLeft(previousValue, period){
        var diff = period.end - Math.max(period.start, currentTime);
        return previousValue + Math.max(0, diff);
      }
      if(day === 'wed'){
        return application.wedPeriodList.reduce(sumTimeLeft, 0);
      } else {
        return application.thurPeriodList.reduce(sumTimeLeft, 0);
      }
    }
    function timeLeftAvailableWednesday(application, currentTime){
      return timeLeftToday(application, currentTime, 'wed');
    }
    function timeLeftAvailableThursday(application, currentTime){
      return timeLeftToday(application, currentTime, 'thur');
    }

    // Check if periodList contains given period.
    function isAvailable(periodList, start, end){
      function isStartEndWithinPeriod(period){
        return period.start <= start && period.end >= end;
      }
      return periodList.filter(isStartEndWithinPeriod).length > 0;
    }

    // Iterate periodList and add pause to colliding period
    function addPause(periodList, start, end){
      function addPauseToPeriod(period){
        if(period.start <= start && period.end >= end){
          var newPeriod1 = { start: period.start, end: start };
          var newPeriod2 = { start: end, end: period.end };
          if(newPeriod1.start !== newPeriod1.end){
            period = newPeriod1;
            if(newPeriod2.start !== newPeriod2.end){
              periodList.push(newPeriod2);
              periodList = periodList.sort(sortByStart);
            }
          } else {
            period.start = end;
          }
        }
      }
      periodList.forEach(addPauseToPeriod);
    }

  }
})();
<|endoftext|>