<commit_before>/*
	Main Miso MVC generator - this is a singleton to load all controllers
	and map their routes on startup of app. If a route is unmapped, we throw an
	error
*/
var fs			= require('fs'),
	path		= require('path'),
	_			= require('lodash'),
	routes		= {},
	m = require('mithril'),
	sugartags = require('../server/mithril.sugartags.node.js')(m),
	bindings = require('../server/mithril.bindings.node.js')(m),
	//templates = require('../server/mithril.templates.node.js'),
	vm = require('vm'),
	exec = require('child_process').exec,
	render = require('mithril-node-render'),

	//	Force the browserify to run? (Note: this usually makes it loop a couple of times)
	forceBrowserify = true,
	attachmentNode = "document.getElementById('misoAttachmentNode')",

	//	TODO: below belongs in layout templates
	//  Puts the lotion on its...
	skin = function(content) {
		return [
			'<!doctype html>',
			'',
			'',
			'<link rel="stylesheet" href="/css/style.css"/>',
			'',
			'',
			'',
			'
',
			'MISO',
			'
',
			'',
			'
',
			content,
			'
',
			//	The generated client script
			'',
			'',
			''
		].join('');
	},
	getExtension = function(filename) {
		var ext = path.extname(filename||'').split('.');
		return ext[ext.length - 1];
	},
	hasMappedRouteActions = {};

//	Map the routes for the controllers
//	This generates the client side code from our routes/controller/views
module.exports = function(app, options) {
	//	Add configured routes
	if(options.routeConfig) {
		_.forOwn(options.routeConfig, function(routeObj, routePath){
			var file = routeObj.name + ".js",
				routeFile = path.join(__dirname, file),
				route = require(routeFile),
				routeStats = fs.statSync(routeFile),
				routeName = file.substr(0, file.lastIndexOf("."));

			routes[routePath] = routes[routePath] || {};

			_.assign(routes[routePath], {
				route: route,
				method: routeObj.method,
				name: routeObj.name,
				action: routeObj.action,
				path: routePath,
				file: file,
				stats: routeStats
			});

			hasMappedRouteActions[routeObj.name + "." + routeObj.action] = routes[routePath];

		});
	}

	//	Import non configured routes
	fs.readdirSync(__dirname)
		.filter(function(file) {
			//	All js files that don't start with '.' and are not index.js or main.js
			return (file.indexOf('.') !== 0) && (file !== 'index.js') && (file !== 'mvcmain.js') && (file !== 'miso.js') && getExtension(file) == "js";
		})
		.forEach(function(file) {
			var routeFile = path.join(__dirname, file),
				route = require(routeFile),
				routeStats = fs.statSync(routeFile),
				routeName = file.substr(0, file.lastIndexOf(".")),
				routePath = "/" + routeName,
				method = "get",
				//	TODO: The id, delete, new can be translated perhaps?
				idPostfix = "_id",
				deleteKeyword = "delete",
				newKeyword = "new";

			//	Create routes using path as key
			//	TODO: different methods would change the key
			_.forOwn(route, function(idx, action){
				if(!hasMappedRouteActions[routeName + "." + action]) {

					//	Note: The list is pluralised with an s always,
					//	so name your controller accordingly, eg: don't
					//	name it 'users', it should be 'user'
					//	TODO: provide international pluralisation via
					//	i18next or similar
					switch (action) {
						//	Display an index page with a list of items
						case 'index':
							method = 'get';
							routePath = '/' + routeName + 's';
							break;
						//	An item to edit
						case 'edit':
							method = 'get';
							routePath = '/' + routeName + '/:' + routeName + idPostfix;
							break;
						//	Delete an item
						case 'delete':
							method = 'post';
							routePath = '/' + routeName + '/:' + routeName + idPostfix + '/' + deleteKeyword;
							break;
						case 'new':
							method = 'get';
							routePath = '/' + routeName + '/' + newKeyword;
							break;
						//	Create an item
						case 'create':
							method = 'post';
							routePath = '/' + routeName;
							break;
						//	Update an item
						case 'update':
							method = 'post';
							routePath = '/' + routeName + '/:' + routeName + idPostfix;
							break;
						default:
							var message = 'ERROR: unmapped action: "' + routeName + '.' + action + '" - please map it or make it a private function';
							if(options.throwUnmappedActions) {
								throw new Error(message);
							} else {
								options.verbose && console.log(message);
							}
					}

					routes[routePath] = {
						route: route,
						method: method,
						name: routeName,
						action: action,
						path: routePath,
						file: file,
						stats: routeStats
					};
				}
			});
		});

	var routeMap = {},
		//	route, name, path, method, action
		createRoute = function(args) {
			//	Setup the route on the app
			app[args.method](args.path, function(req, res) {
				try{
					var scope = _.isFunction(args.route[args.action].controller)?
							args.route[args.action].controller(req.params):
							args.route[args.action].controller,
						mvc = args.route[args.action];
					if (!scope || !scope.onReady) {
						var result = render(_.isFunction(mvc.view)? mvc.view(scope): mvc.view, scope);
						res.end(skin(result));
					} else {
						scope.onReady.bind(function() {
							var result = render(_.isFunction(mvc.view)? mvc.view(scope): mvc.view, scope);
							res.end(skin(result));
						});
					}
				} catch(ex){
					//	TODO: Soemthing with this
					throw ex;
				}
			});

			options.verbose && console.log('     %s %s -> %s.%s', args.method.toUpperCase(), args.path, args.name, args.action);

			routeMap[args.path] = args;
		};

	//	TODO:
	//
	//	* Might want to externalise this?
	//	* Add ability to configure things, add/remove required libs, etc.
	//
	var view = function(ctrl) {
		var usedRoute = {};
		return [
			"/* NOTE: This is a generated file, please do not modify, your changes will be lost */",
			"var m = require('mithril');",

			//	Required libs
			"var sugartags = require('../server/mithril.sugartags.node.js')(m);",
			"var bindings = require('../server/mithril.bindings.node.js')(m);",
			"var store = require('../server/store.js');",

			//	All our route files
			(ctrl.routes.map(function(route, idx) {
				var result = usedRoute[route.name]? "" :
					"var " + route.name + " = require('../mvc/" + route.name + ".js');";
				usedRoute[route.name] = route;
				return result;
			})).join("\n"),

			//	Expose mithril - might be good for debugging...
			"if(typeof window !== 'undefined') {",
			"	window.m = m;",
			"}",

			"	",
			"m.route.mode = 'pathname';",
			"m.route("+attachmentNode+", '/', {",

			//	Add the route map for mithril here
			(ctrl.routes.map(function(route, idx) {
				return [
					"'" + route.path + "': " + route.name + "." + route.action
				].join("\n");
			})).join(",\n"),
			"});"
		].join("\n");
	};

	//	Grab our controller file names
	var routeList = [],
		mainFile = './mvc/mvcmain.js',
		output = "./client/miso.js",
		outputMap = "./client/miso.js.map.json",
		browserifyCmd = "browserify " + mainFile + " >" + output,
		//browserifyCmd = "browserify " + mainFile + " -d -p [minifyify --map /miso.js.map.json --output "+outputMap+"] >" + output,

		mainFileModified = fs.existsSync(mainFile)? fs.statSync(mainFile).mtime: new Date(1970,0,1),
		lastRouteModified = new Date(1970,0,1);

	//	Generate list of routes
	_.forOwn(routes, function(route, idx){
		//	Check controller timestamp
		lastRouteModified = (lastRouteModified > route.stats.mtime)?
			lastRouteModified:
		 	route.stats.mtime;

		routeList.push(route);
		createRoute(route);
	});

	//	Output our main JS file for browserify
	fs.writeFileSync(mainFile, render(view({
		routes: routeList
	})));

	//	Run browserify when either a controller or view has changed.
	//	We use exec to run it - this gives us a little more flexibility

	if(forceBrowserify || lastRouteModified > mainFileModified) {
		exec(browserifyCmd, function (error, stdout, stderr) {
			if(error) {
				throw error
			}
		});
	}
};<commit_msg>Document routes<commit_after>/*
	Main Miso MVC generator - this is a singleton to load all controllers
	and map their routes on startup of app. If a route is unmapped, we throw an
	error
*/
var fs			= require('fs'),
	path		= require('path'),
	_			= require('lodash'),
	routes		= {},
	m = require('mithril'),
	sugartags = require('../server/mithril.sugartags.node.js')(m),
	bindings = require('../server/mithril.bindings.node.js')(m),
	//templates = require('../server/mithril.templates.node.js'),
	vm = require('vm'),
	exec = require('child_process').exec,
	render = require('mithril-node-render'),

	//	Force the browserify to run? (Note: this usually makes it loop a couple of times)
	forceBrowserify = true,
	attachmentNode = "document.getElementById('misoAttachmentNode')",

	//	TODO: below belongs in layout templates
	//  Puts the lotion on its...
	skin = function(content) {
		return [
			'<!doctype html>',
			'',
			'',
			'<link rel="stylesheet" href="/css/style.css"/>',
			'',
			'',
			'',
			'
',
			'MISO',
			'
',
			'',
			'
',
			content,
			'
',
			//	The generated client script
			'',
			'',
			''
		].join('');
	},
	getExtension = function(filename) {
		var ext = path.extname(filename||'').split('.');
		return ext[ext.length - 1];
	},
	hasMappedRouteActions = {};

