<commit_before>var dbWorker = require('./db.js');
var path = require('path');
var appDir = path.resolve('./');
var bodyParser = require('body-parser');
var fs = require("fs");
var morgan = require('morgan');
var jwt = require('jwt-simple');
var app = require("./app").app;
var express = require("./app").express;
app.set('jwtTokenSecret', 'Big Beardy Beary Gnome');

app.use(bodyParser.json()); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
    extended: true
}));
//app.use(compression());
const accessLogStream = fs.createWriteStream(__dirname + '/server_logs.log', {
    flags: 'a'
});
app.use(require('skipper')());

var port = 8080;
var server = app.listen(port, function () {
    console.log(this.address());
});

const getRawToken = function (req) {
    var token = (req.body && req.body.access_token) || (req.query && req.query.access_token) || req.headers['x-access-token'];
    return token;
}

const getToken = function (req) {
    var token = getRawToken(req);
    if (token) {
        try {
            var decoded = jwt.decode(token, app.get('jwtTokenSecret'));
        } catch (err) {
            return "invalid token"
        }
    } else {
        return "no_token";
    }
    return decoded;
}

const checkToken = function (req, res, callback) {
    var token = getToken(req);
    if (token == "no_token" || token == "invalid token") {
        res.send(token);
        return;
    } else {
        if (token.exp <= Date.now()) {
            res.end('Access token has expired', 400);
            return;
        }
        if (token.userid == 1) {
            callback(req, res);
        }
    }
}

app.use(morgan(':method :url :response-time :remote-addr :date', {
    stream: accessLogStream
}));
var browser = require('file-manager-js');
app.all('/browse_url', browser.browse);
app.post('/upload_url', browser.upload);

app.get('/database/drop', function (req, res) {
    //dbWorker.schemaOps.drop();
    res.send("Don't even try")
});

app.get('/googlee*.html', function (req, res) {
    res.sendFile(appDir + "/node/googlee0b3bf85105efcd6.html");
})

app.get('/dbprepare',
    function (req, res) {
        dbWorker.schemaOps.createCrypto();
    });

var userSessions = {};

app.post("/checkSession", function (req, res) {
    req.setTimeout(0) // no timeout
    const token = getToken(req);
    if (token == "no_token" || token == "invalid token") {
        res.send("not logged in");
    } else {
        var intervalId = setInterval(() => {
            if (token.exp <= Date.now()) {
                res.send("session over");
                clearInterval(intervalId);
            }
        }, 1000);
        userSessions[JSON.stringify(token)] = ({
            endSession: () => {
                res.send("session over");
                clearInterval(intervalId);
            }
        });
    }
});

app.post("/endSession", function (req, res) {
    const token = getToken(req);
    const stringToken = JSON.stringify(token);
    if (token && userSessions[stringToken] && userSessions[stringToken].endSession) {
        userSessions[stringToken].endSession();
        userSessions[stringToken] = null;
        res.send("session ended");
    }
});

app.get('/database/create', function (req, res) {
    //dbWorker.schemaOps.createDb();
    res.send('Db created successfully(20 years ago)');
})

app.post('/admin/login', function (req, res) {
    dbWorker.entities['admin'].checkPassword(req.body, res);
});

app.post('/structure/update', function (req, res) {
    const callback = (req, res) => {
        var name = req.body;
        dbWorker.entities['structure'].updateStructure(req.body, res);
    };
    checkToken(req, res, callback);
});
app.get('/structure/get', function (req, res) {
    var result = dbWorker.entities['structure'].getStructure(res);
});
app.get('/data/viewsList', function (req, res) {
    dbWorker.entities['structure'].getViewsList(res);
});

app.post('/data/addArticle', function (req, res) {
    const callback = (req, res) => {
        var name = req.body;
        dbWorker.entities['articles'].create(req.body, res);
    };
    checkToken(req, res, callback);
});
app.post('/data/updateArticle', function (req, res) {
    const callback = (req, res) => {
        var name = req.body;
        dbWorker.entities['articles'].update(req.body, res);
    };
    checkToken(req, res, callback);
});
app.post('/data/updateArticleGrid', function (req, res) {
    const callback = (req, res) => {
        dbWorker.entities['articles'].updateGrid(req.body, res);
    };
    checkToken(req, res, callback);
});
app.get('/data/articles', function (req, res) {
    var result = dbWorker.entities['articles'].getAll(res);
});
app.get('/data/articles/:alias', function (req, res) {
    var result = dbWorker.entities['articles'].getSingleByAlias(req.params.alias, res);
});
app.get('/data/articles/byViewId/:id', function (req, res) {
    var result = dbWorker.entities['articles'].getSingleByViewId(req.params.id, res);
});
app.get('/data/articles/byViewAlias/:alias', function (req, res) {
    var result = dbWorker.entities['articles'].getSingleByViewAlias(req.params.alias, res);
});

app.use(function (err, req, res, next) {
    console.error(err.stack);
    res.status(500).send('Something broke!');
});

module.exports = app;
<commit_msg>robots<commit_after>var dbWorker = require('./db.js');
var path = require('path');
var appDir = path.resolve('./');
var bodyParser = require('body-parser');
var fs = require("fs");
var morgan = require('morgan');
var jwt = require('jwt-simple');
var app = require("./app").app;
var express = require("./app").express;
app.set('jwtTokenSecret', 'Big Beardy Beary Gnome');

app.use(bodyParser.json()); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
    extended: true
}));
//app.use(compression());
const accessLogStream = fs.createWriteStream(__dirname + '/server_logs.log', {
    flags: 'a'
});
app.use(require('skipper')());

var port = 8080;
var server = app.listen(port, function () {
    console.log(this.address());
});

const getRawToken = function (req) {
    var token = (req.body && req.body.access_token) || (req.query && req.query.access_token) || req.headers['x-access-token'];
    return token;
}

const getToken = function (req) {
    var token = getRawToken(req);
    if (token) {
        try {
            var decoded = jwt.decode(token, app.get('jwtTokenSecret'));
        } catch (err) {
            return "invalid token"
        }
    } else {
        return "no_token";
    }
    return decoded;
}

const checkToken = function (req, res, callback) {
    var token = getToken(req);
    if (token == "no_token" || token == "invalid token") {
        res.send(token);
        return;
    } else {
        if (token.exp <= Date.now()) {
            res.end('Access token has expired', 400);
            return;
        }
        if (token.userid == 1) {
            callback(req, res);
        }
    }
}

app.use(morgan(':method :url :response-time :remote-addr :date', {
    stream: accessLogStream
}));
var browser = require('file-manager-js');
app.all('/browse_url', browser.browse);
app.post('/upload_url', browser.upload);

app.get('/database/drop', function (req, res) {
    //dbWorker.schemaOps.drop();
    res.send("Don't even try")
});

app.get('/googlee*.html', function (req, res) {
    res.sendFile(appDir + "/node/googlee0b3bf85105efcd6.html");
})

app.get('/robots.txt', function (req, res) {
    res.sendFile(appDir + "/robots.txt");
})

app.get('/dbprepare',
    function (req, res) {
        dbWorker.schemaOps.createCrypto();
    });

var userSessions = {};

app.post("/checkSession", function (req, res) {
    req.setTimeout(0) // no timeout
    const token = getToken(req);
    if (token == "no_token" || token == "invalid token") {
        res.send("not logged in");
    } else {
        var intervalId = setInterval(() => {
            if (token.exp <= Date.now()) {
                res.send("session over");
                clearInterval(intervalId);
            }
        }, 1000);
        userSessions[JSON.stringify(token)] = ({
            endSession: () => {
                res.send("session over");
                clearInterval(intervalId);
            }
        });
    }
});

app.post("/endSession", function (req, res) {
    const token = getToken(req);
    const stringToken = JSON.stringify(token);
    if (token && userSessions[stringToken] && userSessions[stringToken].endSession) {
        userSessions[stringToken].endSession();
        userSessions[stringToken] = null;
        res.send("session ended");
    }
});

app.get('/database/create', function (req, res) {
    //dbWorker.schemaOps.createDb();
    res.send('Db created successfully(20 years ago)');
})

app.post('/admin/login', function (req, res) {
    dbWorker.entities['admin'].checkPassword(req.body, res);
});

app.post('/structure/update', function (req, res) {
    const callback = (req, res) => {
        var name = req.body;
        dbWorker.entities['structure'].updateStructure(req.body, res);
    };
    checkToken(req, res, callback);
});
app.get('/structure/get', function (req, res) {
    var result = dbWorker.entities['structure'].getStructure(res);
});
app.get('/data/viewsList', function (req, res) {
    dbWorker.entities['structure'].getViewsList(res);
});

app.post('/data/addArticle', function (req, res) {
    const callback = (req, res) => {
        var name = req.body;
        dbWorker.entities['articles'].create(req.body, res);
    };
    checkToken(req, res, callback);
});
app.post('/data/updateArticle', function (req, res) {
    const callback = (req, res) => {
        var name = req.body;
        dbWorker.entities['articles'].update(req.body, res);
    };
    checkToken(req, res, callback);
});
app.post('/data/updateArticleGrid', function (req, res) {
    const callback = (req, res) => {
        dbWorker.entities['articles'].updateGrid(req.body, res);
    };
    checkToken(req, res, callback);
});
app.get('/data/articles', function (req, res) {
    var result = dbWorker.entities['articles'].getAll(res);
});
app.get('/data/articles/:alias', function (req, res) {
    var result = dbWorker.entities['articles'].getSingleByAlias(req.params.alias, res);
});
app.get('/data/articles/byViewId/:id', function (req, res) {
    var result = dbWorker.entities['articles'].getSingleByViewId(req.params.id, res);
});
app.get('/data/articles/byViewAlias/:alias', function (req, res) {
    var result = dbWorker.entities['articles'].getSingleByViewAlias(req.params.alias, res);
});

