<commit_before>/*
Copyright (c) 2009-2016, Dan "Ducky" Little & GeoMOOSE.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/*
 * Class: ResultsAsGrid
 *
 * Renders the results of the query as an interactive grid.
 *
 * WARNING! This class will not work out of the box! It is necessary
 *          to create a subclass!!!
 *
 */

dojo.require('dojo.store.Memory');
dojo.require('dojo.data.ItemFileWriteStore');
dojo.require('dojo.data.ObjectStore');
dojo.require('dojox.grid.DataGrid');
dojo.require("dojox.grid._CheckBoxSelector")
dojo.require('dijit.Toolbar');
dojo.require('dijit.layout.BorderContainer');

dojo.provide("extensions.ResultsAsGrid");
dojo.declare("extensions.ResultsAsGrid", null, {
	load: function() {
		var self = this;

		// Install the required CSS for datagrid.
		var head = document.getElementsByTagName('head')[0];
		var style_urls = [
			'libs/dojo/dojox/grid/resources/Grid.css',
			'libs/dojo/dojox/grid/resources/tundraGrid.css'
		];

		for(var i = 0 ; i < style_urls.length; i++) {
			//<link type="text/css" rel="stylesheet" href="css/user_tools.css"/>
			var link = document.createElement('link');
			link.setAttribute('type', 'text/css');
			link.setAttribute('rel', 'stylesheet');
			link.setAttribute('href', style_urls[i]);
			head.appendChild(link);
		}

		//this.data = new dojo.data.ObjectStore({objectStore: this.dataStore});
		GeoMOOSE.register('onMapbookLoaded', this, this.startup);
	},

	conf: {
		targetLayer: 'vector_highlight'
	},

	/** Tracking time. */
	lastUpdate: 0,

	/** Update interval time. Every half-second. */
	updateInterval: 500,

	/** The current update interval */
	interval: null,

	gridStructure: null,

	/** This is a demo function that collects all of the selected rows and
	 *  displays the list of PIN numbers in a dialog.  This can serve
	 *  as a starting point for a service that may print mailing labels,
	 *  report on the parcels, all sorts of fun options.
	 */

	demoListParcels: function() {
		var pins = [];
		var items = this.dataGrid.selection.getSelected();
		for(var i = 0, ii = items.length; i < ii; i++) {
			var pin = this.objectStore.getValue(items[i], "PIN");
			pins.push(pin);
		}

		alert('You have selected the following PINs: '+pins.join(', '));
	},

	/** Render the toolbar, override with custom code when you need a toolbar.
	 */
	renderToolbar: function(layout) {
		/* Below is an example of how to add a button to the toolbar,
		 * WARNING! OVERRIDE THIS CLASS, DO NOT MODIFY IT!
		// add the toolbar to the layout containing the grid.
		var toolbar = new dijit.Toolbar({region: 'top'});
		layout.addChild(toolbar);
		toolbar.startup();

		var list_button = new dijit.form.Button({
			label: "List Parcels",
			iconClass: "dijitEditorIcon dijitEditorIconInsertTable",
			onClick: dojo.hitch(this, this.demoListParcels)
		});
		toolbar.addChild(list_button);
		toolbar.startup();
		*/
	},

	highlightFeatures: [],

	clearHighlightFeatures: function() {
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
		ol_layer.removeFeatures(this.highlightFeatures, {silent: true});
	},

	/** Highlight the feature when the mouse if over the row.
	 */
	mouseOver: function(e) {
		var row = e.rowIndex;
		var item = this.dataGrid.getItem(row);

		//this.clearHighlightFeatures();

		if(item) {
			var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
			var f = ol_layer.getFeatureById(item.id);
			var clone = f.clone();

			clone.style =  {
				strokeColor: 'red',
				strokeOpacity: 1.0,
				fillColor: 'red',
				fillOpacity: 0.8
			};

			this.highlightFeatures.push(clone);

			ol_layer.addFeatures([clone], {silent: true});

			//ol_layer.redraw();
		}
	},

	/** Clear all 'highlighting' when the mouse leaves the grid.
	 */
	mouseOut: function(e) {
		this.clearHighlightFeatures();
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
		ol_layer.redraw();
	},

	/** Style a selected feature.
	 *
	 */
	styleSelectedFeature: function(f) {
		f.style = {
			strokeColor: 'blue',
			strokeOpacity: .75,
			fillColor: 'blue',
			fillOpacity: .5
		};
	},

	/** Find the selected features and style
	 *  them to show what is "checked".
	 */
	renderSelected: function() {
		// get the OpenLayers layer class.
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
		// get the list of selected "items"
		var items = this.dataGrid.selection.getSelected();
		for(var i = 0, len = items.length; i < len; i++) {
			if(items[i]) {
				var item = items[i];
				var f = ol_layer.getFeatureById(item.id);
				if(f) {
					this.styleSelectedFeature(f);
					ol_layer.drawFeature(f);
				}
			}
		}
	},

	/** Delayed update when lists are changed.
	 */
	updateGrid: function() {
		var now = (new Date()).getTime();
		// check to see if an update is appropriate
		if(now - this.lastUpdate > this.updateInterval) {
			// clear out the repeating inveral
			clearInterval(this.interval);
			this.interval = null;

			// get the count
			var n_elements = 0;
			function showCount(size, req) {
				n_elements = size;
			}
			this.objectStore.fetch({query: {}, onBegin: showCount, start: 0, count: 0});

			if(n_elements == 0) {
				// clear out the data grid if it exists.
			} else {
				if(this.dataGrid == null) {
					var dg_layout = new dijit.layout.BorderContainer({
						region: 'bottom', gutters: false,
						splitter: true, liveSplitters: false,
						style: "height: 30%" //125px"
					});

					var middle = dijit.byId('middle');
					middle.addChild(dg_layout);

					this.renderToolbar(dg_layout);

					this.dataGrid = new dojox.grid.DataGrid({
						region: 'center',
						store: this.objectStore,
						query: {id: '*' },
						structure: this.gridStructure
					});

					dojo.connect(this.dataGrid, 'onMouseOver', this, this.mouseOver);
					dojo.connect(this.dataGrid, 'onMouseOut', this, this.mouseOut);
					dojo.connect(this.dataGrid, 'onSelectionChanged', this, this.renderSelected);

					//var middle = dijit.byId('middle');
					dg_layout.addChild(this.dataGrid);
					this.dataGrid.startup();
					middle.resize();
				}

				this.dataGrid.setQuery({id: '*'});
			}
		}
	},

	triggerUpdate: function() {
		this.lastUpdate = (new Date()).getTime();
		if(this.interval == null) {
			this.interval = setInterval(dojo.hitch(this, this.updateGrid), 400);
		}
	},

	startup: function() {
		// NOTE: This is a bit hacky, whenever possible directly interacting
		//       with an OpenLayers class should be avoided.  That said, I needed
		//       to access the events object.
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;

		this.memoryStore = new dojo.store.Memory({data: []});
		this.objectStore = new dojo.data.ObjectStore({objectStore: this.memoryStore});

		// this will bridge the gap between the openlayers events/features
		//  and the Dojo dataStore object.
		ol_layer.events.register('featureadded', this, function(evt) {
			var obj = evt.feature.attributes;
			// bridge the ID from OpenLayers
			obj['id'] = evt.feature.id;
			this.memoryStore.add(obj, {});
			// check for a structure update
			if(this.gridStructure == null) {
				var view = [
					new dojox.grid.cells.RowIndex({ width: "20px" })
				];

				for(var attr in evt.feature.attributes) {
					view.push({
						name: attr, field: attr, width: 8
					});
				}

				var structure = [{
					type: "dojox.grid._CheckBoxSelector"
				}, view];
				this.gridStructure = structure;
			}
			this.triggerUpdate();
		});

		ol_layer.events.register('featureremoved', this, function(evt) {
			this.memoryStore.remove(evt.feature.id);
			this.triggerUpdate();
		});

	}
});

<commit_msg>Old gridstructure is now removed when all the features go away<commit_after>/*
Copyright (c) 2009-2016, Dan "Ducky" Little & GeoMOOSE.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/*
 * Class: ResultsAsGrid
 *
 * Renders the results of the query as an interactive grid.
 *
 * WARNING! This class will not work out of the box! It is necessary
 *          to create a subclass!!!
 *
 */

dojo.require('dojo.store.Memory');
dojo.require('dojo.data.ItemFileWriteStore');
dojo.require('dojo.data.ObjectStore');
dojo.require('dojox.grid.DataGrid');
dojo.require("dojox.grid._CheckBoxSelector")
dojo.require('dijit.Toolbar');
dojo.require('dijit.layout.BorderContainer');