//	Map the routes for the controllers
//	This generates the client side code from our routes/controller/views
module.exports = function(app, options) {
	//	Add configured routes
	if(options.routeConfig) {
		_.forOwn(options.routeConfig, function(routeObj, routePath){
			var file = routeObj.name + ".js",
				routeFile = path.join(__dirname, file),
				route = require(routeFile),
				routeStats = fs.statSync(routeFile),
				routeName = file.substr(0, file.lastIndexOf("."));

			routes[routePath] = routes[routePath] || {};

			_.assign(routes[routePath], {
				route: route,
				method: routeObj.method,
				name: routeObj.name,
				action: routeObj.action,
				path: routePath,
				file: file,
				stats: routeStats
			});

			hasMappedRouteActions[routeObj.name + "." + routeObj.action] = routes[routePath];

		});
	}

	//	Import non configured routes
	fs.readdirSync(__dirname)
		.filter(function(file) {
			//	All js files that don't start with '.' and are not index.js or main.js
			return (file.indexOf('.') !== 0) && (file !== 'index.js') && (file !== 'mvcmain.js') && (file !== 'miso.js') && getExtension(file) == "js";
		})
		.forEach(function(file) {
			var routeFile = path.join(__dirname, file),
				route = require(routeFile),
				routeStats = fs.statSync(routeFile),
				routeName = file.substr(0, file.lastIndexOf(".")),
				routePath = "/" + routeName,
				method = "get",
				//	TODO: The id, delete, new can be translated perhaps?
				idPostfix = "_id",
				deleteKeyword = "delete",
				newKeyword = "new";

			/*
				Here we generate routes based on supported action names, auto-mapped actions are:

				Action 		Method 		URL 						Description

				index 		GET 		[controller] + 's'			List the items
				edit 		GET 		[controller]/[id]			Display a form to edit the item
				delete 		POST 		[controller]/[id]/delete 	Deletes an item
				new 		GET 		[controller]/new 			Display a form to add a new item
				create 		POST 		[controller] 				Creates a new item
				update 		POST 		[controller]/[id] 			Updates an item

				Note: We are using RESTful-style URLs, but only GET and POST here,
					we could obviously add PUT, DELETE and so on, but we're keeping
					this basic for now. This is because some browsers, eg: IE7/IE8
					do not properly support PUT and DELETE, so it's safer to exclude
					those methods. I know this is opinionated behaviour, but you can
					always add a custom route if you really want it.

				Ref:
				http://stackoverflow.com/questions/2456820/problem-with-jquery-ajax-with-delete-method-in-ie

				Action naming convention refs:
				http://mvccontrib.codeplex.com/wikipage?title=SimplyRestfulRouting&referringTitle=Documentation
				http://stephenwalther.com/archive/2008/06/27/asp-net-mvc-tip-11-use-standard-controller-action-names

			*/
			_.forOwn(route, function(idx, action){
				if(!hasMappedRouteActions[routeName + "." + action]) {
					//	Note: The list is pluralised with an s always,
					//	so name your controller accordingly, eg: don't
					//	name it 'users', it should be 'user'
					//	TODO: provide international pluralisation via
					//	i18next or similar
					switch (action) {
						//	Display an index page with a list of items
						case 'index':
							method = 'get';
							routePath = '/' + routeName + 's';
							break;
						//	An item to edit
						case 'edit':
							method = 'get';
							routePath = '/' + routeName + '/:' + routeName + idPostfix;
							break;
						//	Delete an item
						case 'delete':
							method = 'post';
							routePath = '/' + routeName + '/:' + routeName + idPostfix + '/' + deleteKeyword;
							break;
						case 'new':
							method = 'get';
							routePath = '/' + routeName + '/' + newKeyword;
							break;
						//	Create an item
						case 'create':
							method = 'post';
							routePath = '/' + routeName;
							break;
						//	Update an item
						case 'update':
							method = 'post';
							routePath = '/' + routeName + '/:' + routeName + idPostfix;
							break;
						default:
							var message = 'ERROR: unmapped action: "' + routeName + '.' + action + '" - please map it or make it a private function';
							if(options.throwUnmappedActions) {
								throw new Error(message);
							} else {
								options.verbose && console.log(message);
							}
					}

					routes[routePath] = {
						route: route,
						method: method,
						name: routeName,
						action: action,
						path: routePath,
						file: file,
						stats: routeStats
					};
				}
			});
		});

	var routeMap = {},
		//	route, name, path, method, action
		createRoute = function(args) {
			//	Setup the route on the app
			app[args.method](args.path, function(req, res) {
				try{
					var scope = _.isFunction(args.route[args.action].controller)?
							args.route[args.action].controller(req.params):
							args.route[args.action].controller,
						mvc = args.route[args.action];
					if (!scope || !scope.onReady) {
						var result = render(_.isFunction(mvc.view)? mvc.view(scope): mvc.view, scope);
						res.end(skin(result));
					} else {
						scope.onReady.bind(function() {
							var result = render(_.isFunction(mvc.view)? mvc.view(scope): mvc.view, scope);
							res.end(skin(result));
						});
					}
				} catch(ex){
					//	TODO: Soemthing with this
					throw ex;
				}
			});

			options.verbose && console.log('     %s %s -> %s.%s', args.method.toUpperCase(), args.path, args.name, args.action);

			routeMap[args.path] = args;
		};

	//	TODO:
	//
	//	* Might want to externalise this?
	//	* Add ability to configure things, add/remove required libs, etc.
	//
	var view = function(ctrl) {
		var usedRoute = {};
		return [
			"/* NOTE: This is a generated file, please do not modify, your changes will be lost */",
			"var m = require('mithril');",

			//	Required libs
			"var sugartags = require('../server/mithril.sugartags.node.js')(m);",
			"var bindings = require('../server/mithril.bindings.node.js')(m);",
			"var store = require('../server/store.js');",

			//	All our route files
			(ctrl.routes.map(function(route, idx) {
				var result = usedRoute[route.name]? "" :
					"var " + route.name + " = require('../mvc/" + route.name + ".js');";
				usedRoute[route.name] = route;
				return result;
			})).join("\n"),

			//	Expose mithril - might be good for debugging...
			"if(typeof window !== 'undefined') {",
			"	window.m = m;",
			"}",

			"	",
			"m.route.mode = 'pathname';",
			"m.route("+attachmentNode+", '/', {",

			//	Add the route map for mithril here
			(ctrl.routes.map(function(route, idx) {
				return [
					"'" + route.path + "': " + route.name + "." + route.action
				].join("\n");
			})).join(",\n"),
			"});"
		].join("\n");
	};

	//	Grab our controller file names
	var routeList = [],
		mainFile = './mvc/mvcmain.js',
		output = "./client/miso.js",
		outputMap = "./client/miso.js.map.json",
		browserifyCmd = "browserify " + mainFile + " >" + output,
		//browserifyCmd = "browserify " + mainFile + " -d -p [minifyify --map /miso.js.map.json --output "+outputMap+"] >" + output,

		mainFileModified = fs.existsSync(mainFile)? fs.statSync(mainFile).mtime: new Date(1970,0,1),
		lastRouteModified = new Date(1970,0,1);

	//	Generate list of routes
	_.forOwn(routes, function(route, idx){
		//	Check controller timestamp
		lastRouteModified = (lastRouteModified > route.stats.mtime)?
			lastRouteModified:
		 	route.stats.mtime;

		routeList.push(route);
		createRoute(route);
	});

	//	Output our main JS file for browserify
	fs.writeFileSync(mainFile, render(view({
		routes: routeList
	})));

	//	Run browserify when either a controller or view has changed.
	//	We use exec to run it - this gives us a little more flexibility

	if(forceBrowserify || lastRouteModified > mainFileModified) {
		exec(browserifyCmd, function (error, stdout, stderr) {
			if(error) {
				throw error
			}
		});
	}
};<|endoftext|>


<commit_before>var cldr = Npm.require('cldr');

var convertCldrToMoment = function (dateString) {
  var replace = function (c, r) {
    var char = c.charAt(0);
    var rx = new RegExp('(^|[^' + char + '])' + c + '($|[^' + char + '])', 'g');
    dateString = dateString.replace(rx, '$1' + r + '$2');
  }
  //http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns
  // era (XXX missing support in moment)
  replace('G', '');
  // year
  replace('Y', 'GGGG'); // XXX no support for year without padding
  replace('YYY?', 'GG'); // XXX missing support for 3 digit year
  replace('YYYY', 'GGGG');
  replace('y', 'YYYY'); // XXX no support for year without padding
  replace('yyy?', 'YY'); // XXX missing support for 3 digit year
  replace('yyyy', 'YYYY');
  // quater
  //replace('Q', 'Q'); // native
  replace('QQ', '0Q');
  replace('QQQQ?', 'Q'); // XXX again missing support
  replace('q', 'Q');
  replace('qq', '0G');
  replace('qqqq?', 'Q'); // XXX again missing support
  // month
  // nearly native support, just misses the L variation
  dateString = dateString.replace(/L/g, 'M');
  // week
  // week of the year is equal
  // XXX week of the month is not supported
  // days
  replace('DD?', 'DDD'); // XXX
  replace('DDD', 'DDDD');
  replace('d', 'D');
  replace('dd', 'DD');
  // week day
  replace('EE?', 'dd');
  replace('EEE', 'ddd');
  replace('EEEE', 'dddd');
  replace('EEEEE', 'd'); // XXX
  // replace('e', 'e');
  replace('ee', '0e');
  replace('eee', 'ddd');
  replace('eeee', 'dddd');
  replace('eeeee', 'e'); // XXX
  replace('c', 'E');
  replace('cc', '0E');
  replace('ccc', 'ddd');
  replace('cccc', 'dddd');
  replace('ccccc', 'E'); // XXX
  // period
  // - compatible
  // hour
  replace('k', 'h'); // XXX
  replace('kk', 'hh'); // XXX
  replace('K', 'H'); // XXX
  replace('KK', 'HH'); // XXX
  // minutes
  // - compatible
  // secound
  replace('A+', ''); // XXX not supported
  // zone
  replace('z{1,3}', 'zz'); // FIXME deprecated in moment
  replace('Z{1,3}', 'ZZ');
  replace('ZZZZ', 'Z');
  replace('v{1,4}', ''); // XXX not supported

  // cldr uses another schema to escape literals
  dateString = dateString.replace(/'((?:[^']|'')+)'/, function (a, content) {
    return '[' + content.replace("''", "'") + ']'; // moment uses [] for escaping
  });
  return dateString;
};

var momentAddition = function (data) {
  // add cldr information
  var meta = data.meta;
  if (! meta.hasOwnProperty('moment')) {
    var locale = data.locale.toString();
    var months = cldr.extractMonthNames(locale, 'gregorian').format;
    var days = cldr.extractDayNames(locale, 'gregorian').format;
    meta['moment'] = {
      months: months.wide,
      monthsShort: months.abbreviated,
      weekdays: days.wide,
      weekdaysShort: days.abbreviated,
      weekdaysMin: days.short,
      /*longDateFormat: {
          LT: "HH:mm [Uhr]",
          L: "DD.MM.YYYY",
          LL: "D. MMMM YYYY",
          LLL: "D. MMMM YYYY LT",
          LLLL: "dddd, D. MMMM YYYY LT"
      },
      calendar: {
          sameDay: "[Heute um] LT",
          sameElse: "L",
          nextDay: '[Morgen um] LT',
          nextWeek: 'dddd [um] LT',
          lastDay: '[Gestern um] LT',
          lastWeek: '[letzten] dddd [um] LT'
      },
      relativeTime : {
          future: "in %s",
          past: "vor %s",
          s: "ein paar Sekunden",
          m: processRelativeTime,
          mm: "%d Minuten",
          h: processRelativeTime,
          hh: "%d Stunden",
          d: processRelativeTime,
          dd: processRelativeTime,
          M: processRelativeTime,
          MM: processRelativeTime,
          y: processRelativeTime,
          yy: processRelativeTime
      },
      ordinal: '%d.',
      week: {
          dow: 1, // Monday is the first day of the week.
          doy: 4  // The week that contains Jan 4th is the first week of the year.
      }*/
    };
  }
};

