<commit_before>import React from 'react'
import { shallow } from 'enzyme'
import PledgeCommentList from './PledgeCommentList'
import QuestionForm from './QuestionForm'

describe('<PledgeCommentList />', function () {
  const props = {
    comments: [
      {
        id: 1,
        content: 'question1',
        response: 'answer1'
      },
      {
        id: 2,
        content: 'question2'
      }
    ],
    forms: {
      questionForm: {},
      answerForm: {}
    },
    userCanAskQuestions: true,
    userCanAnswer: false,
    isSubmitting: false
  }

  it('should render', function () {
    const wrapper = shallow(<PledgeCommentList {...props} />)

    wrapper.find('div').length.should.equal(1)
  })

  it('should render a text message if there are no comments yet', function () {
    const wrapper = shallow(<PledgeCommentList {...props} comments={[]} />)

    wrapper.find('p').length.should.equal(1)
  })

  it('should render the question form with an id based on length of comments array', function() {
    const wrapper = shallow(<PledgeCommentList {...props} />)

    const questionFormProps = wrapper.find('QuestionForm').props()
    questionFormProps.id.should.equal(2)
  })
})
<commit_msg>Better name for PledgeCommentList test<commit_after>import React from 'react'
import { shallow } from 'enzyme'
import PledgeCommentList from './PledgeCommentList'
import QuestionForm from './QuestionForm'

describe('<PledgeCommentList />', function () {
  const props = {
    comments: [
      {
        id: 1,
        content: 'question1',
        response: 'answer1'
      },
      {
        id: 2,
        content: 'question2'
      }
    ],
    forms: {
      questionForm: {},
      answerForm: {}
    },
    userCanAskQuestions: true,
    userCanAnswer: false,
    isSubmitting: false
  }

  it('should render', function () {
    const wrapper = shallow(<PledgeCommentList {...props} />)

    wrapper.find('div').length.should.equal(1)
  })

  it('should render a text message if there are no comments yet', function () {
    const wrapper = shallow(<PledgeCommentList {...props} comments={[]} />)

    wrapper.find('p').length.should.equal(1)
  })

  it('should render the question form with a unique id so that it gets cleared on submit', function() {
    const wrapper = shallow(<PledgeCommentList {...props} />)

    const questionFormProps = wrapper.find('QuestionForm').props()
    questionFormProps.id.should.equal(2)
  })
})
<|endoftext|>


<commit_before>jdata {
  //cout is the inflow of progB
  cout as inflow of app://progB.outF;
  //shellOut is the outflow of the shell (from progB)
  shellOut as outflow of cout;

  char* selfName as broadcaster;

  //Job logger
  char* jobs as logger; //Job logger
  //TODO: This can be removed when device status is kept from the J Node instead
  struct nodeInfo {
    char* nodeName;
      char* nodeType;
      char* fog;
      char* cloud;
  } nodeInfo as logger; //Device info logger
}

var chance = require('chance').Chance();
var nodeName = chance.first();
console.log("Node name: " + nodeName);

jcond {
  namechk: selfName == nodeInfo.nodeName;
  cloudonly: sys.type == "cloud";
  deviceonly: sys.type =="device";
}

/**----------------REQUIRE-----------------------------------------**/
var shell = require('vorpal')();
var spawn = require('child_process').spawn;
var fs = require('fs');

/**----------------NODE INIT-----------------------------------------**/
var fogName;
var cloudName;
var outputFileName;

nodeInfo.addDatastream(nodeName);
jobs.addDatastream(nodeName);
var selfInfo = {"name": nodeName, "type": jsys.type};

/**----------------ADVERTISE SELF----------------------------------**/
jsys.ad_up('name',selfInfo)

jsys.ad_up_fogcback('name', function(x) {
  fogName = x;
});
jsys.ad_up_cloudcback('name', function(x) {
  cloudName = x;
});

/**----------------VARS--------------------------------------------**/
var jobList = [];

/**----------------JAMSCRIPT FUNCTIONS-----------------------------**/
jasync function changeDirectory(path) {
  process.chdir(path);
}

jasync {namechk} function displayHealth(node) {
  console.log("JCond broadcast is working!");
  console.log("If this prints, it means jcond succeeded and namechk = nodename");
}

jasync function getHealth(node) {
  selfName.broadcast(nodeName);
  console.log("Succesful broadcast");
  selfName.addHook(function(x) {
    console.log("Message: " + x.message);
  });
  //displayHealth(node);
}

jasync {cloudonly} function getAllNodes() {
  console.log("Cloud received node info command...");
  getNodeInfo();
}

/**----------------HELPER FUNCTIONS-------------------------------**/
/**
* Execute a program
*/
//runj progName.jxe --app=progName
jasync{deviceonly} function executeProgram(path) {
  console.log("Executing external JAMProgram...");
    var currPath = process.cwd();
    var progPath = process.cwd() + "/" + path;
    var progName = path.split("/").slice(-1)[0];
    console.log('Program to be executed: ' + progName);
    console.log('Changing directories to program path...');
    process.chdir(progPath);
    console.log('Spawning program...');
    var child = spawn('runj', [progName +'.jxe', '--app=' + progName]);
    var job = {name: progName, pid:child.pid};
    jobList.push(job);
    logJob(0,job);
    console.log("Pushed child: "+ child.pid + " to joblist");
    console.log('Returning to previous directory...');
    process.chdir(currPath);
    child.stdout.on('data',
      function (data) {
          console.log(''+ data);
    });
    execProg(progPath, progName);
}

/**
* Get node info from connected devices.
*/
var getNodeInfo = function(key,entry) {
  var i = 0;
  while(nodeInfo[i] !== undefined && !nodeInfo[i].isEmpty()) {
    console.log(i,'key: ',nodeInfo[i].key);
    // console.log('data',nodeInfo[i].lastData());
    // console.log('value',nodeInfo[i].lastValue());
    i++;
  }
}

/**
* Logging utility for self-logging node-info
*/
function logNodeInfo(index, value){
    setTimeout(function() {
        nodeInfo[index].log(value, function (result) {
            if (!result.status)
                console.log(result.error);
        });
    }, 30);
}

function logJob(index, value) {
    setTimeout(function() {
        jobs[index].log(value, function (result) {
            if (!result.status)
                console.log('yyyy',result.error);
        });
    }, 30);
}

var getJobs = function(key,entry) {
  var i = 0;
  while(jobs[i] !== undefined && !jobs[i].isEmpty()) {
    console.log(jobs[i].lastValue());
    i++;
  }
}

/**
* Generate node info and log it
*/
function generateNodeInfo() {
  var val = {
    nodeName: nodeName,
    nodeType: jsys.type,
    fog: fogName,
    cloud: cloudName
  };
  logNodeInfo(0,val);
}

function listener(raw) {
  if(outputFileName == undefined) {
    console.log("Output file not specified...");
    return;
  }
  fs.writeFile(outputFileName, raw.data, function(err) {
    if(err) return console.log(err);
  });
}

function peek(raw) {
  console.log(raw.data);
}

/**----------------SPECIAL COMMANDS-------------------------------**/
shell
  .command('exec <progPath> [location] [locationNames...]', 'Execute a JAMProgram')
  .action(function(args, callback) {
    if(args.location == undefined) {
      executeProgram(args.progPath);
    }
    if(args.location !== undefined) {
      if(args.location == '@all') {
        console.log("Received @all exec command...Executing at all nodes");
      }
      if(args.location == '@fog') {
        console.log("Received @fog exec command...Executing at fog node");
      }
      if(args.location == '@device') {
        console.log("Received @device exec command...Executing at device node");
      }
      if(args.location == 'pipe') {
        console.log("Piping command received...Building pipe..");
        executeProgram(args.progPath);
        shellOut.start();
        executeProgram(args.locationNames[0]);
      }
      if(args.location == '>') {
        console.log("Output Redirection command received...");
        outputFileName = args. locationNames[0];
        executeProgram(args.progPath);
        var outRedirect = new OutFlow("redirectOut", cout);
        outRedirect.addChannel(listener);
      }
      // fileA => shell.inflow ==> shell.outflow ==> progD.inflow
      if(args.location == '<') {
        console.log("Input redirection command received...");
        executeProgram(args.progPath);
        var inputFilename = args.locationNames[0];
        // var fileInputFlow = Flow.from("fs://" + process.cwd() + "/" + inputFilename)
        //   .foreach((line) => console.log(line));
        var fileInputFlow = Flow.from("fs://" + process.cwd() + "/" + inputFilename)
        var shellFileOutflow = new OutFlow("shellFileOutflow", fileInputFlow);
        shellFileOutflow.addChannel(peek);
      }
    }
    callback();
  });

shell
  .command('nodes [location]', 'Displays node information')
  .action(function(args, callback) {
    if(args.location == undefined) {
      console.log("Displaying node info....");
      getNodeInfo();
    }
    if(args.location == 'all') {
      console.log("Displaying global node info....");
      getGlobalNodeInfo();
    }
    callback();
  });

shell
  .command('roots', 'Displays node hierarchy')
  .action(function(args, callback) {
    console.log("Displaying node hierarchy....");
    console.log("Cloud: ",  cloudName);
    console.log("Fog: ",  fogName);
    callback();
  });

shell
  .command('health <node>', 'Displays node health')
  .action(function(args, callback) {
    //forwardHealthCommand(args.node);
    console.log("Displaying node health....");
    var nodeHealth = {
      uptime: Math.floor(process.uptime())
    };
    console.log(nodeHealth);
    callback();
  });

shell
  .command('jobs [node]', 'Displays jobs started')
  .action(function(args, callback) {
    console.log("Displaying jobs started....");
    getJobs();
    callback();
  });

/**----------------BUILT-IN COMMANDS-------------------------------**/
shell
  .command('jcd <path>', 'Change directories')
  .action(function(args, callback) {
    console.log("Received jcd command..Changing directories....");
    changeDirectory(args.path);
    callback();
  });

shell
  .command('jpwd', 'Print present directory')
  .action(function(args, callback) {
    console.log("Received jpwd command..printing directory....");
    console.log(process.cwd());
    callback();
  });

shell
  .command('jls', 'List directory')
  .action(function(args, callback) {
    console.log("Received jls command..listing directory....");
    fs.readdir(process.cwd(), (err, files) => {
        files.forEach(file => {
        console.log(file);
      });
    })
    callback();
  });

/**----------------CLEANUP-----------------------------------------**/
process.on('exit', (code) => {
    console.log('killing', jobList.length, 'child processes');
    jobList.forEach(function(job) {
      console.log('received', job.pid);
      process.kill(job.pid);
    });
    console.log('Exiting JAMShell...');
});

/**----------------SHELL INIT-------------------------------**/
generateNodeInfo();

shell
  .delimiter('>>')
  .show();
<commit_msg>Input redirection working<commit_after>jdata {
  //cout is the inflow of progB
  cout as inflow of app://progB.outF;
  //shellOut is the outflow of the shell (from progB)
  shellOut as outflow of cout;

  char* selfName as broadcaster;

  //Job logger
  char* jobs as logger; //Job logger
  //TODO: This can be removed when device status is kept from the J Node instead
  struct nodeInfo {
    char* nodeName;
      char* nodeType;
      char* fog;
      char* cloud;
  } nodeInfo as logger; //Device info logger
}

var chance = require('chance').Chance();
var nodeName = chance.first();
console.log("Node name: " + nodeName);

jcond {
  namechk: selfName == nodeInfo.nodeName;
  cloudonly: sys.type == "cloud";
  deviceonly: sys.type =="device";
}