dojo.provide("extensions.ResultsAsGrid");
dojo.declare("extensions.ResultsAsGrid", null, {
	load: function() {
		var self = this;

		// Install the required CSS for datagrid.
		var head = document.getElementsByTagName('head')[0];
		var style_urls = [
			'libs/dojo/dojox/grid/resources/Grid.css',
			'libs/dojo/dojox/grid/resources/tundraGrid.css'
		];

		for(var i = 0 ; i < style_urls.length; i++) {
			//<link type="text/css" rel="stylesheet" href="css/user_tools.css"/>
			var link = document.createElement('link');
			link.setAttribute('type', 'text/css');
			link.setAttribute('rel', 'stylesheet');
			link.setAttribute('href', style_urls[i]);
			head.appendChild(link);
		}

		//this.data = new dojo.data.ObjectStore({objectStore: this.dataStore});
		GeoMOOSE.register('onMapbookLoaded', this, this.startup);
	},

	conf: {
		targetLayer: 'vector_highlight'
	},

	/** Tracking time. */
	lastUpdate: 0,

	/** Update interval time. Every half-second. */
	updateInterval: 500,

	/** The current update interval */
	interval: null,

	gridStructure: null,

	/** This is a demo function that collects all of the selected rows and
	 *  displays the list of PIN numbers in a dialog.  This can serve
	 *  as a starting point for a service that may print mailing labels,
	 *  report on the parcels, all sorts of fun options.
	 */

	demoListParcels: function() {
		var pins = [];
		var items = this.dataGrid.selection.getSelected();
		for(var i = 0, ii = items.length; i < ii; i++) {
			var pin = this.objectStore.getValue(items[i], "PIN");
			pins.push(pin);
		}

		alert('You have selected the following PINs: '+pins.join(', '));
	},

	/** Render the toolbar, override with custom code when you need a toolbar.
	 */
	renderToolbar: function(layout) {
		/* Below is an example of how to add a button to the toolbar,
		 * WARNING! OVERRIDE THIS CLASS, DO NOT MODIFY IT!
		// add the toolbar to the layout containing the grid.
		var toolbar = new dijit.Toolbar({region: 'top'});
		layout.addChild(toolbar);
		toolbar.startup();

		var list_button = new dijit.form.Button({
			label: "List Parcels",
			iconClass: "dijitEditorIcon dijitEditorIconInsertTable",
			onClick: dojo.hitch(this, this.demoListParcels)
		});
		toolbar.addChild(list_button);
		toolbar.startup();
		*/
	},

	highlightFeatures: [],

	clearHighlightFeatures: function() {
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
		ol_layer.removeFeatures(this.highlightFeatures, {silent: true});
	},

	/** Highlight the feature when the mouse if over the row.
	 */
	mouseOver: function(e) {
		var row = e.rowIndex;
		var item = this.dataGrid.getItem(row);

		//this.clearHighlightFeatures();

		if(item) {
			var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
			var f = ol_layer.getFeatureById(item.id);
			var clone = f.clone();

			clone.style =  {
				strokeColor: 'red',
				strokeOpacity: 1.0,
				fillColor: 'red',
				fillOpacity: 0.8
			};

			this.highlightFeatures.push(clone);

			ol_layer.addFeatures([clone], {silent: true});

			//ol_layer.redraw();
		}
	},

	/** Clear all 'highlighting' when the mouse leaves the grid.
	 */
	mouseOut: function(e) {
		this.clearHighlightFeatures();
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
		ol_layer.redraw();
	},

	/** Style a selected feature.
	 *
	 */
	styleSelectedFeature: function(f) {
		f.style = {
			strokeColor: 'blue',
			strokeOpacity: .75,
			fillColor: 'blue',
			fillOpacity: .5
		};
	},

	/** Find the selected features and style
	 *  them to show what is "checked".
	 */
	renderSelected: function() {
		// get the OpenLayers layer class.
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
		// get the list of selected "items"
		var items = this.dataGrid.selection.getSelected();
		for(var i = 0, len = items.length; i < len; i++) {
			if(items[i]) {
				var item = items[i];
				var f = ol_layer.getFeatureById(item.id);
				if(f) {
					this.styleSelectedFeature(f);
					ol_layer.drawFeature(f);
				}
			}
		}
	},

	/** Delayed update when lists are changed.
	 */
	updateGrid: function() {
		var now = (new Date()).getTime();
		// check to see if an update is appropriate
		if(now - this.lastUpdate > this.updateInterval) {
			// clear out the repeating inveral
			clearInterval(this.interval);
			this.interval = null;

			// get the count
			var n_elements = 0;
			function showCount(size, req) {
				n_elements = size;
			}
			this.objectStore.fetch({query: {}, onBegin: showCount, start: 0, count: 0});

			if(n_elements == 0) {
				// clear out the data grid if it exists.
			} else {
				if(this.dataGrid == null) {
					var dg_layout = new dijit.layout.BorderContainer({
						region: 'bottom', gutters: false,
						splitter: true, liveSplitters: false,
						style: "height: 30%" //125px"
					});

					var middle = dijit.byId('middle');
					middle.addChild(dg_layout);

					this.renderToolbar(dg_layout);

					this.dataGrid = new dojox.grid.DataGrid({
						region: 'center',
						store: this.objectStore,
						query: {id: '*' },
						structure: this.gridStructure
					});

					dojo.connect(this.dataGrid, 'onMouseOver', this, this.mouseOver);
					dojo.connect(this.dataGrid, 'onMouseOut', this, this.mouseOut);
					dojo.connect(this.dataGrid, 'onSelectionChanged', this, this.renderSelected);

					//var middle = dijit.byId('middle');
					dg_layout.addChild(this.dataGrid);
					this.dataGrid.startup();
					middle.resize();
				}

				this.dataGrid.setQuery({id: '*'});
			}
		}
	},

	triggerUpdate: function() {
		this.lastUpdate = (new Date()).getTime();
		if(this.interval == null) {
			this.interval = setInterval(dojo.hitch(this, this.updateGrid), 400);
		}
	},

	startup: function() {
		// NOTE: This is a bit hacky, whenever possible directly interacting
		//       with an OpenLayers class should be avoided.  That said, I needed
		//       to access the events object.
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;

		this.memoryStore = new dojo.store.Memory({data: []});
		this.objectStore = new dojo.data.ObjectStore({objectStore: this.memoryStore});

		// this will bridge the gap between the openlayers events/features
		//  and the Dojo dataStore object.
		ol_layer.events.register('featureadded', this, function(evt) {
			var obj = evt.feature.attributes;
			// bridge the ID from OpenLayers
			obj['id'] = evt.feature.id;
			this.memoryStore.add(obj, {});

			if(this.gridStructure == null) {
				var view = [
					new dojox.grid.cells.RowIndex({ width: "20px" })
				];

				for(var attr in evt.feature.attributes) {
					view.push({
						name: attr, field: attr, width: 8
					});
				}

				var structure = [{
					type: "dojox.grid._CheckBoxSelector"
				}, view];
				this.gridStructure = structure;
			}
			this.triggerUpdate();
		});

		ol_layer.events.register('featureremoved', this, function(evt) {
			this.memoryStore.remove(evt.feature.id);
			this.triggerUpdate();

			// when the feature are cleared out, reset the grid structure.
			if(this.memoryStore.query(function() { return true; }).total == 0) {
				this.gridStructure = null;
			}

		});

	}
});

<|endoftext|>