MessageFormatPreprocess.date = function (object, data) {
  momentAddition(data);
  var formats = cldr.extractDateFormats(data.locale.toString(), 'gregorian');
  var format = (object.args && formats[object.args[0]]) || formats.medium;
  return {
    name: object.name,
    method: object.method,
    format: convertCldrToMoment(format)
  };
};
MessageFormatPreprocess.time = function (object, data) {
  //momentAddition(data); // not needed for time
  var formats = cldr.extractTimeFormats(data.locale.toString(), 'gregorian');
  var format = (object.args && formats[object.args[0]]) || formats.medium;
  return {
    name: object.name,
    method: object.method,
    format: convertCldrToMoment(format)
  };
};

// this method is virtual
// it uses date and time on the client
MessageFormatPreprocess.datetime = function (object, data) {
  var formats = cldr.extractDateTimePatterns(data.locale.toString(), 'gregorian');
  var format = (object.args && formats[object.args[0]]) || formats.medium;
  var subFormats = object.args && object.args[0] && (', ' + object.args[0]) || '';
  format = format.replace(/\{1\}/g, '{' + object.name + ', date' + subFormats + '}');
  format = format.replace(/\{0\}/g, '{' + object.name + ', time' + subFormats + '}');
  return messageFormatPreprocess(format, data);
};
MessageFormatPreprocess.duration = function (object, data) {
  momentAddition(data);
};
<commit_msg>missed global flag for escape regex<commit_after>var cldr = Npm.require('cldr');

var convertCldrToMoment = function (dateString) {
  var replace = function (c, r) {
    var char = c.charAt(0);
    var rx = new RegExp('(^|[^' + char + '])' + c + '($|[^' + char + '])', 'g');
    dateString = dateString.replace(rx, '$1' + r + '$2');
  }
  //http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns
  // era (XXX missing support in moment)
  replace('G', '');
  // year
  replace('Y', 'GGGG'); // XXX no support for year without padding
  replace('YYY?', 'GG'); // XXX missing support for 3 digit year
  replace('YYYY', 'GGGG');
  replace('y', 'YYYY'); // XXX no support for year without padding
  replace('yyy?', 'YY'); // XXX missing support for 3 digit year
  replace('yyyy', 'YYYY');
  // quater
  //replace('Q', 'Q'); // native
  replace('QQ', '0Q');
  replace('QQQQ?', 'Q'); // XXX again missing support
  replace('q', 'Q');
  replace('qq', '0G');
  replace('qqqq?', 'Q'); // XXX again missing support
  // month
  // nearly native support, just misses the L variation
  dateString = dateString.replace(/L/g, 'M');
  // week
  // week of the year is equal
  // XXX week of the month is not supported
  // days
  replace('DD?', 'DDD'); // XXX
  replace('DDD', 'DDDD');
  replace('d', 'D');
  replace('dd', 'DD');
  // week day
  replace('EE?', 'dd');
  replace('EEE', 'ddd');
  replace('EEEE', 'dddd');
  replace('EEEEE', 'd'); // XXX
  // replace('e', 'e');
  replace('ee', '0e');
  replace('eee', 'ddd');
  replace('eeee', 'dddd');
  replace('eeeee', 'e'); // XXX
  replace('c', 'E');
  replace('cc', '0E');
  replace('ccc', 'ddd');
  replace('cccc', 'dddd');
  replace('ccccc', 'E'); // XXX
  // period
  // - compatible
  // hour
  replace('k', 'h'); // XXX
  replace('kk', 'hh'); // XXX
  replace('K', 'H'); // XXX
  replace('KK', 'HH'); // XXX
  // minutes
  // - compatible
  // secound
  replace('A+', ''); // XXX not supported
  // zone
  replace('z{1,3}', 'zz'); // FIXME deprecated in moment
  replace('Z{1,3}', 'ZZ');
  replace('ZZZZ', 'Z');
  replace('v{1,4}', ''); // XXX not supported

  // cldr uses another schema to escape literals
  dateString = dateString.replace(/'((?:[^']|'')+)'/g, function (a, content) {
    return '[' + content.replace("''", "'") + ']'; // moment uses [] for escaping
  });
  return dateString;
};

var momentAddition = function (data) {
  // add cldr information
  var meta = data.meta;
  if (! meta.hasOwnProperty('moment')) {
    var locale = data.locale.toString();
    var months = cldr.extractMonthNames(locale, 'gregorian').format;
    var days = cldr.extractDayNames(locale, 'gregorian').format;
    meta['moment'] = {
      months: months.wide,
      monthsShort: months.abbreviated,
      weekdays: days.wide,
      weekdaysShort: days.abbreviated,
      weekdaysMin: days.short,
      /*longDateFormat: {
          LT: "HH:mm [Uhr]",
          L: "DD.MM.YYYY",
          LL: "D. MMMM YYYY",
          LLL: "D. MMMM YYYY LT",
          LLLL: "dddd, D. MMMM YYYY LT"
      },
      calendar: {
          sameDay: "[Heute um] LT",
          sameElse: "L",
          nextDay: '[Morgen um] LT',
          nextWeek: 'dddd [um] LT',
          lastDay: '[Gestern um] LT',
          lastWeek: '[letzten] dddd [um] LT'
      },
      relativeTime : {
          future: "in %s",
          past: "vor %s",
          s: "ein paar Sekunden",
          m: processRelativeTime,
          mm: "%d Minuten",
          h: processRelativeTime,
          hh: "%d Stunden",
          d: processRelativeTime,
          dd: processRelativeTime,
          M: processRelativeTime,
          MM: processRelativeTime,
          y: processRelativeTime,
          yy: processRelativeTime
      },
      ordinal: '%d.',
      week: {
          dow: 1, // Monday is the first day of the week.
          doy: 4  // The week that contains Jan 4th is the first week of the year.
      }*/
    };
  }
};

MessageFormatPreprocess.date = function (object, data) {
  momentAddition(data);
  var formats = cldr.extractDateFormats(data.locale.toString(), 'gregorian');
  var format = (object.args && formats[object.args[0]]) || formats.medium;
  return {
    name: object.name,
    method: object.method,
    format: convertCldrToMoment(format)
  };
};
MessageFormatPreprocess.time = function (object, data) {
  //momentAddition(data); // not needed for time
  var formats = cldr.extractTimeFormats(data.locale.toString(), 'gregorian');
  var format = (object.args && formats[object.args[0]]) || formats.medium;
  return {
    name: object.name,
    method: object.method,
    format: convertCldrToMoment(format)
  };
};

// this method is virtual
// it uses date and time on the client
MessageFormatPreprocess.datetime = function (object, data) {
  var formats = cldr.extractDateTimePatterns(data.locale.toString(), 'gregorian');
  var format = (object.args && formats[object.args[0]]) || formats.medium;
  var subFormats = object.args && object.args[0] && (', ' + object.args[0]) || '';
  format = format.replace(/\{1\}/g, '{' + object.name + ', date' + subFormats + '}');
  format = format.replace(/\{0\}/g, '{' + object.name + ', time' + subFormats + '}');
  return messageFormatPreprocess(format, data);
};
MessageFormatPreprocess.duration = function (object, data) {
  momentAddition(data);
};
<|endoftext|>


<commit_before>/**
 * Copyright Marc J. Schmidt. See the LICENSE file at the top-level
 * directory of this distribution and at
 * https://github.com/marcj/css-element-queries/blob/master/LICENSE.
 */
;
(function() {

    var requestAnimationFrame = window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        function (fn) {
            return window.setTimeout(fn, 20);
        };

    /**
     * Class for dimension change detection.
     *
     * @param {Element|Element[]|Elements|jQuery} element
     * @param {Function} callback
     *
     * @constructor
     */
    var ResizeSensor = function(element, callback) {
        /**
         *
         * @constructor
         */
        function EventQueue() {
            this.q = [];
            this.add = function(ev) {
                this.q.push(ev);
            };

            var i, j;
            this.call = function() {
                for (i = 0, j = this.q.length; i < j; i++) {
                    this.q[i].call();
                }
            };
        }

        /**
         * @param {HTMLElement} element
         * @param {String}      prop
         * @returns {String|Number}
         */
        function getComputedStyle(element, prop) {
            if (element.currentStyle) {
                return element.currentStyle[prop];
            } else if (window.getComputedStyle) {
                return window.getComputedStyle(element, null).getPropertyValue(prop);
            } else {
                return element.style[prop];
            }
        }

        /**
         *
         * @param {HTMLElement} element
         * @param {Function}    resized
         */
        function attachResizeEvent(element, resized) {
            if (!element.resizedAttached) {
                element.resizedAttached = new EventQueue();
                element.resizedAttached.add(resized);
            } else if (element.resizedAttached) {
                element.resizedAttached.add(resized);
                return;
            }

            element.resizeSensor = document.createElement('div');
            element.resizeSensor.className = 'resize-sensor';
            var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;';
            var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;';

            element.resizeSensor.style.cssText = style;
            element.resizeSensor.innerHTML =
                '
' +
                    '

' +
                '
' +
                '
' +
                    '

' +
                '
';
            element.appendChild(element.resizeSensor);

            if (getComputedStyle(element, 'position') == 'static') {
                element.style.position = 'relative';
            }

            var expand = element.resizeSensor.childNodes[0];
            var expandChild = expand.childNodes[0];
            var shrink = element.resizeSensor.childNodes[1];

            var reset = function() {
                expandChild.style.width  = 100000 + 'px';
                expandChild.style.height = 100000 + 'px';

                expand.scrollLeft = 100000;
                expand.scrollTop = 100000;

                shrink.scrollLeft = 100000;
                shrink.scrollTop = 100000;
            };

            reset();
            var dirty = false;

            var dirtyChecking = function() {
                if (!element.resizedAttached) return;

                if (dirty) {
                    element.resizedAttached.call();
                    dirty = false;
                }

                requestAnimationFrame(dirtyChecking);
            };

            requestAnimationFrame(dirtyChecking);
            var lastWidth, lastHeight;
            var cachedWidth, cachedHeight; //useful to not query offsetWidth twice

            var onScroll = function() {
              if ((cachedWidth = element.offsetWidth) != lastWidth || (cachedHeight = element.offsetHeight) != lastHeight) {
                  dirty = true;

                  lastWidth = cachedWidth;
                  lastHeight = cachedHeight;
              }
              reset();
            };

            var addEvent = function(el, name, cb) {
                if (el.attachEvent) {
                    el.attachEvent('on' + name, cb);
                } else {
                    el.addEventListener(name, cb);
                }
            };

            addEvent(expand, 'scroll', onScroll);
            addEvent(shrink, 'scroll', onScroll);
        }

        var elementType = Object.prototype.toString.call(element);
        var isCollectionTyped = ('[object Array]' === elementType
            || ('[object NodeList]' === elementType)
            || ('[object HTMLCollection]' === elementType)
            || ('undefined' !== typeof jQuery && element instanceof jQuery) //jquery
            || ('undefined' !== typeof Elements && element instanceof Elements) //mootools
        );

        if (isCollectionTyped) {
            var i = 0, j = element.length;
            for (; i < j; i++) {
                attachResizeEvent(element[i], callback);
            }
        } else {
            attachResizeEvent(element, callback);
        }

        this.detach = function() {
            if (isCollectionTyped) {
                var i = 0, j = element.length;
                for (; i < j; i++) {
                    ResizeSensor.detach(element[i]);
                }
            } else {
                ResizeSensor.detach(element);
            }
        };
    };

    ResizeSensor.detach = function(element) {
        if (element.resizeSensor) {
            element.removeChild(element.resizeSensor);
            delete element.resizeSensor;
            delete element.resizedAttached;
        }
    };

    // make available to common module loader
    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        module.exports = ResizeSensor;
    }
    else {
        window.ResizeSensor = ResizeSensor;
    }

})();
<commit_msg>Add info about requestAnimationFrame<commit_after>/**
 * Copyright Marc J. Schmidt. See the LICENSE file at the top-level
 * directory of this distribution and at
 * https://github.com/marcj/css-element-queries/blob/master/LICENSE.
 */