/**----------------REQUIRE-----------------------------------------**/
var shell = require('vorpal')();
var spawn = require('child_process').spawn;
var fs = require('fs');

/**----------------NODE INIT-----------------------------------------**/
var fogName;
var cloudName;
var outputFileName;

nodeInfo.addDatastream(nodeName);
jobs.addDatastream(nodeName);
var selfInfo = {"name": nodeName, "type": jsys.type};

/**----------------ADVERTISE SELF----------------------------------**/
jsys.ad_up('name',selfInfo)

jsys.ad_up_fogcback('name', function(x) {
  fogName = x;
});
jsys.ad_up_cloudcback('name', function(x) {
  cloudName = x;
});

/**----------------VARS--------------------------------------------**/
var jobList = [];

/**----------------JAMSCRIPT FUNCTIONS-----------------------------**/
jasync function changeDirectory(path) {
  process.chdir(path);
}

jasync {namechk} function displayHealth(node) {
  console.log("JCond broadcast is working!");
  console.log("If this prints, it means jcond succeeded and namechk = nodename");
}

jasync function getHealth(node) {
  selfName.broadcast(nodeName);
  console.log("Succesful broadcast");
  selfName.addHook(function(x) {
    console.log("Message: " + x.message);
  });
  //displayHealth(node);
}

jasync {cloudonly} function getAllNodes() {
  console.log("Cloud received node info command...");
  getNodeInfo();
}

/**----------------HELPER FUNCTIONS-------------------------------**/
/**
* Execute a program
*/
//runj progName.jxe --app=progName
jasync{deviceonly} function executeProgram(path) {
  console.log("Executing external JAMProgram...");
    var currPath = process.cwd();
    var progPath = process.cwd() + "/" + path;
    var progName = path.split("/").slice(-1)[0];
    console.log('Program to be executed: ' + progName);
    console.log('Changing directories to program path...');
    process.chdir(progPath);
    console.log('Spawning program...');
    var child = spawn('runj', [progName +'.jxe', '--app=' + progName]);
    var job = {name: progName, pid:child.pid};
    jobList.push(job);
    logJob(0,job);
    console.log("Pushed child: "+ child.pid + " to joblist");
    console.log('Returning to previous directory...');
    process.chdir(currPath);
    child.stdout.on('data',
      function (data) {
          console.log(''+ data);
    });
    execProg(progPath, progName);
}

/**
* Get node info from connected devices.
*/
var getNodeInfo = function(key,entry) {
  var i = 0;
  while(nodeInfo[i] !== undefined && !nodeInfo[i].isEmpty()) {
    console.log(i,'key: ',nodeInfo[i].key);
    // console.log('data',nodeInfo[i].lastData());
    // console.log('value',nodeInfo[i].lastValue());
    i++;
  }
}

/**
* Logging utility for self-logging node-info
*/
function logNodeInfo(index, value){
    setTimeout(function() {
        nodeInfo[index].log(value, function (result) {
            if (!result.status)
                console.log(result.error);
        });
    }, 30);
}

function logJob(index, value) {
    setTimeout(function() {
        jobs[index].log(value, function (result) {
            if (!result.status)
                console.log('yyyy',result.error);
        });
    }, 30);
}

var getJobs = function(key,entry) {
  var i = 0;
  while(jobs[i] !== undefined && !jobs[i].isEmpty()) {
    console.log(jobs[i].lastValue());
    i++;
  }
}

/**
* Generate node info and log it
*/
function generateNodeInfo() {
  var val = {
    nodeName: nodeName,
    nodeType: jsys.type,
    fog: fogName,
    cloud: cloudName
  };
  logNodeInfo(0,val);
}

function listener(raw) {
  if(outputFileName == undefined) {
    console.log("Output file not specified...");
    return;
  }
  fs.writeFile(outputFileName, raw.data, function(err) {
    if(err) return console.log(err);
  });
}

function peek(raw) {
  console.log(raw.data);
}

/**----------------SPECIAL COMMANDS-------------------------------**/
shell
  .command('exec <progPath> [location] [locationNames...]', 'Execute a JAMProgram')
  .action(function(args, callback) {
    if(args.location == undefined) {
      executeProgram(args.progPath);
    }
    if(args.location !== undefined) {
      if(args.location == '@all') {
        console.log("Received @all exec command...Executing at all nodes");
      }
      if(args.location == '@fog') {
        console.log("Received @fog exec command...Executing at fog node");
      }
      if(args.location == '@device') {
        console.log("Received @device exec command...Executing at device node");
      }
      if(args.location == '<>') {
        console.log("Piping command received...Building pipe..");
        executeProgram(args.progPath);
        shellOut.start();
        executeProgram(args.locationNames[0]);
      }
      if(args.location == '>') {
        console.log("Output Redirection command received...");
        outputFileName = args. locationNames[0];
        executeProgram(args.progPath);
        var outRedirect = new OutFlow("redirectOut", cout);
        outRedirect.addChannel(listener);
      }
      // fileA => shell.inflow ==> shell.outflow ==> progD.inflow
      if(args.location == '<') {
        console.log("Input redirection command received...");
        executeProgram(args.progPath);
        var inputFilename = args.locationNames[0];
        var fileInputFlow = Flow.fromFile(process.cwd() + "/" + inputFilename);
        var shellFileOutflow = new OutFlow("shellFileOutflow", fileInputFlow);
        shellFileOutflow.start();
      }
    }
    callback();
  });

shell
  .command('nodes [location]', 'Displays node information')
  .action(function(args, callback) {
    if(args.location == undefined) {
      console.log("Displaying node info....");
      getNodeInfo();
    }
    if(args.location == 'all') {
      console.log("Displaying global node info....");
      getGlobalNodeInfo();
    }
    callback();
  });

shell
  .command('roots', 'Displays node hierarchy')
  .action(function(args, callback) {
    console.log("Displaying node hierarchy....");
    console.log("Cloud: ",  cloudName);
    console.log("Fog: ",  fogName);
    callback();
  });

shell
  .command('health <node>', 'Displays node health')
  .action(function(args, callback) {
    //forwardHealthCommand(args.node);
    console.log("Displaying node health....");
    var nodeHealth = {
      uptime: Math.floor(process.uptime())
    };
    console.log(nodeHealth);
    callback();
  });

shell
  .command('jobs [node]', 'Displays jobs started')
  .action(function(args, callback) {
    console.log("Displaying jobs started....");
    getJobs();
    callback();
  });

/**----------------BUILT-IN COMMANDS-------------------------------**/
shell
  .command('jcd <path>', 'Change directories')
  .action(function(args, callback) {
    console.log("Received jcd command..Changing directories....");
    changeDirectory(args.path);
    callback();
  });

shell
  .command('jpwd', 'Print present directory')
  .action(function(args, callback) {
    console.log("Received jpwd command..printing directory....");
    console.log(process.cwd());
    callback();
  });

shell
  .command('jls', 'List directory')
  .action(function(args, callback) {
    console.log("Received jls command..listing directory....");
    fs.readdir(process.cwd(), (err, files) => {
        files.forEach(file => {
        console.log(file);
      });
    })
    callback();
  });

/**----------------CLEANUP-----------------------------------------**/
process.on('exit', (code) => {
    console.log('killing', jobList.length, 'child processes');
    jobList.forEach(function(job) {
      console.log('received', job.pid);
      process.kill(job.pid);
    });
    console.log('Exiting JAMShell...');
});

/**----------------SHELL INIT-------------------------------**/
generateNodeInfo();

shell
  .delimiter('>>')
  .show();
<|endoftext|>


<commit_before>/* global window, chrome */

(function(global, debug) {
	'use strict';

	var fileCache = {};

	function xhrSrc($http, $window) {
		var directive = {
			restrict: 'A',
			scope: true,
			template: '',
			replace: true,
			link: linkFunction
		};

		return directive;

		function linkFunction(scope, element, attrs) {
			var elt = element[0];

			attrs.$observe('xhrSrc', sourceAttributeObserver);
			attrs.$observe('xhrHref', sourceAttributeObserver);

			function sourceAttributeObserver(source) {
				var cachedFileObject;

				if (source !== undefined) {
					cachedFileObject = retrieve(source);

					if (cachedFileObject !== undefined) {
						// cache hit, use it
						elementAssignResource(elt, cachedFileObject);

						updateDebug();

						return;
					}
					else {
						// not a cache hit, request it
						$http.get(source, {responseType: 'blob'})
							.then(
								function(response) {
									// received response, cache it
									store(source, $window.URL.createObjectURL(response.data));

									// and use it
									elementAssignResource(elt, retrieve(source));

									updateDebug();
								},
								function(result) {
									var data = typeof(result.data) === "object" ? JSON.stringify(result.data) : result.data;
									var resultMessage = 'status code: ' + result.status + ' status: ' + result.statusText + ' data: ' + data;
									throw new Error('Result retrieving source ' + source + ': ' + resultMessage);
								}
							);
					}
				}
			}

			function elementAssignResource(element, resource) {
				if (element.tagName === 'LINK') {
					element.href = resource;
				}
				else if (element.tagName === 'IMG') {
					element.src = resource;
				}
				else {
					throw new Error('xhrSrc directive only supports setting LINK href and IMG src');
				}
			}
		}
	}

	function useChromeStorage() {
		return chrome !== undefined &&
			chrome.storage !== undefined &&
			chrome.storage.local !== undefined;
	}

	function store(key, fileObject) {
		if (useChromeStorage()) {
			storeChromeStorage(key, fileObject);
		}
		else {
			storeFileCacheHash(key, fileObject);
		}
	}

	function storeFileCacheHash(key, fileObject) {
		fileCache[key] = fileObject;
	}

	function storeChromeStorage(key, fileObject) {
		storeFileCacheHash(key, fileObject);
	}

	function retrieve(key) {
		if (useChromeStorage()) {
			return retrieveChromeStorage(key);
		}
		else {
			return retrieveFileCacheHash(key);
		}
	}

	function retrieveFileCacheHash(key) {
		return fileCache[key];
	}

	function retrieveChromeStorage(key) {
		return retrieveFileCacheHash(key);
	}

	if (debug) {
		// put on global scope so we can get time from console
		global.xhrSrcShowTime = function() {
			console.log((global.xhrSrcLastTime - global.xhrSrcFirstTime) / 1000);

			global.xhrSrcItemCount = 0;
			global.xhrSrcFirstTime = global.xhrSrcLastTime = undefined;
		};
	}

	function updateDebug() {
		if (debug) {
			if (global.xhrSrcItemCount === undefined) {
				global.xhrSrcItemCount = 0;
			}

			if (global.xhrSrcItemCount === 0) {
				global.xhrSrcFirstTime = global.xhrSrcLastTime = +(new Date());
				global.xhrSrcItemCount++;
			}
			else {
				global.xhrSrcLastTime = +(new Date());
				global.xhrSrcItemCount++;
			}
		}
	}

	/* --------------------------------------------------------- */

	function xhrArrayBufferSrc($http, $window) {
		var directive = {
			restrict: 'A',
			scope: true,
			template: '',
			replace: true,
			link: linkFunction
		};

		return directive;

		function linkFunction(scope, element, attrs) {
			var elt = element[0];

			attrs.$observe('xhrArrayBufferSrc', sourceAttributeObserver);
			attrs.$observe('xhrArrayBufferHref', sourceAttributeObserver);

			function sourceAttributeObserver(source) {
				if (source !== undefined) {

					$http.get(source, {responseType: 'arraybuffer'})
						.then(
							function(response) {
								var uInt8Array = new Uint8Array(response.data);
								var i = uInt8Array.length;
								var binaryString = new Array(i);
								var imageType, lowercaseSource;

								while (i--)
								{
									binaryString[i] = String.fromCharCode(uInt8Array[i]);
								}
								var data = binaryString.join('');
								var base64 = window.btoa(data);

								if (elt.tagName === 'LINK') {
									elt.href = 'data:text/css;base64,' + base64;
								}
								else if (elt.tagName === 'IMG') {
									lowercaseSource = source.toLowerCase();

									if (lowercaseSource.indexOf('.jpg') !== -1) {
										imageType = 'image/jpg';
									}
									else if (lowercaseSource.indexOf('.gif') !== -1) {
										imageType = 'image/gif';
									}
									else {
										imageType = 'image/png';
									}

									elt.src = 'data:' + imageType + ';base64,' + base64;
								}
								else {
									throw new Error('xhrArrayBufferSrc directive only supports setting LINK href and IMG src');
								}
							},
							function(result) {
								var data = typeof(result.data) === "object" ? JSON.stringify(result.data) : result.data;
								var resultMessage = 'status code: ' + result.status + ' status: ' + result.statusText + ' data: ' + data;
								throw new Error('Result retrieving source ' + source + ': ' + resultMessage);
							}
						);
				}
			}
		}
	}

	angular.module('xhrSrc', []);
	angular.module('xhrSrc')
		.directive('xhrSrc', xhrSrc)
		.directive('xhrHref', xhrSrc);

	angular.module('xhrArrayBufferSrc', []);
	angular.module('xhrArrayBufferSrc')
		.directive('xhrArrayBufferSrc', xhrArrayBufferSrc)
		.directive('xhrArrayBufferHref', xhrArrayBufferSrc);

})(window, true);
<commit_msg>Updated to use chrome.storage.local when its available.  This does not work for storing blobs as chrome.storage is only good at storing standard primitives such as string.  So this does not help for xhr-src (needs to be removed there).  It works great for xhr-array-buffer-src.<commit_after>/* global window, chrome */