<commit_before>/**
 *	Copyright (C) 2014 3D Repo Ltd
 *
 *	This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU Affero General Public License as
 *	published by the Free Software Foundation, either version 3 of the
 *	License, or (at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU Affero General Public License for more details.
 *
 *	You should have received a copy of the GNU Affero General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

(function() {
	"use strict";

	angular.module("3drepo")
	.config([
	"$stateProvider", "$urlRouterProvider", "$locationProvider", "structure",
	function($stateProvider, $urlRouterProvider, $locationProvider, structure) {

		$locationProvider.html5Mode(true);

		$stateProvider.state("home", {
			name: "home",
			url: "/",
			resolve: {
				init: function(Auth, StateManager, $q)
				{
					StateManager.state.authInitialized = false;

					var finishedAuth = $q.defer();

					StateManager.state.changing = true;

					Auth.init().then(function (loggedIn) {
						StateManager.state.authInitialized = true;
						StateManager.state.loggedIn = loggedIn;

						finishedAuth.resolve();
					});

					return finishedAuth.promise;
				}
			}
		});

		var stateStack       = [structure];
		var stateNameStack   = ["home"];

		//console.log("stateStack", stateStack);
		while (stateStack.length > 0)
		{
			var stackLength      = stateStack.length;
			var parentState      = stateStack[0];
			var parentStateName  = stateNameStack[0];

			// First loop through the list of functions as these are
			// more specific than the
			if (parentState.functions)
			{
				for (var i = 0; i < parentState.functions.length; i++)
				{
					var childFunction	  = parentState.functions[i];
					var childFunctionName = parentStateName + "." + childFunction;

					(function(childFunction) {
						$stateProvider.state(childFunctionName, {
							name: childFunction,
							url: childFunction,
							resolve: {
								init: function (StateManager, $location, $stateParams) {
									$stateParams[childFunction] = true;

									StateManager.setState($stateParams);
								}
							}
						});
					})(childFunction);
				}
			}

			if (parentState.children)
			{
				for (var i = 0; i < parentState.children.length; i++)
				{
					var childState     = parentState.children[i];
					var childStateName = parentStateName + "." + childState.plugin;

					stateNameStack.push(childStateName);
					stateStack.push(parentState.children[i]);

					(function(childState){
						//console.log('childState.url', childState.url);
						$stateProvider.state(childStateName, {
							name: parentState.children[i].plugin,
							params: childState.params,
							url: childState.url || (parentStateName !== "home" ? "/" : "") + ":" + childState.plugin,
							reloadOnSearch : false,
							resolve: {
								init: function(StateManager, $location, $stateParams)
								{
									StateManager.setState($stateParams);
									//console.log('##state', StateManager.state);
								}
							}
						});
					})(childState);
				}
			}

			stateStack.splice(0,1);
			stateNameStack.splice(0,1);
		}

		$urlRouterProvider.otherwise("");
	}])
	.run(["$location", "$rootScope", "$state", "uiState", "StateManager", "Auth", "$timeout", "AnalyticService",
		function($location, $rootScope, $state, uiState, StateManager, Auth, $timeout, AnalyticService) {
		$rootScope.$on("$stateChangeStart",function(event, toState, toParams, fromState, fromParams){
			console.log("stateChangeStart: " + JSON.stringify(fromState) + " --> " + JSON.stringify(toState));

			StateManager.state.changing = true;

			for(var i = 0; i < StateManager.functions.length; i++)
			{
				StateManager.setStateVar(StateManager.functions[i], false);
			}

			StateManager.clearQuery();

			var stateChangeObject = {
				toState    : toState,
				toParams   : toParams,
				fromState  : fromState,
				fromParams : fromParams
			};

			StateManager.startStateChange(stateChangeObject);
		});

		$rootScope.$on("$stateChangeSuccess",function(event, toState, toParams, fromState, fromParams){
			console.log("stateChangeSuccess: " + JSON.stringify(fromState) + " --> " + JSON.stringify(toState));

			var stateChangeObject = {
				toState    : toState,
				toParams   : toParams,
				fromState  : fromState,
				fromParams : fromParams
			};

			StateManager.handleStateChange(stateChangeObject);
		});

		$rootScope.$on('$locationChangeStart', function(event, next, current) {
			console.log("locationChange");
		});

		$rootScope.$on('$locationChangeSuccess', function() {
			console.log("locationChangeSucc");

			AnalyticService.sendPageView(location);

			var queryParams = $location.search();

			if (Object.keys(queryParams).length === 0)
			{
				//StateManager.clearQuery();
			} else {
				//StateManager.setQuery(queryParams);
			}
		});
	}])
	.service("StateManager", ["$q", "$state", "$rootScope", "$timeout", "structure", "EventService", "$window", "Auth", function($q, $state, $rootScope, $timeout, structure, EventService, $window, Auth) {
		var self = this;

		$window.StateManager = this;

		// Stores the state, required as ui-router does not allow inherited
		// stateParams, and we need to dynamically generate state diagram.
		// One day this might change.
		// https://github.com/angular-ui/ui-router/wiki/URL-Routing
		this.state = {
			changing: true
		};

		this.changedState = {};
		this.structure  = structure;
		this.destroy = function()  {
			delete this.state;
			this.state = {};

			delete this.ui;
			this.ui = {};

			delete this.Data;
			this.Data = {};
		};

		// Has a state variable changed. Is this necessary ?
		this.changed     = {};

		this.state       = { loggedIn : false };
		this.query       = {};
		this.functions   = [];

		var stateStack       = [structure];

		// Populate list of functions
		while (stateStack.length > 0)
		{
			var stackLength      = stateStack.length;
			var parentState      = stateStack[stackLength - 1];

			var i = 0;
			var functionName;

			if (parentState.functions)
			{
				for(i=0; i<parentState.functions.length; i++)
				{
					functionName = parentState.functions[i];

					if (this.functions.indexOf(functionName) > -1) {
						console.error("Duplicate function name when loading in StateManager : " + functionName);
					} else {
						this.functions.push(functionName);
					}
				}
			}

			if (parentState.children)
			{
				for (var i = 0; i < parentState.children.length; i++)
				{
					stateStack.push(parentState.children[i]);
				}
			}

			stateStack.splice(0,1);
		}

		this.clearChanged = function()
		{
			for(var i in self.changed) {
				if (self.changed.hasOwnProperty(i)) {
					self.changed[i] = false;
				}
			}
		};

		self.clearChanged();

		this.stateChangeQueue = [];

		var compareStateChangeObjects = function(stateChangeA, stateChangeB)
		{
			return	(stateChangeA.toState	 === stateChangeB.toState) &&
					(stateChangeA.toParams	 === stateChangeB.toParams) &&
					(stateChangeA.fromState  === stateChangeB.fromState) &&
					(stateChangeA.fromParams === stateChangeB.fromParams);
		};

		this.startStateChange = function(stateChangeObject) {
			self.stateChangeQueue.push(stateChangeObject);
		};

		this.handleStateChange = function(stateChangeObject) {
			var param;
			var fromParams = stateChangeObject.fromParams;
			var toParams   = stateChangeObject.toParams;

			// Switch off all parameters that we came from
			// but are not the same as where we are going to
			for (param in fromParams)
			{
				if (fromParams.hasOwnProperty(param))
				{
					if (!toParams.hasOwnProperty(param))
					{
						self.setStateVar(param, null);
					}
				}
			}

			for (param in toParams)
			{
				if (toParams.hasOwnProperty(param))
				{
					if (fromParams.hasOwnProperty(param))
					{
						if (fromParams[param] !== toParams[param])
						{
							self.setStateVar(param, toParams[param]);
						}
					} else {
						self.setStateVar(param, toParams[param]);
					}
				}
			}

			// Loop through structure. If a parent is null, then we must clear
			// it's children
			var stateStack       = [structure];
			var stateNameStack   = ["home"];
			var clearBelow       = false;

			while (stateStack.length > 0)
			{
				var stackLength      = stateStack.length;
				var parentState      = stateStack[stackLength - 1];
				var parentStateName  = stateNameStack[stackLength - 1];

				if (parentStateName !== "home" && !self.state[parentStateName])
				{
					clearBelow = true;
				}

				if (parentState.children)
				{
					for (var i = 0; i < parentState.children.length; i++)
					{
						var childStateName = parentState.children[i].plugin;

						stateNameStack.push(childStateName);
						stateStack.push(parentState.children[i]);

						if (clearBelow)
						{
							self.setStateVar(childStateName, null);
						}
					}
				}

				stateStack.splice(0,1);
				stateNameStack.splice(0,1);
			}

			if (compareStateChangeObjects(stateChangeObject, self.stateChangeQueue[0]))
			{
				self.stateChangeQueue.pop();

				var functionList = self.functionsUsed();

				// If we are not trying to access a function
				// and yet there is no account set. Then
				// we need to go back to the account page if possible.
				if ((functionList.length === 0) && self.state.loggedIn && !self.state.account)
				{
					self.setStateVar("account", Auth.username);
					self.updateState();
				} else {
					self.updateState(true);
				}
			} else {
				self.stateChangeQueue.pop();
				self.handleStateChange(self.stateChangeQueue[self.stateChangeQueue.length - 1]);
			}
		};

		this.stateVars   = {};

		this.clearState = function(state) {
			for (var state in self.state)
			{
				if ((["changing", "authInitialized", "loggedIn"].indexOf(state) === -1) && self.state.hasOwnProperty(state))
				{
					self.setStateVar(state, null);
				}
			}
		};

		this.clearQuery = function(state) {
			for(var param in self.query)
			{
				delete self.query[param];
			}
		};

		this.functionsUsed = function ()
		{
			var functionList = [];

			// First loop through the list of functions
			// belonging to parent structure.
			// Only deals with functions on home directory
			if (self.structure.functions)
			{
				for(i = 0; i < self.structure.functions.length; i++)
				{
					functionName = self.structure.functions[i];

					if (self.state[functionName])
					{
						functionList.push(functionName);
						break;
					}
				}
			}

			return functionList;
		}

		this.genStateName = function ()
		{
			var currentChildren = self.structure.children;
			var childidx        = 0;
			var stateName       = "home."; // Assume that the base state is there.
			var i               = 0;
			var functionList    = self.functionsUsed();
			var usesFunction    = (functionList.length > 0);

			if (usesFunction)
			{
				stateName += functionList.join(".") + ".";
			} else
			{
				while(childidx < currentChildren.length)
				{
					var child  = currentChildren[childidx];
					var plugin = child.plugin;

					if (self.state.hasOwnProperty(plugin) && self.state[plugin])
					{
						stateName += plugin + ".";

						if (child.children) {
							currentChildren = child.children;
						} else {
							currentChildren = [];
						}

						childidx = -1;
					}

					childidx += 1;
				}
			}

			return stateName.substring(0, stateName.length - 1);
		};

		this.setStateVar = function(varName, value)
		{
			if (value === null)
			{
				delete self.state[varName];
			} else {
				if (self.state[varName] !== value) {
					self.state.changing = true;
					self.changedState[varName] = value;
				}
			}
			//console.log(varName, value);
			self.state[varName] = value;
		};

		this.setState = function(stateParams) {
			// Copy all state parameters and extra parameters
			// to the state#
			//console.log('stateParams', stateParams);
			if(stateParams.noSet){
				return;
			}

			for (var state in stateParams) {
				if (stateParams.hasOwnProperty(state)) {
					self.setStateVar(state, stateParams[state]);
				}
			}
		};

		this.setQuery = function(queryParams)
		{
			for(var param in queryParams)
			{
				if (queryParams.hasOwnProperty(param))
				{
					self.query[param] = queryParams[param];
				}
			}
		};

		this.updateState = function(dontUpdateLocation)
		{
			var newStateName = self.genStateName();

			if (Object.keys(self.changedState).length)
			{
				EventService.send(EventService.EVENT.STATE_CHANGED, self.changedState);
				self.changedState = {};
			}

			var updateLocation = !dontUpdateLocation ? true: false; // In case of null
			$state.transitionTo(newStateName, self.state, { location: updateLocation });

			$timeout(function () {
				self.state.changing = false;
			});
		};

		$rootScope.$watch(EventService.currentEvent, function(event) {
			if (angular.isDefined(event) && angular.isDefined(event.type)) {
				if (event.type === EventService.EVENT.SET_STATE) {
					for (var key in event.value)
					{
						if (key !== "updateLocation" && event.value.hasOwnProperty(key))
						{
							self.setStateVar(key, event.value[key]);
						}
					}

					self.updateState();
				} else if (event.type === EventService.EVENT.CLEAR_STATE) {
					self.clearState();
				} else if (event.type === EventService.EVENT.UPDATE_STATE){
					self.updateState(true);
				}
			}
		});
	}]);

})();
<commit_msg>ISSUE #343 - uncomment code in state manager to make it to be able the get query string from url again<commit_after>/**
 *	Copyright (C) 2014 3D Repo Ltd
 *
 *	This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU Affero General Public License as
 *	published by the Free Software Foundation, either version 3 of the
 *	License, or (at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU Affero General Public License for more details.
 *
 *	You should have received a copy of the GNU Affero General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

(function() {
	"use strict";

	angular.module("3drepo")
	.config([
	"$stateProvider", "$urlRouterProvider", "$locationProvider", "structure",
	function($stateProvider, $urlRouterProvider, $locationProvider, structure) {

		$locationProvider.html5Mode(true);

		$stateProvider.state("home", {
			name: "home",
			url: "/",
			resolve: {
				init: function(Auth, StateManager, $q)
				{
					StateManager.state.authInitialized = false;

					var finishedAuth = $q.defer();

					StateManager.state.changing = true;

					Auth.init().then(function (loggedIn) {
						StateManager.state.authInitialized = true;
						StateManager.state.loggedIn = loggedIn;

						finishedAuth.resolve();
					});

					return finishedAuth.promise;
				}
			}
		});

		var stateStack       = [structure];
		var stateNameStack   = ["home"];

		//console.log("stateStack", stateStack);
		while (stateStack.length > 0)
		{
			var stackLength      = stateStack.length;
			var parentState      = stateStack[0];
			var parentStateName  = stateNameStack[0];

			// First loop through the list of functions as these are
			// more specific than the
			if (parentState.functions)
			{
				for (var i = 0; i < parentState.functions.length; i++)
				{
					var childFunction	  = parentState.functions[i];
					var childFunctionName = parentStateName + "." + childFunction;

					(function(childFunction) {
						$stateProvider.state(childFunctionName, {
							name: childFunction,
							url: childFunction,
							resolve: {
								init: function (StateManager, $location, $stateParams) {
									$stateParams[childFunction] = true;

									StateManager.setState($stateParams);
								}
							}
						});
					})(childFunction);
				}
			}

			if (parentState.children)
			{
				for (var i = 0; i < parentState.children.length; i++)
				{
					var childState     = parentState.children[i];
					var childStateName = parentStateName + "." + childState.plugin;

					stateNameStack.push(childStateName);
					stateStack.push(parentState.children[i]);

					(function(childState){
						//console.log('childState.url', childState.url);
						$stateProvider.state(childStateName, {
							name: parentState.children[i].plugin,
							params: childState.params,
							url: childState.url || (parentStateName !== "home" ? "/" : "") + ":" + childState.plugin,
							reloadOnSearch : false,
							resolve: {
								init: function(StateManager, $location, $stateParams)
								{
									StateManager.setState($stateParams);
									//console.log('##state', StateManager.state);
								}
							}
						});
					})(childState);
				}
			}

			stateStack.splice(0,1);
			stateNameStack.splice(0,1);
		}

		$urlRouterProvider.otherwise("");
	}])
	.run(["$location", "$rootScope", "$state", "uiState", "StateManager", "Auth", "$timeout", "AnalyticService",
		function($location, $rootScope, $state, uiState, StateManager, Auth, $timeout, AnalyticService) {
		$rootScope.$on("$stateChangeStart",function(event, toState, toParams, fromState, fromParams){
			console.log("stateChangeStart: " + JSON.stringify(fromState) + " --> " + JSON.stringify(toState));

			StateManager.state.changing = true;

			for(var i = 0; i < StateManager.functions.length; i++)
			{
				StateManager.setStateVar(StateManager.functions[i], false);
			}

			StateManager.clearQuery();

			var stateChangeObject = {
				toState    : toState,
				toParams   : toParams,
				fromState  : fromState,
				fromParams : fromParams
			};

			StateManager.startStateChange(stateChangeObject);
		});

		$rootScope.$on("$stateChangeSuccess",function(event, toState, toParams, fromState, fromParams){
			console.log("stateChangeSuccess: " + JSON.stringify(fromState) + " --> " + JSON.stringify(toState));

			var stateChangeObject = {
				toState    : toState,
				toParams   : toParams,
				fromState  : fromState,
				fromParams : fromParams
			};

			StateManager.handleStateChange(stateChangeObject);
		});

		$rootScope.$on('$locationChangeStart', function(event, next, current) {
			console.log("locationChange");
		});

		$rootScope.$on('$locationChangeSuccess', function() {
			console.log("locationChangeSucc");

			AnalyticService.sendPageView(location);

			var queryParams = $location.search();

			if (Object.keys(queryParams).length === 0)
			{
				StateManager.clearQuery();
			} else {
				StateManager.setQuery(queryParams);
			}
		});
	}])
	.service("StateManager", ["$q", "$state", "$rootScope", "$timeout", "structure", "EventService", "$window", "Auth", function($q, $state, $rootScope, $timeout, structure, EventService, $window, Auth) {
		var self = this;

		$window.StateManager = this;

		// Stores the state, required as ui-router does not allow inherited
		// stateParams, and we need to dynamically generate state diagram.
		// One day this might change.
		// https://github.com/angular-ui/ui-router/wiki/URL-Routing
		this.state = {
			changing: true
		};

		this.changedState = {};
		this.structure  = structure;
		this.destroy = function()  {
			delete this.state;
			this.state = {};

			delete this.ui;
			this.ui = {};

			delete this.Data;
			this.Data = {};
		};

		// Has a state variable changed. Is this necessary ?
		this.changed     = {};

		this.state       = { loggedIn : false };
		this.query       = {};
		this.functions   = [];

		var stateStack       = [structure];

		// Populate list of functions
		while (stateStack.length > 0)
		{
			var stackLength      = stateStack.length;
			var parentState      = stateStack[stackLength - 1];

			var i = 0;
			var functionName;

			if (parentState.functions)
			{
				for(i=0; i<parentState.functions.length; i++)
				{
					functionName = parentState.functions[i];

					if (this.functions.indexOf(functionName) > -1) {
						console.error("Duplicate function name when loading in StateManager : " + functionName);
					} else {
						this.functions.push(functionName);
					}
				}
			}

			if (parentState.children)
			{
				for (var i = 0; i < parentState.children.length; i++)
				{
					stateStack.push(parentState.children[i]);
				}
			}

			stateStack.splice(0,1);
		}

		this.clearChanged = function()
		{
			for(var i in self.changed) {
				if (self.changed.hasOwnProperty(i)) {
					self.changed[i] = false;
				}
			}
		};

		self.clearChanged();

		this.stateChangeQueue = [];

		var compareStateChangeObjects = function(stateChangeA, stateChangeB)
		{
			return	(stateChangeA.toState	 === stateChangeB.toState) &&
					(stateChangeA.toParams	 === stateChangeB.toParams) &&
					(stateChangeA.fromState  === stateChangeB.fromState) &&
					(stateChangeA.fromParams === stateChangeB.fromParams);
		};

		this.startStateChange = function(stateChangeObject) {
			self.stateChangeQueue.push(stateChangeObject);
		};

		this.handleStateChange = function(stateChangeObject) {
			var param;
			var fromParams = stateChangeObject.fromParams;
			var toParams   = stateChangeObject.toParams;

			// Switch off all parameters that we came from
			// but are not the same as where we are going to
			for (param in fromParams)
			{
				if (fromParams.hasOwnProperty(param))
				{
					if (!toParams.hasOwnProperty(param))
					{
						self.setStateVar(param, null);
					}
				}
			}

			for (param in toParams)
			{
				if (toParams.hasOwnProperty(param))
				{
					if (fromParams.hasOwnProperty(param))
					{
						if (fromParams[param] !== toParams[param])
						{
							self.setStateVar(param, toParams[param]);
						}
					} else {
						self.setStateVar(param, toParams[param]);
					}
				}
			}

			// Loop through structure. If a parent is null, then we must clear
			// it's children
			var stateStack       = [structure];
			var stateNameStack   = ["home"];
			var clearBelow       = false;

			while (stateStack.length > 0)
			{
				var stackLength      = stateStack.length;
				var parentState      = stateStack[stackLength - 1];
				var parentStateName  = stateNameStack[stackLength - 1];

				if (parentStateName !== "home" && !self.state[parentStateName])
				{
					clearBelow = true;
				}

				if (parentState.children)
				{
					for (var i = 0; i < parentState.children.length; i++)
					{
						var childStateName = parentState.children[i].plugin;

						stateNameStack.push(childStateName);
						stateStack.push(parentState.children[i]);

						if (clearBelow)
						{
							self.setStateVar(childStateName, null);
						}
					}
				}

				stateStack.splice(0,1);
				stateNameStack.splice(0,1);
			}

			if (compareStateChangeObjects(stateChangeObject, self.stateChangeQueue[0]))
			{
				self.stateChangeQueue.pop();

				var functionList = self.functionsUsed();

				// If we are not trying to access a function
				// and yet there is no account set. Then
				// we need to go back to the account page if possible.
				if ((functionList.length === 0) && self.state.loggedIn && !self.state.account)
				{
					self.setStateVar("account", Auth.username);
					self.updateState();
				} else {
					self.updateState(true);
				}
			} else {
				self.stateChangeQueue.pop();
				self.handleStateChange(self.stateChangeQueue[self.stateChangeQueue.length - 1]);
			}
		};

		this.stateVars   = {};

		this.clearState = function(state) {
			for (var state in self.state)
			{
				if ((["changing", "authInitialized", "loggedIn"].indexOf(state) === -1) && self.state.hasOwnProperty(state))
				{
					self.setStateVar(state, null);
				}
			}
		};

		this.clearQuery = function(state) {
			for(var param in self.query)
			{
				delete self.query[param];
			}
		};

		this.functionsUsed = function ()
		{
			var functionList = [];

			// First loop through the list of functions
			// belonging to parent structure.
			// Only deals with functions on home directory
			if (self.structure.functions)
			{
				for(i = 0; i < self.structure.functions.length; i++)
				{
					functionName = self.structure.functions[i];

					if (self.state[functionName])
					{
						functionList.push(functionName);
						break;
					}
				}
			}

			return functionList;
		}

		this.genStateName = function ()
		{
			var currentChildren = self.structure.children;
			var childidx        = 0;
			var stateName       = "home."; // Assume that the base state is there.
			var i               = 0;
			var functionList    = self.functionsUsed();
			var usesFunction    = (functionList.length > 0);

			if (usesFunction)
			{
				stateName += functionList.join(".") + ".";
			} else
			{
				while(childidx < currentChildren.length)
				{
					var child  = currentChildren[childidx];
					var plugin = child.plugin;

					if (self.state.hasOwnProperty(plugin) && self.state[plugin])
					{
						stateName += plugin + ".";

						if (child.children) {
							currentChildren = child.children;
						} else {
							currentChildren = [];
						}

						childidx = -1;
					}

					childidx += 1;
				}
			}

			return stateName.substring(0, stateName.length - 1);
		};

		this.setStateVar = function(varName, value)
		{
			if (value === null)
			{
				delete self.state[varName];
			} else {
				if (self.state[varName] !== value) {
					self.state.changing = true;
					self.changedState[varName] = value;
				}
			}
			//console.log(varName, value);
			self.state[varName] = value;
		};

		this.setState = function(stateParams) {
			// Copy all state parameters and extra parameters
			// to the state#
			//console.log('stateParams', stateParams);
			if(stateParams.noSet){
				return;
			}

			for (var state in stateParams) {
				if (stateParams.hasOwnProperty(state)) {
					self.setStateVar(state, stateParams[state]);
				}
			}
		};

		this.setQuery = function(queryParams)
		{
			for(var param in queryParams)
			{
				if (queryParams.hasOwnProperty(param))
				{
					self.query[param] = queryParams[param];
				}
			}
		};

		this.updateState = function(dontUpdateLocation)
		{
			var newStateName = self.genStateName();

			if (Object.keys(self.changedState).length)
			{
				EventService.send(EventService.EVENT.STATE_CHANGED, self.changedState);
				self.changedState = {};
			}

			var updateLocation = !dontUpdateLocation ? true: false; // In case of null
			$state.transitionTo(newStateName, self.state, { location: updateLocation });

			$timeout(function () {
				self.state.changing = false;
			});
		};

		$rootScope.$watch(EventService.currentEvent, function(event) {
			if (angular.isDefined(event) && angular.isDefined(event.type)) {
				if (event.type === EventService.EVENT.SET_STATE) {
					for (var key in event.value)
					{
						if (key !== "updateLocation" && event.value.hasOwnProperty(key))
						{
							self.setStateVar(key, event.value[key]);
						}
					}

					self.updateState();
				} else if (event.type === EventService.EVENT.CLEAR_STATE) {
					self.clearState();
				} else if (event.type === EventService.EVENT.UPDATE_STATE){
					self.updateState(true);
				}
			}
		});
	}]);

})();
<|endoftext|>


<commit_before>import Matrix4 from '../../math/Matrix4.js';
import GestureObserver from '../../input/GestureObserver.js';

export default class OrbitController {
  constructor({
    matrix = new Matrix4(),
    domElement = null,
    pan = 0,
    tilt = 0,
    invertRotation = true,
    distance = 1,
    distanceMin = 0,
    distanceMax = Infinity,
    tiltMin = -Infinity,
    tiltMax = Infinity,
    tiltDisabled = false,
    panMin = -Infinity,
    panMax = Infinity,
    panDisabled = false,
    rotationEasing = .1,
    rotationVelocity = .005,
    zoomEasing = .1,
    zoomVelocity = .1,
    zoomDisabled = false,
  }) {
    this.matrix = matrix;
    this.invertRotation = invertRotation;
    this.distanceMax = distanceMax;
    this.distanceMin = distanceMin;
    this.zoomEasing = zoomEasing;
    this.tiltMax = tiltMax;
    this.tiltMin = tiltMin;
    this.tiltDisabled = tiltDisabled;
    this.panMax = panMax;
    this.panMin = panMin;
    this.panDisabled = panDisabled;
    this.rotationEasing = rotationEasing;
    this.rotationVelocity = rotationVelocity;
    this.zoomDisabled = zoomDisabled;
    this.zoomVelocity = zoomVelocity;

    this._distance = distance;
    this._tilt = tilt;
    this._pan = pan;

    this._panEased = this._pan;
    this._tiltEased = this._tilt;
    this._distanceEased = this._distance;

    this._multiTouchMode = false;

    if (domElement) {
      domElement.addEventListener('wheel', (event) => {
        if (this.zoomDisabled) return;
        this._distance = Math.max(this._distance, .001) * (1 + event.deltaY * this.zoomVelocity * .01);
        this._distance = Math.max(this.distanceMin, Math.min(this.distanceMax, this._distance));
      }, { passive: true });

      const gestureObserver = new GestureObserver((gesture) => {
        if (!this.panDisabled) {
          this._pan += (this.invertRotation ? -1 : 1) * gesture.movementX * rotationVelocity;
          this._pan = Math.max(this.panMin, Math.min(this.panMax, this._pan));
        }

        if (!this.tiltDisabled) {
          this._tilt += (this.invertRotation ? 1 : -1) * gesture.movementY * rotationVelocity;
          this._tilt = Math.max(this.tiltMin, Math.min(this.tiltMax, this._tilt));
        }

        if (!this.zoomDisabled) {
          this._distance /= 1 + gesture.movementScale * this.zoomVelocity * .01;
          this._distance = Math.max(this.distanceMin, Math.min(this.distanceMax, this._distance));
        }
      }, { pointerCapture: true });
      gestureObserver.observe(domElement);
    }

    this.update();
  }

  get pan() {
    return this._pan;
  }

  set pan(value) {
    this._pan = value;
    this._panEased = value;
  }

  get tilt() {
    return this._tilt;
  }

  set tilt(value) {
    this._tilt = value;
    this._tiltEased = value;
  }

  get distance() {
    return this._distance;
  }

  set distance(value) {
    this._distance = value;
    this._distanceEased = value;
  }

  get panEased() {
    return this._panEased;
  }

  get tiltEased() {
    return this._tiltEased;
  }

  get distanceEased() {
    return this._distanceEased;
  }

  update() {
    this._tiltEased += (this._tilt - this._tiltEased) * this.rotationEasing;
    this._panEased += (this._pan - this._panEased) * this.rotationEasing;
    this._distanceEased += (this._distance - this._distanceEased) * this.zoomEasing;

    this.matrix.identity();
    this.matrix.rotateY(this._panEased);
    this.matrix.rotateX(-this._tiltEased);
    const sinPan = Math.sin(this._panEased);
    const cosPan = Math.cos(this._panEased);
    const cosTilt = Math.cos(this._tiltEased);
    const sinTilt = Math.sin(this._tiltEased);
    this.matrix.x = this._distanceEased * sinPan * cosTilt;
    this.matrix.y = sinTilt * this._distanceEased;
    this.matrix.z = this._distanceEased * cosPan * cosTilt;
  }
}
<commit_msg>OrbitController: follow transform changes<commit_after>import Matrix4 from '../../math/Matrix4.js';
import GestureObserver from '../../input/GestureObserver.js';

export default class OrbitController {
  constructor({
    matrix = new Matrix4(),
    domElement = null,
    pan = 0,
    tilt = 0,
    invertRotation = true,
    distance = 1,
    distanceMin = 0,
    distanceMax = Infinity,
    tiltMin = -Infinity,
    tiltMax = Infinity,
    tiltDisabled = false,
    panMin = -Infinity,
    panMax = Infinity,
    panDisabled = false,
    rotationEasing = .1,
    rotationVelocity = .005,
    zoomEasing = .1,
    zoomVelocity = .1,
    zoomDisabled = false,
  }) {
    this._selfMatrix = new Matrix4();

    this.matrix = matrix;
    this.invertRotation = invertRotation;
    this.distanceMax = distanceMax;
    this.distanceMin = distanceMin;
    this.zoomEasing = zoomEasing;
    this.tiltMax = tiltMax;
    this.tiltMin = tiltMin;
    this.tiltDisabled = tiltDisabled;
    this.panMax = panMax;
    this.panMin = panMin;
    this.panDisabled = panDisabled;
    this.rotationEasing = rotationEasing;
    this.rotationVelocity = rotationVelocity;
    this.zoomDisabled = zoomDisabled;
    this.zoomVelocity = zoomVelocity;

    this._distance = distance;
    this._tilt = tilt;
    this._pan = pan;

    this._panEased = this._pan;
    this._tiltEased = this._tilt;
    this._distanceEased = this._distance;

    this._multiTouchMode = false;

    if (domElement) {
      domElement.addEventListener('wheel', (event) => {
        if (this.zoomDisabled) return;
        this._distance = Math.max(this._distance, .001) * (1 + event.deltaY * this.zoomVelocity * .01);
        this._distance = Math.max(this.distanceMin, Math.min(this.distanceMax, this._distance));
      }, { passive: true });

      const gestureObserver = new GestureObserver((gesture) => {
        if (!this.panDisabled) {
          this._pan += (this.invertRotation ? -1 : 1) * gesture.movementX * rotationVelocity;
          this._pan = Math.max(this.panMin, Math.min(this.panMax, this._pan));
        }

        if (!this.tiltDisabled) {
          this._tilt += (this.invertRotation ? 1 : -1) * gesture.movementY * rotationVelocity;
          this._tilt = Math.max(this.tiltMin, Math.min(this.tiltMax, this._tilt));
        }

        if (!this.zoomDisabled) {
          this._distance /= 1 + gesture.movementScale * this.zoomVelocity * .01;
          this._distance = Math.max(this.distanceMin, Math.min(this.distanceMax, this._distance));
        }
      }, { pointerCapture: true });
      gestureObserver.observe(domElement);
    }

    this.update();
  }

  get pan() {
    return this._pan;
  }

  set pan(value) {
    this._pan = value;
    this._panEased = value;
  }

  get tilt() {
    return this._tilt;
  }

  set tilt(value) {
    this._tilt = value;
    this._tiltEased = value;
  }

  get distance() {
    return this._distance;
  }

  set distance(value) {
    this._distance = value;
    this._distanceEased = value;
  }

  get panEased() {
    return this._panEased;
  }

  get tiltEased() {
    return this._tiltEased;
  }

  get distanceEased() {
    return this._distanceEased;
  }

  update() {
    this._tiltEased += (this._tilt - this._tiltEased) * this.rotationEasing;
    this._panEased += (this._pan - this._panEased) * this.rotationEasing;
    this._distanceEased += (this._distance - this._distanceEased) * this.zoomEasing;

    this._selfMatrix.invert();
    this.matrix.multiply(this._selfMatrix);

    this._selfMatrix.identity();
    this._selfMatrix.rotateY(this._panEased);
    this._selfMatrix.rotateX(-this._tiltEased);
    const sinPan = Math.sin(this._panEased);
    const cosPan = Math.cos(this._panEased);
    const cosTilt = Math.cos(this._tiltEased);
    const sinTilt = Math.sin(this._tiltEased);
    this._selfMatrix.x = this._distanceEased * sinPan * cosTilt;
    this._selfMatrix.y = sinTilt * this._distanceEased;
    this._selfMatrix.z = this._distanceEased * cosPan * cosTilt;

    this.matrix.multiply(this._selfMatrix);
  }
}
<|endoftext|>


<commit_before>// Require React
React = require('react/addons');
const createDOMPurify = require('dompurify');
const classNames = require('classnames');

// Material UI
import mui from 'material-ui';
const Colors = mui.Styles.Colors;

// Define component
const MemberStatus= React.createClass({
  render() {
    const paragraphStyles = {
      height: 'auto',
      margin: '5px',
      marginLeft: '0px'
    };

    const bioStyles = {
      fontSize: '14px',
      maxWidth: '70%',
      display: 'block',
      marginTop: '10px',
      fontWeight: '400',
    };

    const badgeStyles = {
      fontSize: '11px',
      lineHeight: '22px',
      padding: '2px 8px',
      marginRight: '5px',
      maxWidth: '100px',
      textOverflow: 'ellipsis',
      whiteSpace: 'nowrap',
      color: Colors.white,
      overflow: 'hidden',
      fontWeight: 'bold',
      display: 'inline-block',
      borderRadius: 2
    };

    const emailStyles = {
      backgroundColor: '#555',
      cursor: 'pointer'
    };

    const bio = createDOMPurify.sanitize(
      this.props.member.bio,
      { ALLOWED_TAGS: ['b', 'i'] }
    );

    return (

            {this.props.member.location}

          {this.props.member.bio?  : ""}

            {this.props.member.email && this.props.member.hireable ?
              Email
             : '' }
            {this.props.member.company ?
              {this.props.member.company}
             : "" }

            {this.props.member.hireable ?
              Hireable
             : !this.props.member.company ?
              May be hireable
             : ''}

              Profile

            {this.props.member.blog ?
              Website
             : ''}

              Github

      );
  },

  openUrl(url) {
    window.open(url);
  },

  openMail(e) {
    e.preventDefault();
    window.location.href = 'mailto:' + this.props.member.email
    e.stopPropagation();
  }

});

module.exports = MemberStatus;
<commit_msg>Add protocol if not present<commit_after>// Require React
React = require('react/addons');
const createDOMPurify = require('dompurify');
const classNames = require('classnames');

// Material UI
import mui from 'material-ui';
const Colors = mui.Styles.Colors;

// Define component
const MemberStatus= React.createClass({
  render() {
    const paragraphStyles = {
      height: 'auto',
      margin: '5px',
      marginLeft: '0px'
    };

    const bioStyles = {
      fontSize: '14px',
      maxWidth: '70%',
      display: 'block',
      marginTop: '10px',
      fontWeight: '400',
    };

    const badgeStyles = {
      fontSize: '11px',
      lineHeight: '22px',
      padding: '2px 8px',
      marginRight: '5px',
      maxWidth: '100px',
      textOverflow: 'ellipsis',
      whiteSpace: 'nowrap',
      color: Colors.white,
      overflow: 'hidden',
      fontWeight: 'bold',
      display: 'inline-block',
      borderRadius: 2
    };

    const emailStyles = {
      backgroundColor: '#555',
      cursor: 'pointer'
    };

    const bio = createDOMPurify.sanitize(
      this.props.member.bio,
      { ALLOWED_TAGS: ['b', 'i'] }
    );

    return (

            {this.props.member.location}

          {this.props.member.bio?  : ""}

            {this.props.member.email && this.props.member.hireable ?
              Email
             : '' }
            {this.props.member.company ?
              {this.props.member.company}
             : "" }

            {this.props.member.hireable ?
              Hireable
             : !this.props.member.company ?
              May be hireable
             : ''}

              Profile

            {this.props.member.blog ?
              Website
             : ''}

              Github

      );
  },

  openUrl(url) {
    var urlWithProtocol = url.match(/^http[s]*:\/\//) ? url : 'http://' + url;
    // open with protocol
    window.open(urlWithProtocol);
  },

  openMail(e) {
    e.preventDefault();
    window.location.href = 'mailto:' + this.props.member.email
    e.stopPropagation();
  }

});

module.exports = MemberStatus;
<|endoftext|>


<commit_before>/* global app:true */