app.use(function (err, req, res, next) {
    console.error(err.stack);
    res.status(500).send('Something broke!');
});

module.exports = app;
<|endoftext|>


<commit_before>angular.module('main')
.controller('porjectCtrl', ['$rootScope', '$scope', '$http', '$routeParams', '$mdDialog', '$mdToast',
	function($rootScope, $scope, $http, $routeParams, $mdDialog, $mdToast) {
		$scope.confirm = false;
		$scope.getUsers = function() {
			$http.get('/getUsers').success(function(users) {
				for (i in $scop.project.users) {
					var found = false;
					for (j in users) {
						if ($scop.project.users[i] === users[j]) {
							found = true;
						}
					}
					if (found) {
						$scope.allUsers.push(users);
					}
				}
			});
		};
		$scope.addUser = function(user) {
			$scope.project.users.push(user.email);
		}
		$scope.undoToolTip = function(node, removeNode, newSubItem) {
			//debugger;
			var tree = $.extend(true, [], $scope.project.children);
			removeNode(node);
			var toast = $mdToast.simple()
				.content('Node deleted')
				.action('UNDO')
				.highlightAction(false)
				.position($scope.getToastPosition());
			$mdToast.show(toast).then(function() {
				$scope.project.children = $.extend(true, [], tree);
			});
		};
		var project = {'heading': $routeParams.id};
		$http({method:'POST', url:'/project', data: project}).success(function(data) {
			$scope.project = data[0];
			$scope.tree = $scope.project.children;
			$scope.userIndex = -1;
			for (var u in $scope.project.users) {
				if ($scope.project.users[u] === $rootScope.currentUser) {
					$scope.userIndex = u;
				}
			}
			// UserIndex is -1 if user trying to estimate is not in users array
			if ($scope.userIndex === -1) {
				var toast = $mdToast.simple()
				.content('Not authorised to estimate')
				.action('')
				.highlightAction(false)
				.position($scope.getToastPosition());
				$mdToast.show(toast);
			}
			$scope.rootIsEmpty = function() {
				//debugger;
				if (typeof $scope.project.children !== undefined) {
					if ($scope.project.children.length < 1) {
						return true;
					}
				} else {
					return false;
				}
			};
		});
		$scope.saveProject = function() {
			$http({method:'POST', url:'/addChild', data: $scope.project}).success(function() {
				$mdToast.show(
					$mdToast.simple()
					.content('Project saved')
					.position($scope.getToastPosition())
					.hideDelay(3000)
			);
			});

		};

		$scope.addRootNode = function() {
			$scope.project.children.push({id: 'node', title:"Root Node", nodes: [], collapsed : false, estimations : []});
		};
		$scope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
			// Appending dialog to document.body to cover sidenav in docs app
			if (!$scope.confirm) {
				event.preventDefault();
				//console.log(newUrl);
				var confirm = $mdDialog.confirm()
				.parent(angular.element(document.body))
				.title('Are you sure you want to leave this page?')
				.content('All unsaved changes will be lost.')
				.ariaLabel('Yes')
				.ok('Yes')
				.cancel('No')
				.targetEvent(event);
				$mdDialog.show(confirm).then(function() {
				newUrl = newUrl.split('#');
				$scope.goTo(newUrl[1]);
				$scope.confirm = true;
				//$scope.saveProject();
			}, function() {

			});
			}

		});

		$scope.saveProjectDialog = function() {

		};

		$scope.removeUser = function(user) {
			for (i in $scope.project.users) {
				if ($scope.project.users[i] === user) {
					$scope.project.users.splice(i, 1);
				}
			}
		};

		$scope.moveLastToTheBeginning = function() {
			var a = $scope.data.pop();
			$scope.data.splice(0, 0, a);
		};

		$scope.newSubItem = function(scope) {
			var nodeData = scope.$modelValue;
			nodeData.nodes.push({
				id: nodeData.id * 10 + nodeData.nodes.length,
				title: nodeData.title + '.' + (nodeData.nodes.length + 1),
				nodes: [],
				collapsed : false,
				estimations : []
			});
		};

		$scope.collapseAll = function() {
			$scope.$broadcast('collapseAll');
		};

		$scope.expandAll = function() {
			$scope.$broadcast('expandAll');
		};

		$scope.getToastPosition = function() {
			return Object.keys($scope.toastPosition)
			.filter(function(pos) { return $scope.toastPosition[pos]; })
			.join(' ');
		};

		$scope.toastPosition = {
			bottom: true,
			top: false,
			left: false,
			right: true
		};

		$scope.estimate = function(node) {
			var currnode = $scope.project.children[0];
			var n;
			$scope.searchTree([currnode], node.$$hashKey, function(res) {
				n = res;
			});
			$scope.estimateForUser(n, node.qty);
		};

		$scope.estimateForUser = function(node, qty) {
			var user = $rootScope.currentUser;
			var count = 0;
			var found = false;
			for (var u in node.users) {
				if (node.users[u] === user) {
					found = true;
					count = u;
					break;
				}
			}
			if (!found) {
				node.users.push(user);
				node.estimations.push(qty);
			} else {
				node.estimations[count] = qty;
			}
		};

		$scope.searchTree = function(node, id, callback) {
			var found = false;
			var result;
			for (var i in node) {
				if (id === node[i].$$hashKey) {
					found = true;
					callback(node[i]);
				}
			}
			if (found === false) {
				for (i in node) {
					$scope.searchTree(node[i].nodes, id, callback);
				}
			}
		};

		$scope.currentNode = undefined;

		$scope.showDescriptionDialog = function(ev, node) {
			$scope.currentNode = node;

			$scope.setCurrentNode(node, function() {
				var newScope = $scope.$new();

				$mdDialog.show({
					controller: DialogController,
					templateUrl: '../../templates/dialogs/project_node_description.tmpl.html',
					parent: angular.element(document.body),
					targetEvent: ev,
					scope: newScope
				});
			});
		};

		$scope.setCurrentNode = function(node, callback) {
			$scope.currentNode = node;

			if (callback !== undefined) {
				callback();
			}
		};
	}]);

function DialogController($scope, $mdDialog) {
	$scope.hide = function() {
		$mdDialog.hide();
	};
	$scope.cancel = function() {
		$mdDialog.cancel();
	};
}
<commit_msg>add user is now working<commit_after>angular.module('main')
.controller('porjectCtrl', ['$rootScope', '$scope', '$http', '$routeParams', '$mdDialog', '$mdToast',
	function($rootScope, $scope, $http, $routeParams, $mdDialog, $mdToast) {
		$scope.confirm = false;
		$scope.allUsers = [];

		$scope.getUsers = function() {
			$http.get('/getUsers').success(function(users) {
				for (i in users) {
					var found = false;
					var j = 0;
					for (j in $scope.project.users) {
						if ($scope.project.users[i] === users[j].email) {

							found = true;
						}
					}
					//debugger;
					if (!found) {
						$scope.allUsers.push(users[i]);
					}
				}
				if ($scope.project.users.length === 0) {
					for (i in users) {
						$scope.allUsers.push(users[i]);
						//debugger;
					}
				}
			});
		};
		$scope.addUser = function(user) {
			$scope.project.users.push(user.email);
			for (i in $scope.allUsers) {
				if ($scope.allUsers[i].email === user.email) {
					$scope.allUsers.splice(i, 1);
				}
			}
		}
		$scope.undoToolTip = function(node, removeNode, newSubItem) {
			//debugger;
			var tree = $.extend(true, [], $scope.project.children);
			removeNode(node);
			var toast = $mdToast.simple()
				.content('Node deleted')
				.action('UNDO')
				.highlightAction(false)
				.position($scope.getToastPosition());
			$mdToast.show(toast).then(function() {
				$scope.project.children = $.extend(true, [], tree);
			});
		};
		var project = {'heading': $routeParams.id};
		$http({method:'POST', url:'/project', data: project}).success(function(data) {
			$scope.project = data[0];
			$scope.tree = $scope.project.children;
			$scope.userIndex = -1;
			for (var u in $scope.project.users) {
				if ($scope.project.users[u] === $rootScope.currentUser) {
					$scope.userIndex = u;
				}
			}
			// UserIndex is -1 if user trying to estimate is not in users array
			if ($scope.userIndex === -1) {
				var toast = $mdToast.simple()
				.content('Not authorised to estimate')
				.action('')
				.highlightAction(false)
				.position($scope.getToastPosition());
				$mdToast.show(toast);
			}
			$scope.rootIsEmpty = function() {
				//debugger;
				if (typeof $scope.project.children !== undefined) {
					if ($scope.project.children.length < 1) {
						return true;
					}
				} else {
					return false;
				}
			};
		});
		$scope.saveProject = function() {
			$http({method:'POST', url:'/addChild', data: $scope.project}).success(function() {
				$mdToast.show(
					$mdToast.simple()
					.content('Project saved')
					.position($scope.getToastPosition())
					.hideDelay(3000)
			);
			});

		};

		$scope.addRootNode = function() {
			$scope.project.children.push({id: 'node', title:"Root Node", nodes: [], collapsed : false, estimations : []});
		};
		$scope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
			// Appending dialog to document.body to cover sidenav in docs app
			if (!$scope.confirm) {
				event.preventDefault();
				//console.log(newUrl);
				var confirm = $mdDialog.confirm()
				.parent(angular.element(document.body))
				.title('Are you sure you want to leave this page?')
				.content('All unsaved changes will be lost.')
				.ariaLabel('Yes')
				.ok('Yes')
				.cancel('No')
				.targetEvent(event);
				$mdDialog.show(confirm).then(function() {
				newUrl = newUrl.split('#');
				$scope.goTo(newUrl[1]);
				$scope.confirm = true;
				//$scope.saveProject();
			}, function() {

			});
			}

		});

		$scope.saveProjectDialog = function() {

		};

		$scope.removeUser = function(user) {
			for (i in $scope.project.users) {
				if ($scope.project.users[i] === user) {
					$scope.project.users.splice(i, 1);
				}
			}
		};

		$scope.moveLastToTheBeginning = function() {
			var a = $scope.data.pop();
			$scope.data.splice(0, 0, a);
		};

		$scope.newSubItem = function(scope) {
			var nodeData = scope.$modelValue;
			nodeData.nodes.push({
				id: nodeData.id * 10 + nodeData.nodes.length,
				title: nodeData.title + '.' + (nodeData.nodes.length + 1),
				nodes: [],
				collapsed : false,
				estimations : []
			});
		};

		$scope.collapseAll = function() {
			$scope.$broadcast('collapseAll');
		};

		$scope.expandAll = function() {
			$scope.$broadcast('expandAll');
		};

		$scope.getToastPosition = function() {
			return Object.keys($scope.toastPosition)
			.filter(function(pos) { return $scope.toastPosition[pos]; })
			.join(' ');
		};

		$scope.toastPosition = {
			bottom: true,
			top: false,
			left: false,
			right: true
		};

		$scope.estimate = function(node) {
			var currnode = $scope.project.children[0];
			var n;
			$scope.searchTree([currnode], node.$$hashKey, function(res) {
				n = res;
			});
			$scope.estimateForUser(n, node.qty);
		};

		$scope.estimateForUser = function(node, qty) {
			var user = $rootScope.currentUser;
			var count = 0;
			var found = false;
			for (var u in node.users) {
				if (node.users[u] === user) {
					found = true;
					count = u;
					break;
				}
			}
			if (!found) {
				node.users.push(user);
				node.estimations.push(qty);
			} else {
				node.estimations[count] = qty;
			}
		};

		$scope.searchTree = function(node, id, callback) {
			var found = false;
			var result;
			for (var i in node) {
				if (id === node[i].$$hashKey) {
					found = true;
					callback(node[i]);
				}
			}
			if (found === false) {
				for (i in node) {
					$scope.searchTree(node[i].nodes, id, callback);
				}
			}
		};

		$scope.currentNode = undefined;

		$scope.showDescriptionDialog = function(ev, node) {
			$scope.currentNode = node;

			$scope.setCurrentNode(node, function() {
				var newScope = $scope.$new();

				$mdDialog.show({
					controller: DialogController,
					templateUrl: '../../templates/dialogs/project_node_description.tmpl.html',
					parent: angular.element(document.body),
					targetEvent: ev,
					scope: newScope
				});
			});
		};

		$scope.setCurrentNode = function(node, callback) {
			$scope.currentNode = node;

			if (callback !== undefined) {
				callback();
			}
		};
	}]);