(function(global, debug) {
	'use strict';

	var fileCache = {};

	function xhrSrc($http, $window) {
		var directive = {
			restrict: 'A',
			scope: true,
			template: '',
			replace: true,
			link: linkFunction
		};

		return directive;

		function linkFunction(scope, element, attrs) {
			var elt = element[0];

			attrs.$observe('xhrSrc', sourceAttributeObserver);
			attrs.$observe('xhrHref', sourceAttributeObserver);

			function sourceAttributeObserver(source) {
				if (source !== undefined) {
					retrieve(source, function(cachedFileObject) {
						if (cachedFileObject !== undefined) {

							console.log('found in the cache');
							// cache hit, use it
							elementAssignResource(elt, $window.URL.createObjectURL(cachedFileObject));

							updateDebug();

							return;
						}
						else {
							console.log('NOT found in the cache, retrieving from XHR');
							// not a cache hit, request it
							$http.get(source, {responseType: 'blob'})
								.then(
									function(response) {
										// received response, cache it
										store(source, response.data);

										// and use it
										elementAssignResource(elt, $window.URL.createObjectURL(response.data));

										updateDebug();
									},
									function(result) {
										var data = typeof(result.data) === "object" ? JSON.stringify(result.data) : result.data;
										var resultMessage = 'status code: ' + result.status + ' status: ' + result.statusText + ' data: ' + data;
										throw new Error('Result retrieving source ' + source + ': ' + resultMessage);
									}
								);
						}
					});
				}
			}
		}
	}

	function elementAssignResource(element, resource) {
		if (element.tagName === 'LINK') {
			element.href = resource;
		}
		else if (element.tagName === 'IMG') {
			element.src = resource;
		}
		else {
			throw new Error('xhrSrc directive only supports setting LINK href and IMG src');
		}
	}

	function useChromeStorage() {
		return chrome !== undefined &&
			chrome.storage !== undefined &&
			chrome.storage.local !== undefined;
	}

	function store(key, fileObject) {
		if (useChromeStorage()) {
			storeChromeStorage(key, fileObject);
		}
		else {
			storeFileCacheHash(key, fileObject);
		}
	}

	function storeFileCacheHash(key, fileObject) {
		fileCache[key] = fileObject;
	}

	function storeChromeStorage(key, fileObject) {
		var valueToSet = {};
		valueToSet[key] = fileObject;

		chrome.storage.local.set(valueToSet, function () {
			if (chrome.runtime.lastError !== undefined) {
				throw new Error('Error caching file object results: ' + chrome.runtime.lastError.message);
			}
		});
	}

	function retrieve(key, callback) {
		if (useChromeStorage()) {
			retrieveChromeStorage(key, callback);
		}
		else {
			retrieveFileCacheHash(key, callback);
		}
	}

	function retrieveFileCacheHash(key, callback) {
		callback(fileCache[key]);
	}

	function retrieveChromeStorage(key, callback) {
		chrome.storage.local.get(key, function(items) {
			callback(items[key]);
		});
	}

	if (debug) {
		// put on global scope so we can get time from console
		global.xhrSrcShowTime = function() {
			console.log((global.xhrSrcLastTime - global.xhrSrcFirstTime) / 1000);

			global.xhrSrcItemCount = 0;
			global.xhrSrcFirstTime = global.xhrSrcLastTime = undefined;
		};
	}

	function updateDebug() {
		if (debug) {
			if (global.xhrSrcItemCount === undefined) {
				global.xhrSrcItemCount = 0;
			}

			if (global.xhrSrcItemCount === 0) {
				global.xhrSrcFirstTime = global.xhrSrcLastTime = +(new Date());
				global.xhrSrcItemCount++;
			}
			else {
				global.xhrSrcLastTime = +(new Date());
				global.xhrSrcItemCount++;
			}
		}
	}

	/* --------------------------------------------------------- */

	function xhrArrayBufferSrc($http) {
		var directive = {
			restrict: 'A',
			scope: true,
			template: '',
			replace: true,
			link: linkFunction
		};

		return directive;

		function linkFunction(scope, element, attrs) {
			var elt = element[0];

			attrs.$observe('xhrArrayBufferSrc', sourceAttributeObserver);
			attrs.$observe('xhrArrayBufferHref', sourceAttributeObserver);

			function sourceAttributeObserver(source) {
				if (source !== undefined) {

					retrieve(source, function(cachedFileObject) {
						if (cachedFileObject !== undefined) {
							console.log('found in the cache');

							// cache hit, use it
							elementAssignResource(elt, cachedFileObject);

							updateDebug();

							return;
						}
						else {
							console.log('NOT found in the cache, retrieving from XHR');

							$http.get(source, {responseType: 'arraybuffer'})
								.then(
									function(response) {
										var base64 = getBase64Content(response.data),
											dataUrl = getDataUrl(source, elt.tagName, base64);

										// received response, cache it
										store(source, dataUrl);

										elementAssignResource(elt, dataUrl);

										updateDebug();
									},
									function(result) {
										var data = typeof(result.data) === "object" ? JSON.stringify(result.data) : result.data;
										var resultMessage = 'status code: ' + result.status + ' status: ' + result.statusText + ' data: ' + data;
										throw new Error('Result retrieving source ' + source + ': ' + resultMessage);
									}
								);
						}
					});

				}
			}
		}

		function getBase64Content(responseData) {
			var uInt8Array = new Uint8Array(responseData);
			var i = uInt8Array.length;
			var binaryString = new Array(i);

			while (i--)
			{
				binaryString[i] = String.fromCharCode(uInt8Array[i]);
			}
			var data = binaryString.join('');
			return window.btoa(data);
		}

		function getDataUrl(source, tagName, base64Content) {
			var lowercaseSource = source.toLowerCase(),
				imageType;

			if (tagName === 'LINK' && lowercaseSource.indexOf('.css')) {
				return 'data:text/css;base64,' + base64Content;
			}
			else if (tagName === 'IMG') {

				if (lowercaseSource.indexOf('.jpg') !== -1) {
					imageType = 'image/jpg';
				}
				else if (lowercaseSource.indexOf('.gif') !== -1) {
					imageType = 'image/gif';
				}
				else {
					imageType = 'image/png';
				}

				return 'data:' + imageType + ';base64,' + base64Content;
			}

			return '';
		}
	}

	angular.module('xhrSrc', []);
	angular.module('xhrSrc')
		.directive('xhrSrc', xhrSrc)
		.directive('xhrHref', xhrSrc);

	angular.module('xhrArrayBufferSrc', []);
	angular.module('xhrArrayBufferSrc')
		.directive('xhrArrayBufferSrc', xhrArrayBufferSrc)
		.directive('xhrArrayBufferHref', xhrArrayBufferSrc);

})(window, true);
<|endoftext|>


<commit_before>var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = false;
ga.src = 'http://www.youtube.com/player_api';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);

// Stub for JSON object from server
var mediaStatus = {
  videoId: "",
  startSeconds: 0
};

var done = false;
var player;
var playerInstaniated = false;

// Initialize player if user joins room and video is currently playing or serve static message if not
var onYouTubePlayerAPIReady = function () {
  if (!mediaStatus.videoId) {
    serveStaticImg();
  } else {
    createPlayer(mediaStatus.videoId);
  }
};

var setVideoTime = function (time) {
  player.seekTo(time, true);
};

// Serve "No current DJ" Message
var serveStaticImg = function () {
  if ($('#noVideoImg')) {
    $('#noVideoImg').remove();
  }
  if ($('#videoContainer')) {
    $('#videoContainer').append('

No current DJ

Grab some friends and start a playlist!

');
  }
};

// Destroy the player and serve "No current DJ" Message
var removeVideo = function () {
  player.destroy();
  playerInstaniated = false;
  serveStaticImg();
};

// Instantiate player and load mediastatus videoID/start playing video
var createPlayer = function (currentVideoId) {
  playerInstaniated = true;
  player = new YT.Player('videoContainer', {
    // TODO Tweak these or dynamically generate based on page size
    height: '390',
    width: '640',
    videoId: currentVideoId,
    playerVars: {
      controls: 0,
      autoplay: 0,
      disablekb: 1,
      showInfo: 0,
      enablejsapi: 1,
      rel: 1,
      modestbranding: 1
    },
    events: {
      'onReady': onPlayerReady,
      // TODO Temp for testing
      'onStateChange': onPlayerStateChange
    }
  });
};

// Start playing video at MSO's startSeconds time after play loads
var onPlayerReady = function (evt) {
  console.log('heard onPlayerReady', evt);
    setVideoTime(mediaStatus.startSeconds);
};

// TODO - Discuss w/Kyle the format of what client should expect to recieve/message codes and refactor accordingly
// Socket Event Listener for Media Status Object for
socket.on('media status', function (data) {
  console.log("HEARD MEDIA STATUS!!!")
  data.startSeconds = data.startSeconds || 0;
  mediaStatus = data;
  heardNewMediaStatus();
});

// Wrapper for loadVideo/serveStaticImg
var heardNewMediaStatus = function () {
  if (!mediaStatus.videoId) {
    serveStaticImg();
  } else {
    loadVideo(mediaStatus.videoId, mediaStatus.startSeconds);
  }
};

// DEBUGGER FUNCTIONS
// TODO Remove before production
// * * *
var onPlayerStateChange = function (evt) {
  console.log('heard onPlayerStateChange', evt);
  if (evt.data === YT.PlayerState.PLAYING && !done) {
    console.log('Heard state change');
  }
};

var playVideo = function () {
  player.playVideo();
};

var stopVideo = function () {
  player.stopVideo();
};
// * * *
// END DEBUGGER FUNCTIONS

// Main Load Video
// Expects 11 character string (YouTube Video ID) and the starttime of video
var loadVideo = function (videoId, startSeconds) {
  // If player already exists...
  if (playerInstaniated) {
    // Load new player if current video is different from new video or same video is being played again
    if ((player.getVideoData().video_id !== videoId) || mediaStatus.status === 'start') {
      player.loadVideoById(videoId, startSeconds);
    } else {
      // If same video, check if desync is greater than 10 seconds
      console.log("media timer dif: ", mediaStatus.startSeconds - player.getCurrentTime());
      if (mediaStatus.startSeconds - player.getCurrentTime() > 10) {
        setVideoTime(mediaStatus.videoTime);
      }
    }
  // If no player in room, reinstaniate it
  } else {
    playerInstaniated = true;
    player = new YT.Player('videoContainer', {
      // TODO Tweak these or dynamically generate based on page size
      height: '390',
      width: '640',
      videoId: videoId,
      startSeconds: startSeconds,
      playerVars: {
        controls: 0,
        autoplay: 1,
        disablekb: 1,
        showInfo: 0,
        enablejsapi: 1,
        rel: 0,
        modestbranding: 1
      },
      events: {
        'onReady': onPlayerReady,
        // TODO Temp for testing
        'onStateChange': onPlayerStateChange
      }
    });
  }
};<commit_msg>(client) Fix desync<commit_after>var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = false;
ga.src = 'http://www.youtube.com/player_api';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);