var socket;
var currenttime;
var username='noname';

var width = 720,
	height = 1000,
	cellWidth = 80,
	cellHeight = 72, // cell size
	gapWidth = 12;
	gapHeight = 6;

var hour = d3.time.format("%I"),
	minute = d3.time.format("%M"),
	month = d3.time.format("%b"),
	weekday = d3.time.format("%a"),
	date = d3.time.format("%d"),
	ampm = d3.time.format("%p"),
	format = d3.time.format("%b/%d %H:%M%p");

(function() {
  'use strict';

	var myClock;

	function myTimer(){
		socket.emit('lookclock');
	}

  var blockdata = [];
  var clockbar = $('#clock_bar');

  var callBlocks = function(ticket){
	// +- 1 hour blocks range
	var beginT = d3.time.hour.floor(ticket);
	beginT = d3.time.hour.offset(beginT, -1);
	var endT = d3.time.hour.offset(beginT, 3);

	socket.emit('timeline', { type: 'callblocks', user:username, begintime: beginT, endtime: endT});
  }

  var draw = function(blockdata){
    d3.select("svg")
       .remove();
	var tip = d3.tip()
		.attr('class', 'd3-tip')
		.direction('e')
		.offset([0,10])
		.html(function(d) {
			return "time: "+d.time;
		});

	var svg = d3.select("#d3Area").append("svg")
		.attr("width", width)
		.attr("height", height)
		.append("g")
		.attr("transform", "translate(24, 24)");

	svg.call(tip);

	var block = svg.selectAll(".block")
		.data(blockdata)
	.enter().append("g")
		.attr("class", "block")
		.attr("transform", function (d,i)
		{
			var dx = (i-i%12)/12*(cellWidth+gapWidth);
			var dy =  i%12 * (cellHeight+gapHeight);
			return "translate(" + dx + ","+ dy + ")";
		})
		.on('mouseover', tip.show)
		.on('mouseout', tip.hide);

	block.append("rect")
		.attr("class", function (d)
		{
			if (d.lock==1){
				class_name="block-locked";
			}else{
				if (d.current==1){
					class_name="block-current";
				}else{
					var class_name="block-default";
				}
			}
			return class_name;
		})
		.attr("width", cellWidth)
		.attr("height", cellHeight)
		.attr("block-hour", function (d,i){
			var class_name;
			var hour = (i-i%12)/12;

			if (hour==1){
				class_name="current";
			}else{
				if (hour>1){
					class_name="future";
				}else{
					var class_name="past";
				}
			}
			return class_name;
		})
		.on('mouseover', tip.show)
		.on('mouseout', tip.hide)
		.on('click', mouseclick);

	block.append("text")
		.attr("class", "block-name")
		.attr("x",10)
		.attr("y",10)
		.attr("dy", ".3em")
		//.style("text-anchor", "middle")
		.text(function(d) {
			var h = d.time.getHours();
			var ampm = (h<12 ? "AM":"PM");
			//h=("0" + h%12).slice(-2);
			h=(h%12);
			if(h==0){h="12";}
			var m = d.time.getMinutes();
			m=("0" + m).slice(-2);
			return h+":"+m+" "+ampm;
		})
		.on('mouseover', tip.show)
		.on('mouseout', tip.hide);
  }

  function mouseclick(d,i){
  	// reset to default layout
  	d3.selectAll(".block rect").transition().duration(1000)
  		.attr("width", cellWidth);

	// expand the selected block
  	d3.select(this).transition().duration(1000)
        .attr("width", cellWidth*(3-(i-i%12)/12)+50);
  }

  socket = io.connect('http://171.65.102.132:3006');

  socket.on('postblocks', function(data){
  	blockdata = [];
  	var num_ele = 9;
	for (var i=0;i<=data.length/num_ele;i++){
		var block = new Object();
		block.id = i;

		var d = new Date(0);
		d.setTime(data[i*num_ele]);
		block.time = d;

		block.lock = data[i*num_ele+1];
		block.user_id = data[i*num_ele+2];
		block.exp_id = data[i*num_ele+3];
		block.pattern_id = data[i*num_ele+4];
		block.past = data[i*num_ele+5];
		block.admin = data[i*num_ele+6];
		block.current = data[i*num_ele+7];
		block.image = data[i*num_ele+8];

		blockdata.push(block);
	}
	blockdata.length = blockdata.length-2;
	console.dir(blockdata);
	draw(blockdata);
  });

  socket.on('server_clock', function(data){
  	//console.log(data);
  	clockbar.html(""+data+"");
  });

  socket.on('message', function(msg){
	console.log(msg);
  });

  socket.on('connect', function() {
	console.log("Connected!");
	currenttime = new Date();
	callBlocks(currenttime);
	myClock=setInterval(function(){myTimer()},500);
  });

  socket.on('disconnect', function() {
	console.log('disconnected');
  });

  app = app || {};

  app.Blocks = Backbone.Model.extend({
    url: '/account/timeline/',
    defaults: {
      username: '',
      timestamp: ''
    }
  });

  app.BlocksView = Backbone.View.extend({
    el: '#blocks_nav',
    template: _.template( $('#tmpl-blocks_nav').html() ),
    events: {
      'click .btn-prev': 'reqPrev',
      'click .btn-now': 'reqNow',
      'click .btn-next': 'reqNext'
    },
    initialize: function() {
      this.model = new app.Blocks();
      this.listenTo(this.model, 'sync', this.render);
      this.render();
    },
    render: function() {
      this.$el.html(this.template( this.model.attributes));
    },
    reqPrev: function() {
      currenttime.setHours(currenttime.getHours() - 1);
      callBlocks(currenttime);
    },
    reqNow: function() {
      currenttime = new Date();
      callBlocks(currenttime);
    },
    reqNext: function() {
      currenttime.setHours(currenttime.getHours() + 1);
      callBlocks(currenttime);
    }
  });

  $(document).ready(function() {
    app.blocksView = new app.BlocksView();
  });

}());
<commit_msg>display<commit_after>/* global app:true */