;
(function() {

    // Only used for the dirty checking, so the event callback count is limted to max 1 call per fps per sensor.
    // In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and
    // would generate too many unnecessary events.
    var requestAnimationFrame = window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        function (fn) {
            return window.setTimeout(fn, 20);
        };

    /**
     * Class for dimension change detection.
     *
     * @param {Element|Element[]|Elements|jQuery} element
     * @param {Function} callback
     *
     * @constructor
     */
    var ResizeSensor = function(element, callback) {
        /**
         *
         * @constructor
         */
        function EventQueue() {
            this.q = [];
            this.add = function(ev) {
                this.q.push(ev);
            };

            var i, j;
            this.call = function() {
                for (i = 0, j = this.q.length; i < j; i++) {
                    this.q[i].call();
                }
            };
        }

        /**
         * @param {HTMLElement} element
         * @param {String}      prop
         * @returns {String|Number}
         */
        function getComputedStyle(element, prop) {
            if (element.currentStyle) {
                return element.currentStyle[prop];
            } else if (window.getComputedStyle) {
                return window.getComputedStyle(element, null).getPropertyValue(prop);
            } else {
                return element.style[prop];
            }
        }

        /**
         *
         * @param {HTMLElement} element
         * @param {Function}    resized
         */
        function attachResizeEvent(element, resized) {
            if (!element.resizedAttached) {
                element.resizedAttached = new EventQueue();
                element.resizedAttached.add(resized);
            } else if (element.resizedAttached) {
                element.resizedAttached.add(resized);
                return;
            }

            element.resizeSensor = document.createElement('div');
            element.resizeSensor.className = 'resize-sensor';
            var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;';
            var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;';

            element.resizeSensor.style.cssText = style;
            element.resizeSensor.innerHTML =
                '
' +
                    '

' +
                '
' +
                '
' +
                    '

' +
                '
';
            element.appendChild(element.resizeSensor);

            if (getComputedStyle(element, 'position') == 'static') {
                element.style.position = 'relative';
            }

            var expand = element.resizeSensor.childNodes[0];
            var expandChild = expand.childNodes[0];
            var shrink = element.resizeSensor.childNodes[1];

            var reset = function() {
                expandChild.style.width  = 100000 + 'px';
                expandChild.style.height = 100000 + 'px';

                expand.scrollLeft = 100000;
                expand.scrollTop = 100000;

                shrink.scrollLeft = 100000;
                shrink.scrollTop = 100000;
            };

            reset();
            var dirty = false;

            var dirtyChecking = function() {
                if (!element.resizedAttached) return;

                if (dirty) {
                    element.resizedAttached.call();
                    dirty = false;
                }

                requestAnimationFrame(dirtyChecking);
            };

            requestAnimationFrame(dirtyChecking);
            var lastWidth, lastHeight;
            var cachedWidth, cachedHeight; //useful to not query offsetWidth twice

            var onScroll = function() {
              if ((cachedWidth = element.offsetWidth) != lastWidth || (cachedHeight = element.offsetHeight) != lastHeight) {
                  dirty = true;

                  lastWidth = cachedWidth;
                  lastHeight = cachedHeight;
              }
              reset();
            };

            var addEvent = function(el, name, cb) {
                if (el.attachEvent) {
                    el.attachEvent('on' + name, cb);
                } else {
                    el.addEventListener(name, cb);
                }
            };

            addEvent(expand, 'scroll', onScroll);
            addEvent(shrink, 'scroll', onScroll);
        }

        var elementType = Object.prototype.toString.call(element);
        var isCollectionTyped = ('[object Array]' === elementType
            || ('[object NodeList]' === elementType)
            || ('[object HTMLCollection]' === elementType)
            || ('undefined' !== typeof jQuery && element instanceof jQuery) //jquery
            || ('undefined' !== typeof Elements && element instanceof Elements) //mootools
        );

        if (isCollectionTyped) {
            var i = 0, j = element.length;
            for (; i < j; i++) {
                attachResizeEvent(element[i], callback);
            }
        } else {
            attachResizeEvent(element, callback);
        }

        this.detach = function() {
            if (isCollectionTyped) {
                var i = 0, j = element.length;
                for (; i < j; i++) {
                    ResizeSensor.detach(element[i]);
                }
            } else {
                ResizeSensor.detach(element);
            }
        };
    };

    ResizeSensor.detach = function(element) {
        if (element.resizeSensor) {
            element.removeChild(element.resizeSensor);
            delete element.resizeSensor;
            delete element.resizedAttached;
        }
    };

    // make available to common module loader
    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        module.exports = ResizeSensor;
    }
    else {
        window.ResizeSensor = ResizeSensor;
    }

})();
<|endoftext|>


<commit_before>(function () {
    'use strict';

    const { utils } = require('data-service');
    const BaseConfigService = utils.ConfigService;

    const factory = function (Base, createPoll) {

        class ConfigService extends Base {

            change;
            /**
             * @type Promise<IConfig>
             */
            configReady;
            /**
             * @type Promise<void>
             */
            configReadyPromise;

            service = new BaseConfigService(WavesApp);

            constructor() {
                super();
                this.change = this.service.change;
                this.configReadyPromise = this.service.configReady;
                this.configReady = createPoll(this, this.service.fetchConfig, () => null, 30000);
            }

            /**
             * @param {string} path
             */
            get(path) {
                return this.service.getConfig(path);
            }

            /**
             * @return {ConfigService.IFeeConfig}
             */
            getFeeConfig() {
                return this.service.getFeeConfig();
            }

        }

        return new ConfigService();
    };

    factory.$inject = ['Base', 'createPoll'];

    angular.module('app').factory('configService', factory);
})();

/**
 * @name ConfigService
 */

/**
 * @typedef {object} ConfigService#IConfig
 * @property {object} PERMISSIONS
 * @property {object} SETTINGS
 * @property {boolean} SERVICE_TEMPORARILY_UNAVAILABLE
 * @property {object} SETTINGS.DEX
 * @property {Array<string>} SETTINGS.DEX.WATCH_LIST_PAIRS
 */

/**
 * @typedef {object} ConfigService#IFeeConfig
 * @property {BigNumber} smart_asset_extra_fee
 * @property {BigNumber} smart_account_extra_fee
 * @property {Record<number, Partial<ConfigService.IFeeItem>> & {default: ConfigService.IFeeItem}} calculate_fee_rules
 */

/**
 * @typedef {object} ConfigService#IFeeItem
 * @property {boolean} add_smart_asset_fee
 * @property {boolean} add_smart_account_fee
 * @property {BigNumber} min_price_step
 * @property {BigNumber} fee
 */
<commit_msg>DEXW-2225: fix<commit_after>(function () {
    'use strict';

    const { utils } = require('data-service');
    const BaseConfigService = utils.ConfigService;

    const factory = function (Base, createPoll) {

        class ConfigService extends Base {

            change;
            /**
             * @type Promise<IConfig>
             */
            configReady;
            /**
             * @type Promise<void>
             */
            configReadyPromise;

            service = new BaseConfigService(WavesApp);

            constructor() {
                super();
                this.change = this.service.change;
                this.configReadyPromise = this.service.configReady;
                this.configReady = createPoll(this, () => this.service.fetchConfig(), () => null, 30000);
            }

            /**
             * @param {string} path
             */
            get(path) {
                return this.service.getConfig(path);
            }

            /**
             * @return {ConfigService.IFeeConfig}
             */
            getFeeConfig() {
                return this.service.getFeeConfig();
            }

        }

        return new ConfigService();
    };

    factory.$inject = ['Base', 'createPoll'];

    angular.module('app').factory('configService', factory);
})();

/**
 * @name ConfigService
 */

/**
 * @typedef {object} ConfigService#IConfig
 * @property {object} PERMISSIONS
 * @property {object} SETTINGS
 * @property {boolean} SERVICE_TEMPORARILY_UNAVAILABLE
 * @property {object} SETTINGS.DEX
 * @property {Array<string>} SETTINGS.DEX.WATCH_LIST_PAIRS
 */

/**
 * @typedef {object} ConfigService#IFeeConfig
 * @property {BigNumber} smart_asset_extra_fee
 * @property {BigNumber} smart_account_extra_fee
 * @property {Record<number, Partial<ConfigService.IFeeItem>> & {default: ConfigService.IFeeItem}} calculate_fee_rules
 */

/**
 * @typedef {object} ConfigService#IFeeItem
 * @property {boolean} add_smart_asset_fee
 * @property {boolean} add_smart_account_fee
 * @property {BigNumber} min_price_step
 * @property {BigNumber} fee
 */
<|endoftext|>


<commit_before>angular.module('sfSense', ['ionic'])