// Stub for JSON object from server
var mediaStatus = {
  videoId: "",
  startSeconds: 0
};

var done = false;
var player;
var playerInstaniated = false;

// Initialize player if user joins room and video is currently playing or serve static message if not
var onYouTubePlayerAPIReady = function () {
  if (!mediaStatus.videoId) {
    serveStaticImg();
  } else {
    createPlayer(mediaStatus.videoId);
  }
};

var setVideoTime = function (time) {
  player.seekTo(time, true);
};

// Serve "No current DJ" Message
var serveStaticImg = function () {
  if ($('#noVideoImg')) {
    $('#noVideoImg').remove();
  }
  if ($('#videoContainer')) {
    $('#videoContainer').append('

No current DJ

Grab some friends and start a playlist!

');
  }
};

// Destroy the player and serve "No current DJ" Message
var removeVideo = function () {
  player.destroy();
  playerInstaniated = false;
  serveStaticImg();
};

// Instantiate player and load mediastatus videoID/start playing video
var createPlayer = function (currentVideoId) {
  playerInstaniated = true;
  player = new YT.Player('videoContainer', {
    // TODO Tweak these or dynamically generate based on page size
    height: '390',
    width: '640',
    videoId: currentVideoId,
    playerVars: {
      controls: 0,
      autoplay: 0,
      disablekb: 1,
      showInfo: 0,
      enablejsapi: 1,
      rel: 1,
      modestbranding: 1
    },
    events: {
      'onReady': onPlayerReady,
      // TODO Temp for testing
      'onStateChange': onPlayerStateChange
    }
  });
};

// Start playing video at MSO's startSeconds time after play loads
var onPlayerReady = function (evt) {
  console.log('heard onPlayerReady', evt);
    setVideoTime(mediaStatus.startSeconds);
};

// TODO - Discuss w/Kyle the format of what client should expect to recieve/message codes and refactor accordingly
// Socket Event Listener for Media Status Object for
socket.on('media status', function (data) {
  console.log("HEARD MEDIA STATUS!!!")
  mediaStatus = data;
  heardNewMediaStatus();
});

// Wrapper for loadVideo/serveStaticImg
var heardNewMediaStatus = function () {
  if (!mediaStatus.videoId) {
    serveStaticImg();
  } else {
    loadVideo(mediaStatus.videoId, mediaStatus.startSeconds);
  }
};

// DEBUGGER FUNCTIONS
// TODO Remove before production
// * * *
var onPlayerStateChange = function (evt) {
  console.log('heard onPlayerStateChange', evt);
  if (evt.data === YT.PlayerState.PLAYING && !done) {
    console.log('Heard state change');
  }
};

var playVideo = function () {
  player.playVideo();
};

var stopVideo = function () {
  player.stopVideo();
};
// * * *
// END DEBUGGER FUNCTIONS

// Main Load Video
// Expects 11 character string (YouTube Video ID) and the starttime of video
var loadVideo = function (videoId, startSeconds) {
  // If player already exists...
  if (playerInstaniated) {
    // Load new player if current video is different from new video or same video is being played again
    if ((player.getVideoData().video_id !== videoId) || mediaStatus.status === 'start') {
      player.loadVideoById(videoId, startSeconds);
    } else {
      // If same video, check if desync is greater than 10 seconds
      console.log("media timer dif: ", mediaStatus.startSeconds - player.getCurrentTime());
      if (mediaStatus.startSeconds - player.getCurrentTime() > 10) {
        setVideoTime(startSeconds);
      }
    }
  // If no player in room, reinstaniate it
  } else {
    playerInstaniated = true;
    player = new YT.Player('videoContainer', {
      // TODO Tweak these or dynamically generate based on page size
      height: '390',
      width: '640',
      videoId: videoId,
      startSeconds: startSeconds,
      playerVars: {
        controls: 0,
        autoplay: 1,
        disablekb: 1,
        showInfo: 0,
        enablejsapi: 1,
        rel: 0,
        modestbranding: 1
      },
      events: {
        'onReady': onPlayerReady,
        // TODO Temp for testing
        'onStateChange': onPlayerStateChange
      }
    });
  }
};<|endoftext|>


<commit_before>var calOptions = {
  destCssSelector: ".calHeatMap",
  width: 1147,
  height: 147,
  margins: {top:15.5, right:5.5, bottom:5.5, left:40.5},
  title: "",
  colorScale: d3.scale.quantize().domain([0, 900]).range(d3.range(6)),
  colorScaleSize: 6,
  showMonthGroups: true,
  showMonthContour: true,
  showAllWeekDays: false,
  showLegend: true
};

Template.monthMondayCalHeatmap.onCreated(function() {
  var instance = this;
  instance.firstGo = true;
  calOptions.colorScaleSize = instance.data.colorScaleSize;
});

Template.monthMondayCalHeatmap.onRendered(function() {
  var instance = this;
  if (instance.firstGo){
    calendarHeatMap(calOptions);
    instance.firstGo = false;
  }

  // create reactive (on session vars) subscription
  this.autorun(function(tComp) {
    var selectedJobs = Session.get("SelectedEventsStats") || [];
    var indicatorType = Session.get("SelectedBreakdown") || "rtBreakdown";

    var tzOffset = new Date().getTimezoneOffset(); // We want the aggregation for the user timezone, so we need the client offset with UTC
    clearData();
    //FIXME when paginating: get endPeriod from instanceVar/session
    Meteor.call("compileDailyAgg", indicatorType, selectedJobs, new Date(), tzOffset, function(error, result) {
      if (error) {
        console.log("compileDailyAgg error: " + error);
      }
      else {
        var dat = {data: result, indicatorType: indicatorType};

        var acceptableThreshold = 0;
        switch (indicatorType)
        {
          case "rtBreakdown":
            var jobs = Jobs.find({_id: {$in: selectedJobs}});
            jobs.forEach(function (job) {
              acceptableThreshold += job.ert;
            });
            acceptableThreshold /= jobs.count();
          break;
          case "errorBreakdown":
            acceptableThreshold = defaultErrorThreshold;//@see environment.js
          break;
        }

        calOptions.colorScale = d3.scale.quantize()
          .domain([0, acceptableThreshold*1.5])
          .range(d3.range(calOptions.colorScaleSize));

        updateData(dat.data, dat.indicatorType, calOptions.colorScale);
      }
    });
  });
});

var calendarHeatMap = function(options) {
  //-- formats
  var day     = function(d) { return (d.getDay() + 6) % 7;};
  var week    = d3.time.format("%W");
  var month   = d3.time.format("%b");
  var year    = d3.time.format("%Y");
  var year2   = d3.time.format("%y");
  var date    = d3.time.format("%Y-%m-%d");
  var theDay  = d3.time.format("%A");

  //-- calculated variables
  var width       = options.width - options.margins.left - options.margins.right;
  var height      = options.height - options.margins.top - options.margins.bottom;
  var size        = height/7;

  //-- data manipulation
  var maxDate             = new Date(); //FIXME when implementing pager
  var minDate             = new Date(maxDate.getFullYear()-1, maxDate.getMonth(), 1);
  var minFirstDayOfMonth  = new Date(minDate.getFullYear(), minDate.getMonth(), 1);
  var minWeek             = parseInt(week(minFirstDayOfMonth));
  var minYear             = parseInt(year(minDate));
  var maxYear             = parseInt(year(maxDate));
  var numYears            = maxYear - minYear + 1;

  var dateRangeDays           = d3.time.days(new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate()), maxDate);
  var dateRangeMonths         = d3.time.months(new Date(minDate.getFullYear(), minDate.getMonth(), 1), maxDate);

  var svg = d3.select(options.destCssSelector)
    .append("svg")
      .attr("class", "jobCalHeatMap greenOrangeRedGrad")
      .attr("shape-rendering", "crisp-edges")
      .attr("stroke-linecap","round")
      .attr("stroke-linejoin","round")
      .attr("width", options.width)
      .attr("height", options.height)
      .attr("title", options.title)
    .append("g")
      .attr("class", "calHeatmapGroup")
      .attr("transform", "translate("+options.margins.left+", " + options.margins.top + ")")
  ;

  //-- legend
  var lgdSvg = d3.select(options.destCssSelector)
    .append("svg")
      .attr("class", "jobCalHeatMapLegend greenOrangeRedGrad")
      .attr("shape-rendering", "crisp-edges")
      .attr("stroke-linecap","round")
      .attr("stroke-linejoin","round")
      .attr("width", options.width)
      .attr("height", size)
    .append("g")
      .attr("class", "calHeatmapLegendGroup")
      //.attr("transform", "translate("+(options.width - options.margins.right - (size*calOptions.colorScaleSize)) +", " + (options.height - options.margins.bottom - size) + ")")
  ;

  lgdSvg.selectAll("rect")
    .data(d3.range(options.colorScaleSize))
    .enter()
      .append("rect")
      .attr("class", function(d, i) {return "day lgd c" + i; })
      .attr("width", size)
      .attr("height", size)
      .attr("x", function(d, i) { return i * size; })
      .attr("y", 0)
      .attr("transform", "translate(" + ( options.margins.left) +", 0)")
  ;

  //-- title label
  var calJobLabel =
  svg
    .append("text")
    .attr("class", "jobName")
    .attr("transform", "translate(-" + (options.margins.left/9*7) + "," + (height/2) + ")rotate(-90)")
    .attr("text-anchor", "middle")
    .text(options.title)
  ;
  //-- year label
  var calYearLabel =
  svg
    .append("text")
    .attr("class", "yearLabel")
    .attr("transform", "translate(-" + (options.margins.left/9*4) + "," + (height/2) + ")rotate(-90)")
    .attr("text-anchor", "middle")
    .text(function(d) { return year(minDate) + "-" + year(maxDate); })
  ;

  //-- week days label
  var weekDays = ["M", "T", "W", "T", "F", "S", "S"];
  var calWeekDayLabels =
  svg
    .append("g")
    .attr("class", "jobWeekDays")
    .selectAll("text")
    .data(function(d) {return weekDays;})
    .enter()
      .append("text")
      .attr("x", -options.margins.left/9*2)
      .attr("y", function(d, i) { return i*size+options.margins.top;})
      .attr("class", "dayName")
      .attr("text-anchor", "middle")
      .text(function(d, i) {return options.showAllWeekDays?d:(i%2 === 0?d:"");})
  ;
  //weeks for each years
  var calDays =
  svg
    .append("g")
    .attr("class", "jobDays")
    .selectAll("rect")
      .data(dateRangeDays)
      .enter()
        .append("rect")
        .attr("class", "day")
        .attr("theDate", function(d) { return date(d);})
        .attr("width", size)
        .attr("height", size)
        .attr("x", function(d) {
          var yearDecal = year(d) - year(minDate);
          return (week(d)-minWeek + 52*yearDecal)  * size;
        })
        .attr("y", function(d) {return day(d) * size;})
  ;

  if (options.showMonthGroups) {
    //months for each years
    var calMonthLabels =
      svg
      .append("g").attr("class", "jobMonths")
      .selectAll(".month")
      .data(dateRangeMonths)
      .enter()
        .append("text")
        .attr("x", function(d, i) {
          var yearDecal = year(d) - year(minDate);
          return  (week(d)-minWeek + 52*yearDecal)  * size;
        })
        .attr("y", -5)
        .attr("class", "monthName")
        .text(function(d) {
          var m = month(d);
          return m === 'Jan'?month(d) + "'" + year2(d):m;
        })
      ;

    if (options.showMonthContour) {
      //-- months contour path
      var calMonthContours =
      svg.select(".jobMonths")
      .selectAll(".monthPath")
      .data(dateRangeMonths)
      .enter()
      .append("path")
      .attr("class", "monthPath")
      .attr("d", monthPath)
      ;
    }
  }

  function monthPath(t0) {
    var yearDecal = year(t0) - year(minDate);
    var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0);
    var w0 = +(week(t0)-minWeek + 52*yearDecal);
    var w1 = +(week(t1)-minWeek + 52*yearDecal);
    var d0 = +day(t0),
        d1 = +day(t1);
    return "M" + (w0 + 1) * size + "," + d0 * size
    + "H" + w0 * size + "V" + 7 * size
    + "H" + w1 * size + "V" + (d1 + 1) * size
    + "H" + (w1 + 1) * size + "V" + 0
    + "H" + (w0 + 1) * size + "Z";
  }
}