var socket;
var currenttime;
var username='noname';

var width = 720,
	height = 1000,
	cellWidth = 80,
	cellHeight = 72, // cell size
	gapWidth = 12;
	gapHeight = 6;

var hour = d3.time.format("%I"),
	minute = d3.time.format("%M"),
	month = d3.time.format("%b"),
	weekday = d3.time.format("%a"),
	date = d3.time.format("%d"),
	ampm = d3.time.format("%p"),
	format = d3.time.format("%b/%d %H:%M%p");

(function() {
  'use strict';

	var myClock;

	function myTimer(){
		socket.emit('lookclock');
	}

  var blockdata = [];
  var clockbar = $('#clock_bar');

  var callBlocks = function(ticket){
	// +- 1 hour blocks range
	var beginT = d3.time.hour.floor(ticket);
	beginT = d3.time.hour.offset(beginT, -1);
	var endT = d3.time.hour.offset(beginT, 3);

	socket.emit('timeline', { type: 'callblocks', user:username, begintime: beginT, endtime: endT});
  }

  var draw = function(blockdata){
    d3.select("svg")
       .remove();
	var tip = d3.tip()
		.attr('class', 'd3-tip')
		.direction('e')
		.offset([0,10])
		.html(function(d) {
			return "time: "+d.time;
		});

	var svg = d3.select("#d3Area").append("svg")
		.attr("width", width)
		.attr("height", height)
		.append("g")
		.attr("transform", "translate(24, 24)");

	svg.call(tip);

	var block = svg.selectAll(".block")
		.data(blockdata)
	.enter().append("g")
		.attr("class", "block")
		.attr("transform", function (d,i)
		{
			var dx = (i-i%12)/12*(cellWidth+gapWidth);
			var dy =  i%12 * (cellHeight+gapHeight);
			return "translate(" + dx + ","+ dy + ")";
		})
		.on('mouseover', tip.show)
		.on('mouseout', tip.hide);

	block.append("rect")
		.attr("class", function (d)
		{
			if (d.lock==1){
				class_name="block-locked";
			}else{
				if (d.current==1){
					class_name="block-current";
				}else{
					var class_name="block-default";
				}
			}
			return class_name;
		})
		.attr("width", cellWidth)
		.attr("height", cellHeight)
		.attr("block-hour", function (d,i){
			var class_name;
			var hour = (i-i%12)/12;

			if (hour==1){
				class_name="current";
			}else{
				if (hour>1){
					class_name="future";
				}else{
					var class_name="past";
				}
			}
			return class_name;
		})
		.on('mouseover', tip.show)
		.on('mouseout', tip.hide)
		.on('click', mouseclick);

	block.append("text")
		.attr("class", "block-name")
		.attr("x",10)
		.attr("y",10)
		.attr("dy", ".3em")
		//.style("text-anchor", "middle")
		.text(function(d) {
			var h = d.time.getHours();
			var ampm = (h<12 ? "AM":"PM");
			//h=("0" + h%12).slice(-2);
			h=(h%12);
			if(h==0){h="12";}
			var m = d.time.getMinutes();
			m=("0" + m).slice(-2);
			return h+":"+m+" "+ampm;
		})
		.on('mouseover', tip.show)
		.on('mouseout', tip.hide);
  }

  function mouseclick(d,i){
  	// reset to default layout
  	d3.selectAll(".block rect").transition().duration(1000)
  		.attr("width", cellWidth);

	// expand the selected block
  	d3.select(this).transition().duration(1000)
        .attr("width", cellWidth*(3-(i-i%12)/12)+100);
  }

  socket = io.connect('http://171.65.102.132:3006');

  socket.on('postblocks', function(data){
  	blockdata = [];
  	var num_ele = 9;
	for (var i=0;i<=data.length/num_ele;i++){
		var block = new Object();
		block.id = i;

		var d = new Date(0);
		d.setTime(data[i*num_ele]);
		block.time = d;

		block.lock = data[i*num_ele+1];
		block.user_id = data[i*num_ele+2];
		block.exp_id = data[i*num_ele+3];
		block.pattern_id = data[i*num_ele+4];
		block.past = data[i*num_ele+5];
		block.admin = data[i*num_ele+6];
		block.current = data[i*num_ele+7];
		block.image = data[i*num_ele+8];

		blockdata.push(block);
	}
	blockdata.length = blockdata.length-2;
	console.dir(blockdata);
	draw(blockdata);
  });

  socket.on('server_clock', function(data){
  	//console.log(data);
  	clockbar.html(""+data+"");
  });

  socket.on('message', function(msg){
	console.log(msg);
  });

  socket.on('connect', function() {
	console.log("Connected!");
	currenttime = new Date();
	callBlocks(currenttime);
	myClock=setInterval(function(){myTimer()},500);
  });

  socket.on('disconnect', function() {
	console.log('disconnected');
  });

  app = app || {};

  app.Blocks = Backbone.Model.extend({
    url: '/account/timeline/',
    defaults: {
      username: '',
      timestamp: ''
    }
  });

  app.BlocksView = Backbone.View.extend({
    el: '#blocks_nav',
    template: _.template( $('#tmpl-blocks_nav').html() ),
    events: {
      'click .btn-prev': 'reqPrev',
      'click .btn-now': 'reqNow',
      'click .btn-next': 'reqNext'
    },
    initialize: function() {
      this.model = new app.Blocks();
      this.listenTo(this.model, 'sync', this.render);
      this.render();
    },
    render: function() {
      this.$el.html(this.template( this.model.attributes));
    },
    reqPrev: function() {
      currenttime.setHours(currenttime.getHours() - 1);
      callBlocks(currenttime);
    },
    reqNow: function() {
      currenttime = new Date();
      callBlocks(currenttime);
    },
    reqNext: function() {
      currenttime.setHours(currenttime.getHours() + 1);
      callBlocks(currenttime);
    }
  });

  $(document).ready(function() {
    app.blocksView = new app.BlocksView();
  });

}());
<|endoftext|>