.factory('googleMaps', function(){
  // OVERVIEW
  // 1. setup map options
  // 2. create map
  // 3. get data for crime lat and lng
  // 4. create markers
  // 5. add markers to map
  var map;
  var markers = {};
  var filterOn = 'all';

  var iconPath = '../www/img/icons/markers/';

  // TODO: add marker img for each category
  var markerImg = {
    'theft': 'theft.png',
    'assault': 'assult.png',
    'other': 'other.png'
  };

  var createMarker = function(crime) {

    if (markers[crime.id] === undefined) { // If crime isn't displayed yet add it
      var latlng = new google.maps.LatLng(crime.latitude,crime.longitude);

      var icon;

      if(markerImg[crime.type]){
        icon = iconPath + markerImg[crime.type];
      } else {
        icon = iconPath + markerImg.other;
      }

      var newMarker = new google.maps.Marker({
        position: latlng,
        animation: google.maps.Animation.DROP,
        title: crime.category,
        icon: icon,
        id: crime.id,
        category: crime.category,
        date: crime.date,
        time: crime.time,
        description: crime.descript,
        type: crime.type.toLowerCase()
      });

      // Check if the marker should be displayed or not
      if (filterOn === 'all' || filterOn === crime.type) {
        newMarker.setMap(map);
      }
      // Make a new InfoWindow and associate it to the marker
      newMarker.info = new google.maps.InfoWindow({
        content: '
' + newMarker.description + '
',
        maxWidth: 100
      });
      // Add the map listener here
      google.maps.event.addListener(newMarker, 'mouseover', function(){
        // Close all open crime info windows first
        for (var crimeID in markers) {
          markers[crimeID].info.close();
        }
        // Open the pertinent info window
        newMarker.info.open(map, newMarker);
      });
      markers[crime.id] = newMarker; // Add it to the markers object
    }
  };

  return {

    // Add in google maps functions here

    createMap: function(lat, lng){
      var mapOptions = {
        center: new google.maps.LatLng(lat, lng),
        zoom: 17,
        panControl: false,
        zoomControl: false,
        mapTypeControl: false,
        streetViewControl: false,
        overviewMapControl: false,
        // Begin map options to fix pinch displaying info windows bug
        maxZoom: 17,
        minZoom: 17,
        scrollwheel: false
      };

      map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);
    },

    // CreateMap is not invoked yet so we define addListener to add the event listener
    // Once the map has been created, call addListener
    // Moving method outside of the create call allows access to other services in googleMaps
    // There will only be one map variable so accept only the event and function
    addListener: function(event, func) {
      google.maps.event.addListener(map, event, func);
    },

    createMarkers: function(crimes) {
      for(var i = 0; i < crimes.length; i++){
        createMarker(crimes[i]);
      }
    },

    moveTo: function(lat, lng){
      var latlng = new google.maps.LatLng(lat,lng);
      map.panTo(latlng);
    },

    getCenter: function() {
      // Returns latlng object with lat() and lng() methods
      return map.getCenter();
    },

    searchLocByAddress: function(address, cb) {
      var city = 'san francisco';
      var re = RegExp(city, 'i');

      if(!re.exec(address)){
        address = address + ' ' + city;
      }

      geocoder = new google.maps.Geocoder();

      geocoder.geocode( {'address': address}, function(results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
          map.setCenter(results[0].geometry.location);

          cb(results[0].geometry.location.k, results[0].geometry.location.A);

        } else {
          navigator.notification.alert('Error with find location: ' + status);
        }
      });
    },

    searchLoc: function(lat, lng, cb){
      var latlng = new google.maps.LatLng(lat,lng);
      map.setCenter(latlng);
      cb(lat, lng);
    },

    filterBy: function(filter){
      filterOn = filter;
      // The filter will match the type field on the markers
      for (var markerID in markers) {

        var marker = markers[markerID];
        var cat = marker.type;

        // Check if the filter is all, if so, show all markers
        if (filter === 'all') {
          marker.setMap(map);
        } else { // A filter was specified
          if (filter === cat) {
            marker.setMap(map);
          } else {
            marker.setMap(null);
          }
        }
      }
    }
  };
})

.factory('loaderService', function($rootScope, $ionicLoading) {
  return {
    show : function() {
      $ionicLoading.show({

        // The text to display in the loading indicator
        content: ' Loading',

        // The animation to use
        animation: 'fade-in',

        // Will a dark overlay or backdrop cover the entire view
        showBackdrop: true,

        // The maximum width of the loading indicator
        // Text will be wrapped if longer than maxWidth
        maxWidth: 200,

        // The delay in showing the indicator
        showDelay: 10
      });
    },

    hide : function(){
      $ionicLoading.hide();
    }
  }
})

.controller('MapCtrl', function($scope, $http, googleMaps, loaderService){

  // add an event listener to update the map location on resume
  document.addEventListener("resume", function(){$scope.resume();}, false);

  $scope.filters = ['other', 'assault', 'theft'];
  $scope.searchClear = true;

  var hideKeyboard = function() {
    document.activeElement.blur();
    $("input").blur();
  };

  var init = function() {
    // SF center lat and lng
    var lat = 37.783522;
    var lng = -122.408964;

    googleMaps.createMap(lat, lng);
    $scope.getCrimes(lat, lng);

    // After map has been created, add listeners here
    googleMaps.addListener('dragend', function(){
      // Get the lng and lat and call getCrimes with them
      var newCenter = googleMaps.getCenter();
      $scope.getCrimes(newCenter.lat(), newCenter.lng());
    });

    $scope.trackLocation(pushAlert);
  };

  $scope.resume = function() {
    $scope.gpsSearchCrime();
  };

  $scope.gpsSearchCrime = function() {

    var onSuccess = function(pos){
      var lat = pos.coords.latitude;
      var lng = pos.coords.longitude;
      googleMaps.searchLoc(lat, lng, $scope.getCrimes);
    };

    var onError = function(error) {
      navigator.notification.alert('Code: ' + error.code + '\n' + 'Message:' + error.message);
    };

    navigator.geolocation.getCurrentPosition(onSuccess, onError);
  };

  $scope.getCrimes = function(lat, lng, cb){

    loaderService.show();

    var url = "http://sf-sense-server.herokuapp.com/near?longitude=" + lng + "&latitude="+ lat + "&distance=0.3";

    $http({
      headers: {
      "Authorization" : "Basic " + btoa("sf-sense:858F8CDDB1F324A762DBEFDC77844")
      },
      url: url,
      dataType: 'json',
      method: "GET"
    }).success(function(response){
      if (!cb){
        googleMaps.createMarkers(response);
        loaderService.hide();
        pushAlert(response);
      } else {
        cb(response);
      }
    }).error(function(error){
      loaderService.hide();
      navigator.notification.alert('There was an error: ' + error);
    });
  };

  $scope.searchCrime = function() {
    // $scope.mapSearch is a street address
    // On success, calls getCrimes with the lat/lng
    // If map search if undefined use current location. Placeholder is current location.
    if(!$scope.mapSearch) {
      $scope.gpsSearchCrime();
    } else {
      googleMaps.searchLocByAddress($scope.mapSearch, $scope.getCrimes);
    }

    searchHide();
  };

  $scope.filterBy = function (filterArg) {
    filterArg = filterArg || 'all';
    googleMaps.filterBy(filterArg);
  };

  var pushAlert = function(incidents) {

    var alertMessage = function(incidentsOcc) {
      if (incidentsOcc.assault > 1 && incidentsOcc.theft > 2) {
        return "Warning! You are entering an area where assaults and thefts have been reported";
      } else if (incidentsOcc.assault > 1) {
        return "Warning! You are entering an area where assaults have been reported";
      } else if (incidentsOcc.theft > 2) {
        return "Warning! You are entering an area where thefts have been reported";
      }
    };

    var incidentsOcc = {
      assault : 0,
      theft : 0,
      other : 0
    };

    for (var i = 0; i < incidents.length; i++) {
      incidentsOcc[incidents[i].type]++;
    }

    if (incidentsOcc.assault > 1 || incidentsOcc.theft > 2) {
      window.plugin.notification.local.add({ message: alertMessage(incidentsOcc) });
    }
  };

  $scope.trackLocation = function(onSuccessCallback) {
    var bgGeo = window.plugins.backgroundGeoLocation;

    var onSuccess = function(pos) {
      var lat = pos.latitude;
      var lng = pos.longitude;

      $scope.getCrimes(lat, lng, function(crimes){
        onSuccessCallback(crimes);
      });
    };

    var onError = function(error) {
      navigator.notification.alert('Code: ' + error.code + '\n' + 'Message:' + error.message);
    };

    bgGeo.configure(onSuccess, onError, {
      desiredAccuracy: 1,
      stationaryRadius: 1,
      distanceFilter: 1,
      // debug: true // <-- enable this hear sounds for background-geolocation life-cycle.
    });

    bgGeo.start();
  };

  $scope.onFocusSearch = function(){
    $scope.searchClear = false;
  };

  $scope.clearSearch = function(){
    $scope.mapSearch = '';
  };

  var searchHide = function(){
    $scope.searchClear = true;
    hideKeyboard();
  };

  $scope.cancelSearch = function(){
    searchHide();
    hideKeyboard();
  };

  init();
})

.run(function($ionicPlatform) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      StatusBar.styleDefault();
    }
  });
});

<commit_msg>customized alert notifications<commit_after>angular.module('sfSense', ['ionic'])

.factory('googleMaps', function(){
  // OVERVIEW
  // 1. setup map options
  // 2. create map
  // 3. get data for crime lat and lng
  // 4. create markers
  // 5. add markers to map
  var map;
  var markers = {};
  var filterOn = 'all';

  var iconPath = '../www/img/icons/markers/';

  // TODO: add marker img for each category
  var markerImg = {
    'theft': 'theft.png',
    'assault': 'assult.png',
    'other': 'other.png'
  };

  var createMarker = function(crime) {

    if (markers[crime.id] === undefined) { // If crime isn't displayed yet add it
      var latlng = new google.maps.LatLng(crime.latitude,crime.longitude);

      var icon;

      if(markerImg[crime.type]){
        icon = iconPath + markerImg[crime.type];
      } else {
        icon = iconPath + markerImg.other;
      }

      var newMarker = new google.maps.Marker({
        position: latlng,
        animation: google.maps.Animation.DROP,
        title: crime.category,
        icon: icon,
        id: crime.id,
        category: crime.category,
        date: crime.date,
        time: crime.time,
        description: crime.descript,
        type: crime.type.toLowerCase()
      });

      // Check if the marker should be displayed or not
      if (filterOn === 'all' || filterOn === crime.type) {
        newMarker.setMap(map);
      }
      // Make a new InfoWindow and associate it to the marker
      newMarker.info = new google.maps.InfoWindow({
        content: '
' + newMarker.description + '
',
        maxWidth: 100
      });
      // Add the map listener here
      google.maps.event.addListener(newMarker, 'mouseover', function(){
        // Close all open crime info windows first
        for (var crimeID in markers) {
          markers[crimeID].info.close();
        }
        // Open the pertinent info window
        newMarker.info.open(map, newMarker);
      });
      markers[crime.id] = newMarker; // Add it to the markers object
    }
  };

  return {

    // Add in google maps functions here

    createMap: function(lat, lng){
      var mapOptions = {
        center: new google.maps.LatLng(lat, lng),
        zoom: 17,
        panControl: false,
        zoomControl: false,
        mapTypeControl: false,
        streetViewControl: false,
        overviewMapControl: false,
        // Begin map options to fix pinch displaying info windows bug
        maxZoom: 17,
        minZoom: 17,
        scrollwheel: false
      };

      map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);
    },

    // CreateMap is not invoked yet so we define addListener to add the event listener
    // Once the map has been created, call addListener
    // Moving method outside of the create call allows access to other services in googleMaps
    // There will only be one map variable so accept only the event and function
    addListener: function(event, func) {
      google.maps.event.addListener(map, event, func);
    },

    createMarkers: function(crimes) {
      for(var i = 0; i < crimes.length; i++){
        createMarker(crimes[i]);
      }
    },

    moveTo: function(lat, lng){
      var latlng = new google.maps.LatLng(lat,lng);
      map.panTo(latlng);
    },

    getCenter: function() {
      // Returns latlng object with lat() and lng() methods
      return map.getCenter();
    },

    searchLocByAddress: function(address, cb) {
      var city = 'san francisco';
      var re = RegExp(city, 'i');

      if(!re.exec(address)){
        address = address + ' ' + city;
      }

      geocoder = new google.maps.Geocoder();

      geocoder.geocode( {'address': address}, function(results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
          map.setCenter(results[0].geometry.location);

          cb(results[0].geometry.location.k, results[0].geometry.location.A);

        } else {
          navigator.notification.alert('Error with find location: ' + status);
        }
      });
    },

    searchLoc: function(lat, lng, cb){
      var latlng = new google.maps.LatLng(lat,lng);
      map.setCenter(latlng);
      cb(lat, lng);
    },

    filterBy: function(filter){
      filterOn = filter;
      // The filter will match the type field on the markers
      for (var markerID in markers) {

        var marker = markers[markerID];
        var cat = marker.type;

        // Check if the filter is all, if so, show all markers
        if (filter === 'all') {
          marker.setMap(map);
        } else { // A filter was specified
          if (filter === cat) {
            marker.setMap(map);
          } else {
            marker.setMap(null);
          }
        }
      }
    }
  };
})