function DialogController($scope, $mdDialog) {
	$scope.hide = function() {
		$mdDialog.hide();
	};
	$scope.cancel = function() {
		$mdDialog.cancel();
	};
}
<|endoftext|>


<commit_before>// TODOS:
//
// * Mouseover of DOM elements in preview area should highlight the relevant
//   source code.
//
// * Cursor-over of attributes on page should show relevant
//   MDN help tooltip, with link to more information on MDN (e.g. via
//   shift-click).
//
// * On mouseover of word "here" in error message, the associated source code
//   should scroll into view if not already visible.
//
// * Suggestions for unrecognized tags/properties/attrs should have MDN
//   blurbs next to them.
//
// * We should detect unrecognized attributes for the current tag and
//   provide suggestions for those.
//
// * Images that don't load should also dim the preview area and provide
//   help atop it, e.g. telling the user to check the URL and make sure
//   it's an image and not a web page. Relevant source code should
//   be highlighted.

var editor;
var helpIndex = [];
var cursorHelpMarks = [];

_.templateSettings = {
  escape: /\{\{(.+?)\}\}/g
};

function selectInterval(interval) {
  var start = editor.coordsFromIndex(interval.start);
  var end = editor.coordsFromIndex(interval.end);
  editor.setSelection(start, end);
  editor.focus();
}

$(document).on("mouseover", "[data-highlight]", function(event) {
  var interval = $(this).attr("data-highlight").split(",");
  selectInterval({
    start: parseInt(interval[0]),
    end: parseInt(interval[1])
  });
});

function getIndexFromPos(editor, pos) {
  var index = pos.ch;
  for (var i = 0; i < pos.line; i++)
    index += editor.getLine(i).length + 1;
  return index;
}

function buildHelpIndex(element, index) {
  var i, child,
      html = editor.getValue(),
      pi = element.parseInfo,
      tagInfo = {
        type: "tag",
        value: element.nodeName.toLowerCase(),
        highlights: []
      };
  if (pi) {
    if (pi.openTag) {
      tagInfo.highlights.push(pi.openTag);
      for (i = pi.openTag.start; i < pi.openTag.end; i++)
        index[i] = tagInfo;
    }
    if (pi.closeTag) {
      tagInfo.highlights.push(pi.closeTag);
      for (i = pi.closeTag.start; i < pi.closeTag.end; i++)
        index[i] = tagInfo;
    }
  }
  for (i = 0; i < element.childNodes.length; i++) {
    child = element.childNodes[i];
    if (child.nodeType == element.ELEMENT_NODE) {
      buildHelpIndex(child, index);
    }
    if (element.nodeName == "STYLE" && child.parseInfo.rules) {
      child.parseInfo.rules.forEach(function(rule) {
        var selectorInfo = {
          type: "cssSelector",
          highlights: [rule.selector]
        };
        for (var i = rule.selector.start; i < rule.selector.end; i++)
          index[i] = selectorInfo;
        rule.declarations.properties.forEach(function(prop) {
          var cssInfo = {
            type: "cssProperty",
            value: html.slice(prop.name.start, prop.name.end).toLowerCase(),
            highlights: [prop.name]
          };
          for (var i = prop.name.start; i < prop.name.end; i++)
            index[i] = cssInfo;
        });
      });
    }
  }
}

function getHelp(pos) {
  var index = getIndexFromPos(editor, pos),
      help = helpIndex[index];
  if (help) {
    if (help.type == "tag" &&
        help.value in HacktionaryData["html-element-docs"])
      return {
        html: HacktionaryData["html-element-docs"][help.value],
        url: MDN_URLS.html + help.value,
        highlights: help.highlights
      };
    else if (help.type == "cssProperty" &&
             help.value in HacktionaryData["css-property-docs"])
      return {
        html: HacktionaryData["css-property-docs"][help.value],
        url: MDN_URLS.css + help.value,
        highlights: help.highlights
      };
    else if (help.type == "cssSelector")
      return {
        html: $("#templates .selector-help").html(),
        url: MDN_URLS.cssSelectors,
        highlights: help.highlights
      };
  }
}

function createSuggestions(options) {
  // TODO: Substring matching isn't very useful; we should use something
  // better, like string distance, and/or hard-code particular suggestions
  // that we see in the wild (e.g. suggest 'color' when user writes
  // 'font-color' or 'text-color').
  var name = options.name.toLowerCase();
  var matches = options.lexicon.filter(function(s) {
    return s.indexOf(name) != -1;
  });
  if (!matches.length)
    return $();
  var suggs = $("#templates .suggestions").clone();
  var li = suggs.find("li").remove();
  matches.forEach(function(sugg) {
    var suggItem = li.clone();
    suggItem.find('a')
      .attr("href", options.baseURL + sugg)
      .text(sugg);
    if (sugg in options.blurbs)
      suggItem.find('p').html(options.blurbs[sugg]);
    suggItem.appendTo(suggs.find('ul'));
  });
  return suggs;
}

var MDN_URLS = {
  html: "https://developer.mozilla.org/en/HTML/Element/",
  css: "https://developer.mozilla.org/en/CSS/",
  cssSelectors: "https://developer.mozilla.org/en/CSS/Getting_Started/Selectors"
};

function reportError(error) {
  var template = $("#templates .error-msg." + error.type);
  $(".error").html(_.template(template.html(), error)).show()
    .find("[data-highlight]").each(setErrorHighlight);
  if (error.type == "INVALID_TAG_NAME") {
    createSuggestions({
      name: error.openTag.name,
      lexicon: Slowparse.HTML_ELEMENT_NAMES,
      baseURL: MDN_URLS.html,
      blurbs: HacktionaryData["html-element-docs"]
    }).appendTo(".error");
  } else if (error.type == "INVALID_CSS_PROPERTY_NAME") {
    createSuggestions({
      name: error.cssProperty.name,
      lexicon: Slowparse.CSS_PROPERTY_NAMES,
      baseURL: MDN_URLS.css,
      blurbs: HacktionaryData["css-property-docs"]
    }).appendTo(".error");
  }
}

function setErrorHighlight(i) {
  var className = "highlight-" + (i+1);
  var interval = $(this).attr("data-highlight").split(",");
  var start = editor.coordsFromIndex(interval[0]);
  var end = editor.coordsFromIndex(interval[1]);
  var mark = editor.markText(start, end, className);
  $(this).addClass(className).data("mark", mark);
}

function clearErrorHighlights() {
  $(".error").find("[data-highlight]").each(function() {
    // Odd, from the CodeMirror docs you'd think this would remove
    // the class from the highlighted text, too, but it doesn't.
    // I guess we're just garbage collecting here.
    $(this).data("mark").clear();
  });
  for (var i = 1; i <= 5; i++)
    $(".CodeMirror .highlight-" + i).removeClass("highlight-" + i);
}