<commit_before>$(function() {
  // Main body titles
  $('a h2, a h3, h2 a, h3 a').satanify();
  $('#main ul.link-list a').satanify();

  // Breaking news
  $('[id^=breakingNewsItem] a').satanify();

  // Sidebar most read
  $('h2.currentlyread').satanify();
  $('[id^=mostRead_] .numberbullet-list li a').satanify(' ');

  // Sidebar latest
  $('[id^=latestArticles-] h2:first').satanify();

  // Sidebar most commented
  $('.most-commented-text').satanify(' ');

  // Generic sidebar link lists
  $('[id^=configLinkList] li a, [id^=latestArticles] li a').satanify(' ');

  // Individual news page titles
  $('#content #main h1:first').satanify();
});

<commit_msg>IS surveys<commit_after>$(function() {
  // Main body titles
  $('a h2, a h3, h2 a, h3 a').satanify();
  $('#main ul.link-list a').satanify();

  // Surveys
  $('[class^=quick-survey] h3, a.survey-button').satanify();

  // Breaking news
  $('[id^=breakingNewsItem] a').satanify();

  // Sidebar most read
  $('h2.currentlyread').satanify();
  $('[id^=mostRead_] .numberbullet-list li a').satanify(' ');

  // Sidebar latest
  $('[id^=latestArticles-] h2:first').satanify();

  // Sidebar most commented
  $('.most-commented-text').satanify(' ');

  // Generic sidebar link lists
  $('[id^=configLinkList] li a, [id^=latestArticles] li a').satanify(' ');

  // Individual news page titles
  $('#content #main h1:first').satanify();
});

<|endoftext|>


<commit_before>
cdb.admin.Visualization = cdb.core.Model.extend({

  defaults: {
    bindMap: true
  },

  urlRoot: '/api/v1/viz',

  INHERIT_TABLE_ATTRIBUTES: [
    'name', 'privacy'
  ],

  initialize: function() {
    this.map = new cdb.admin.Map();

    this.permission = new cdb.admin.Permission(this.get('permission'));

    // Check if there are related tables and generate the collection
    if (this.get('type') == "derived" && this.get('related_tables')) this.generateRelatedTables();

    // Check if there are dependant visualizations and generate the collection //
    // TODO //

    if (this.get('bindMap')) this._bindMap();
    this.on(_(this.INHERIT_TABLE_ATTRIBUTES).map(function(t) { return 'change:' + t }).join(' '),  this._changeAttributes, this);
  },

  generateRelatedTables: function(callback) {
    var tables = this.get('related_tables');

    if (tables.length) {
      var collection = new Backbone.Collection([]);

      for (var i = 0, l = tables.length; i < l; i++) {
        var table = new cdb.admin.CartoDBTableMetadata(tables[i]);
        collection.add(table);
      }

      this.related_tables = collection;
      callback && callback.success && callback.success();
    }
  },

  getRelatedTables: function(callback, options) {
    options = options || {}
    if (this.get('type') == "derived") {

      if (!options.force && this.get('related_tables')) {
        this.generateRelatedTables(callback);
        return;
      }

      var self = this;
      this.fetch({
        success: function() {
          self.generateRelatedTables(callback)
        },
        error: callback && callback.error && callback.error
      })
    }
  },

  // _getDependantVisualizations: function() {

  // },

  _bindMap: function() {

    this.on('change:map_id', this._fetchMap, this);

    this.map.bind('change:id', function() {
      this.set('map_id', this.map.id);
    }, this);

    this.map.set('id', this.get('map_id'));

    // when the layers change we should reload related_tables
    this.map.layers.bind('change:id remove', function() {
      this.getRelatedTables(null, {
        force: true
      });
    }, this);

  },

  viewUrl: function() {
    return cdb.config.prefixUrl() + "/viz/" + this.id + "/";
  },

  /**
   *  Is this model a true visualization?
   */
  isVisualization: function() {
    return this.get('type') === "derived";
  },

  /**
   *  Change current visualization by new one without
   *  creating a new instance.
   *
   *  When turn table visualization to derived visualization,
   *  it needs to wait until reset layers. If not, adding a new
   *  layer after create the new visualization won't work...
   *
   */
  changeTo: function(new_vis, callbacks) {
    this.set(new_vis.attributes, { silent: true });

    var success = function() {
      this.map.layers.unbind('reset', success);
      this.map.layers.unbind('error', error);
      callbacks && callbacks.success && callbacks.success(this);
    };

    var error = function() {
      this.map.layers.unbind('reset', success);
      this.map.layers.unbind('error', error);
      callbacks && callbacks.error && callbacks.error();
    }

    this.map.layers.bind('reset', success, this);
    this.map.layers.bind('error', error, this)
    this.set({ map_id: new_vis.map.id });
    // Get related tables from the new visualization
    this.getRelatedTables();
  },

  /**
   *  Transform a table visualization/model to a original visualization
   */
  changeToVisualization: function(callback) {
    var self = this;
    if (!this.isVisualization()) {
      var callbacks = {
        success: function(new_vis) {
          self.changeTo(new_vis, callback);
          self.notice('', '', 1000);
        },
        error: function(e) {
          var msg = 'error changing to visualization';
          self.error(msg, e);
          callback && callback.error(e, msg);
        }
      };
      // Name is not saved in the back end, due to that
      // we need to pass it as parameter
      this.copy({ name: this.get('name') }, callbacks);
    } else {
      self.notice('', '', 1000);
    }
    return this;
  },

  toJSON: function() {
    var attr = _.clone(this.attributes);
    delete attr.bindMap;
    delete attr.stats;
    delete attr.related_tables;
    attr.map_id = this.map.id;
    return attr;
  },

  /**
   *  Create a copy of the visualization model
   */
  copy: function(attrs, options) {
    attrs = attrs || {};
    options = options || {};
    var vis = new cdb.admin.Visualization(
      _.extend({
          source_visualization_id: this.id
        },
        attrs
      )
    );
    vis.save(null, options);
    return vis;
  },

  /**
   *  Fetch map information
   */
  _fetchMap: function() {
    this.map
      .set('id', this.get('map_id'))
      .fetch();
  },

  /**
   *  Generic function to catch up new attribute changes
   */
  _changeAttributes: function(m, c) {
    if (!this.isVisualization()) {

      // Change table attribute if layer is CartoDB-layer
      var self = this;

      this.map.layers.each(function(layer) {
        if (layer.get('type').toLowerCase() == "cartodb") {

          // If there isn't any changed attribute
          if (!self.changedAttributes()) { return false; }

          var attrs = _.pick(self.changedAttributes(), self.INHERIT_TABLE_ATTRIBUTES);

          if (attrs) layer.fetch();
        }
      })
    }
  },

  // PUBLIC FUNCTIONS

  publicURL: function() {
    return "/viz/" + this.get('id') + "/public_map";
  },

  embedURL: function() {
    return "/viz/" + this.get('id') + "/embed_map";
  },

  notice: function(msg, type, timeout) {
    this.trigger('notice', msg, type, timeout);
  },

  error: function(msg, resp) {
    this.trigger('notice', msg, 'error');
  }
});

/**
 * Visualizations endpoint available for a given user.
 *
 * Usage:
 *
 *   var visualizations = new cdb.admin.Visualizations()
 *   visualizations.fetch();
 *
 */