function updateData(dataInput, indicatorType, colorScale) {
  var date = d3.time.format("%Y-%m-%d");
  var svg = d3.select("svg g.calHeatmapGroup");

  var tipRT = d3.tip().attr("class", "d3-tip").html(function(d) {
    var obj = dataInput[date(d)];
    return typeof obj === 'undefined'?date(d): '
' + date(d) + "
" + obj.toFixed(0) + "ms";
  });
  svg.call(tipRT);
  var tipError = d3.tip().attr("class", "d3-tip").html(function(d) {
    var obj = dataInput[date(d)];
    return typeof obj === 'undefined'?date(d): '
' + date(d) + "
" + obj.toFixed(2) + " %
";
  });
  svg.call(tipError);

  svg.selectAll("g.jobDays .day")
    .filter(function(d) {
      return date(d) in dataInput;
    })
    .attr("class", function(d) {
      var obj = dataInput[date(d)];
      return "day c" + colorScale(obj);
    })
    .on('mouseover', indicatorType === "rtBreakdown"?tipRT.show:tipError.show)
    .on('mouseout', function() {tipRT.hide();tipError.hide();})
  ;

  //-- legend
  var lgdSvg = d3.select("svg g.calHeatmapLegendGroup");

  var tipLgd = d3.tip().attr("class", "d3-tip").html(function(d, i) {
    var range = colorScale.invertExtent(i);
    var label = indicatorType === "rtBreakdown"?"ms":"%";
    var colorscaleRange = colorScale.range();

    if (colorscaleRange.length - 1 === i) {
      return "≥" + range[0] + label;
    }
    else{
      return "From " + range[0] + label + " to " +  range[1] + label;
    }
  });
  lgdSvg.call(tipLgd);

  lgdSvg.selectAll(".lgd")
    .attr("range", function(d, i) {
      var range = colorScale.invertExtent(i);
      var label = indicatorType === "rtBreakdown"?"ms":"%";
      var colorscaleRange = colorScale.range();
      var toLabel = colorscaleRange.length - 1 === i ? "∞" : range[1] + label;
      return "From " + range[0] + label + " to " + toLabel;
    })
    .on('mouseover', tipLgd.show)
    .on('mouseout', tipLgd.hide)
  ;
}

function clearData(){
  var svg = d3.select("svg g.calHeatmapGroup");
  svg.selectAll("g.jobDays .day")
    .attr("class", "day")
    .on('mouseover', null)
    .on('mouseout', null)
  ;
}
<commit_msg>Fix issue with d3 tooltip<commit_after>var calOptions = {
  destCssSelector: ".calHeatMap",
  width: 1147,
  height: 147,
  margins: {top:15.5, right:5.5, bottom:5.5, left:40.5},
  title: "",
  colorScale: d3.scale.quantize().domain([0, 900]).range(d3.range(6)),
  colorScaleSize: 6,
  showMonthGroups: true,
  showMonthContour: true,
  showAllWeekDays: false,
  showLegend: true
};

Template.monthMondayCalHeatmap.onCreated(function() {
  var instance = this;
  instance.firstGo = true;
  calOptions.colorScaleSize = instance.data.colorScaleSize;
});

Template.monthMondayCalHeatmap.onRendered(function() {
  var instance = this;
  if (instance.firstGo){
    calendarHeatMap(calOptions);
    instance.firstGo = false;
  }

  // create reactive (on session vars) subscription
  this.autorun(function(tComp) {
    var selectedJobs = Session.get("SelectedEventsStats") || [];
    var indicatorType = Session.get("SelectedBreakdown") || "rtBreakdown";

    var tzOffset = new Date().getTimezoneOffset(); // We want the aggregation for the user timezone, so we need the client offset with UTC
    clearData();
    //FIXME when paginating: get endPeriod from instanceVar/session
    Meteor.call("compileDailyAgg", indicatorType, selectedJobs, new Date(), tzOffset, function(error, result) {
      if (error) {
        console.log("compileDailyAgg error: " + error);
      }
      else {
        var dat = {data: result, indicatorType: indicatorType};

        var acceptableThreshold = 0;
        switch (indicatorType)
        {
          case "rtBreakdown":
            var jobs = Jobs.find({_id: {$in: selectedJobs}});
            jobs.forEach(function (job) {
              acceptableThreshold += job.ert;
            });
            acceptableThreshold /= jobs.count();
          break;
          case "errorBreakdown":
            acceptableThreshold = defaultErrorThreshold;//@see environment.js
          break;
        }

        calOptions.colorScale = d3.scale.quantize()
          .domain([0, acceptableThreshold*1.5])
          .range(d3.range(calOptions.colorScaleSize));

        updateData(dat.data, dat.indicatorType, calOptions.colorScale);
      }
    });
  });
});

var calendarHeatMap = function(options) {
  //-- formats
  var day     = function(d) { return (d.getDay() + 6) % 7;};
  var week    = d3.time.format("%W");
  var month   = d3.time.format("%b");
  var year    = d3.time.format("%Y");
  var year2   = d3.time.format("%y");
  var date    = d3.time.format("%Y-%m-%d");
  var theDay  = d3.time.format("%A");

  //-- calculated variables
  var width       = options.width - options.margins.left - options.margins.right;
  var height      = options.height - options.margins.top - options.margins.bottom;
  var size        = height/7;

  //-- data manipulation
  var maxDate             = new Date(); //FIXME when implementing pager
  var minDate             = new Date(maxDate.getFullYear()-1, maxDate.getMonth(), 1);
  var minFirstDayOfMonth  = new Date(minDate.getFullYear(), minDate.getMonth(), 1);
  var minWeek             = parseInt(week(minFirstDayOfMonth));
  var minYear             = parseInt(year(minDate));
  var maxYear             = parseInt(year(maxDate));
  var numYears            = maxYear - minYear + 1;

  var dateRangeDays           = d3.time.days(new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate()), maxDate);
  var dateRangeMonths         = d3.time.months(new Date(minDate.getFullYear(), minDate.getMonth(), 1), maxDate);

  var svg = d3.select(options.destCssSelector)
    .append("svg")
      .attr("class", "jobCalHeatMap greenOrangeRedGrad")
      .attr("shape-rendering", "crisp-edges")
      .attr("stroke-linecap","round")
      .attr("stroke-linejoin","round")
      .attr("width", options.width)
      .attr("height", options.height)
      .attr("title", options.title)
    .append("g")
      .attr("class", "calHeatmapGroup")
      .attr("transform", "translate("+options.margins.left+", " + options.margins.top + ")")
  ;

  //-- legend
  var lgdSvg = d3.select(options.destCssSelector)
    .append("svg")
      .attr("class", "jobCalHeatMapLegend greenOrangeRedGrad")
      .attr("shape-rendering", "crisp-edges")
      .attr("stroke-linecap","round")
      .attr("stroke-linejoin","round")
      .attr("width", options.width)
      .attr("height", size)
    .append("g")
      .attr("class", "calHeatmapLegendGroup")
      //.attr("transform", "translate("+(options.width - options.margins.right - (size*calOptions.colorScaleSize)) +", " + (options.height - options.margins.bottom - size) + ")")
  ;

  lgdSvg.selectAll("rect")
    .data(d3.range(options.colorScaleSize))
    .enter()
      .append("rect")
      .attr("class", function(d, i) {return "day lgd c" + i; })
      .attr("width", size)
      .attr("height", size)
      .attr("x", function(d, i) { return i * size; })
      .attr("y", 0)
      .attr("transform", "translate(" + ( options.margins.left) +", 0)")
  ;

  //-- title label
  var calJobLabel =
  svg
    .append("text")
    .attr("class", "jobName")
    .attr("transform", "translate(-" + (options.margins.left/9*7) + "," + (height/2) + ")rotate(-90)")
    .attr("text-anchor", "middle")
    .text(options.title)
  ;
  //-- year label
  var calYearLabel =
  svg
    .append("text")
    .attr("class", "yearLabel")
    .attr("transform", "translate(-" + (options.margins.left/9*4) + "," + (height/2) + ")rotate(-90)")
    .attr("text-anchor", "middle")
    .text(function(d) { return year(minDate) + "-" + year(maxDate); })
  ;

  //-- week days label
  var weekDays = ["M", "T", "W", "T", "F", "S", "S"];
  var calWeekDayLabels =
  svg
    .append("g")
    .attr("class", "jobWeekDays")
    .selectAll("text")
    .data(function(d) {return weekDays;})
    .enter()
      .append("text")
      .attr("x", -options.margins.left/9*2)
      .attr("y", function(d, i) { return i*size+options.margins.top;})
      .attr("class", "dayName")
      .attr("text-anchor", "middle")
      .text(function(d, i) {return options.showAllWeekDays?d:(i%2 === 0?d:"");})
  ;
  //weeks for each years
  var calDays =
  svg
    .append("g")
    .attr("class", "jobDays")
    .selectAll("rect")
      .data(dateRangeDays)
      .enter()
        .append("rect")
        .attr("class", "day")
        .attr("theDate", function(d) { return date(d);})
        .attr("width", size)
        .attr("height", size)
        .attr("x", function(d) {
          var yearDecal = year(d) - year(minDate);
          return (week(d)-minWeek + 52*yearDecal)  * size;
        })
        .attr("y", function(d) {return day(d) * size;})
  ;

  if (options.showMonthGroups) {
    //months for each years
    var calMonthLabels =
      svg
      .append("g").attr("class", "jobMonths")
      .selectAll(".month")
      .data(dateRangeMonths)
      .enter()
        .append("text")
        .attr("x", function(d, i) {
          var yearDecal = year(d) - year(minDate);
          return  (week(d)-minWeek + 52*yearDecal)  * size;
        })
        .attr("y", -5)
        .attr("class", "monthName")
        .text(function(d) {
          var m = month(d);
          return m === 'Jan'?month(d) + "'" + year2(d):m;
        })
      ;

    if (options.showMonthContour) {
      //-- months contour path
      var calMonthContours =
      svg.select(".jobMonths")
      .selectAll(".monthPath")
      .data(dateRangeMonths)
      .enter()
      .append("path")
      .attr("class", "monthPath")
      .attr("d", monthPath)
      ;
    }
  }

  function monthPath(t0) {
    var yearDecal = year(t0) - year(minDate);
    var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0);
    var w0 = +(week(t0)-minWeek + 52*yearDecal);
    var w1 = +(week(t1)-minWeek + 52*yearDecal);
    var d0 = +day(t0),
        d1 = +day(t1);
    return "M" + (w0 + 1) * size + "," + d0 * size
    + "H" + w0 * size + "V" + 7 * size
    + "H" + w1 * size + "V" + (d1 + 1) * size
    + "H" + (w1 + 1) * size + "V" + 0
    + "H" + (w0 + 1) * size + "Z";
  }
}