function updatePreview(html) {
  $(".error").hide();
  var doc = $(".preview").contents()[0];
  doc.open();
  doc.write(html);
  doc.close();
}

function onChange() {
  var html = editor.getValue();
  var result = Slowparse.HTML(document, html);
  helpIndex = [];
  clearErrorHighlights();
  if (result.error) {
    $(".help").hide();
    reportError(result.error);
  } else {
    buildHelpIndex(result.document, helpIndex);
    updatePreview(html);
  }
  // Cursor activity would've been fired before us, so call it again
  // to make sure it displays the right context-sensitive help based
  // on the new state of the document.
  onCursorActivity();
}

function onCursorActivity() {
  $(".CodeMirror .highlight").removeClass("cursor-help-highlight");
  cursorHelpMarks.forEach(function(mark) {
    // Odd, from the CodeMirror docs you'd think this would remove
    // the class from the highlighted text, too, but it doesn't.
    // I guess we're just garbage collecting here.
    mark.clear();
  });
  cursorHelpMarks = [];
  var help = getHelp(editor.getCursor());
  if (help) {
    var learn = $("#templates .learn-more").clone()
      .attr("href", help.url);
    $(".help").html(help.html).append(learn).show();
    help.highlights.forEach(function(interval) {
      var start = editor.coordsFromIndex(interval.start);
      var end = editor.coordsFromIndex(interval.end);
      var mark = editor.markText(start, end, "cursor-help-highlight");
      cursorHelpMarks.push(mark);
    });
  } else
    $(".help").hide();
}

$(window).load(function() {
  $(".html").val($("#initial-html").text().trim());
  $("#templates .error-msgs").load("../error-msgs.html", function() {
    editor = CodeMirror.fromTextArea($(".html")[0], {
      mode: "text/html",
      theme: "jsbin",
      tabMode: "indent",
      lineWrapping: true,
      lineNumbers: true,
      onChange: onChange,
      onCursorActivity: onCursorActivity
    });
    editor.focus();
    onChange();
    onCursorActivity();
  });
});
<commit_msg>added some comments/docs to editor.js.<commit_after>// TODOS:
//
// * Mouseover of DOM elements in preview area should highlight the relevant
//   source code.
//
// * Cursor-over of attributes on page should show relevant
//   MDN help tooltip, with link to more information on MDN (e.g. via
//   shift-click).
//
// * On mouseover of word "here" in error message, the associated source code
//   should scroll into view if not already visible.
//
// * Suggestions for unrecognized tags/properties/attrs should have MDN
//   blurbs next to them.
//
// * We should detect unrecognized attributes for the current tag and
//   provide suggestions for those.
//
// * Images that don't load should also dim the preview area and provide
//   help atop it, e.g. telling the user to check the URL and make sure
//   it's an image and not a web page. Relevant source code should
//   be highlighted.

// The CodeMirror2 editor instance.
var editor;

// A mapping from character indices in the editor to associated
// context-sensitive help information.
var helpIndex = [];

// Keep track of CodeMirror2 mark objects corresponding to help
// highlighting.
var cursorHelpMarks = [];

// Use mustache-style templating.
_.templateSettings = {
  escape: /\{\{(.+?)\}\}/g
};

// Select the given {start,end} interval in the editor.
function selectInterval(interval) {
  var start = editor.coordsFromIndex(interval.start);
  var end = editor.coordsFromIndex(interval.end);
  editor.setSelection(start, end);
  editor.focus();
}

// When the user moves over anything with a data-highlight attribute,
// select the text in the editor that corresponds to the highlight.
$(document).on("mouseover", "[data-highlight]", function(event) {
  var interval = $(this).attr("data-highlight").split(",");
  selectInterval({
    start: parseInt(interval[0]),
    end: parseInt(interval[1])
  });
});

// This is the reverse of CodeMirror2's editor.coordsFromIndex().
function getIndexFromPos(editor, pos) {
  var index = pos.ch;
  for (var i = 0; i < pos.line; i++)
    index += editor.getLine(i).length + 1;
  return index;
}

// Recursively build the help index mapping editor character indices
// to context-sensitive help.
function buildHelpIndex(element, index) {
  var i, child,
      html = editor.getValue(),
      pi = element.parseInfo,
      tagInfo = {
        type: "tag",
        value: element.nodeName.toLowerCase(),
        highlights: []
      };
  if (pi) {
    if (pi.openTag) {
      tagInfo.highlights.push(pi.openTag);
      for (i = pi.openTag.start; i < pi.openTag.end; i++)
        index[i] = tagInfo;
    }
    if (pi.closeTag) {
      tagInfo.highlights.push(pi.closeTag);
      for (i = pi.closeTag.start; i < pi.closeTag.end; i++)
        index[i] = tagInfo;
    }
  }
  for (i = 0; i < element.childNodes.length; i++) {
    child = element.childNodes[i];
    if (child.nodeType == element.ELEMENT_NODE) {
      buildHelpIndex(child, index);
    }
    if (element.nodeName == "STYLE" && child.parseInfo.rules) {
      child.parseInfo.rules.forEach(function(rule) {
        var selectorInfo = {
          type: "cssSelector",
          highlights: [rule.selector]
        };
        for (var i = rule.selector.start; i < rule.selector.end; i++)
          index[i] = selectorInfo;
        rule.declarations.properties.forEach(function(prop) {
          var cssInfo = {
            type: "cssProperty",
            value: html.slice(prop.name.start, prop.name.end).toLowerCase(),
            highlights: [prop.name]
          };
          for (var i = prop.name.start; i < prop.name.end; i++)
            index[i] = cssInfo;
        });
      });
    }
  }
}

// Return the context-sensitive help information for a particular position
// in the editor, or undefined if no help is available.
function getHelp(pos) {
  var index = getIndexFromPos(editor, pos),
      help = helpIndex[index];
  if (help) {
    if (help.type == "tag" &&
        help.value in HacktionaryData["html-element-docs"])
      return {
        html: HacktionaryData["html-element-docs"][help.value],
        url: MDN_URLS.html + help.value,
        highlights: help.highlights
      };
    else if (help.type == "cssProperty" &&
             help.value in HacktionaryData["css-property-docs"])
      return {
        html: HacktionaryData["css-property-docs"][help.value],
        url: MDN_URLS.css + help.value,
        highlights: help.highlights
      };
    else if (help.type == "cssSelector")
      return {
        html: $("#templates .selector-help").html(),
        url: MDN_URLS.cssSelectors,
        highlights: help.highlights
      };
  }
}

// Create a list of suggested HTML tags/CSS properties for the given
// input. Return the result as a jQuery selection.
function createSuggestions(options) {
  // TODO: Substring matching isn't very useful; we should use something
  // better, like string distance, and/or hard-code particular suggestions
  // that we see in the wild (e.g. suggest 'color' when user writes
  // 'font-color' or 'text-color').
  var name = options.name.toLowerCase();
  var matches = options.lexicon.filter(function(s) {
    return s.indexOf(name) != -1;
  });
  if (!matches.length)
    return $();
  var suggs = $("#templates .suggestions").clone();
  var li = suggs.find("li").remove();
  matches.forEach(function(sugg) {
    var suggItem = li.clone();
    suggItem.find('a')
      .attr("href", options.baseURL + sugg)
      .text(sugg);
    if (sugg in options.blurbs)
      suggItem.find('p').html(options.blurbs[sugg]);
    suggItem.appendTo(suggs.find('ul'));
  });
  return suggs;
}

// URLs for help on the Mozilla Developer Network.
var MDN_URLS = {
  html: "https://developer.mozilla.org/en/HTML/Element/",
  css: "https://developer.mozilla.org/en/CSS/",
  cssSelectors: "https://developer.mozilla.org/en/CSS/Getting_Started/Selectors"
};

// Report the given Slowparse error, optionally providing suggestions to
// the user.
function reportError(error) {
  var template = $("#templates .error-msg." + error.type);
  $(".error").html(_.template(template.html(), error)).show()
    .find("[data-highlight]").each(setErrorHighlight);
  if (error.type == "INVALID_TAG_NAME") {
    createSuggestions({
      name: error.openTag.name,
      lexicon: Slowparse.HTML_ELEMENT_NAMES,
      baseURL: MDN_URLS.html,
      blurbs: HacktionaryData["html-element-docs"]
    }).appendTo(".error");
  } else if (error.type == "INVALID_CSS_PROPERTY_NAME") {
    createSuggestions({
      name: error.cssProperty.name,
      lexicon: Slowparse.CSS_PROPERTY_NAMES,
      baseURL: MDN_URLS.css,
      blurbs: HacktionaryData["css-property-docs"]
    }).appendTo(".error");
  }
}

// Assuming "this" is an element with a data-highlight attribute,
// give the highlighted text interval in the editor a numbered error
// highlight class.
function setErrorHighlight(i) {
  var className = "highlight-" + (i+1);
  var interval = $(this).attr("data-highlight").split(",");
  var start = editor.coordsFromIndex(interval[0]);
  var end = editor.coordsFromIndex(interval[1]);
  var mark = editor.markText(start, end, className);
  $(this).addClass(className).data("mark", mark);
}

// Remove all highlights made by setErrorHighlight().
function clearErrorHighlights() {
  $(".error").find("[data-highlight]").each(function() {
    // Odd, from the CodeMirror docs you'd think this would remove
    // the class from the highlighted text, too, but it doesn't.
    // I guess we're just garbage collecting here.
    $(this).data("mark").clear();
  });
  for (var i = 1; i <= 5; i++)
    $(".CodeMirror .highlight-" + i).removeClass("highlight-" + i);
}