.factory('loaderService', function($rootScope, $ionicLoading) {
  return {
    show : function() {
      $ionicLoading.show({

        // The text to display in the loading indicator
        content: ' Loading',

        // The animation to use
        animation: 'fade-in',

        // Will a dark overlay or backdrop cover the entire view
        showBackdrop: true,

        // The maximum width of the loading indicator
        // Text will be wrapped if longer than maxWidth
        maxWidth: 200,

        // The delay in showing the indicator
        showDelay: 10
      });
    },

    hide : function(){
      $ionicLoading.hide();
    }
  }
})

.controller('MapCtrl', function($scope, $http, googleMaps, loaderService){

  // add an event listener to update the map location on resume
  document.addEventListener("resume", function(){$scope.resume();}, false);

  $scope.filters = ['other', 'assault', 'theft'];
  $scope.searchClear = true;

  var hideKeyboard = function() {
    document.activeElement.blur();
    $("input").blur();
  };

  var init = function() {
    // SF center lat and lng
    var lat = 37.783522;
    var lng = -122.408964;

    googleMaps.createMap(lat, lng);
    $scope.getCrimes(lat, lng);

    // After map has been created, add listeners here
    googleMaps.addListener('dragend', function(){
      // Get the lng and lat and call getCrimes with them
      var newCenter = googleMaps.getCenter();
      $scope.getCrimes(newCenter.lat(), newCenter.lng());
    });

    $scope.trackLocation(pushAlert);
  };

  $scope.resume = function() {
    $scope.gpsSearchCrime();
  };

  $scope.gpsSearchCrime = function() {

    var onSuccess = function(pos){
      var lat = pos.coords.latitude;
      var lng = pos.coords.longitude;
      googleMaps.searchLoc(lat, lng, $scope.getCrimes);
    };

    var onError = function(error) {
      navigator.notification.alert('Code: ' + error.code + '\n' + 'Message:' + error.message);
    };

    navigator.geolocation.getCurrentPosition(onSuccess, onError);
  };

  $scope.getCrimes = function(lat, lng, cb){

    loaderService.show();

    var url = "http://sf-sense-server.herokuapp.com/near?longitude=" + lng + "&latitude="+ lat + "&distance=0.3";

    $http({
      headers: {
      "Authorization" : "Basic " + btoa("sf-sense:858F8CDDB1F324A762DBEFDC77844")
      },
      url: url,
      dataType: 'json',
      method: "GET"
    }).success(function(response){
      if (!cb){
        googleMaps.createMarkers(response);
        loaderService.hide();
      } else {
        cb(response);
      }
    }).error(function(error){
      loaderService.hide();
      navigator.notification.alert('There was an error: ' + error);
    });
  };

  $scope.searchCrime = function() {
    // $scope.mapSearch is a street address
    // On success, calls getCrimes with the lat/lng
    // If map search if undefined use current location. Placeholder is current location.
    if(!$scope.mapSearch) {
      $scope.gpsSearchCrime();
    } else {
      googleMaps.searchLocByAddress($scope.mapSearch, $scope.getCrimes);
    }

    searchHide();
  };

  $scope.filterBy = function (filterArg) {
    filterArg = filterArg || 'all';
    googleMaps.filterBy(filterArg);
  };

  var pushAlert = function(incidents) {

    var alertMessage = function(incidentsOcc) {
      if (incidentsOcc.assault > 1 && incidentsOcc.theft > 2) {
        return "Warning! You are entering an area where assaults and thefts have been reported";
      } else if (incidentsOcc.assault > 1) {
        return "Warning! You are entering an area where assaults have been reported";
      } else if (incidentsOcc.theft > 2) {
        return "Warning! You are entering an area where thefts have been reported";
      }
    };

    var incidentsOcc = {
      assault : 0,
      theft : 0,
      other : 0
    };

    for (var i = 0; i < incidents.length; i++) {
      incidentsOcc[incidents[i].type]++;
    }

    if (incidentsOcc.assault > 1 || incidentsOcc.theft > 2) {
      window.plugin.notification.local.add({ message: alertMessage(incidentsOcc) });
    }
  };

  $scope.trackLocation = function(onSuccessCallback) {
    var bgGeo = window.plugins.backgroundGeoLocation;

    var onSuccess = function(pos) {
      var lat = pos.latitude;
      var lng = pos.longitude;

      $scope.getCrimes(lat, lng, function(crimes){
        onSuccessCallback(crimes);
      });
    };

    var onError = function(error) {
      navigator.notification.alert('Code: ' + error.code + '\n' + 'Message:' + error.message);
    };

    bgGeo.configure(onSuccess, onError, {
      desiredAccuracy: 1,
      stationaryRadius: 1,
      distanceFilter: 1,
      // debug: true // <-- enable this hear sounds for background-geolocation life-cycle.
    });

    bgGeo.start();
  };

  $scope.onFocusSearch = function(){
    $scope.searchClear = false;
  };

  $scope.clearSearch = function(){
    $scope.mapSearch = '';
  };

  var searchHide = function(){
    $scope.searchClear = true;
    hideKeyboard();
  };

  $scope.cancelSearch = function(){
    searchHide();
    hideKeyboard();
  };

  init();
})

.run(function($ionicPlatform) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      StatusBar.styleDefault();
    }
  });
});

<|endoftext|>


<commit_before>/**
 * @file 时间选择组件
 * @author Brian Li
 * @author Han Bing Feng
 * @email lbxxlht@163.com
 * @version 0.0.2
 */