function updateData(dataInput, indicatorType, colorScale) {
  var date = d3.time.format("%Y-%m-%d");
  var svg = d3.select("svg g.calHeatmapGroup");

  var tipRT = d3.tip().attr("class", "d3-tip").html(function(d) {
    var obj = dataInput[date(d)];
    return typeof obj === 'undefined'?date(d): '
' + date(d) + "
" + obj.toFixed(0) + "ms";
  });
  svg.call(tipRT);
  var tipError = d3.tip().attr("class", "d3-tip").html(function(d) {
    var obj = dataInput[date(d)];
    return typeof obj === 'undefined'?date(d): '
' + date(d) + "
" + obj.toFixed(2) + " %
";
  });
  svg.call(tipError);

  svg.selectAll("g.jobDays .day")
    .filter(function(d) {
      return date(d) in dataInput;
    })
    .attr("class", function(d) {
      var obj = dataInput[date(d)];
      return "day c" + colorScale(obj);
    })
    .on('mouseover', indicatorType === "rtBreakdown"?tipRT.show:tipError.show)
    .on('mouseout', function() {tipRT.hide;tipError.hide;})
  ;

  //-- legend
  var lgdSvg = d3.select("svg g.calHeatmapLegendGroup");

  var tipLgd = d3.tip().attr("class", "d3-tip").html(function(d, i) {
    var range = colorScale.invertExtent(i);
    var label = indicatorType === "rtBreakdown"?"ms":"%";
    var colorscaleRange = colorScale.range();

    if (colorscaleRange.length - 1 === i) {
      return "≥" + range[0] + label;
    }
    else{
      return "From " + range[0] + label + " to " +  range[1] + label;
    }
  });
  lgdSvg.call(tipLgd);

  lgdSvg.selectAll(".lgd")
    .attr("range", function(d, i) {
      var range = colorScale.invertExtent(i);
      var label = indicatorType === "rtBreakdown"?"ms":"%";
      var colorscaleRange = colorScale.range();
      var toLabel = colorscaleRange.length - 1 === i ? "∞" : range[1] + label;
      return "From " + range[0] + label + " to " + toLabel;
    })
    .on('mouseover', tipLgd.show)
    .on('mouseout', tipLgd.hide)
  ;
}

function clearData(){
  var svg = d3.select("svg g.calHeatmapGroup");
  svg.selectAll("g.jobDays .day")
    .attr("class", "day")
    .on('mouseover', null)
    .on('mouseout', null)
  ;
  $(".d3-tip.n").remove();
}
<|endoftext|>


<commit_before>export default {
  widget: 'traseCommodities',
  title: 'Commodities flow from {location}',
  large: true,
  categories: ['land-use'],
  types: ['country'],
  admins: ['adm0'],
  options: {
    startYears: true,
    endYears: true,
    units: ['t', '%'],
    commodities: true
  },
  dataType: 'trase',
  colors: 'extent',
  metaKey: 'widget_trase',
  sortOrder: {
    landUse: 1
  },
  sentence:
    'According to TRASE, from {startYear} to {endYear}, the top sourcing country for {commodity} was {source}, with a trade volume of {volume}, representing {percentage} of total {location} exports.',
  whitelists: {
    adm0: ['BRA', 'PRY', 'COL', 'PER', 'IDN', 'ECU', 'BOL']
  }
};
<commit_msg>update trase sentence to use trade<commit_after>export default {
  widget: 'traseCommodities',
  title: 'Commodities flow from {location}',
  large: true,
  categories: ['land-use'],
  types: ['country'],
  admins: ['adm0'],
  options: {
    startYears: true,
    endYears: true,
    units: ['t', '%'],
    commodities: true
  },
  dataType: 'trase',
  colors: 'extent',
  metaKey: 'widget_trase',
  sortOrder: {
    landUse: 1
  },
  sentence:
    'According to TRASE, from {startYear} to {endYear}, the top sourcing country for {commodity}, including domestic markets, was {source}. The total trade volume was {volume}, which represented {percentage} of total {location} trade.',
  whitelists: {
    adm0: ['BRA', 'PRY', 'COL', 'PER', 'IDN', 'ECU', 'BOL']
  }
};
<|endoftext|>