// Update the preview area with the given HTML.
function updatePreview(html) {
  $(".error").hide();
  var doc = $(".preview").contents()[0];
  doc.open();
  doc.write(html);
  doc.close();
}

// Called whenever content of the editor area changes.
function onChange() {
  var html = editor.getValue();
  var result = Slowparse.HTML(document, html);
  helpIndex = [];
  clearErrorHighlights();
  if (result.error) {
    $(".help").hide();
    reportError(result.error);
  } else {
    buildHelpIndex(result.document, helpIndex);
    updatePreview(html);
  }
  // Cursor activity would've been fired before us, so call it again
  // to make sure it displays the right context-sensitive help based
  // on the new state of the document.
  onCursorActivity();
}

// Called whenever the user moves their cursor in the editor area.
function onCursorActivity() {
  $(".CodeMirror .highlight").removeClass("cursor-help-highlight");
  cursorHelpMarks.forEach(function(mark) {
    // Odd, from the CodeMirror docs you'd think this would remove
    // the class from the highlighted text, too, but it doesn't.
    // I guess we're just garbage collecting here.
    mark.clear();
  });
  cursorHelpMarks = [];
  var help = getHelp(editor.getCursor());
  if (help) {
    var learn = $("#templates .learn-more").clone()
      .attr("href", help.url);
    $(".help").html(help.html).append(learn).show();
    help.highlights.forEach(function(interval) {
      var start = editor.coordsFromIndex(interval.start);
      var end = editor.coordsFromIndex(interval.end);
      var mark = editor.markText(start, end, "cursor-help-highlight");
      cursorHelpMarks.push(mark);
    });
  } else
    $(".help").hide();
}

$(window).load(function() {
  $(".html").val($("#initial-html").text().trim());
  $("#templates .error-msgs").load("../error-msgs.html", function() {
    editor = CodeMirror.fromTextArea($(".html")[0], {
      mode: "text/html",
      theme: "jsbin",
      tabMode: "indent",
      lineWrapping: true,
      lineNumbers: true,
      onChange: onChange,
      onCursorActivity: onCursorActivity
    });
    editor.focus();
    onChange();
    onCursorActivity();
  });
});
<|endoftext|>


<commit_before>const db = require('../index.js');
const Sequelize = require('sequelize');

const cart_product = db.define('cart_product', {
  quantity: {
    type: Sequelize.INTEGER,
    validate: {
      min: 1
    }
  }
});

module.exports = cart_product;
<commit_msg>remove minimum quantity for cart_product<commit_after>const db = require('../index.js');
const Sequelize = require('sequelize');

const cart_product = db.define('cart_product', {
  quantity: {
    type: Sequelize.INTEGER
  }
});

module.exports = cart_product;
<|endoftext|>


<commit_before>app.controller(
    'PanelTemplateController',
    ['$scope', '$controller', 'ModelService', function ($scope, $controller, ModelService) {
        // Inherits ProjectDetailController $scope
        $controller('ProjectDetailController', {$scope: $scope});

        function get_list() {
            var response = ModelService.getPanelTemplates(
                {
                    'project': $scope.current_project.id
                }
            );

            $scope.expand_params = [];
            response.$promise.then(function (panel_templates) {
                panel_templates.forEach(function () {
                    $scope.expand_params.push(false);
                })
            });
            return response;
        }

        if ($scope.current_project != undefined) {
            $scope.panel_templates = get_list();
        }

        $scope.$on('current_project:updated', function () {
            $scope.panel_templates = get_list();
        });

        $scope.$on('panel_templates:updated', function () {
            $scope.panel_templates = get_list();
        });

        $scope.toggle_params = function (i) {
            $scope.expand_params[i] = $scope.expand_params[i] != true;
        };

        $scope.expand_all_panels = function () {
            for (var i = 0; i < $scope.panel_templates.length; i++) {
                $scope.expand_params[i] = true;
            }
        };

        $scope.collapse_all_panels = function () {
            for (var i = 0; i < $scope.panel_templates.length; i++) {
                $scope.expand_params[i] = false;
            }
        };

        $scope.copy_panel = function (panel) {
            var new_panel = angular.copy(panel);

            delete new_panel.id;
            new_panel.panel_name = new_panel.panel_name + ' [copy]';

            new_panel.parameters.forEach(function(param) {
                delete param.id;
                var markers = [];
                param.markers.forEach(function(m) {
                    markers.push(m.marker_id);
                });
                param.markers = markers;
            });

            var response = ModelService.createUpdatePanelTemplate(new_panel);

            response.$promise.then(function() {
                $scope.panel_templates = get_list();
            }, function (error) {
                $scope.errors = error.data;
            });
        };
    }
]);