define(function (require) {
    let React = require('react');
    let _ = require('underscore');

    let InputWidgetBase = require('./mixins/InputWidgetBase');
    let InputWidgetInForm = require('./mixins/InputWidgetInForm');

    let util = require('./core/util');
    let tools = require('./core/scheduleTools');
    let language = require('./core/language').schedule;

    let CheckBox = require('./CheckBox.jsx');

    return React.createClass({
        propTypes: {
            /**
             * 是否使用列选择器。
             */
            enableColumnSelector: React.PropTypes.bool,
            /**
             * 是否使用行选择器。
             */
            enableRowSelector: React.PropTypes.bool,
            /**
             * 选定的时段值。
             * 是一个有7x24元素的数组JSON.stringify后的值。日优先存放一星期每天24小时的时段选择情况。
             * 每个元素可为null，或者一个string。
             * 当为null时，表示该时段没有被选择。
             * 当为string时，表示该时段被选择，string的内容为当前时段的label。
             * 相邻时段相同值的label会被合并。
             * 若label为空串（''），则显示默认label。默认为时段跨度。如1:00-2:00
             */
            value: React.PropTypes.string,
            /**
             * 划出了一个新的时段块时的回调。
             * 回调可通过e.preventDefault()阻止更新发生。此时组件会通过设置
             * isPreventMouseEvent state来阻止后续鼠标事件响应。
             * 使用方做适当处理后，需设置isPreventMouseEvent为false来恢复鼠标响应。
             * @param {SyntheticEvent} onScheduleSelected.e 鼠标mouseup事件
             * @param {number} onScheduleSelected.startHour
             * @param {number} onScheduleSelected.startWeekday
             * @param {number} onScheduleSelected.endHour
             * @param {number} onScheduleSelected.endWeekday
             * @param {HTMLElement} onScheduleSelected.cursorDom 鼠标拖拽所产生的drag layer的dom节点
             */
            onScheduleSelected: React.PropTypes.func,
            /**
             * 绘制悬浮层
             * @return {ReactElement} 悬浮层Element
             */
            titleLayerRenderer: React.PropTypes.func,
            /**
             * 预设的labels列表。
             * @param {string} presetLables[].value 同value定义
             */
            presetLabels: React.PropTypes.arrayOf(React.PropTypes.shape({
                style: React.PropTypes.object,
                value: React.PropTypes.string,
                name: React.PropTypes.string
            })),
            /**
             * 显示在schedule顶部的info中的文字
             */
            infoText: React.PropTypes.string,
            isShowLegend: React.PropTypes.bool
        },
        // @override
        mixins: [InputWidgetBase, InputWidgetInForm],
        // @override
        getDefaultProps() {
            return {
                className: '',
                disabled: false,
                /**
                 * 快捷按钮配置
                 * 上方的快捷按钮配置，按钮的一切都由外部导入，包括处理函数，元素格式如下：
                 * {label: '全部', getValues: function (currentValue) {return '';}}
                 */
                shortCut: [],
                valueTemplate: '',
                enableColumnSelector: false,
                enableRowSelector: false,
                onScheduleSelected: _.noop,
                infoText: '',
                isShowLegend: false
            };
        },
        // @override
        getInitialState() {
            return {
                mouseDownX: -1,
                mouseDownY: -1,
                mouseCurrentX: -1,
                mouseCurrentY: -1,
                isPreventMouseEvent: false
            };
        },
        optDownHandler(e) {
            if (this.props.disabled) {
                return;
            }
            if (this.state.isPreventMouseEvent) {
                return;
            }
            let pos = util.getDOMPosition(this.refs.optArea);
            this.setState({
                mouseDownX: e.clientX - pos.x,
                mouseDownY: e.clientY - pos.y
            });
        },
        optMoveHandler(e) {
            if (this.props.disabled) {
                return;
            }
            if (this.state.isPreventMouseEvent) {
                return;
            }
            let pos = util.getDOMPosition(this.refs.optArea);
            this.setState({
                mouseCurrentX: e.clientX - pos.x,
                mouseCurrentY: e.clientY - pos.y
            });
        },
        optUpHandler(e) {
            if (this.props.disabled) {
                return;
            }
            if (this.state.isPreventMouseEvent) {
                return;
            }
            let scheduleRange = tools.getScheduleRangeByMouse(this.state);
            e.target = this.refs.container;
            e.target.value = tools.updateValueByMouse(this.___getValue___(), this.state);
            this.props.onScheduleSelected(
                e,
                scheduleRange.startHour,
                scheduleRange.endHour,
                scheduleRange.startWeekday,
                scheduleRange.endWeekday,
                this.refs.cursor
            );
            if (e.isDefaultPrevented()) {
                this.setState({isPreventMouseEvent: true});
                return;
            }
            this.___dispatchChange___(e);
            this.setState({mouseDownX: -1, mouseDownY: -1});
        },
        optLeaveHandler(e) {
            if (this.props.disabled) {
                return;
            }
            if (this.state.isPreventMouseEvent) {
                return;
            }
            this.setState({
                mouseDownX: -1,
                mouseDownY: -1,
                mouseCurrentX: -1,
                mouseCurrentY: -1
            });
        },
        columnSelectorHandler(e) {
            if (this.props.disabled) {
                return;
            }
            let i = util.getDataset(e.target).uiCmd * 1;
            let value = this.___getValue___();
            let selected = tools.selectedCount(value, {x: i, y: 0}, {x: i, y: 6});
            e.target = this.refs.container;
            e.target.value = tools.updateValueByAxis(value, {x: i, y: 0}, {x: i, y: 6}, selected === 7 ? null : '');
            this.___dispatchChange___(e);
        },
        rowSelectorHandler(e) {
            if (this.props.disabled) {
                return;
            }
            let i = e.target.value * 1;
            let v = e.target.checked;
            let value = this.___getValue___();
            e.target = this.refs.container;
            e.target.value = tools.updateValueByAxis(value, {x: 0, y: i}, {x: 24, y: i}, v ? '' : null);
            this.___dispatchChange___(e);
        },
        shortCutClickHandler(e) {
            if (this.props.disabled) {
                return;
            }
            let i = util.getDataset(e.target).uiCmd * 1;
            e.target = this.refs.container;
            e.target.value = this.props.shortCut[i].getValues(this.___getValue___());
            this.___dispatchChange___(e);
        },

        /**
         * 混合presets label，得到最终的value。
         *
         * @return {string} 混合后的value
         */
        getFinalValue: function () {
            let value = this.___getValue___();
            if (this.props.presetLabels == null) {
                return value;
            }

            let maxLength = 0;
            let labels = _.map(this.props.presetLabels.concat({
                style: null,
                name: null,
                value: value
            }), function (item) {
                let v;
                try {
                    v = JSON.parse(item.value);
                }
                catch (e) {
                    v = [];
                }
                if (v.length > maxLength) {
                    maxLength = v.length;
                }
                return {
                    style: item.style,
                    name: item.name,
                    value: v
                };
            });

            let res = [];
            for (let i = 0; i < maxLength; i++) {
                _.each(labels, function (label) {
                    if (label.value[i] != null) {
                        res[i] = label.style == null ? label.value[i] : label.style;
                    }
                });
            }
            return JSON.stringify(res);
        },
        renderTitleLayer: function () {
            if (this.props.titleLayerRenderer) {
                let Renderer = this.props.titleLayerRenderer;
                return <Renderer value={this.getFinalValue()} {...this.props} {...this.state} />;
            }

            let cAxis = tools.gridAxis(this.state.mouseCurrentX, this.state.mouseCurrentY);
            let titleProp = {
                style: tools.titleLayerSize(cAxis, this.state.mouseDownX > -1 || this.state.mouseCurrentX < 0)
            };

            return (

{cAxis.x + ':00 - ' + cAxis.x + ':59'}

{language.dragAble}

            );
        },
        renderLegend: function () {
            if (!this.props.isShowLegend) {
                return '';
            }

            return (

                    已选
                    {
                        this.props.presetLabels == null
                        ? ''
                        : (
                            _.map(this.props.presetLabels, function (label) {
                                return (

                                        {label.name}

                                );
                            })
                        )
                    }

                    未投放

            )
        },
        render() {
            let value = this.getFinalValue();
            let dragLayerProp = {
                onMouseDown: this.optDownHandler,
                onMouseUp: this.optUpHandler,
                onMouseMove: this.optMoveHandler,
                onMouseLeave: this.optLeaveHandler
            };
            /**
             * 操作区由5层组成，从下到上依次是：
             * 1.网格层，不透明
             * 2.选中时间展示层，半透明，蓝色
             * 3.虚拟光标层，半透明，黄色，蓝边框
             * 4.鼠标信息显示层，不透明，白色
             * 5.鼠标事件响应层，透明
             */
            return (

{this.props.infoText}

                        {this.renderLegend()}

                        {shortCutFactory(this)}

                    {
                        this.props.enableColumnSelector ? (

{columnSelectorFactory(this, value)}

                        ) : ''
                    }

{gridFactory(this)}

{labelFactory(value)}

                        {this.renderTitleLayer()}

                        {rowSelectorFactory(this, value)}

{axisFactory()}

            );
        }
    });

    function axisFactory() {
        let doms = [];
        for (let i = 0; i <= 24; i++) {
            doms.push(
{i}
);
        }
        return doms;
    }

    function gridFactory(me) {
        let grid = [];
        for (let i = 0; i < 7; i++) {
            for (let j = 0; j < 24; j++) {
                let props = {
                    key: j + '-' + i,
                    style: {left: j * 24, top: i * 24}
                };
                grid.push(

);
            }
        }
        return grid;
    }

    function labelFactory(value) {
        value = tools.parseValue(value);
        let doms = [];
        for (let i = 0; i < value.length; i++) {
            let labels = tools.value2label(value[i]);
            for (let j = 0; j < labels.length; j++) {
                let label = labels[j];
                let style = {
                    top: i * 24 + 1,
                    left: label.begin * 24 + 1,
                    height: 23,
                    width: (label.end - label.begin + 1) * 24
                };
                let text;
                if (typeof label.value === 'object') {
                    text = '';
                    style = _.extend(style, label.value);
                }
                else {
                    text = label.value === '' ? tools.value2text(label.begin, label.end) : label.value;
                }
                let key = 'label-' + i + '-' + j;
                let prop = {
                    key: key,
                    style: style,
                    ref: key
                };
                doms.push(
{text}
);
            }
        }
        return doms.length > 0 ? doms : '';
    }

    function columnSelectorFactory(me, value) {
        let doms = [];
        for (let i = 0; i < 24; i++) {
            let prop = {
                'className': 'font-icon font-icon-largeable-arrow-down',
                'data-ui-cmd': i,
                'key': i,
                'onClick': me.columnSelectorHandler
            };
            let selected = tools.selectedCount(value, {x: i, y: 0}, {x: i, y: 6});
            if (selected > 0) {
                prop.style = {color: '#3E3E3E'};
            }
            if (selected === 7) {
                prop.style = {color: '#2F82F5'};
            }
            doms.push(

);
        }
        return doms;
    }

    function rowSelectorFactory(me, value) {
        let doms = [];
        if (me.props.enableRowSelector) {
            for (let i = 0; i < 7; i++) {
                let selected = tools.selectedCount(value, {x: 0, y: i}, {x: 24, y: i});
                let prop = {
                    label: language.day[i],
                    labelPosition: 'right',
                    key: i,
                    value: i,
                    indeterminate: selected > 0,
                    checked: selected === 24,
                    style: {top: i * 24},
                    onChange: me.rowSelectorHandler
                };
                doms.push(<CheckBox {...prop} />);
            }
            return doms;
        }

        for (let i = 0; i < 7; i++) {
            doms.push(
{language.day[i]}
);
        }
        return doms;
    }

    function shortCutFactory(me) {
        let shortCut = me.props.shortCut;
        if (!(shortCut instanceof Array) || shortCut.length === 0) {
            return '';
        }
        let doms = [];
        for (let i = 0; i < shortCut.length; i++) {
            let props = {
                'key': 'shortcut-' + i,
                'data-ui-cmd': i,
                'onClick': me.shortCutClickHandler
            };
            doms.push(
{shortCut[i].label}
);
        }
        return doms;
    }
});
<commit_msg>fix schedule renderLabel bug<commit_after>/**
 * @file 时间选择组件
 * @author Brian Li
 * @author Han Bing Feng
 * @email lbxxlht@163.com
 * @version 0.0.2
 */