<commit_before>Imcms.define(
    "imcms-image-editor-left-side-builder",
    [
        "imcms-bem-builder", "imcms-components-builder", "imcms-i18n-texts", "jquery",
        "imcms-image-crop-angles", "imcms-image-cropping-elements", "imcms-image-rotate", "imcms-image-resize",
        "imcms-image-edit-size-controls"
    ],

    function (BEM, components, texts, $, croppingAngles, cropElements, imageRotate, imageResize, imageEditSizeControls) {
        texts = texts.editors.image;

        function buildPreviewImageArea(imageDataContainers) {
            var previewImageAreaBEM = new BEM({
                block: "imcms-preview-img-area",
                elements: {
                    "container": "imcms-preview-img-container",
                    "img": "imcms-preview-img"
                }
            });

            imageDataContainers.$previewImgContainer = previewImageAreaBEM.buildElement("container", "<div>");
            imageDataContainers.$previewImg = previewImageAreaBEM.buildElement("img", "<img>");
            imageDataContainers.$previewImg.appendTo(imageDataContainers.$previewImgContainer);

            return previewImageAreaBEM.buildBlock("<div>", [
                {"container": imageDataContainers.$previewImgContainer}
            ]);
        }

        function buildEditableImageArea(imageDataContainers) {
            var editableImgAreaBEM = new BEM({
                block: "imcms-editable-img-area",
                elements: {
                    "img": "imcms-editable-img",
                    "layout": "",
                    "crop-area": "imcms-crop-area",
                    "angle": "imcms-angle"
                }
            });

            imageDataContainers.$shadow = editableImgAreaBEM.buildElement("layout", "<div>");

            return editableImgAreaBEM.buildBlock("<div>", [
                {"img": cropElements.buildImage()},
                {"layout": imageDataContainers.$shadow},
                {"crop-area": cropElements.buildCropArea()},
                {"angle": croppingAngles.topLeft.buildAngle()},
                {"angle": croppingAngles.topRight.buildAngle()},
                {"angle": croppingAngles.bottomRight.buildAngle()},
                {"angle": croppingAngles.bottomLeft.buildAngle()}
            ]);
        }

        function zoom(zoomCoefficient, imageDataContainers) {
            var newHeight = ~~(cropElements.$image.height() * zoomCoefficient),
                newWidth = ~~(cropElements.$image.width() * zoomCoefficient),
                newCropAreaHeight = ~~(cropElements.$cropArea.height() * zoomCoefficient),
                newCropAreaWeight = ~~(cropElements.$cropArea.width() * zoomCoefficient),
                newCropAreaLeft = ~~(cropElements.$cropArea.position().left * zoomCoefficient),
                newCropAreaTop = ~~(cropElements.$cropArea.position().top * zoomCoefficient)
            ;

            var borderSize = croppingAngles.getBorderSize();

            if (newCropAreaLeft <= borderSize) {
                newCropAreaLeft = borderSize;
            }

            if (newCropAreaTop <= borderSize) {
                newCropAreaTop = borderSize;
            }

            imageResize.resize({
                    image: {
                        width: newWidth,
                        height: newHeight
                    },
                    cropArea: {
                        height: newCropAreaHeight,
                        width: newCropAreaWeight,
                        top: newCropAreaTop,
                        left: newCropAreaLeft
                    }
                },
                imageDataContainers,
                imageRotate.isProportionsInverted()
            );
            var heightControlInput = imageDataContainers.$heightControlInput.getInput();
            var widthControlInput = imageDataContainers.$widthControlInput.getInput();

            heightControlInput.val(newHeight);
            widthControlInput.val(newWidth);
        }

        function zoomPlus() {
            zoom(1.1, this);
        }

        function zoomMinus() {
            zoom(0.9, this);
        }

        function zoomContain(imageDataContainers, $editableImageArea) {
            // fixme: save proportions! now image becomes just as editable area
            // only one side should be as area's side and one as needed to save proportions
            var proportionsInverted = imageRotate.isProportionsInverted();
            var newHeight = $editableImageArea.height(),
                newWidth = $editableImageArea.width(),
                newCropAreaHeight = (proportionsInverted)
                    ? $editableImageArea.width() : $editableImageArea.height(),
                newCropAreaWeight = (proportionsInverted)
                    ? $editableImageArea.height() : $editableImageArea.width(),
                newCropAreaLeft = croppingAngles.getBorderSize(),
                newCropAreaTop = croppingAngles.getBorderSize(),
                twiceAngleBorderSize = croppingAngles.getDoubleBorderSize()
            ;

            imageResize.resize({
                    image: {
                        width: newWidth - twiceAngleBorderSize,
                        height: newHeight - twiceAngleBorderSize
                    },
                    cropArea: {
                        height: newCropAreaHeight - twiceAngleBorderSize,
                        width: newCropAreaWeight - twiceAngleBorderSize,
                        top: newCropAreaTop,
                        left: newCropAreaLeft
                    }
                },
                imageDataContainers,
                proportionsInverted
            );
            var heightControlInput = imageDataContainers.$heightControlInput.getInput();
            var widthControlInput = imageDataContainers.$widthControlInput.getInput();

            heightControlInput.val(newHeight);
            widthControlInput.val(newWidth);
        }

        function revertImageChanges(imageDataContainers) {
            imageRotate.rotateImage("NORTH");

            var newWidth = imageDataContainers.original.width;
            var newHeight = imageDataContainers.original.height;

            imageResize.resize({
                    image: {
                        width: newWidth,
                        height: newHeight
                    },
                    cropArea: {
                        height: newHeight,
                        width: newWidth,
                        top: croppingAngles.getBorderSize(),
                        left: croppingAngles.getBorderSize()
                    }
                },
                imageDataContainers,
                false
            );

            imageDataContainers.$heightControlInput.getInput().val(newHeight);
            imageDataContainers.$widthControlInput.getInput().val(newWidth);
        }

        function buildRevertButton(imageDataContainers) {
            return components.buttons.negativeButton({
                text: "Revert",
                click: function () {
                    revertImageChanges(imageDataContainers);
                }
            })
        }

        function buildScaleAndRotateControls(imageDataContainers, $editableImageArea) {
            imageRotate.setDataContainers(imageDataContainers);

            return new BEM({
                block: "imcms-edit-image",
                elements: {
                    "button": [
                        components.buttons.zoomPlusButton({click: zoomPlus.bind(imageDataContainers)}),
                        components.buttons.zoomMinusButton({click: zoomMinus.bind(imageDataContainers)}),
                        components.buttons.zoomContainButton({
                            click: function () {
                                zoomContain(imageDataContainers, $editableImageArea);
                            }
                        }),
                        components.buttons.rotateLeftButton({click: imageRotate.rotateLeft}),
                        components.buttons.rotateRightButton({click: imageRotate.rotateRight})
                    ]
                }
            }).buildBlockStructure("<div>");
        }

        function buildSwitchViewControls(toggleImgArea, imageDataContainers) {
            var $preview = components.texts.titleText("<div>", texts.preview, {
                "data-tab": "prev",
                click: toggleImgArea
            });
            var $origin = components.texts.titleText("<div>", texts.original, {
                "data-tab": "origin",
                click: toggleImgArea
            });
            $origin.modifiers = ["active"];

            imageDataContainers.$tabOriginal = $origin;

            return new BEM({
                block: "imcms-editable-img-control-tabs",
                elements: {
                    "tab": [$preview, $origin]
                }
            }).buildBlockStructure("<div>");
        }

        function buildBottomPanel(toggleImgArea, imageDataContainers, $editableImageArea) {
            return new BEM({
                block: "imcms-editable-img-controls",
                elements: {
                    "control-size": imageEditSizeControls.buildEditSizeControls(imageDataContainers),
                    "control-scale-n-rotate": buildScaleAndRotateControls(imageDataContainers, $editableImageArea),
                    "image-revert": buildRevertButton(imageDataContainers),
                    "control-view": buildSwitchViewControls(toggleImgArea, imageDataContainers)
                }
            }).buildBlockStructure("<div>");
        }

        return {
            build: function (opts) {
                opts.imageDataContainers.$editableImageArea = buildEditableImageArea(opts.imageDataContainers);
                var $previewImageArea = buildPreviewImageArea(opts.imageDataContainers);

                opts.imageDataContainers.$bottomPanel = buildBottomPanel(
                    opts.toggleImgArea, opts.imageDataContainers, opts.imageDataContainers.$editableImageArea
                );

                return $("<div>").append(
                    opts.imageDataContainers.$editableImageArea,
                    $previewImageArea,
                    opts.imageDataContainers.$bottomPanel
                );
            }
        };
    }
);
<commit_msg>IMCMS-299 - Original - button in image editor: - Cleanup.<commit_after>Imcms.define(
    "imcms-image-editor-left-side-builder",
    [
        "imcms-bem-builder", "imcms-components-builder", "imcms-i18n-texts", "jquery",
        "imcms-image-crop-angles", "imcms-image-cropping-elements", "imcms-image-rotate", "imcms-image-resize",
        "imcms-image-edit-size-controls"
    ],

    function (BEM, components, texts, $, croppingAngles, cropElements, imageRotate, imageResize, imageEditSizeControls) {
        texts = texts.editors.image;

        function buildPreviewImageArea(imageDataContainers) {
            var previewImageAreaBEM = new BEM({
                block: "imcms-preview-img-area",
                elements: {
                    "container": "imcms-preview-img-container",
                    "img": "imcms-preview-img"
                }
            });

            imageDataContainers.$previewImgContainer = previewImageAreaBEM.buildElement("container", "<div>");
            imageDataContainers.$previewImg = previewImageAreaBEM.buildElement("img", "<img>");
            imageDataContainers.$previewImg.appendTo(imageDataContainers.$previewImgContainer);

            return previewImageAreaBEM.buildBlock("<div>", [
                {"container": imageDataContainers.$previewImgContainer}
            ]);
        }

        function buildEditableImageArea(imageDataContainers) {
            var editableImgAreaBEM = new BEM({
                block: "imcms-editable-img-area",
                elements: {
                    "img": "imcms-editable-img",
                    "layout": "",
                    "crop-area": "imcms-crop-area",
                    "angle": "imcms-angle"
                }
            });

            imageDataContainers.$shadow = editableImgAreaBEM.buildElement("layout", "<div>");

            return editableImgAreaBEM.buildBlock("<div>", [
                {"img": cropElements.buildImage()},
                {"layout": imageDataContainers.$shadow},
                {"crop-area": cropElements.buildCropArea()},
                {"angle": croppingAngles.topLeft.buildAngle()},
                {"angle": croppingAngles.topRight.buildAngle()},
                {"angle": croppingAngles.bottomRight.buildAngle()},
                {"angle": croppingAngles.bottomLeft.buildAngle()}
            ]);
        }

        function resizeImage(opts) {
            imageResize.resize(opts, opts.imageDataContainers, false);
            opts.imageDataContainers.$heightControlInput.getInput().val(opts.image.height);
            opts.imageDataContainers.$widthControlInput.getInput().val(opts.image.width);
        }

        function zoom(zoomCoefficient, imageDataContainers) {
            var newHeight = ~~(cropElements.$image.height() * zoomCoefficient),
                newWidth = ~~(cropElements.$image.width() * zoomCoefficient),
                newCropAreaHeight = ~~(cropElements.$cropArea.height() * zoomCoefficient),
                newCropAreaWeight = ~~(cropElements.$cropArea.width() * zoomCoefficient),
                newCropAreaLeft = ~~(cropElements.$cropArea.position().left * zoomCoefficient),
                newCropAreaTop = ~~(cropElements.$cropArea.position().top * zoomCoefficient)
            ;

            var borderSize = croppingAngles.getBorderSize();

            if (newCropAreaLeft <= borderSize) {
                newCropAreaLeft = borderSize;
            }

            if (newCropAreaTop <= borderSize) {
                newCropAreaTop = borderSize;
            }

            resizeImage({
                imageDataContainers: imageDataContainers,
                isProportionsInverted: imageRotate.isProportionsInverted(),
                image: {
                    width: newWidth,
                    height: newHeight
                },
                cropArea: {
                    height: newCropAreaHeight,
                    width: newCropAreaWeight,
                    top: newCropAreaTop,
                    left: newCropAreaLeft
                }
            });
        }

        function zoomPlus() {
            zoom(1.1, this);
        }

        function zoomMinus() {
            zoom(0.9, this);
        }

        function zoomContain(imageDataContainers, $editableImageArea) {
            // fixme: save proportions! now image becomes just as editable area
            // only one side should be as area's side and one as needed to save proportions
            var proportionsInverted = imageRotate.isProportionsInverted();
            var newHeight = $editableImageArea.height(),
                newWidth = $editableImageArea.width(),
                newCropAreaHeight = (proportionsInverted)
                    ? $editableImageArea.width() : $editableImageArea.height(),
                newCropAreaWeight = (proportionsInverted)
                    ? $editableImageArea.height() : $editableImageArea.width(),
                newCropAreaLeft = croppingAngles.getBorderSize(),
                newCropAreaTop = croppingAngles.getBorderSize(),
                twiceAngleBorderSize = croppingAngles.getDoubleBorderSize()
            ;

            resizeImage({
                imageDataContainers: imageDataContainers,
                isProportionsInverted: proportionsInverted,
                image: {
                    width: newWidth - twiceAngleBorderSize,
                    height: newHeight - twiceAngleBorderSize
                },
                cropArea: {
                    height: newCropAreaHeight - twiceAngleBorderSize,
                    width: newCropAreaWeight - twiceAngleBorderSize,
                    top: newCropAreaTop,
                    left: newCropAreaLeft
                }
            });
        }

        function revertImageChanges(imageDataContainers) {
            imageRotate.rotateImage("NORTH");

            var newWidth = imageDataContainers.original.width;
            var newHeight = imageDataContainers.original.height;

            resizeImage({
                imageDataContainers: imageDataContainers,
                isProportionsInverted: false,
                image: {
                    width: newWidth,
                    height: newHeight
                },
                cropArea: {
                    height: newHeight,
                    width: newWidth,
                    top: croppingAngles.getBorderSize(),
                    left: croppingAngles.getBorderSize()
                }
            });
        }

        function buildRevertButton(imageDataContainers) {
            return components.buttons.negativeButton({
                text: "Revert",
                click: function () {
                    revertImageChanges(imageDataContainers);
                }
            })
        }

        function buildScaleAndRotateControls(imageDataContainers, $editableImageArea) {
            imageRotate.setDataContainers(imageDataContainers);

            return new BEM({
                block: "imcms-edit-image",
                elements: {
                    "button": [
                        components.buttons.zoomPlusButton({click: zoomPlus.bind(imageDataContainers)}),
                        components.buttons.zoomMinusButton({click: zoomMinus.bind(imageDataContainers)}),
                        components.buttons.zoomContainButton({
                            click: function () {
                                zoomContain(imageDataContainers, $editableImageArea);
                            }
                        }),
                        components.buttons.rotateLeftButton({click: imageRotate.rotateLeft}),
                        components.buttons.rotateRightButton({click: imageRotate.rotateRight})
                    ]
                }
            }).buildBlockStructure("<div>");
        }

        function buildSwitchViewControls(toggleImgArea, imageDataContainers) {
            var $preview = components.texts.titleText("<div>", texts.preview, {
                "data-tab": "prev",
                click: toggleImgArea
            });
            var $origin = components.texts.titleText("<div>", texts.original, {
                "data-tab": "origin",
                click: toggleImgArea
            });
            $origin.modifiers = ["active"];

            imageDataContainers.$tabOriginal = $origin;

            return new BEM({
                block: "imcms-editable-img-control-tabs",
                elements: {
                    "tab": [$preview, $origin]
                }
            }).buildBlockStructure("<div>");
        }

        function buildBottomPanel(toggleImgArea, imageDataContainers, $editableImageArea) {
            return new BEM({
                block: "imcms-editable-img-controls",
                elements: {
                    "control-size": imageEditSizeControls.buildEditSizeControls(imageDataContainers),
                    "control-scale-n-rotate": buildScaleAndRotateControls(imageDataContainers, $editableImageArea),
                    "image-revert": buildRevertButton(imageDataContainers),
                    "control-view": buildSwitchViewControls(toggleImgArea, imageDataContainers)
                }
            }).buildBlockStructure("<div>");
        }

        return {
            build: function (opts) {
                opts.imageDataContainers.$editableImageArea = buildEditableImageArea(opts.imageDataContainers);
                var $previewImageArea = buildPreviewImageArea(opts.imageDataContainers);

                opts.imageDataContainers.$bottomPanel = buildBottomPanel(
                    opts.toggleImgArea, opts.imageDataContainers, opts.imageDataContainers.$editableImageArea
                );

                return $("<div>").append(
                    opts.imageDataContainers.$editableImageArea,
                    $previewImageArea,
                    opts.imageDataContainers.$bottomPanel
                );
            }
        };
    }
);
<|endoftext|>