cdb.admin.Visualizations = Backbone.Collection.extend({

  model: cdb.admin.Visualization,

  _PREVIEW_TABLES_PER_PAGE: 10,
  _TABLES_PER_PAGE: 20,
  _PREVIEW_ITEMS_PER_PAGE: 3,
  _ITEMS_PER_PAGE: 9,

  initialize: function() {

    var default_options = new cdb.core.Model({
      tag_name    : "",
      q           : "",
      page        : 1,
      type        : "derived",
      only_shared : false,
      per_page    : this._ITEMS_PER_PAGE
    });

    this.options = _.extend(default_options, this.options);

    this.total_entries = 0;

    this.options.bind("change", this._changeOptions, this);
    this.bind("reset",          this._checkPage, this);
    this.bind("update",         this._checkPage, this);
    this.bind("add",            this._fetchAgain, this);

  },

  getTotalPages: function() {
    return Math.ceil(this.total_entries / this.options.get("per_page"));
  },

  _checkPage: function() {
    var total = this.getTotalPages();
    var page = this.options.get('page') - 1;

    if (this.options.get("page") > total ) {
      this.options.set({ page: total + 1});
    } else if (this.options.get("page") < 1) {
      this.options.set({ page: 1});
    }

  },

  _createUrlOptions: function() {
    return _.compact(_(this.options.attributes).map(
      function(v, k) {
        // We avoid to send false parameters,
        // for example, only_shared
        if (v === false) {
          return null
        }

        return k + "=" + encodeURIComponent(v)
      }
    )).join('&');
  },

  url: function() {
    var u = '/api/v1/viz/';
    u += "?" + this._createUrlOptions();
    return u;
  },

  remove: function(options) {
    this.total_entries--;
    this.elder('remove', options);
  },

  // add bindMap: false for all the visulizations
  // vis model does not need map information in dashboard
  parse: function(response) {
    this.total_entries = response.total_entries;
    return _.map(response.visualizations, function(v) {
      v.bindMap = false;
      return v;
    });
  },

  _changeOptions: function() {
    // this.trigger('updating');

    // var self = this;
    // $.when(this.fetch()).done(function(){
    //   self.trigger('forceReload')
    // });
  },

  create: function(m) {
    var dfd = $.Deferred();
    Backbone.Collection.prototype.create.call(this,
      m,
      {
        wait: true,
        success: function() {
          dfd.resolve();

        },
        error: function() {
          dfd.reject();
        }
      }
    );
    return dfd.promise();
  },

  fetch: function(opts) {
    var dfd = $.Deferred();
    var self = this;
    this.trigger("loading", this);

    $.when(Backbone.Collection.prototype.fetch.call(this,opts))
    .done(function(res) {
      self.trigger('loaded');
      dfd.resolve();
    }).fail(function(res) {
      self.trigger('loadFailed');
      dfd.reject(res);
    });

    return dfd.promise();
  }
});
<commit_msg>vis model cleanup<commit_after>
cdb.admin.Visualization = cdb.core.Model.extend({

  defaults: {
    bindMap: true
  },

  urlRoot: '/api/v1/viz',

  INHERIT_TABLE_ATTRIBUTES: [
    'name', 'privacy'
  ],

  initialize: function() {
    this.map = new cdb.admin.Map();

    this.permission = new cdb.admin.Permission(this.get('permission'));

    // Check if there are related tables and generate the collection
    if (this.get('type') == "derived" && this.get('related_tables')) this.generateRelatedTables();

    // Check if there are dependant visualizations and generate the collection //
    // TODO //

    if (this.get('bindMap')) this._bindMap();
    this.on(_(this.INHERIT_TABLE_ATTRIBUTES).map(function(t) { return 'change:' + t }).join(' '),  this._changeAttributes, this);
  },

  generateRelatedTables: function(callback) {
    var tables = this.get('related_tables');

    if (tables.length) {
      var collection = new Backbone.Collection([]);

      for (var i = 0, l = tables.length; i < l; i++) {
        var table = new cdb.admin.CartoDBTableMetadata(tables[i]);
        collection.add(table);
      }

      this.related_tables = collection;
      callback && callback.success && callback.success();
    }
  },

  getRelatedTables: function(callback, options) {
    options = options || {};
    if (this.get('type') === "derived") {

      if (!options.force && this.get('related_tables')) {
        this.generateRelatedTables(callback);
        return;
      }

      var self = this;
      this.fetch({
        success: function() {
          self.generateRelatedTables(callback);
        },
        error: callback && callback.error && callback.error
      });
    }
  },

  // _getDependantVisualizations: function() {

  // },

  _bindMap: function() {

    this.on('change:map_id', this._fetchMap, this);

    this.map.bind('change:id', function() {
      this.set('map_id', this.map.id);
    }, this);

    this.map.set('id', this.get('map_id'));

    // when the layers change we should reload related_tables
    this.map.layers.bind('change:id remove', function() {
      this.getRelatedTables(null, {
        force: true
      });
    }, this);

  },

  viewUrl: function() {
    return cdb.config.prefixUrl() + "/viz/" + this.id + "/";
  },

  /**
   *  Is this model a true visualization?
   */
  isVisualization: function() {
    return this.get('type') === "derived";
  },

  /**
   *  Change current visualization by new one without
   *  creating a new instance.
   *
   *  When turn table visualization to derived visualization,
   *  it needs to wait until reset layers. If not, adding a new
   *  layer after create the new visualization won't work...
   *
   */
  changeTo: function(new_vis, callbacks) {
    this.set(new_vis.attributes, { silent: true });

    var success = function() {
      this.map.layers.unbind('reset', success);
      this.map.layers.unbind('error', error);
      callbacks && callbacks.success && callbacks.success(this);
    };

    var error = function() {
      this.map.layers.unbind('reset', success);
      this.map.layers.unbind('error', error);
      callbacks && callbacks.error && callbacks.error();
    }

    this.map.layers.bind('reset', success, this);
    this.map.layers.bind('error', error, this)
    this.set({ map_id: new_vis.map.id });
    // Get related tables from the new visualization
    this.getRelatedTables();
  },

  /**
   *  Transform a table visualization/model to a original visualization
   */
  changeToVisualization: function(callback) {
    var self = this;
    if (!this.isVisualization()) {
      var callbacks = {
        success: function(new_vis) {
          self.changeTo(new_vis, callback);
          self.notice('', '', 1000);
        },
        error: function(e) {
          var msg = 'error changing to visualization';
          self.error(msg, e);
          callback && callback.error(e, msg);
        }
      };
      // Name is not saved in the back end, due to that
      // we need to pass it as parameter
      this.copy({ name: this.get('name') }, callbacks);
    } else {
      self.notice('', '', 1000);
    }
    return this;
  },

  toJSON: function() {
    var attr = _.clone(this.attributes);
    delete attr.bindMap;
    delete attr.stats;
    delete attr.related_tables;
    attr.map_id = this.map.id;
    return attr;
  },

  /**
   *  Create a copy of the visualization model
   */
  copy: function(attrs, options) {
    attrs = attrs || {};
    options = options || {};
    var vis = new cdb.admin.Visualization(
      _.extend({
          source_visualization_id: this.id
        },
        attrs
      )
    );
    vis.save(null, options);
    return vis;
  },

  /**
   *  Fetch map information
   */
  _fetchMap: function() {
    this.map
      .set('id', this.get('map_id'))
      .fetch();
  },

  /**
   *  Generic function to catch up new attribute changes
   */
  _changeAttributes: function(m, c) {
    if (!this.isVisualization()) {

      // Change table attribute if layer is CartoDB-layer
      var self = this;

      this.map.layers.each(function(layer) {
        if (layer.get('type').toLowerCase() == "cartodb") {

          // If there isn't any changed attribute
          if (!self.changedAttributes()) { return false; }

          var attrs = _.pick(self.changedAttributes(), self.INHERIT_TABLE_ATTRIBUTES);

          if (attrs) layer.fetch();
        }
      })
    }
  },

  // PUBLIC FUNCTIONS

  publicURL: function() {
    return "/viz/" + this.get('id') + "/public_map";
  },

  embedURL: function() {
    return "/viz/" + this.get('id') + "/embed_map";
  },

  notice: function(msg, type, timeout) {
    this.trigger('notice', msg, type, timeout);
  },

  error: function(msg, resp) {
    this.trigger('notice', msg, 'error');
  }
});

/**
 * Visualizations endpoint available for a given user.
 *
 * Usage:
 *
 *   var visualizations = new cdb.admin.Visualizations()
 *   visualizations.fetch();
 *
 */

cdb.admin.Visualizations = Backbone.Collection.extend({

  model: cdb.admin.Visualization,

  _PREVIEW_TABLES_PER_PAGE: 10,
  _TABLES_PER_PAGE: 20,
  _PREVIEW_ITEMS_PER_PAGE: 3,
  _ITEMS_PER_PAGE: 9,

  initialize: function() {

    var default_options = new cdb.core.Model({
      tag_name    : "",
      q           : "",
      page        : 1,
      type        : "derived",
      only_shared : false,
      per_page    : this._ITEMS_PER_PAGE
    });

    this.options = _.extend(default_options, this.options);

    this.total_entries = 0;

    this.options.bind("change", this._changeOptions, this);
    this.bind("reset",          this._checkPage, this);
    this.bind("update",         this._checkPage, this);
    this.bind("add",            this._fetchAgain, this);

  },

  getTotalPages: function() {
    return Math.ceil(this.total_entries / this.options.get("per_page"));
  },

  _checkPage: function() {
    var total = this.getTotalPages();
    var page = this.options.get('page') - 1;

    if (this.options.get("page") > total ) {
      this.options.set({ page: total + 1});
    } else if (this.options.get("page") < 1) {
      this.options.set({ page: 1});
    }

  },

  _createUrlOptions: function() {
    return _.compact(_(this.options.attributes).map(
      function(v, k) {
        // We avoid to send false parameters,
        // for example, only_shared
        if (v === false) {
          return null
        }

        return k + "=" + encodeURIComponent(v)
      }
    )).join('&');
  },

  url: function() {
    var u = '/api/v1/viz/';
    u += "?" + this._createUrlOptions();
    return u;
  },

  remove: function(options) {
    this.total_entries--;
    this.elder('remove', options);
  },

  // add bindMap: false for all the visulizations
  // vis model does not need map information in dashboard
  parse: function(response) {
    this.total_entries = response.total_entries;
    return _.map(response.visualizations, function(v) {
      v.bindMap = false;
      return v;
    });
  },

  _changeOptions: function() {
    // this.trigger('updating');

    // var self = this;
    // $.when(this.fetch()).done(function(){
    //   self.trigger('forceReload')
    // });
  },

  create: function(m) {
    var dfd = $.Deferred();
    Backbone.Collection.prototype.create.call(this,
      m,
      {
        wait: true,
        success: function() {
          dfd.resolve();

        },
        error: function() {
          dfd.reject();
        }
      }
    );
    return dfd.promise();
  },

  fetch: function(opts) {
    var dfd = $.Deferred();
    var self = this;
    this.trigger("loading", this);

    $.when(Backbone.Collection.prototype.fetch.call(this,opts))
    .done(function(res) {
      self.trigger('loaded');
      dfd.resolve();
    }).fail(function(res) {
      self.trigger('loadFailed');
      dfd.reject(res);
    });

    return dfd.promise();
  }
});
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');

var options = {
  includes: 'assign watchers project',
  defaults: {
    project: undefined,
    assign: undefined,
    discussions: [],
    watchers: []
  }
};

exports.defaultOptions = options;

var crud = require('../controllers/crud.js');
var task = crud('tasks', options);

var Task = require('../models/task'),
  mean = require('meanio');

Object.keys(task).forEach(function(methodName) {
  if (methodName !== 'create' || methodName !== 'update') {
    exports[methodName] = task[methodName];
  }
});

exports.create = function(req, res, next) {
  if (req.locals.error) {
    return next();
  }

  req.body.discussions = [];
  if (req.body.discussion) {
    req.body.discussions = [req.body.discussion];
    req.body.tags = ['Agenda'];
  }

  task.create(req, res, next);
};