define(function (require) {
    let React = require('react');
    let _ = require('underscore');

    let InputWidgetBase = require('./mixins/InputWidgetBase');
    let InputWidgetInForm = require('./mixins/InputWidgetInForm');

    let util = require('./core/util');
    let tools = require('./core/scheduleTools');
    let language = require('./core/language').schedule;

    let CheckBox = require('./CheckBox.jsx');

    return React.createClass({
        propTypes: {
            /**
             * 是否使用列选择器。
             */
            enableColumnSelector: React.PropTypes.bool,
            /**
             * 是否使用行选择器。
             */
            enableRowSelector: React.PropTypes.bool,
            /**
             * 选定的时段值。
             * 是一个有7x24元素的数组JSON.stringify后的值。日优先存放一星期每天24小时的时段选择情况。
             * 每个元素可为null，或者一个string。
             * 当为null时，表示该时段没有被选择。
             * 当为string时，表示该时段被选择，string的内容为当前时段的label。
             * 相邻时段相同值的label会被合并。
             * 若label为空串（''），则显示默认label。默认为时段跨度。如1:00-2:00
             */
            value: React.PropTypes.string,
            /**
             * 划出了一个新的时段块时的回调。
             * 回调可通过e.preventDefault()阻止更新发生。此时组件会通过设置
             * isPreventMouseEvent state来阻止后续鼠标事件响应。
             * 使用方做适当处理后，需设置isPreventMouseEvent为false来恢复鼠标响应。
             * @param {SyntheticEvent} onScheduleSelected.e 鼠标mouseup事件
             * @param {number} onScheduleSelected.startHour
             * @param {number} onScheduleSelected.startWeekday
             * @param {number} onScheduleSelected.endHour
             * @param {number} onScheduleSelected.endWeekday
             * @param {HTMLElement} onScheduleSelected.cursorDom 鼠标拖拽所产生的drag layer的dom节点
             */
            onScheduleSelected: React.PropTypes.func,
            /**
             * 绘制悬浮层
             * @return {ReactElement} 悬浮层Element
             */
            titleLayerRenderer: React.PropTypes.func,
            /**
             * 预设的labels列表。
             * @param {string} presetLables[].value 同value定义
             */
            presetLabels: React.PropTypes.arrayOf(React.PropTypes.shape({
                style: React.PropTypes.object,
                value: React.PropTypes.string,
                name: React.PropTypes.string
            })),
            /**
             * 显示在schedule顶部的info中的文字
             */
            infoText: React.PropTypes.string,
            isShowLegend: React.PropTypes.bool
        },
        // @override
        mixins: [InputWidgetBase, InputWidgetInForm],
        // @override
        getDefaultProps() {
            return {
                className: '',
                disabled: false,
                /**
                 * 快捷按钮配置
                 * 上方的快捷按钮配置，按钮的一切都由外部导入，包括处理函数，元素格式如下：
                 * {label: '全部', getValues: function (currentValue) {return '';}}
                 */
                shortCut: [],
                valueTemplate: '',
                enableColumnSelector: false,
                enableRowSelector: false,
                onScheduleSelected: _.noop,
                infoText: '',
                isShowLegend: false
            };
        },
        // @override
        getInitialState() {
            return {
                mouseDownX: -1,
                mouseDownY: -1,
                mouseCurrentX: -1,
                mouseCurrentY: -1,
                isPreventMouseEvent: false
            };
        },
        optDownHandler(e) {
            if (this.props.disabled) {
                return;
            }
            if (this.state.isPreventMouseEvent) {
                return;
            }
            let pos = util.getDOMPosition(this.refs.optArea);
            this.setState({
                mouseDownX: e.clientX - pos.x,
                mouseDownY: e.clientY - pos.y
            });
        },
        optMoveHandler(e) {
            if (this.props.disabled) {
                return;
            }
            if (this.state.isPreventMouseEvent) {
                return;
            }
            let pos = util.getDOMPosition(this.refs.optArea);
            this.setState({
                mouseCurrentX: e.clientX - pos.x,
                mouseCurrentY: e.clientY - pos.y
            });
        },
        optUpHandler(e) {
            if (this.props.disabled) {
                return;
            }
            if (this.state.isPreventMouseEvent) {
                return;
            }
            let scheduleRange = tools.getScheduleRangeByMouse(this.state);
            e.target = this.refs.container;
            e.target.value = tools.updateValueByMouse(this.___getValue___(), this.state);
            this.props.onScheduleSelected(
                e,
                scheduleRange.startHour,
                scheduleRange.endHour,
                scheduleRange.startWeekday,
                scheduleRange.endWeekday,
                this.refs.cursor
            );
            if (e.isDefaultPrevented()) {
                this.setState({isPreventMouseEvent: true});
                return;
            }
            this.___dispatchChange___(e);
            this.setState({mouseDownX: -1, mouseDownY: -1});
        },
        optLeaveHandler(e) {
            if (this.props.disabled) {
                return;
            }
            if (this.state.isPreventMouseEvent) {
                return;
            }
            this.setState({
                mouseDownX: -1,
                mouseDownY: -1,
                mouseCurrentX: -1,
                mouseCurrentY: -1
            });
        },
        columnSelectorHandler(e) {
            if (this.props.disabled) {
                return;
            }
            let i = util.getDataset(e.target).uiCmd * 1;
            let value = this.___getValue___();
            let selected = tools.selectedCount(value, {x: i, y: 0}, {x: i, y: 6});
            e.target = this.refs.container;
            e.target.value = tools.updateValueByAxis(value, {x: i, y: 0}, {x: i, y: 6}, selected === 7 ? null : '');
            this.___dispatchChange___(e);
        },
        rowSelectorHandler(e) {
            if (this.props.disabled) {
                return;
            }
            let i = e.target.value * 1;
            let v = e.target.checked;
            let value = this.___getValue___();
            e.target = this.refs.container;
            e.target.value = tools.updateValueByAxis(value, {x: 0, y: i}, {x: 24, y: i}, v ? '' : null);
            this.___dispatchChange___(e);
        },
        shortCutClickHandler(e) {
            if (this.props.disabled) {
                return;
            }
            let i = util.getDataset(e.target).uiCmd * 1;
            e.target = this.refs.container;
            e.target.value = this.props.shortCut[i].getValues(this.___getValue___());
            this.___dispatchChange___(e);
        },

        /**
         * 混合presets label，得到最终的value。
         *
         * @return {string} 混合后的value
         */
        getFinalValue: function () {
            let value = this.___getValue___();
            if (this.props.presetLabels == null) {
                return value;
            }

            let maxLength = 0;
            let labels = _.map(this.props.presetLabels.concat({
                style: null,
                name: null,
                value: value
            }), function (item) {
                let v;
                try {
                    v = JSON.parse(item.value);
                }
                catch (e) {
                    v = [];
                }
                if (v.length > maxLength) {
                    maxLength = v.length;
                }
                return {
                    style: item.style,
                    name: item.name,
                    value: v
                };
            });

            let res = [];
            for (let i = 0; i < maxLength; i++) {
                _.each(labels, function (label) {
                    if (label.value[i] != null) {
                        res[i] = label.style == null ? label.value[i] : label.style;
                    }
                });
            }
            return JSON.stringify(res);
        },
        renderTitleLayer: function () {
            if (this.props.titleLayerRenderer) {
                let Renderer = this.props.titleLayerRenderer;
                return <Renderer value={this.getFinalValue()} {...this.props} {...this.state} />;
            }

            let cAxis = tools.gridAxis(this.state.mouseCurrentX, this.state.mouseCurrentY);
            let titleProp = {
                style: tools.titleLayerSize(cAxis, this.state.mouseDownX > -1 || this.state.mouseCurrentX < 0)
            };

            return (

{cAxis.x + ':00 - ' + cAxis.x + ':59'}

{language.dragAble}

            );
        },
        renderLegend: function () {
            if (!this.props.isShowLegend) {
                return '';
            }

            let presetLabels = this.props.presetLabels;
            if (presetLabels == null) {
                return '';
            }

            // 按照name去重
            let names = {};
            presetLabels = _.filter(presetLabels, label => {
                if (label.name == null) {
                    return false;
                }
                if (names[label.name]) {
                    return false;
                }

                names[label.name] = true;

                return true;
            });

            return (

                    已选
                    {
                        _.map(presetLabels, label => {
                                return (

                                        {label.name}

                                );
                            })
                    }

                    未投放

            )
        },
        render() {
            let value = this.getFinalValue();
            let dragLayerProp = {
                onMouseDown: this.optDownHandler,
                onMouseUp: this.optUpHandler,
                onMouseMove: this.optMoveHandler,
                onMouseLeave: this.optLeaveHandler
            };
            /**
             * 操作区由5层组成，从下到上依次是：
             * 1.网格层，不透明
             * 2.选中时间展示层，半透明，蓝色
             * 3.虚拟光标层，半透明，黄色，蓝边框
             * 4.鼠标信息显示层，不透明，白色
             * 5.鼠标事件响应层，透明
             */
            return (

{this.props.infoText}

                        {this.renderLegend()}

                        {shortCutFactory(this)}

                    {
                        this.props.enableColumnSelector ? (

{columnSelectorFactory(this, value)}

                        ) : ''
                    }

{gridFactory(this)}

{labelFactory(value)}

                        {this.renderTitleLayer()}

                        {rowSelectorFactory(this, value)}

{axisFactory()}

            );
        }
    });

    function axisFactory() {
        let doms = [];
        for (let i = 0; i <= 24; i++) {
            doms.push(
{i}
);
        }
        return doms;
    }

    function gridFactory(me) {
        let grid = [];
        for (let i = 0; i < 7; i++) {
            for (let j = 0; j < 24; j++) {
                let props = {
                    key: j + '-' + i,
                    style: {left: j * 24, top: i * 24}
                };
                grid.push(

);
            }
        }
        return grid;
    }

    function labelFactory(value) {
        value = tools.parseValue(value);
        let doms = [];
        for (let i = 0; i < value.length; i++) {
            let labels = tools.value2label(value[i]);
            for (let j = 0; j < labels.length; j++) {
                let label = labels[j];
                let style = {
                    top: i * 24 + 1,
                    left: label.begin * 24 + 1,
                    height: 23,
                    width: (label.end - label.begin + 1) * 24
                };
                let text;
                if (typeof label.value === 'object') {
                    text = '';
                    style = _.extend(style, label.value);
                }
                else {
                    text = label.value === '' ? tools.value2text(label.begin, label.end) : label.value;
                }
                let key = 'label-' + i + '-' + j;
                let prop = {
                    key: key,
                    style: style,
                    ref: key
                };
                doms.push(
{text}
);
            }
        }
        return doms.length > 0 ? doms : '';
    }

    function columnSelectorFactory(me, value) {
        let doms = [];
        for (let i = 0; i < 24; i++) {
            let prop = {
                'className': 'font-icon font-icon-largeable-arrow-down',
                'data-ui-cmd': i,
                'key': i,
                'onClick': me.columnSelectorHandler
            };
            let selected = tools.selectedCount(value, {x: i, y: 0}, {x: i, y: 6});
            if (selected > 0) {
                prop.style = {color: '#3E3E3E'};
            }
            if (selected === 7) {
                prop.style = {color: '#2F82F5'};
            }
            doms.push(

);
        }
        return doms;
    }

    function rowSelectorFactory(me, value) {
        let doms = [];
        if (me.props.enableRowSelector) {
            for (let i = 0; i < 7; i++) {
                let selected = tools.selectedCount(value, {x: 0, y: i}, {x: 24, y: i});
                let prop = {
                    label: language.day[i],
                    labelPosition: 'right',
                    key: i,
                    value: i,
                    indeterminate: selected > 0,
                    checked: selected === 24,
                    style: {top: i * 24},
                    onChange: me.rowSelectorHandler
                };
                doms.push(<CheckBox {...prop} />);
            }
            return doms;
        }

        for (let i = 0; i < 7; i++) {
            doms.push(
{language.day[i]}
);
        }
        return doms;
    }

    function shortCutFactory(me) {
        let shortCut = me.props.shortCut;
        if (!(shortCut instanceof Array) || shortCut.length === 0) {
            return '';
        }
        let doms = [];
        for (let i = 0; i < shortCut.length; i++) {
            let props = {
                'key': 'shortcut-' + i,
                'data-ui-cmd': i,
                'onClick': me.shortCutClickHandler
            };
            doms.push(
{shortCut[i].label}
);
        }
        return doms;
    }
});
<|endoftext|>