<commit_before>import './BaseCommand'

import { FS } from 'grind-support'
import { InputOption } from 'grind-cli'

const crypto = require('crypto')
const path = require('path')
const Ignore = require('ignore')

export class PublishCommand extends BaseCommand {

	name = 'assets:publish'
	description = 'Compies and publishes all assets'

	assets = { }
	oldAssets = { }
	factory = null
	publishedBaseUrl = null
	topLevel = false

	options = [
		new InputOption('published-base-url', InputOption.VALUE_OPTIONAL, 'Specify the base URL for published assets.'),
	]

	ready() {
		this.factory = this.app.assets
		this.topLevel = this.app.config.get('assets.top_level') === true

		return super.ready()
	}

	async run() {
		this.oldAssets = await this.loadOldAssets()

		if(this.oldAssets.isNil) {
			this.oldAssets = { }
		}

		if(this.containsOption('published-base-url')) {
			this.publishedBaseUrl = this.option('published-base-url').replace(/\/$/g, '')
			this.assets.__base_url = `${this.publishedBaseUrl}/`
		} else {
			this.publishedBaseUrl = path.relative(this.app.paths.public('/'), this.publishPath)
		}

		if(!this.oldAssets.__base_url.isNil) {
			const length = this.oldAssets.__base_url.length
			delete this.oldAssets.__base_url

			for(const key of Object.keys(this.oldAssets)) {
				this.oldAssets[key] = this.oldAssets[key].substring(length)
			}
		}

		await this.compile()

		await this.writeConfig(this.assets)
		await this.removeAssets(this.oldAssets)
	}

	async compile() {
		const assets = await this.findAssets(this.sourcePath)

		for(const asset of assets) {
			let content = null

			try {
				this.comment('Compiling', path.relative(this.app.paths.base(), asset.path))
				content = await asset.compile()
			} catch(err) {
				let message = err.message || 'Unknown error'

				if(!err.file.isNil) {
					message += `\n --> File: ${err.file}`
				}

				if(!err.line.isNil) {
					message += `\n --> Line: ${err.line}`
				}

				if(!err.column.isNil) {
					message += `\n --> Column: ${err.column}`
				}

				throw new Error(message)
			}

			let storePath = path.relative(this.sourcePath, asset.path)

			if(!this.topLevel) {
				storePath = path.join(asset.type, storePath.substr(storePath.indexOf('/')))
			}

			let name = path.basename(storePath, path.extname(storePath))

			if(asset.compiler.wantsHashSuffixOnPublish) {
				const sha1 = crypto.createHash('md5')
				sha1.update(content)
				name += `-${sha1.digest('hex').substring(0, 8)}`
			}

			name += `.${asset.extension}`
			await this.storeAsset(asset, path.join(this.publishPath, path.dirname(storePath), name), content)
		}
	}

	async findAssets(pathname) {
		const files = await FS.recursiveReaddir(pathname)
		const ignoreFiles = files.filter(file => path.basename(file) === '.assetsignore')
		const ignoreRules = Ignore().add([ '**/_*', '**/.*', path.join(path.relative(this.sourcePath, this.publishPath), '/') ])

		for(const ignoreFile of ignoreFiles) {
			const content = await FS.readFile(ignoreFile).then(content => content.toString())
			const dirname = path.relative(pathname, path.dirname(ignoreFile))

			const rules = content.split(/[\n\r]+/).filter(line => {
				line = line.trim()
				return line.length > 0 && line.substring(0, 1) !== '#'
			}).map(line => {
				if(line.substring(0, 1) === '!') {
					return `!${path.join(dirname, line.substring(1))}`
				}

				return path.join(dirname, line)
			})

			ignoreRules.add(rules)
		}

		return files.filter(file => {
			if(ignoreRules.filter([ path.relative(pathname, file) ]).length !== 1) {
				return false
			}

			if(!this.factory.isPathSupported(file)) {
				this.comment('Skipping unsupported asset', path.relative(this.app.paths.base(), file))
				return false
			}

			return true
		}).map(file => this.factory.make(file))
	}

	loadOldAssets() {
		return this.app.config.get('assets-published')
	}

	async storeAsset(asset, file, contents) {
		await FS.mkdirp(path.dirname(file))

		if(!(contents instanceof Buffer)) {
			contents = Buffer.from(contents)
		}

		contents = await this.postProcess(asset, file, contents)

		await FS.writeFile(file, contents)
		const lastModified = await asset.lastModified()

		if(!lastModified.isNil) {
			await FS.touch(file, lastModified)
		}

		const src = path.relative(this.sourcePath, asset.path)
		const dest = path.relative(this.publishPath, file)

		if(this.publishedBaseUrl !== null) {
			this.assets[src] = `${this.publishedBaseUrl}/${dest}`
		} else {
			this.assets[src] = dest
		}

		if(this.oldAssets[src] === dest) {
			delete this.oldAssets[src]
		}
	}

	async postProcess(asset, file, contents) {
		const postProcessors = this.factory.getPostProcessorsFromPath(file)

		if(postProcessors.length === 0) {
			return contents
		}

		for(const postProcessor of postProcessors) {
			this.comment(`Applying ${postProcessor.constructor.name}`, path.relative(this.app.paths.base(), asset.path))
			contents = await postProcessor.process(asset.path, file, contents)

			if(!(contents instanceof Buffer)) {
				contents = Buffer.from(contents)
			}
		}

		return contents
	}

	async removeAssets(assets) {
		for(const key of Object.keys(assets)) {
			await this.removeAsset(assets[key])
		}
	}

	async writeConfig(config) {
		await FS.writeFile(
			this.app.paths.config('assets-published.json'),
			JSON.stringify(config, null, ' ')
		)
	}

}
<commit_msg>Fixed a bug in PublishCommand where if publishedBaseUrl was set, old assets were incorrectly deleted<commit_after>import './BaseCommand'

import { FS } from 'grind-support'
import { InputOption } from 'grind-cli'

const crypto = require('crypto')
const path = require('path')
const Ignore = require('ignore')

export class PublishCommand extends BaseCommand {

	name = 'assets:publish'
	description = 'Compies and publishes all assets'

	assets = { }
	oldAssets = { }
	factory = null
	publishedBaseUrl = null
	topLevel = false

	options = [
		new InputOption('published-base-url', InputOption.VALUE_OPTIONAL, 'Specify the base URL for published assets.'),
	]

	ready() {
		this.factory = this.app.assets
		this.topLevel = this.app.config.get('assets.top_level') === true

		return super.ready()
	}

	async run() {
		this.oldAssets = await this.loadOldAssets()

		if(this.oldAssets.isNil) {
			this.oldAssets = { }
		}

		if(this.containsOption('published-base-url')) {
			this.publishedBaseUrl = this.option('published-base-url').replace(/\/$/g, '')
			this.assets.__base_url = `${this.publishedBaseUrl}/`
		} else {
			this.publishedBaseUrl = path.relative(this.app.paths.public('/'), this.publishPath)
		}

		if(!this.oldAssets.__base_url.isNil) {
			const length = this.oldAssets.__base_url.length
			delete this.oldAssets.__base_url

			for(const key of Object.keys(this.oldAssets)) {
				this.oldAssets[key] = this.oldAssets[key].substring(length)
			}
		}

		await this.compile()

		await this.writeConfig(this.assets)
		await this.removeAssets(this.oldAssets)
	}

	async compile() {
		const assets = await this.findAssets(this.sourcePath)

		for(const asset of assets) {
			let content = null

			try {
				this.comment('Compiling', path.relative(this.app.paths.base(), asset.path))
				content = await asset.compile()
			} catch(err) {
				let message = err.message || 'Unknown error'

				if(!err.file.isNil) {
					message += `\n --> File: ${err.file}`
				}

				if(!err.line.isNil) {
					message += `\n --> Line: ${err.line}`
				}

				if(!err.column.isNil) {
					message += `\n --> Column: ${err.column}`
				}

				throw new Error(message)
			}

			let storePath = path.relative(this.sourcePath, asset.path)

			if(!this.topLevel) {
				storePath = path.join(asset.type, storePath.substr(storePath.indexOf('/')))
			}

			let name = path.basename(storePath, path.extname(storePath))

			if(asset.compiler.wantsHashSuffixOnPublish) {
				const sha1 = crypto.createHash('md5')
				sha1.update(content)
				name += `-${sha1.digest('hex').substring(0, 8)}`
			}

			name += `.${asset.extension}`
			await this.storeAsset(asset, path.join(this.publishPath, path.dirname(storePath), name), content)
		}
	}

	async findAssets(pathname) {
		const files = await FS.recursiveReaddir(pathname)
		const ignoreFiles = files.filter(file => path.basename(file) === '.assetsignore')
		const ignoreRules = Ignore().add([ '**/_*', '**/.*', path.join(path.relative(this.sourcePath, this.publishPath), '/') ])

		for(const ignoreFile of ignoreFiles) {
			const content = await FS.readFile(ignoreFile).then(content => content.toString())
			const dirname = path.relative(pathname, path.dirname(ignoreFile))

			const rules = content.split(/[\n\r]+/).filter(line => {
				line = line.trim()
				return line.length > 0 && line.substring(0, 1) !== '#'
			}).map(line => {
				if(line.substring(0, 1) === '!') {
					return `!${path.join(dirname, line.substring(1))}`
				}

				return path.join(dirname, line)
			})

			ignoreRules.add(rules)
		}

		return files.filter(file => {
			if(ignoreRules.filter([ path.relative(pathname, file) ]).length !== 1) {
				return false
			}

			if(!this.factory.isPathSupported(file)) {
				this.comment('Skipping unsupported asset', path.relative(this.app.paths.base(), file))
				return false
			}

			return true
		}).map(file => this.factory.make(file))
	}

	loadOldAssets() {
		return this.app.config.get('assets-published')
	}

	async storeAsset(asset, file, contents) {
		await FS.mkdirp(path.dirname(file))

		if(!(contents instanceof Buffer)) {
			contents = Buffer.from(contents)
		}

		contents = await this.postProcess(asset, file, contents)

		await FS.writeFile(file, contents)
		const lastModified = await asset.lastModified()

		if(!lastModified.isNil) {
			await FS.touch(file, lastModified)
		}

		const src = path.relative(this.sourcePath, asset.path)
		let dest = path.relative(this.publishPath, file)

		if(this.publishedBaseUrl !== null) {
			dest = `${this.publishedBaseUrl}/${dest}`
		}

		this.assets[src] = dest

		if(this.oldAssets[src] === dest) {
			delete this.oldAssets[src]
		}
	}

	async postProcess(asset, file, contents) {
		const postProcessors = this.factory.getPostProcessorsFromPath(file)

		if(postProcessors.length === 0) {
			return contents
		}

		for(const postProcessor of postProcessors) {
			this.comment(`Applying ${postProcessor.constructor.name}`, path.relative(this.app.paths.base(), asset.path))
			contents = await postProcessor.process(asset.path, file, contents)

			if(!(contents instanceof Buffer)) {
				contents = Buffer.from(contents)
			}
		}

		return contents
	}

	async removeAssets(assets) {
		for(const key of Object.keys(assets)) {
			await this.removeAsset(assets[key])
		}
	}

	async writeConfig(config) {
		await FS.writeFile(
			this.app.paths.config('assets-published.json'),
			JSON.stringify(config, null, ' ')
		)
	}

}
<|endoftext|>