exports.update = function(req, res, next) {
  if (req.locals.error) {
    return next();
  }

  if (req.body.discussion) {
    var alreadyAdded = _(req.locals.result.discussions).any(function(d) {
      return d.toString() === req.body.discussion;
    });

    if (!alreadyAdded) {
      req.body.discussions = req.locals.result.discussions;
      req.body.discussions.push(req.body.discussion);
    }
  }

  task.update(req, res, next);
};

exports.tagsList = function (req, res, next) {
  if (req.locals.error) {
    return next();
  }

  var query = {
    'query': {'query_string': {'query': '*'}},
    'facets': {
      'tags': {'terms': {'field': 'tags'}}
    }
  };

  mean.elasticsearch.search({index: 'task', 'body': query, size: 3000}, function (err, response) {
    if (err) {
      req.locals.error = { message: 'Can\'t get tags' };
    } else {
      req.locals.result = response.facets ? response.facets.tags.terms : [];
    }

    next();
  });
};

exports.getByEntity = function (req, res, next) {
  if (req.locals.error) {
    return next();
  }

  var entities = {projects: 'project', users: 'assign', discussions: 'discussions', tags: 'tags'},
    entityQuery = {};
  entityQuery[entities[req.params.entity]] = (req.params.id instanceof Array) ? {$in: req.params.id} : req.params.id;

  var starredOnly = false;
  var ids = req.locals.data.ids;
  if (ids && ids.length) {
    entityQuery._id = { $in: ids };
    starredOnly = true;
  }

  var Query = Task.find(entityQuery);
  Query.populate(options.includes);

  var pagination = req.locals.data.pagination;
  if (pagination && pagination.type && pagination.type === 'page') {
    Query.sort(pagination.sort)
      .skip(pagination.start)
      .limit(pagination.limit);
  }

  Query.exec(function (err, tasks) {
    if (err) {
      req.locals.error = { message: 'Can\'t get tags' };
    } else {
      if (starredOnly) {
        tasks.forEach(function(task) {
          task.star = true;
        });
      }
    }

    req.locals.result = tasks;

    next();
  });
};

exports.getZombieTasks = function (req, res, next) {
  if (req.locals.error) {
    return next();
  }

  var Query = Task.find({project: {$eq: null}, discussions: {$size: 0}});
  Query.populate(options.includes);

  Query.exec(function (err, tasks) {
    if (err) {
      req.locals.error = { message: 'Can\'t get zombie tasks' };
    } else {
      req.locals.result = tasks;
    }

    next();
  });
};
<commit_msg>load more tasks - fix<commit_after>'use strict';

var _ = require('lodash');
var q = require('q');

var options = {
  includes: 'assign watchers project',
  defaults: {
    project: undefined,
    assign: undefined,
    discussions: [],
    watchers: []
  }
};

exports.defaultOptions = options;

var crud = require('../controllers/crud.js');
var task = crud('tasks', options);

var Task = require('../models/task'),
  mean = require('meanio');

Object.keys(task).forEach(function(methodName) {
  if (methodName !== 'create' || methodName !== 'update') {
    exports[methodName] = task[methodName];
  }
});

exports.create = function(req, res, next) {
  if (req.locals.error) {
    return next();
  }

  req.body.discussions = [];
  if (req.body.discussion) {
    req.body.discussions = [req.body.discussion];
    req.body.tags = ['Agenda'];
  }

  task.create(req, res, next);
};

exports.update = function(req, res, next) {
  if (req.locals.error) {
    return next();
  }

  if (req.body.discussion) {
    var alreadyAdded = _(req.locals.result.discussions).any(function(d) {
      return d.toString() === req.body.discussion;
    });

    if (!alreadyAdded) {
      req.body.discussions = req.locals.result.discussions;
      req.body.discussions.push(req.body.discussion);
    }
  }

  task.update(req, res, next);
};

exports.tagsList = function (req, res, next) {
  if (req.locals.error) {
    return next();
  }

  var query = {
    'query': {'query_string': {'query': '*'}},
    'facets': {
      'tags': {'terms': {'field': 'tags'}}
    }
  };

  mean.elasticsearch.search({index: 'task', 'body': query, size: 3000}, function (err, response) {
    if (err) {
      req.locals.error = { message: 'Can\'t get tags' };
    } else {
      req.locals.result = response.facets ? response.facets.tags.terms : [];
    }

    next();
  });

};

exports.getByEntity = function (req, res, next) {
  if (req.locals.error) {
    return next();
  }

  var entities = {projects: 'project', users: 'assign', discussions: 'discussions', tags: 'tags'},
    entityQuery = {};
  entityQuery[entities[req.params.entity]] = (req.params.id instanceof Array) ? {$in: req.params.id} : req.params.id;

  var starredOnly = false;
  var ids = req.locals.data.ids;
  if (ids && ids.length) {
    entityQuery._id = { $in: ids };
    starredOnly = true;
  }
  var Query = Task.find(entityQuery);
  Query.populate(options.includes);

  Task.find(entityQuery).count({}, function(err, c) {
    req.locals.data.pagination.count = c;

    var pagination = req.locals.data.pagination;
	  if (pagination && pagination.type && pagination.type === 'page') {
	    Query.sort(pagination.sort)
	      .skip(pagination.start)
	      .limit(pagination.limit);
	  }

	  Query.exec(function (err, tasks) {
	    if (err) {
	      req.locals.error = { message: 'Can\'t get tags' };
	    } else {
	      // req.locals.data.pagination.count = tasks.length;
	      if (starredOnly) {
	        tasks.forEach(function(task) {
	          task.star = true;
	        });
	      }
	    }
	    req.locals.result = tasks;

	    next();
	  });
  });

};

exports.getZombieTasks = function (req, res, next) {
  if (req.locals.error) {
    return next();
  }

  var Query = Task.find({project: {$eq: null}, discussions: {$size: 0}});
  Query.populate(options.includes);

  Query.exec(function (err, tasks) {
    if (err) {
      req.locals.error = { message: 'Can\'t get zombie tasks' };
    } else {
      req.locals.result = tasks;
    }

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


<commit_before>import afterTransition from 'discourse/lib/after-transition';
import positioningWorkaround from 'discourse/lib/safari-hacks';
import { headerHeight } from 'discourse/views/header';
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
import Composer from 'discourse/models/composer';

const ComposerView = Ember.View.extend(Ember.Evented, {
  _lastKeyTimeout: null,
  elementId: 'reply-control',
  classNameBindings: ['composer.creatingPrivateMessage:private-message',
                      'composeState',
                      'composer.loading',
                      'composer.canEditTitle:edit-title',
                      'composer.createdPost:created-post',
                      'composer.creatingTopic:topic'],

  composer: Em.computed.alias('controller.model'),

  @computed('composer.composeState')
  composeState(composeState) {
    return composeState || Composer.CLOSED;
  },

  movePanels() {

    // Do we need these anymore? causes issues on iPad. Reply sometimes does not
    // show composer due to bottom padding.

    // $('#main-outlet').css('padding-bottom', sizePx);
    // $('.composer-popup').css('bottom', sizePx);

    // signal the progress bar it should move!
    this.appEvents.trigger("composer:resized");
  },

  @observes('composeState', 'composer.action')
  resize() {
    Ember.run.scheduleOnce('afterRender', () => {
      const h = $('#reply-control').height() || 0;
      this.movePanels(h + "px");

      // Figure out the size of the fields
      const $fields = this.$('.composer-fields');
      const fieldPos = $fields.position();
      if (fieldPos) {
        this.$('.wmd-controls').css('top', $fields.height() + fieldPos.top + 5);
      }

      // get the submit panel height
      const submitPos = this.$('.submit-panel').position();
      if (submitPos) {
        this.$('.wmd-controls').css('bottom', h - submitPos.top + 7);
      }
    });
  },

  keyUp() {
    const controller = this.get('controller');
    controller.checkReplyLength();

    this.get('composer').typing();

    const lastKeyUp = new Date();
    this._lastKeyUp = lastKeyUp;

    // One second from now, check to see if the last key was hit when
    // we recorded it. If it was, the user paused typing.
    Ember.run.cancel(this._lastKeyTimeout);
    this._lastKeyTimeout = Ember.run.later(() => {
      if (lastKeyUp !== this._lastKeyUp) { return; }

      // Search for similar topics if the user pauses typing
      controller.findSimilarTopics();
    }, 1000);
  },

  keyDown(e) {
    if (e.which === 27) {
      this.get('controller').send('hitEsc');
      return false;
    } else if (e.which === 13 && (e.ctrlKey || e.metaKey)) {
      // CTRL+ENTER or CMD+ENTER
      this.get('controller').send('save');
      return false;
    }
  },

  @on('didInsertElement')
  _enableResizing() {
    const $replyControl = $('#reply-control');
    const resize = () => Ember.run(() => this.resize());

    $replyControl.DivResizer({
      resize,
      maxHeight: winHeight => winHeight - headerHeight(),
      onDrag: sizePx => this.movePanels(sizePx)
    });

    afterTransition($replyControl, resize);
    positioningWorkaround(this.$());
  },

  click() {
    this.get('controller').send('openIfDraft');
  }
});

RSVP.EventTarget.mixin(ComposerView);
export default ComposerView;
<commit_msg>FIX: Add back in footer padding. iPad is broken for other reasons.<commit_after>import afterTransition from 'discourse/lib/after-transition';
import positioningWorkaround from 'discourse/lib/safari-hacks';
import { headerHeight } from 'discourse/views/header';
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
import Composer from 'discourse/models/composer';

const ComposerView = Ember.View.extend(Ember.Evented, {
  _lastKeyTimeout: null,
  elementId: 'reply-control',
  classNameBindings: ['composer.creatingPrivateMessage:private-message',
                      'composeState',
                      'composer.loading',
                      'composer.canEditTitle:edit-title',
                      'composer.createdPost:created-post',
                      'composer.creatingTopic:topic'],

  composer: Em.computed.alias('controller.model'),

  @computed('composer.composeState')
  composeState(composeState) {
    return composeState || Composer.CLOSED;
  },

  movePanels(sizePx) {

    $('#main-outlet').css('padding-bottom', sizePx);
    $('.composer-popup').css('bottom', sizePx);

    // signal the progress bar it should move!
    this.appEvents.trigger("composer:resized");
  },

  @observes('composeState', 'composer.action')
  resize() {
    Ember.run.scheduleOnce('afterRender', () => {
      const h = $('#reply-control').height() || 0;
      this.movePanels(h + "px");

      // Figure out the size of the fields
      const $fields = this.$('.composer-fields');
      const fieldPos = $fields.position();
      if (fieldPos) {
        this.$('.wmd-controls').css('top', $fields.height() + fieldPos.top + 5);
      }

      // get the submit panel height
      const submitPos = this.$('.submit-panel').position();
      if (submitPos) {
        this.$('.wmd-controls').css('bottom', h - submitPos.top + 7);
      }
    });
  },

  keyUp() {
    const controller = this.get('controller');
    controller.checkReplyLength();

    this.get('composer').typing();

    const lastKeyUp = new Date();
    this._lastKeyUp = lastKeyUp;

    // One second from now, check to see if the last key was hit when
    // we recorded it. If it was, the user paused typing.
    Ember.run.cancel(this._lastKeyTimeout);
    this._lastKeyTimeout = Ember.run.later(() => {
      if (lastKeyUp !== this._lastKeyUp) { return; }

      // Search for similar topics if the user pauses typing
      controller.findSimilarTopics();
    }, 1000);
  },

  keyDown(e) {
    if (e.which === 27) {
      this.get('controller').send('hitEsc');
      return false;
    } else if (e.which === 13 && (e.ctrlKey || e.metaKey)) {
      // CTRL+ENTER or CMD+ENTER
      this.get('controller').send('save');
      return false;
    }
  },

  @on('didInsertElement')
  _enableResizing() {
    const $replyControl = $('#reply-control');
    const resize = () => Ember.run(() => this.resize());

    $replyControl.DivResizer({
      resize,
      maxHeight: winHeight => winHeight - headerHeight(),
      onDrag: sizePx => this.movePanels(sizePx)
    });

    afterTransition($replyControl, resize);
    positioningWorkaround(this.$());
  },

  click() {
    this.get('controller').send('openIfDraft');
  }
});

RSVP.EventTarget.mixin(ComposerView);
export default ComposerView;
<|endoftext|>