app.controller(
    'PanelTemplateCreateController',
    [
        '$scope', '$state', '$controller', '$stateParams', '$timeout', 'ModelService',
        function ($scope, $state, $controller, $stateParams, $timeout, ModelService) {
            // Inherits ProjectDetailController $scope
            $controller('ProjectDetailController', {$scope: $scope});

            $scope.model = {};
            $scope.model.markers = ModelService.getMarkers($scope.current_project.id);
            $scope.model.fluorochromes = ModelService.getFluorochromes($scope.current_project.id);
            $scope.model.parameter_value_types = ModelService.getParameterValueTypes();

            // everything but bead functions
            $scope.model.parameter_functions = [
                ["FSC", "Forward Scatter"],
                ["SSC", "Side Scatter"],
                ["FLR", "Fluorescence"],
                ["TIM", "Time"]
            ];

            $scope.model.parameter_errors = [];
            $scope.model.template_valid = false;

            // may be trying to edit an existing template
            // Note: had to add a timeout here b/c existing markers wouldn't
            // show up in the view, and $scope.$apply() causes a
            // $rootScope:inprog error. Not happy with this.
            $timeout(function () {
                if ($stateParams.hasOwnProperty('templateID')) {
                    var template_id = parseInt($stateParams.templateID);
                    $scope.model.template = ModelService.getPanelTemplate(
                        template_id
                    );

                    $scope.model.template.$promise.then(function () {
                        $scope.model.panel_name = $scope.model.template.panel_name;

                        $scope.model.channels = [];
                        $scope.model.template.parameters.forEach(function (p) {
                            var channel = {markers: []};
                            if (p.parameter_type) {
                                channel.function = p.parameter_type;
                            }
                            if (p.parameter_value_type) {
                                channel.value_type = p.parameter_value_type;
                            }
                            if (p.markers.length > 0) {
                                p.markers.forEach(function (m) {
                                    channel.markers.push(m.marker_id);
                                });
                            }
                            if (p.fluorochrome) {
                                channel.fluorochrome = p.fluorochrome;
                            }

                            $scope.model.channels.push(channel);
                        });
                        $scope.validatePanel();
                    }, function (error) {
                        $scope.errors = error.data;
                    });
                } else {
                    $scope.model.channels = [{markers: []}];
                }
            }, 100);

            $scope.addChannel = function() {
                $scope.model.channels.push({markers:[]});
                $scope.validatePanel();
            };

            $scope.removeChannel = function(channel) {
                $scope.model.channels.splice($scope.model.channels.indexOf(channel), 1);
                $scope.validatePanel();
            };

            $scope.validatePanel = function() {
                /*
                Validation rules:
                    - existing templates w/site panels cannot be edited
                    - Function and value type are required
                    - No fluorochromes in a scatter parameter
                    - No markers in a scatter parameter
                    - Fluoroscent parameter must specify a fluorochrome
                    - No duplicate fluorochrome + value type combinations
                    - No duplicate forward scatter + value type combinations
                    - No duplicate side scatter + value type combinations
                */

                // start with true and set to false on any error
                var valid = true;
                $scope.model.errors = [];

                // existing templates w/related site panels cannot be edited
                if ($scope.model.template) {
                    if ($scope.model.template.hasOwnProperty('id')) {
                        if ($scope.model.template.site_panel_count > 0) {
                            $scope.model.errors.push(
                                'This template has existing Sample Annotations, and cannot be edited.'
                            );
                            valid = false;
                            $scope.model.template_valid = valid;
                            return valid;
                        }
                    }
                }

                // Name, project are required
                if ($scope.model.panel_name == null || $scope.current_project == null) {
                    valid = false;
                }

                var fluoro_duplicates = [];
                var channel_duplicates = [];

                $scope.model.channels.forEach(function (channel) {
                    channel.errors = [];
                    // Function type is required for all channels
                    if (!channel.function || !channel.value_type) {
                        channel.errors.push('All channels must specify a function and a value type')
                        valid = false;
                        $scope.model.template_valid = valid;
                        return valid;
                    }

                    // Only time channels can have value type 'T'
                    if (channel.function != 'TIM' && channel.value_type == 'T') {
                        channel.errors.push('Only Time channels can have time value type')
                    }

                    // Check for duplicate channels
                    // For fluoro channels we don't consider value type b/c
                    // mixed value type fluoro channels cause issues wiht the
                    // automated analysis (mainly compensation). For scatter
                    // channels we need to use the value type to prevent
                    // "pure" duplicate scatter channels.
                    if (channel.function == 'FSC' || channel.function == 'SSC') {
                        var channel_string = [
                            channel.function,
                            channel.value_type,
                            channel.markers.sort().join("-"),
                            channel.fluorochrome
                        ].join("_");
                    } else {
                        var channel_string = [
                            channel.function,
                            channel.markers.sort().join("-"),
                            channel.fluorochrome
                        ].join("_");
                    }
                    if (channel_duplicates.indexOf(channel_string) >= 0) {
                        channel.errors.push('Duplicate channels are not allowed');
                    } else {
                        channel_duplicates.push(channel_string);
                    }

                    // Check for fluoro duplicates
                    if (channel.fluorochrome) {
                        if (fluoro_duplicates.indexOf(channel.fluorochrome.toString()) >= 0) {
                            channel.errors.push('The same fluorochrome cannot be in multiple channels');
                        } else {
                            fluoro_duplicates.push(channel.fluorochrome.toString());
                        }
                    }

                    // Scatter channels
                    if (channel.function == 'FSC' || channel.function == 'SSC') {
                        // ensure no fluoro or markers in scatter channels
                        if (channel.fluorochrome) {
                            channel.errors.push('Scatter channels cannot have a fluorochrome');
                        }
                        if (channel.markers.length > 0) {
                            channel.errors.push('Scatter channels cannot have markers');
                        }
                    } else if (channel.function == 'FLR') {
                        // fluoro conjugate channels must have a fluoro
                        if (!channel.fluorochrome && channel.markers.length < 1) {
                            channel.errors.push("Fluorescence parameters must " +
                        "specify either a marker or a fluorochrome (or both)");
                        }
                    } else if (channel.function == 'BEA') {
                        // Bead channels must specify a fluoro but no marker
                        if (!channel.fluorochrome) {
                            channel.errors.push('Bead channels must specify a fluorochrome');
                        }
                        if (channel.markers.length > 0) {
                            channel.errors.push('Bead channels cannot have markers');
                        }
                    } else if (channel.function == 'TIM') {
                        // Time channels cannot have a fluoro or Ab, must have value type 'T'
                        if (channel.fluorochrome) {
                            channel.errors.push('Time channel cannot specify a fluorochrome');
                        }
                        if (channel.markers.length > 0) {
                            channel.errors.push('Time channel cannot have markers');
                        }
                        if (channel.value_type != 'T') {
                            channel.errors.push('Time channel must have time value type')
                        }
                    }

                    if (channel.errors.length > 0 ) {
                        valid = false;
                    }
                });

                $scope.model.template_valid = valid;
                return valid;
            };

            $scope.savePanel = function () {
                var is_valid = $scope.validatePanel();

                if (!is_valid) {
                    return;
                }

                var params = [];
                $scope.model.channels.forEach(function (c) {
                    if (!c.value_type) {
                        c.value_type = null;
                    }
                    params.push({
                        parameter_type: c.function,
                        parameter_value_type: c.value_type,
                        markers: c.markers,
                        fluorochrome: c.fluorochrome || null
                    })
                });

                var data = {
                    panel_name: $scope.model.panel_name,
                    project: $scope.current_project.id,
                    parameters: params,
                    panel_description: ""
                };
                if ($scope.model.template) {
                    data.id = $scope.model.template.id;
                }
                var panel_template = ModelService.createUpdatePanelTemplate(
                    data
                );

                panel_template.$promise.then(function (o) {
                    // change to project's Panel template list
                    $state.go('panel-template-list')
                }, function(error) {
                    $scope.model.errors = [];
                    for (var key in error.data) {
                        if (error.data.hasOwnProperty(key)) {
                            if (error.data[key] instanceof Array) {
                                $scope.model.errors.push.apply(
                                    $scope.model.errors, error.data[key]
                                );
                            } else {
                                $scope.model.errors.push(error.data[key]);
                            }
                        }
                    }
                });
            };
        }
    ]
);<commit_msg>minor JS fixes<commit_after>app.controller(
    'PanelTemplateController',
    ['$scope', '$controller', 'ModelService', function ($scope, $controller, ModelService) {
        // Inherits ProjectDetailController $scope
        $controller('ProjectDetailController', {$scope: $scope});

        function get_list() {
            var response = ModelService.getPanelTemplates(
                {
                    'project': $scope.current_project.id
                }
            );

            $scope.expand_params = [];
            response.$promise.then(function (panel_templates) {
                panel_templates.forEach(function () {
                    $scope.expand_params.push(false);
                })
            });
            return response;
        }

        if ($scope.current_project != undefined) {
            $scope.panel_templates = get_list();
        }

        $scope.$on('current_project:updated', function () {
            $scope.panel_templates = get_list();
        });

        $scope.$on('panel_templates:updated', function () {
            $scope.panel_templates = get_list();
        });

        $scope.toggle_params = function (i) {
            $scope.expand_params[i] = $scope.expand_params[i] != true;
        };

        $scope.expand_all_panels = function () {
            for (var i = 0; i < $scope.panel_templates.length; i++) {
                $scope.expand_params[i] = true;
            }
        };

        $scope.collapse_all_panels = function () {
            for (var i = 0; i < $scope.panel_templates.length; i++) {
                $scope.expand_params[i] = false;
            }
        };

        $scope.copy_panel = function (panel) {
            var new_panel = angular.copy(panel);

            delete new_panel.id;
            new_panel.panel_name = new_panel.panel_name + ' [copy]';

            new_panel.parameters.forEach(function(param) {
                delete param.id;
                var markers = [];
                param.markers.forEach(function(m) {
                    markers.push(m.marker_id);
                });
                param.markers = markers;
            });

            var response = ModelService.createUpdatePanelTemplate(new_panel);

            response.$promise.then(function() {
                $scope.panel_templates = get_list();
            }, function (error) {
                $scope.errors = error.data;
            });
        };
    }
]);

app.controller(
    'PanelTemplateCreateController',
    [
        '$scope', '$state', '$controller', '$stateParams', '$timeout', 'ModelService',
        function ($scope, $state, $controller, $stateParams, $timeout, ModelService) {
            // Inherits ProjectDetailController $scope
            $controller('ProjectDetailController', {$scope: $scope});

            $scope.model = {};
            $scope.model.markers = ModelService.getMarkers($scope.current_project.id);
            $scope.model.fluorochromes = ModelService.getFluorochromes($scope.current_project.id);
            $scope.model.parameter_value_types = ModelService.getParameterValueTypes();

            // everything but bead functions
            $scope.model.parameter_functions = [
                ["FSC", "Forward Scatter"],
                ["SSC", "Side Scatter"],
                ["FLR", "Fluorescence"],
                ["TIM", "Time"]
            ];

            $scope.model.parameter_errors = [];
            $scope.model.template_valid = false;

            // may be trying to edit an existing template
            // Note: had to add a timeout here b/c existing markers wouldn't
            // show up in the view, and $scope.$apply() causes a
            // $rootScope:inprog error. Not happy with this.
            $timeout(function () {
                if ($stateParams.hasOwnProperty('templateID')) {
                    var template_id = parseInt($stateParams.templateID);
                    $scope.model.template = ModelService.getPanelTemplate(
                        template_id
                    );

                    $scope.model.template.$promise.then(function () {
                        $scope.model.panel_name = $scope.model.template.panel_name;

                        $scope.model.channels = [];
                        $scope.model.template.parameters.forEach(function (p) {
                            var channel = {markers: []};
                            if (p.parameter_type) {
                                channel.function = p.parameter_type;
                            }
                            if (p.parameter_value_type) {
                                channel.value_type = p.parameter_value_type;
                            }
                            if (p.markers.length > 0) {
                                p.markers.forEach(function (m) {
                                    channel.markers.push(m.marker_id);
                                });
                            }
                            if (p.fluorochrome) {
                                channel.fluorochrome = p.fluorochrome;
                            }

                            $scope.model.channels.push(channel);
                        });
                        $scope.validatePanel();
                    }, function (error) {
                        $scope.errors = error.data;
                    });
                } else {
                    $scope.model.channels = [{markers: []}];
                }
            }, 100);

            $scope.addChannel = function() {
                $scope.model.channels.push({markers:[]});
                $scope.validatePanel();
            };

            $scope.removeChannel = function(channel) {
                $scope.model.channels.splice($scope.model.channels.indexOf(channel), 1);
                $scope.validatePanel();
            };

            $scope.validatePanel = function() {
                /*
                Validation rules:
                    - existing templates w/site panels cannot be edited
                    - Function and value type are required
                    - No fluorochromes in a scatter parameter
                    - No markers in a scatter parameter
                    - Fluoroscent parameter must specify a fluorochrome
                    - No duplicate fluorochrome + value type combinations
                    - No duplicate forward scatter + value type combinations
                    - No duplicate side scatter + value type combinations
                */

                // start with true and set to false on any error
                var valid = true;
                $scope.model.errors = [];

                // existing templates w/related site panels cannot be edited
                if ($scope.model.template) {
                    if ($scope.model.template.hasOwnProperty('id')) {
                        if ($scope.model.template.site_panel_count > 0) {
                            $scope.model.errors.push(
                                'This template has existing Sample Annotations, and cannot be edited.'
                            );
                            valid = false;
                            $scope.model.template_valid = valid;
                            return valid;
                        }
                    }
                }

                // Name, project are required
                if ($scope.model.panel_name == null || $scope.current_project == null) {
                    valid = false;
                }

                var fluoro_duplicates = [];
                var channel_duplicates = [];

                $scope.model.channels.forEach(function (channel) {
                    channel.errors = [];
                    // Function type is required for all channels
                    if (!channel.function || !channel.value_type) {
                        channel.errors.push('All channels must specify a function and a value type');
                        valid = false;
                        $scope.model.template_valid = valid;
                        return valid;
                    }

                    // Only time channels can have value type 'T'
                    if (channel.function != 'TIM' && channel.value_type == 'T') {
                        channel.errors.push('Only Time channels can have time value type')
                    }

                    // Check for duplicate channels
                    // For fluoro channels we don't consider value type b/c
                    // mixed value type fluoro channels cause issues wiht the
                    // automated analysis (mainly compensation). For scatter
                    // channels we need to use the value type to prevent
                    // "pure" duplicate scatter channels.
                    var channel_string;
                    if (channel.function == 'FSC' || channel.function == 'SSC') {
                        channel_string = [
                            channel.function,
                            channel.value_type,
                            channel.markers.sort().join("-"),
                            channel.fluorochrome
                        ].join("_");
                    } else {
                        channel_string = [
                            channel.function,
                            channel.markers.sort().join("-"),
                            channel.fluorochrome
                        ].join("_");
                    }
                    if (channel_duplicates.indexOf(channel_string) >= 0) {
                        channel.errors.push('Duplicate channels are not allowed');
                    } else {
                        channel_duplicates.push(channel_string);
                    }

                    // Check for fluoro duplicates
                    if (channel.fluorochrome) {
                        if (fluoro_duplicates.indexOf(channel.fluorochrome.toString()) >= 0) {
                            channel.errors.push('The same fluorochrome cannot be in multiple channels');
                        } else {
                            fluoro_duplicates.push(channel.fluorochrome.toString());
                        }
                    }

                    // Scatter channels
                    if (channel.function == 'FSC' || channel.function == 'SSC') {
                        // ensure no fluoro or markers in scatter channels
                        if (channel.fluorochrome) {
                            channel.errors.push('Scatter channels cannot have a fluorochrome');
                        }
                        if (channel.markers.length > 0) {
                            channel.errors.push('Scatter channels cannot have markers');
                        }
                    } else if (channel.function == 'FLR') {
                        // fluoro conjugate channels must have a fluoro
                        if (!channel.fluorochrome && channel.markers.length < 1) {
                            channel.errors.push("Fluorescence parameters must " +
                        "specify either a marker or a fluorochrome (or both)");
                        }
                    } else if (channel.function == 'BEA') {
                        // Bead channels must specify a fluoro but no marker
                        if (!channel.fluorochrome) {
                            channel.errors.push('Bead channels must specify a fluorochrome');
                        }
                        if (channel.markers.length > 0) {
                            channel.errors.push('Bead channels cannot have markers');
                        }
                    } else if (channel.function == 'TIM') {
                        // Time channels cannot have a fluoro or Ab, must have value type 'T'
                        if (channel.fluorochrome) {
                            channel.errors.push('Time channel cannot specify a fluorochrome');
                        }
                        if (channel.markers.length > 0) {
                            channel.errors.push('Time channel cannot have markers');
                        }
                        if (channel.value_type != 'T') {
                            channel.errors.push('Time channel must have time value type')
                        }
                    }

                    if (channel.errors.length > 0 ) {
                        valid = false;
                    }
                });

                $scope.model.template_valid = valid;
                return valid;
            };

            $scope.savePanel = function () {
                var is_valid = $scope.validatePanel();

                if (!is_valid) {
                    return;
                }

                var params = [];
                $scope.model.channels.forEach(function (c) {
                    if (!c.value_type) {
                        c.value_type = null;
                    }
                    params.push({
                        parameter_type: c.function,
                        parameter_value_type: c.value_type,
                        markers: c.markers,
                        fluorochrome: c.fluorochrome || null
                    })
                });

                var data = {
                    panel_name: $scope.model.panel_name,
                    project: $scope.current_project.id,
                    parameters: params,
                    panel_description: ""
                };
                if ($scope.model.template) {
                    data.id = $scope.model.template.id;
                }
                var panel_template = ModelService.createUpdatePanelTemplate(
                    data
                );

                panel_template.$promise.then(function () {
                    // change to project's Panel template list
                    $state.go('panel-template-list')
                }, function(error) {
                    $scope.model.errors = [];
                    for (var key in error.data) {
                        if (error.data.hasOwnProperty(key)) {
                            if (error.data[key] instanceof Array) {
                                $scope.model.errors.push.apply(
                                    $scope.model.errors, error.data[key]
                                );
                            } else {
                                $scope.model.errors.push(error.data[key]);
                            }
                        }
                    }
                });
            };
        }
    ]
);<|endoftext|>


<commit_before>'use strict';

const Joi = require('joi');

const Message = require('./Message');
const MessageParsingBlinkError = require('../errors/MessageParsingBlinkError');

class CustomerioSmsBroadcastMessage extends Message {
  constructor(...args) {
    super(...args);

    const whenNullOrEmpty = Joi.valid(['', null]);
    const statusRegex = /broadcastId=.+$/;
    this.schema = Joi.object()
      .keys({
        // Note: first char of To is plus, which is resolved to a space by mistake.
        To: Joi.string().required().empty(whenNullOrEmpty).regex(/^ 1[0-9]+$/, 'valid phone number'),
        Body: Joi.string().required().empty(whenNullOrEmpty),
        MessagingServiceSid: Joi.string().required().empty(whenNullOrEmpty),
        StatusCallback: Joi.string().required().empty(whenNullOrEmpty).regex(statusRegex, 'valid status callback'),
      });
  }

  getBody() {
    return this.getData().Body;
  }

  getPhoneNumber() {
    return this.getData().To.replace(/^ /, '+');
  }

  getBroadcastId() {
    return this.getData().StatusCallback.match(/broadcastId=(.+)/)[1];
  }

  getMessageSid() {
    return this.getMeta().messageSid;
  }

  setMessageSid(messageSid) {
    this.getMeta().messageSid = messageSid;
  }

  static fromCtx(ctx) {
    // TODO: save more metadata
    // TODO: metadata parse helper
    const meta = {
      request_id: ctx.id,
    };
    const message = new CustomerioSmsBroadcastMessage({
      data: ctx.request.body,
      meta,
    });
    return message;
  }

  static fromRabbitMessage(rabbitMessage) {
    const payload = this.parseIncomingPayload(rabbitMessage);
    if (!payload.data || !payload.meta) {
      throw new MessageParsingBlinkError('No data in message', payload);
    }

    // TODO: save more metadata
    // TODO: metadata parse helper
    const message = new CustomerioSmsBroadcastMessage({
      data: payload.data,
      meta: payload.meta,
    });
    message.fields = rabbitMessage.fields;
    return message;
  }
}

module.exports = CustomerioSmsBroadcastMessage;
<commit_msg>Accepts application/json payload from Customer.io webhook<commit_after>'use strict';

const Joi = require('joi');

const Message = require('./Message');
const MessageParsingBlinkError = require('../errors/MessageParsingBlinkError');

class CustomerioSmsBroadcastMessage extends Message {
  constructor(...args) {
    super(...args);

    const whenNullOrEmpty = Joi.valid(['', null]);
    const statusRegex = /broadcastId=.+$/;
    this.schema = Joi.object()
      .keys({
        // Note: first char of To is plus, which is resolved to a space by mistake.
        To: Joi.string().required().empty(whenNullOrEmpty).regex(/^\+1[0-9]+$/, 'valid phone number'),
        Body: Joi.string().required().empty(whenNullOrEmpty),
        StatusCallback: Joi.string().required().empty(whenNullOrEmpty).regex(statusRegex, 'valid status callback'),
      });
  }

  getBody() {
    return this.getData().Body;
  }

  getPhoneNumber() {
    return this.getData().To;
  }

  getBroadcastId() {
    return this.getData().StatusCallback.match(/broadcastId=(.+)/)[1];
  }

  getMessageSid() {
    return this.getMeta().messageSid;
  }

  setMessageSid(messageSid) {
    this.getMeta().messageSid = messageSid;
  }

  static fromCtx(ctx) {
    // TODO: save more metadata
    // TODO: metadata parse helper
    const meta = {
      request_id: ctx.id,
    };
    const message = new CustomerioSmsBroadcastMessage({
      data: ctx.request.body,
      meta,
    });
    return message;
  }

  static fromRabbitMessage(rabbitMessage) {
    const payload = this.parseIncomingPayload(rabbitMessage);
    if (!payload.data || !payload.meta) {
      throw new MessageParsingBlinkError('No data in message', payload);
    }

    // TODO: save more metadata
    // TODO: metadata parse helper
    const message = new CustomerioSmsBroadcastMessage({
      data: payload.data,
      meta: payload.meta,
    });
    message.fields = rabbitMessage.fields;
    return message;
  }
}

module.exports = CustomerioSmsBroadcastMessage;
<|endoftext|>


<commit_before>
describe('RenderQueue', function() {
	var RenderQueue = $.fullCalendar.RenderQueue;

	it('executes atomic events in sequence', function() {
		var ops = [];
		var q = new RenderQueue();

		q.queue(function() {
			ops.push('fooinit');
		}, 'foo', 'init');

		q.queue(function() {
			ops.push('fooremove');
		}, 'foo', 'add');

		q.queue(function() {
			ops.push('fooadd');
		}, 'foo', 'remove');

		q.queue(function() {
			ops.push('foodestroy');
		}, 'foo', 'destroy');

		expect(ops).toEqual([ 'fooinit', 'fooremove', 'fooadd', 'foodestroy' ]);
	});

	describe('when accumulating', function() {

		describe('using clear action', function() {

			it('destroys add/remove operations in same namespace', function() {
				var ops = [];
				var q = new RenderQueue();
				q.pause();

				q.queue(function() {
					ops.push('fooadd');
				}, 'foo', 'add');

				q.queue(function() {
					ops.push('fooremove');
				}, 'foo', 'remove');

				q.queue(function() {
					ops.push('foodestroy');
				}, 'foo', 'destroy');

				expect(ops).toEqual([]);
				q.resume();
				expect(ops).toEqual([ 'foodestroy' ]);
			});

			it('is cancelled out by an init in same namespace', function() {
				var ops = [];
				var q = new RenderQueue();
				q.pause();

				q.queue(function() {
					ops.push('barinit');
				}, 'bar', 'init');

				q.queue(function() {
					ops.push('fooinit');
				}, 'foo', 'init');

				q.queue(function() {
					ops.push('fooadd');
				}, 'foo', 'add');

				q.queue(function() {
					ops.push('fooadd');
				}, 'foo', 'remove');

				q.queue(function() {
					ops.push('fooadd');
				}, 'foo', 'destroy');

				expect(ops).toEqual([]);
				q.resume();
				expect(ops).toEqual([ 'barinit' ]);
			});
		});
	});

	describe('when namespace has a wait value', function() {

		it('unpauses when done', function(done) {
			var ops = [];
			var q = new RenderQueue({
				foo: 100
			});

			q.queue(function() {
				ops.push('fooinit');
			}, 'foo', 'init');

			q.queue(function() {
				ops.push('fooadd');
			}, 'foo', 'add');

			expect(ops).toEqual([]);

			setTimeout(function() {
				expect(ops).toEqual([ 'fooinit', 'fooadd' ]);
				done();
			}, 200);
		});

		it('restarts timer when new operation happens', function(done) {
			var ops = [];
			var q = new RenderQueue({
				foo: 100
			});

			q.queue(function() {
				ops.push('fooinit');
			}, 'foo', 'init');

 			setTimeout(function() {
				q.queue(function() {
					ops.push('fooadd');
				}, 'foo', 'add');
			}, 50);

			setTimeout(function() {
				expect(ops).toEqual([]);
			}, 125);

			setTimeout(function() {
				expect(ops).toEqual([ 'fooinit', 'fooadd' ]);
				done();
			}, 175);
		});

		it('synchronously executes queue when sync non-namespace operation happens', function() {
			var ops = [];
			var q = new RenderQueue({
				foo: 100
			});

			q.queue(function() {
				ops.push('fooinit');
			}, 'foo', 'init');

			q.queue(function() {
				ops.push('fooadd');
			}, 'foo', 'add');

			expect(ops).toEqual([]);

			q.queue(function() {
				ops.push('barinit');
			}, 'bar', 'init');

			expect(ops).toEqual([ 'fooinit', 'fooadd', 'barinit' ]);
		});

		it('synchronously executes queue when async non-namespace operation happens', function(done) {
			var ops = [];
			var q = new RenderQueue({
				foo: 100,
				bar: 100
			});

			q.queue(function() {
				ops.push('fooinit');
			}, 'foo', 'init');

			q.queue(function() {
				ops.push('fooadd');
			}, 'foo', 'add');

			expect(ops).toEqual([]);

			q.queue(function() {
				ops.push('barinit');
			}, 'bar', 'init');

			expect(ops).toEqual([ 'fooinit', 'fooadd' ]);

			setTimeout(function() {
				expect(ops).toEqual([ 'fooinit', 'fooadd', 'barinit' ]);
				done();
			}, 200);
		});

		it('resumes non-waiting tasks when unpaused', function(done) {
			var ops = [];
			var q = new RenderQueue({
				foo: 100
			});

			q.pause();

			q.queue(function() {
				ops.push('barinit');
			}, 'bar', 'init');

			q.queue(function() {
				ops.push('fooinit');
			}, 'foo', 'init');

			q.resume();
			expect(ops).toEqual([ 'barinit' ]);

			setTimeout(function() {
				expect(ops).toEqual([ 'barinit', 'fooinit' ]);
				done();
			}, 200);
		});

		it('paused+queued tasks from a previous namespace wait resume immediately', function(done) {
			var ops = [];
			var q = new RenderQueue({
				foo: 100
			});

			q.pause();

			q.queue(function() {
				ops.push('foodestroy');
			}, 'foo', 'destroy');

			q.queue(function() {
				ops.push('bardestroy');
			}, 'bar', 'destroy');

			expect(ops).toEqual([]);

			q.queue(function() {
				ops.push('barinit');
			}, 'bar', 'init');

			q.queue(function() {
				ops.push('fooinit');
			}, 'foo', 'init');

			expect(ops).toEqual([]);

			q.resume();
			expect(ops).toEqual([ 'foodestroy', 'bardestroy', 'barinit' ]);

			setTimeout(function() {
				expect(ops).toEqual([ 'foodestroy', 'bardestroy', 'barinit', 'fooinit' ]);
				done();
			}, 200);
		});
	});
});
<commit_msg>fix renderqueue tests<commit_after>
describe('RenderQueue', function() {
	var RenderQueue = $.fullCalendar.RenderQueue;

	it('executes atomic events in sequence', function() {
		var ops = [];
		var q = new RenderQueue();

		q.queue('foo', 'init', function() {
			ops.push('fooinit');
		});

		q.queue('foo', 'add', function() {
			ops.push('fooremove');
		});

		q.queue('foo', 'remove', function() {
			ops.push('fooadd');
		});

		q.queue('foo', 'destroy', function() {
			ops.push('foodestroy');
		});

		expect(ops).toEqual([ 'fooinit', 'fooremove', 'fooadd', 'foodestroy' ]);
	});

	describe('when accumulating', function() {

		describe('using clear action', function() {

			it('destroys add/remove operations in same namespace', function() {
				var ops = [];
				var q = new RenderQueue();
				q.pause();

				q.queue('foo', 'add', function() {
					ops.push('fooadd');
				});

				q.queue('foo', 'remove', function() {
					ops.push('fooremove');
				});

				q.queue('foo', 'destroy', function() {
					ops.push('foodestroy');
				});

				expect(ops).toEqual([]);
				q.resume();
				expect(ops).toEqual([ 'foodestroy' ]);
			});

			it('is cancelled out by an init in same namespace', function() {
				var ops = [];
				var q = new RenderQueue();
				q.pause();

				q.queue('bar', 'init', function() {
					ops.push('barinit');
				});

				q.queue('foo', 'init', function() {
					ops.push('fooinit');
				});

				q.queue('foo', 'add', function() {
					ops.push('fooadd');
				});

				q.queue('foo', 'remove', function() {
					ops.push('fooadd');
				});

				q.queue('foo', 'destroy', function() {
					ops.push('fooadd');
				});

				expect(ops).toEqual([]);
				q.resume();
				expect(ops).toEqual([ 'barinit' ]);
			});
		});
	});

	describe('when namespace has a wait value', function() {

		it('unpauses when done', function(done) {
			var ops = [];
			var q = new RenderQueue({
				foo: 100
			});

			q.queue('foo', 'init', function() {
				ops.push('fooinit');
			});

			q.queue('foo', 'add', function() {
				ops.push('fooadd');
			});

			expect(ops).toEqual([]);

			setTimeout(function() {
				expect(ops).toEqual([ 'fooinit', 'fooadd' ]);
				done();
			}, 200);
		});

		it('restarts timer when new operation happens', function(done) {
			var ops = [];
			var q = new RenderQueue({
				foo: 100
			});

			q.queue('foo', 'init', function() {
				ops.push('fooinit');
			});

 			setTimeout(function() {
				q.queue('foo', 'add', function() {
					ops.push('fooadd');
				});
			}, 50);

			setTimeout(function() {
				expect(ops).toEqual([]);
			}, 125);

			setTimeout(function() {
				expect(ops).toEqual([ 'fooinit', 'fooadd' ]);
				done();
			}, 175);
		});

		it('synchronously executes queue when sync non-namespace operation happens', function() {
			var ops = [];
			var q = new RenderQueue({
				foo: 100
			});

			q.queue('foo', 'init', function() {
				ops.push('fooinit');
			});

			q.queue('foo', 'add', function() {
				ops.push('fooadd');
			});

			expect(ops).toEqual([]);

			q.queue('bar', 'init', function() {
				ops.push('barinit');
			});

			expect(ops).toEqual([ 'fooinit', 'fooadd', 'barinit' ]);
		});

		it('synchronously executes queue when async non-namespace operation happens', function(done) {
			var ops = [];
			var q = new RenderQueue({
				foo: 100,
				bar: 100
			});

			q.queue('foo', 'init', function() {
				ops.push('fooinit');
			});

			q.queue('foo', 'add', function() {
				ops.push('fooadd');
			});

			expect(ops).toEqual([]);

			q.queue('bar', 'init', function() {
				ops.push('barinit');
			});

			expect(ops).toEqual([ 'fooinit', 'fooadd' ]);

			setTimeout(function() {
				expect(ops).toEqual([ 'fooinit', 'fooadd', 'barinit' ]);
				done();
			}, 200);
		});

		it('resumes non-waiting tasks when unpaused', function(done) {
			var ops = [];
			var q = new RenderQueue({
				foo: 100
			});

			q.pause();

			q.queue('bar', 'init', function() {
				ops.push('barinit');
			});

			q.queue('foo', 'init', function() {
				ops.push('fooinit');
			});

			q.resume();
			expect(ops).toEqual([ 'barinit' ]);

			setTimeout(function() {
				expect(ops).toEqual([ 'barinit', 'fooinit' ]);
				done();
			}, 200);
		});

		it('paused+queued tasks from a previous namespace wait resume immediately', function(done) {
			var ops = [];
			var q = new RenderQueue({
				foo: 100
			});

			q.pause();

			q.queue('foo', 'destroy', function() {
				ops.push('foodestroy');
			});

			q.queue('bar', 'destroy', function() {
				ops.push('bardestroy');
			});

			expect(ops).toEqual([]);

			q.queue('bar', 'init', function() {
				ops.push('barinit');
			});

			q.queue('foo', 'init', function() {
				ops.push('fooinit');
			});

			expect(ops).toEqual([]);

			q.resume();
			expect(ops).toEqual([ 'foodestroy', 'bardestroy', 'barinit' ]);

			setTimeout(function() {
				expect(ops).toEqual([ 'foodestroy', 'bardestroy', 'barinit', 'fooinit' ]);
				done();
			}, 200);
		});
	});
});
<|endoftext|>