<commit_before>(function ( $ ) {

$.widget( "mobile.page", $.mobile.widget, {
	options: {},

	_create: function() {
		if ( this._trigger( "beforeCreate" ) === false ) {
			return;
		}

		//some of the form elements currently rely on the presence of ui-page and ui-content
		// classes so we'll handle page and content roles outside of the main role processing
		// loop below.
		this.element.find( "[data-role=page],[data-role=content]" ).andSelf().each(function() {
			var $this = $( this );
			$this.addClass( "ui-" + $this.data( "role" ) );
		});

		this.element.find( "[data-role=nojs]" ).addClass( "ui-nojs" );
		this._enchanceControls();

		//pre-find data els
		var $dataEls = this.element.find( "[data-role]" ).andSelf().each(function() {
			var $this = $( this ),
				role = $this.data( "role" ),
				theme = $this.data( "theme" );

			//apply theming and markup modifications to page,header,content,footer
			if ( role === "header" || role === "footer" ) {
				$this.addClass( "ui-bar-" + (theme || "a") );

				//add ARIA role
				if( role == "header" ){
					$this.attr("role","banner");
				}
				else{
					$this.attr("role","contentinfo");
				}

				//right,left buttons
				var $headeranchors = $this.children( "a" ),
					leftbtn = $headeranchors.filter( ".ui-btn-left" ).length,
					rightbtn = $headeranchors.filter( ".ui-btn-right" ).length;

				if ( !leftbtn ) {
					leftbtn = $headeranchors.eq( 0 ).not('.ui-btn-right').addClass( "ui-btn-left" ).length;
				}
				if ( !rightbtn ) {
					rightbtn = $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
				}

				//auto-add back btn on pages beyond first view
				if ( $.mobile.addBackBtn && role === "header" && $.mobile.urlStack.length > 1 && !leftbtn && !$this.data( "noBackBtn" ) ) {
					$( "Back" )
						.tap(function() {
							history.back();
							return false;
						})
						.click(function() {
							return false;
						})
						.prependTo( $this );
				}

				//page title
				$this.children( ":header" )
					.addClass( "ui-title" )
					.attr( "tabindex" , "0")
					.attr( "role" ,"heading")
					.attr( "aria-level", "1" ); //regardless of h element number in src, it becomes h1 for the enhanced page
			} else if ( role === "content" ) {
				if( theme ){
					$this.addClass( "ui-body-" + theme);
				}
				//add ARIA role
				$this.attr("role","main");
			}
			else if( role == "page" ){
				$this.addClass( "ui-body-" + (theme || "c") );
			}

			switch(role) {
			case "header":
			case "footer":
			case "page":
			case "content":
				$this.addClass( "ui-" + role );
				break;
			case "collapsible":
			case "fieldcontain":
			case "navbar":
			case "listview":
			case "dialog":
			case "ajaxform":
				$this[ role ]();
				break;
			}
		});

		//links in bars, or those with data-role become buttons
		this.element.find( "[data-role=button], .ui-bar a, .ui-header a, .ui-footer a" )
			.not( ".ui-btn" )
			.buttonMarkup();

		this.element
			.find('[data-role="controlgroup"]')
			.controlgroup();

		//links within content areas
		this.element.find( "a:not(.ui-btn):not(.ui-link-inherit)" )
			.addClass( "ui-link" );

		//fix toolbars
		this.element.fixHeaderFooter();
	},

	_enchanceControls: function() {
		// degrade inputs to avoid poorly implemented native functionality
		this.element.find( "input" ).each(function() {
			var type = this.getAttribute( "type" );
			if ( $.mobile.degradeInputs[ type ] ) {
				$( this ).replaceWith(
					$( "
" ).html( $(this).clone() ).html()
						.replace( /type="([a-zA-Z]+)"/, "data-type='$1'" ) );
			}
		});

		// enchance form controls
		this.element
			.find( ":radio, :checkbox" )
			.customCheckboxRadio();
		this.element
			.find( ":button, :submit, :reset, :image" )
			.not( ".ui-nojs" )
			.customButton();
		this.element
			.find( "input, textarea" )
			.not( ":radio, :checkbox, :button, :submit, :reset, :image" )
			.customTextInput();
		this.element
			.find( "input, select" )
			.filter( "[data-role=slider], [data-type=range]" )
			.slider();
		this.element
			.find( "select" )
			.not( "[data-role=slider]" )
			.customSelect();
	}
});

})( jQuery );
<commit_msg>changed logic for auto-adding back btns. Hoping this Fixes #143<commit_after>(function ( $ ) {

$.widget( "mobile.page", $.mobile.widget, {
	options: {},

	_create: function() {
		if ( this._trigger( "beforeCreate" ) === false ) {
			return;
		}

		//some of the form elements currently rely on the presence of ui-page and ui-content
		// classes so we'll handle page and content roles outside of the main role processing
		// loop below.
		this.element.find( "[data-role=page],[data-role=content]" ).andSelf().each(function() {
			var $this = $( this );
			$this.addClass( "ui-" + $this.data( "role" ) );
		});

		this.element.find( "[data-role=nojs]" ).addClass( "ui-nojs" );
		this._enchanceControls();

		//pre-find data els
		var $dataEls = this.element.find( "[data-role]" ).andSelf().each(function() {
			var $this = $( this ),
				role = $this.data( "role" ),
				theme = $this.data( "theme" );

			//apply theming and markup modifications to page,header,content,footer
			if ( role === "header" || role === "footer" ) {
				$this.addClass( "ui-bar-" + (theme || "a") );

				//add ARIA role
				if( role == "header" ){
					$this.attr("role","banner");
				}
				else{
					$this.attr("role","contentinfo");
				}

				//right,left buttons
				var $headeranchors = $this.children( "a" ),
					leftbtn = $headeranchors.filter( ".ui-btn-left" ).length,
					rightbtn = $headeranchors.filter( ".ui-btn-right" ).length;

				if ( !leftbtn ) {
					leftbtn = $headeranchors.eq( 0 ).not('.ui-btn-right').addClass( "ui-btn-left" ).length;
				}
				if ( !rightbtn ) {
					rightbtn = $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
				}

				//auto-add back btn on pages beyond first view
				if ( $.mobile.addBackBtn && role === "header" && ($.mobile.urlStack.length > 1 || $('.ui-page').length > 1) && !leftbtn && !$this.data( "noBackBtn" ) ) {
					$( "Back" )
						.tap(function() {
							history.back();
							return false;
						})
						.click(function() {
							return false;
						})
						.prependTo( $this );
				}

				//page title
				$this.children( ":header" )
					.addClass( "ui-title" )
					.attr( "tabindex" , "0")
					.attr( "role" ,"heading")
					.attr( "aria-level", "1" ); //regardless of h element number in src, it becomes h1 for the enhanced page
			} else if ( role === "content" ) {
				if( theme ){
					$this.addClass( "ui-body-" + theme);
				}
				//add ARIA role
				$this.attr("role","main");
			}
			else if( role == "page" ){
				$this.addClass( "ui-body-" + (theme || "c") );
			}

			switch(role) {
			case "header":
			case "footer":
			case "page":
			case "content":
				$this.addClass( "ui-" + role );
				break;
			case "collapsible":
			case "fieldcontain":
			case "navbar":
			case "listview":
			case "dialog":
			case "ajaxform":
				$this[ role ]();
				break;
			}
		});

		//links in bars, or those with data-role become buttons
		this.element.find( "[data-role=button], .ui-bar a, .ui-header a, .ui-footer a" )
			.not( ".ui-btn" )
			.buttonMarkup();

		this.element
			.find('[data-role="controlgroup"]')
			.controlgroup();

		//links within content areas
		this.element.find( "a:not(.ui-btn):not(.ui-link-inherit)" )
			.addClass( "ui-link" );

		//fix toolbars
		this.element.fixHeaderFooter();
	},

	_enchanceControls: function() {
		// degrade inputs to avoid poorly implemented native functionality
		this.element.find( "input" ).each(function() {
			var type = this.getAttribute( "type" );
			if ( $.mobile.degradeInputs[ type ] ) {
				$( this ).replaceWith(
					$( "
" ).html( $(this).clone() ).html()
						.replace( /type="([a-zA-Z]+)"/, "data-type='$1'" ) );
			}
		});

		// enchance form controls
		this.element
			.find( ":radio, :checkbox" )
			.customCheckboxRadio();
		this.element
			.find( ":button, :submit, :reset, :image" )
			.not( ".ui-nojs" )
			.customButton();
		this.element
			.find( "input, textarea" )
			.not( ":radio, :checkbox, :button, :submit, :reset, :image" )
			.customTextInput();
		this.element
			.find( "input, select" )
			.filter( "[data-role=slider], [data-type=range]" )
			.slider();
		this.element
			.find( "select" )
			.not( "[data-role=slider]" )
			.customSelect();
	}
});

})( jQuery );
<|endoftext|>


<commit_before>/**
 *
 *	Copyright (C) 2016 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")
		.component("accountModel", {
			restrict: "E",
			templateUrl: "templates/account-model.html",
			bindings: {
				account: "=",
				model: "=",
				isMobileDevice: "<",
				project: "=",
				userAccount: "=",
				onUploadFile: "&",
				uploadedFile: "=",
				modelToUpload: "=",
				onShowPage: "&",
				onSetupDeleteModel: "&",
				quota: "=",
				isAccountAdmin: "=",
				subscriptions: "="
			},
			controller: AccountModelCtrl,
			controllerAs: "vm",
			link: function (scope, element) {
				// Cleanup when destroyed
				element.on("$destroy", function(){
					scope.vm.uploadedFileWatch(); // Disable events watch
					scope.vm.modelToUploadFileWatch();

				});
			}
		});

	AccountModelCtrl.$inject = ["$scope", "$location", "$timeout", "$interval", "$filter", "UtilsService", "ClientConfigService", "RevisionsService", "NotificationService", "AuthService", "AnalyticService", "AccountService", "AccountUploadService"];

	function AccountModelCtrl ($scope, $location, $timeout, $interval, $filter, UtilsService, ClientConfigService, RevisionsService, NotificationService, AuthService, AnalyticService, AccountService, AccountUploadService) {

		var vm = this;

		// Init

		vm.$onInit = function() {

			vm.infoTimeout = 10000;
			vm.isUserAccount = (vm.account === vm.userAccount);

			vm.modelToUpload = null;
			vm.dialogCloseTo = "accountModelsOptionsMenu_" + vm.account + "_" + vm.model.name;

			vm.dialogCloseToId = "#" + vm.dialogCloseTo;
			if (vm.model.timestamp !== null) {
				vm.model.timestampPretty = $filter("prettyDate")(vm.model.timestamp, {showSeconds: true});
			}

			vm.model.canUpload = true;
			// Options
			vm.modelOptions = {
				upload: {
					label: "Upload file",
					icon: "cloud_upload",
					hidden: !AuthService.hasPermission(ClientConfigService.permissions.PERM_UPLOAD_FILES, vm.model.permissions)
				},
				permissions: {
					label: "Permissions",
					icon: "group",
					hidden: !AuthService.hasPermission("manage_model_permission", vm.model.permissions)
				},
				revision: {
					label: "Revisions",
					icon: "settings_backup_restore",
					hidden: false
				},
				modelsetting: {
					label: "Settings",
					icon: "settings",
					hidden: !AuthService.hasPermission(ClientConfigService.permissions.PERM_CHANGE_MODEL_SETTINGS, vm.model.permissions)
				}
			};
			if(vm.model.timestamp && !vm.model.federate){
				vm.modelOptions.download = {
					label: "Download",
					icon: "cloud_download",
					hidden: !AuthService.hasPermission(ClientConfigService.permissions.PERM_DOWNLOAD_MODEL, vm.model.permissions)
				};
			}
			vm.uploadButtonDisabled = true;
			vm.modelOptions.delete = {
				label: "Delete",
				icon: "delete",
				hidden: !AuthService.hasPermission(ClientConfigService.permissions.PERM_DELETE_MODEL, vm.model.permissions),
				color: "#F44336"
			};

			vm.watchModelStatus();

			vm.revisionsLoading = true;

		};

		vm.modelToUploadFileWatch = $scope.$watch("vm.modelToUpload", function () {
			// Check that the file to be uploaded is valid!
			if(vm.modelToUpload){

				var names = vm.modelToUpload.name.split(".");

				vm.uploadErrorMessage = null;
				var extension = names[names.length - 1].toLowerCase();
				var valid = ClientConfigService.acceptedFormat.indexOf(extension) === -1;

				if(names.length === 1){
					vm.uploadErrorMessage = "Filename must have extension";
					vm.modelToUpload = null;
					vm.uploadButtonDisabled = true;
				} else if(valid) {
					vm.uploadErrorMessage = "File format not supported";
					vm.modelToUpload = null;
					vm.uploadButtonDisabled = true;
				} else {
					vm.uploadButtonDisabled = false;
				}

			}
		});

		/**
		 * Go to the model viewer
		 */
		vm.goToModel = function (event) {

			if (!vm.model.uploading) {
				if (vm.model.timestamp === null) {
					// No timestamp indicates no model previously uploaded
					if(AuthService.hasPermission(ClientConfigService.permissions.PERM_UPLOAD_FILES, vm.model.permissions)){
						vm.tag = null;
						vm.desc = null;
						vm.modelToUpload = null;
						UtilsService.showDialog("upload-model-dialog.html", $scope, event, true, null, false, vm.dialogCloseToId);
					} else {
						console.warn("Incorrect permissions");
					}
				} else {
					$location.path("/" + vm.account + "/" + vm.model.model, "_self").search("page", null);
					AnalyticService.sendEvent({
						eventCategory: "Model",
						eventAction: "view"
					});
				}
			}
		};

		/**
		 * Handle model option selection
		 * @param event
		 * @param option
		 */
		vm.doModelOption = function (event, option) {
			switch (option) {
			case "modelsetting":
				$location.search("modelName", vm.model.name);
				$location.search("modelId", vm.model.model);
				$location.search("targetProj", vm.project.name);
				$location.search("targetAcct", vm.account);
				$location.search("page", "modelsetting");

				vm.onShowPage({page: "modelsetting", callingPage: "permissionsspaces"});
				break;

			case "upload":
				vm.uploadErrorMessage = null;
				vm.modelToUpload = null;
				vm.tag = null;
				vm.desc = null;
				UtilsService.showDialog("upload-model-dialog.html", $scope, event, true, null, false, vm.dialogCloseToId);
				//vm.uploadFile();
				break;

			case "download":
				window.open(
					ClientConfigService.apiUrl(ClientConfigService.GET_API, vm.account + "/" + vm.model.model + "/download/latest"),
					"_blank"
				);

				AnalyticService.sendEvent({
					eventCategory: "Model",
					eventAction: "download"
				});

				break;

			case "permissions":
				goToPermissions(event, vm.account, vm.project, vm.model);
				break;

			case "delete":
				vm.onSetupDeleteModel({event: event, model: vm.model, account: vm.account, project: vm.project});
				break;

			case "revision":
				vm.revisionsLoading = true;
				vm.revisions = null;
				RevisionsService.listAll(vm.account, vm.model.model).then(function(revisions){
					vm.revisions = revisions;
					vm.revisionsLoading = false;
				});
				UtilsService.showDialog("revisions-dialog.html", $scope, event, true, null, false, vm.dialogCloseToId);
				break;
			}
		};

		/**
		 * Go to the billing page to add more licenses
		 */
		vm.setupAddLicenses = function () {
			vm.onShowPage({page: "billing", callingPage: "permissionsspaces"});
			UtilsService.closeDialog();
		};

		/**
		 * Close the dialog
		 */
		vm.closeDialog = function() {
			UtilsService.closeDialog();
		};

		/**
		 * When users click select file
		 */
		vm.selectFile = function(){
			vm.onUploadFile({model: vm.model, account: vm.account});
		};

		/**
		 * When users click upload after selecting
		 */
		vm.uploadFile = function () {

			if (!vm.model) {
				console.error("No file defined: ", vm.model);
			}

			vm.uploading = true;

			vm.uploadErrorMessage = null;

			var uploadFileData = {
				model: vm.model,
				account: vm.account,
				file: vm.modelToUpload,
				tag: vm.tag,
				desc: vm.desc
			};

			AccountUploadService.uploadRevisionToModel(uploadFileData)
				.then(function(){
					vm.addButtons = false;
					vm.addButtonType = "add";
					vm.uploading = false;
					vm.closeDialog();
				})
				.catch(function(errorMessage){
					vm.uploading = false;
					vm.uploadErrorMessage = errorMessage;
				});

		};

		/**
		* Go to the specified revision
		*/
		vm.goToRevision = function(revId){
			$location.path("/" + vm.account + "/" + vm.model.model + "/" + revId , "_self");
			AnalyticService.sendEvent({
				eventCategory: "Model",
				eventAction: "view"
			});
		};

		/**
		 * Watch file upload status
		 */
		vm.watchModelStatus = function(){

			// If we're refreshing the page, handle the data recieved
			vm.handleModelStatus(vm.model, false);

			// Else if there's dynamic updates to the model listen for them
			NotificationService.subscribe.modelStatusChanged(vm.account, vm.model.model, vm.handleModelStatus);

			// Unsubscribe for notifications
			$scope.$on("$destroy", function(){
				NotificationService.unsubscribe.modelStatusChanged(vm.account, vm.model.model);
			});

		};

		vm.isProcessing = function() {
			return vm.model.status === "uploading" ||
					vm.model.status === "processing" || vm.model.status === "queued";
		};

		vm.handleModelStatus = function(modelData, freshModel) {

			if (modelData.status) {
				vm.model.status = modelData.status;
			}

			if ((modelData.status === "ok") || (modelData.status === "failed")) {

				// Check if the model has been successfully uploaded or
				// that the errors are acceptable
				var error = modelData.errorReason;
				var valid = modelData.status === "ok" ||
					(error && error.value === UtilsService.getResponseCode("FILE_IMPORT_MISSING_TEXTURES")) ||
					(error && error.value === UtilsService.getResponseCode("FILE_IMPORT_MISSING_NODES"));

				// We don't want to show the import successful message
				// for models that were there before, so we use the isFreshModel flag
				var isFreshModel = freshModel === undefined;
				if (valid && isFreshModel) {
					vm.model.timestamp = new Date();
					vm.model.timestampPretty = $filter("prettyDate")(vm.model.timestamp, {showSeconds: true});
					vm.fileUploadInfo = "Model imported successfully";
					// clear revisions cache
					vm.revisions = null;
					vm.revisionsLoading = true;

				}

				//status=ok can have an error message too
				var errorReason = modelData.hasOwnProperty("errorReason") &&
									modelData.errorReason.message;
				var errorStatus = modelData.status === "failed";

				if (errorReason) {
					vm.fileUploadInfo = modelData.errorReason.message;
				} else if (errorStatus) {
					vm.fileUploadInfo = "Failed to import model";
				}

				// $timeout(function () {
				// 	vm.fileUploadInfo = "";
				// }, vm.infoTimeout);

			} else if (modelData.status === "queued"){

				vm.fileUploadInfo = "Queued...";

			} else if (modelData.status === "uploading"){

				vm.fileUploadInfo = "Uploading...";

			} else if (modelData.status === "processing"){

				vm.fileUploadInfo = "Processing...";

			}

		};

		/**
		 * Set up permissions of model
		 *
		 * @param {Object} event
		 */
		function goToPermissions(event, account, project, model) {
			//vm.account, vm.project, vm.model

			$location.search("account", account);
			$location.search("project", project.name);
			$location.search("model", model.model);
			$location.search("page", "assign");
			vm.onShowPage({page: "assign", callingPage: "teamspaces"});

		}
	}
}());
<commit_msg>ISSUE #527 - Button should be disabled now<commit_after>/**
 *
 *	Copyright (C) 2016 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")
		.component("accountModel", {
			restrict: "E",
			templateUrl: "templates/account-model.html",
			bindings: {
				account: "=",
				model: "=",
				isMobileDevice: "<",
				project: "=",
				userAccount: "=",
				onUploadFile: "&",
				uploadedFile: "=",
				modelToUpload: "=",
				onShowPage: "&",
				onSetupDeleteModel: "&",
				quota: "=",
				isAccountAdmin: "=",
				subscriptions: "="
			},
			controller: AccountModelCtrl,
			controllerAs: "vm",
			link: function (scope, element) {
				// Cleanup when destroyed
				element.on("$destroy", function(){
					scope.vm.uploadedFileWatch(); // Disable events watch
					scope.vm.modelToUploadFileWatch();

				});
			}
		});

	AccountModelCtrl.$inject = ["$scope", "$location", "$timeout", "$interval", "$filter", "UtilsService", "ClientConfigService", "RevisionsService", "NotificationService", "AuthService", "AnalyticService", "AccountService", "AccountUploadService"];

	function AccountModelCtrl ($scope, $location, $timeout, $interval, $filter, UtilsService, ClientConfigService, RevisionsService, NotificationService, AuthService, AnalyticService, AccountService, AccountUploadService) {

		var vm = this;

		// Init

		vm.$onInit = function() {

			vm.infoTimeout = 10000;
			vm.isUserAccount = (vm.account === vm.userAccount);

			vm.modelToUpload = null;
			vm.dialogCloseTo = "accountModelsOptionsMenu_" + vm.account + "_" + vm.model.name;

			vm.dialogCloseToId = "#" + vm.dialogCloseTo;
			if (vm.model.timestamp !== null) {
				vm.model.timestampPretty = $filter("prettyDate")(vm.model.timestamp, {showSeconds: true});
			}

			vm.model.canUpload = true;
			// Options
			vm.modelOptions = {
				upload: {
					label: "Upload file",
					icon: "cloud_upload",
					hidden: !AuthService.hasPermission(ClientConfigService.permissions.PERM_UPLOAD_FILES, vm.model.permissions)
				},
				permissions: {
					label: "Permissions",
					icon: "group",
					hidden: !AuthService.hasPermission("manage_model_permission", vm.model.permissions)
				},
				revision: {
					label: "Revisions",
					icon: "settings_backup_restore",
					hidden: false
				},
				modelsetting: {
					label: "Settings",
					icon: "settings",
					hidden: !AuthService.hasPermission(ClientConfigService.permissions.PERM_CHANGE_MODEL_SETTINGS, vm.model.permissions)
				}
			};
			if(vm.model.timestamp && !vm.model.federate){
				vm.modelOptions.download = {
					label: "Download",
					icon: "cloud_download",
					hidden: !AuthService.hasPermission(ClientConfigService.permissions.PERM_DOWNLOAD_MODEL, vm.model.permissions)
				};
			}
			vm.uploadButtonDisabled = true;
			vm.modelOptions.delete = {
				label: "Delete",
				icon: "delete",
				hidden: !AuthService.hasPermission(ClientConfigService.permissions.PERM_DELETE_MODEL, vm.model.permissions),
				color: "#F44336"
			};

			vm.watchModelStatus();

			vm.revisionsLoading = true;

		};

		vm.modelToUploadFileWatch = $scope.$watch("vm.modelToUpload", function () {
			// Check that the file to be uploaded is valid!
			if(vm.modelToUpload){

				var names = vm.modelToUpload.name.split(".");

				vm.uploadErrorMessage = null;
				var extension = names[names.length - 1].toLowerCase();
				var valid = ClientConfigService.acceptedFormat.indexOf(extension) === -1;

				if(names.length === 1){
					vm.uploadErrorMessage = "Filename must have extension";
					vm.modelToUpload = null;
					vm.uploadButtonDisabled = true;
				} else if(valid) {
					vm.uploadErrorMessage = "File format not supported";
					vm.modelToUpload = null;
					vm.uploadButtonDisabled = true;
				} else {
					vm.uploadButtonDisabled = false;
				}

			}
		});

		/**
		 * Go to the model viewer
		 */
		vm.goToModel = function (event) {

			if (!vm.model.uploading) {
				if (vm.model.timestamp === null) {
					// No timestamp indicates no model previously uploaded
					if(AuthService.hasPermission(ClientConfigService.permissions.PERM_UPLOAD_FILES, vm.model.permissions)){
						vm.tag = null;
						vm.desc = null;
						vm.modelToUpload = null;
						UtilsService.showDialog("upload-model-dialog.html", $scope, event, true, null, false, vm.dialogCloseToId);
					} else {
						console.warn("Incorrect permissions");
					}
				} else {
					$location.path("/" + vm.account + "/" + vm.model.model, "_self").search("page", null);
					AnalyticService.sendEvent({
						eventCategory: "Model",
						eventAction: "view"
					});
				}
			}
		};

		/**
		 * Handle model option selection
		 * @param event
		 * @param option
		 */
		vm.doModelOption = function (event, option) {
			switch (option) {
			case "modelsetting":
				$location.search("modelName", vm.model.name);
				$location.search("modelId", vm.model.model);
				$location.search("targetProj", vm.project.name);
				$location.search("targetAcct", vm.account);
				$location.search("page", "modelsetting");

				vm.onShowPage({page: "modelsetting", callingPage: "permissionsspaces"});
				break;

			case "upload":
				vm.uploadErrorMessage = null;
				vm.modelToUpload = null;
				vm.tag = null;
				vm.desc = null;
				vm.uploadButtonDisabled = true;
				UtilsService.showDialog("upload-model-dialog.html", $scope, event, true, null, false, vm.dialogCloseToId);
				//vm.uploadFile();
				break;

			case "download":
				window.open(
					ClientConfigService.apiUrl(ClientConfigService.GET_API, vm.account + "/" + vm.model.model + "/download/latest"),
					"_blank"
				);

				AnalyticService.sendEvent({
					eventCategory: "Model",
					eventAction: "download"
				});

				break;

			case "permissions":
				goToPermissions(event, vm.account, vm.project, vm.model);
				break;

			case "delete":
				vm.onSetupDeleteModel({event: event, model: vm.model, account: vm.account, project: vm.project});
				break;

			case "revision":
				vm.revisionsLoading = true;
				vm.revisions = null;
				RevisionsService.listAll(vm.account, vm.model.model).then(function(revisions){
					vm.revisions = revisions;
					vm.revisionsLoading = false;
				});
				UtilsService.showDialog("revisions-dialog.html", $scope, event, true, null, false, vm.dialogCloseToId);
				break;
			}
		};

		/**
		 * Go to the billing page to add more licenses
		 */
		vm.setupAddLicenses = function () {
			vm.onShowPage({page: "billing", callingPage: "permissionsspaces"});
			UtilsService.closeDialog();
		};

		/**
		 * Close the dialog
		 */
		vm.closeDialog = function() {
			UtilsService.closeDialog();
		};

		/**
		 * When users click select file
		 */
		vm.selectFile = function(){
			vm.onUploadFile({model: vm.model, account: vm.account});
		};

		/**
		 * When users click upload after selecting
		 */
		vm.uploadFile = function () {

			if (!vm.model) {
				console.error("No file defined: ", vm.model);
			}

			vm.uploading = true;

			vm.uploadErrorMessage = null;

			var uploadFileData = {
				model: vm.model,
				account: vm.account,
				file: vm.modelToUpload,
				tag: vm.tag,
				desc: vm.desc
			};

			AccountUploadService.uploadRevisionToModel(uploadFileData)
				.then(function(){
					vm.addButtons = false;
					vm.addButtonType = "add";
					vm.uploading = false;
					vm.closeDialog();
				})
				.catch(function(errorMessage){
					vm.uploading = false;
					vm.uploadErrorMessage = errorMessage;
				});

		};

		/**
		* Go to the specified revision
		*/
		vm.goToRevision = function(revId){
			$location.path("/" + vm.account + "/" + vm.model.model + "/" + revId , "_self");
			AnalyticService.sendEvent({
				eventCategory: "Model",
				eventAction: "view"
			});
		};

		/**
		 * Watch file upload status
		 */
		vm.watchModelStatus = function(){

			// If we're refreshing the page, handle the data recieved
			vm.handleModelStatus(vm.model, false);

			// Else if there's dynamic updates to the model listen for them
			NotificationService.subscribe.modelStatusChanged(vm.account, vm.model.model, vm.handleModelStatus);

			// Unsubscribe for notifications
			$scope.$on("$destroy", function(){
				NotificationService.unsubscribe.modelStatusChanged(vm.account, vm.model.model);
			});

		};

		vm.isProcessing = function() {
			return vm.model.status === "uploading" ||
					vm.model.status === "processing" || vm.model.status === "queued";
		};

		vm.handleModelStatus = function(modelData, freshModel) {

			if (modelData.status) {
				vm.model.status = modelData.status;
			}

			if ((modelData.status === "ok") || (modelData.status === "failed")) {

				// Check if the model has been successfully uploaded or
				// that the errors are acceptable
				var error = modelData.errorReason;
				var valid = modelData.status === "ok" ||
					(error && error.value === UtilsService.getResponseCode("FILE_IMPORT_MISSING_TEXTURES")) ||
					(error && error.value === UtilsService.getResponseCode("FILE_IMPORT_MISSING_NODES"));

				// We don't want to show the import successful message
				// for models that were there before, so we use the isFreshModel flag
				var isFreshModel = freshModel === undefined;
				if (valid && isFreshModel) {
					vm.model.timestamp = new Date();
					vm.model.timestampPretty = $filter("prettyDate")(vm.model.timestamp, {showSeconds: true});
					vm.fileUploadInfo = "Model imported successfully";
					// clear revisions cache
					vm.revisions = null;
					vm.revisionsLoading = true;

				}

				//status=ok can have an error message too
				var errorReason = modelData.hasOwnProperty("errorReason") &&
									modelData.errorReason.message;
				var errorStatus = modelData.status === "failed";

				if (errorReason) {
					vm.fileUploadInfo = modelData.errorReason.message;
				} else if (errorStatus) {
					vm.fileUploadInfo = "Failed to import model";
				}

				// $timeout(function () {
				// 	vm.fileUploadInfo = "";
				// }, vm.infoTimeout);

			} else if (modelData.status === "queued"){

				vm.fileUploadInfo = "Queued...";

			} else if (modelData.status === "uploading"){

				vm.fileUploadInfo = "Uploading...";

			} else if (modelData.status === "processing"){

				vm.fileUploadInfo = "Processing...";

			}

		};

		/**
		 * Set up permissions of model
		 *
		 * @param {Object} event
		 */
		function goToPermissions(event, account, project, model) {
			//vm.account, vm.project, vm.model

			$location.search("account", account);
			$location.search("project", project.name);
			$location.search("model", model.model);
			$location.search("page", "assign");
			vm.onShowPage({page: "assign", callingPage: "teamspaces"});

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


<commit_before>const NUM_MATH_FUNCTIONS = 6;

var binaryMathOps = [
  " * ", " /*NODIFF*/ / ", " % ", " + ", " - ", " << ", " >> ", " >>> ", // division issues: bug 997546
  " < ", " > ", " <= ", " >= ",
  " == ", " != ",
  " === ", " !== ",
  " & ", " | ", " ^ ", " && ", " || ",
  " , ",
];

var leftUnaryMathOps = [
  " ! ", " + ", " - ", " ~ ",
];

var unaryMathFunctions = [
  "abs",
  "acos",
  "acosh",
  "asin",
  "asinh",
  "atan",
  "atanh",
  "cbrt",
  "ceil",
  "cos",
  "cosh",
  "exp",
  "expm1",
  // "floor", // avoid breaking rnd.
  "fround",
  "log",
  "log2",
  "log10",
  "log1p",
  // "random", // avoid breaking rnd. avoid non-determinism.
  "round",
  "sign",
  "sin",
  "sinh",
  "sqrt",
  "tan",
  "tanh",
  "trunc",
];

// n-ary functions will also be tested with varying numbers of parameters by makeFunction
var binaryMathFunctions = [
  "atan2",
  "hypot", // n-ary
  "imul",
  "max", // n-ary
  "min", // n-ary
  "pow",
];

function generateMathFunction(d, b, i)
{
  return "(function(x, y) { " + directivePrologue() + "return " + generateMathExpr(d, b, i) + "; })"
}

function generateMathExpr(d, b, i)
{
  // As depth decreases, make it more likely to bottom out
  if (d < rnd(5)) {
    if (rnd(4)) {
      return Random.index(["x", "y"]);
    }
    return Random.index(numericVals);
  }

  if (rnd(500) == 0)
    return makeExpr(d - 1, b);

  function r() { return generateMathExpr(d - 1, b, i); }

  // Frequently, coerce both the inputs and outputs to the same "numeric sub-type"
  // (asm.js formalizes this concept, but JITs may have their own variants)
  var commonCoercion = rnd(10);
  function mc(expr) {
    switch(rnd(3) ? commonCoercion : rnd(10)) {
      case 0: return "(" + " + " + expr + ")";     // f64 (asm.js)
      case 1: return "Math.fround(" + expr + ")";  // f32
      case 2: return "(" + expr + " | 0)";         // i32 (asm.js)
      case 3: return "(" + expr + " >>> 0)";       // u32
      default: return expr;
    }
  }

  if (i > 0 && rnd(10) == 0) {
    // Call a *lower-numbered* mathy function. (This avoids infinite recursion.)
    return mc("mathy" + rnd(i) + "(" + mc(r()) + ", " + mc(r()) + ")");
  }

  if (rnd(20) == 0) {
    return mc("(" + mc(r()) + " ? " + mc(r()) + " : " + mc(r()) + ")");
  }

  switch(rnd(4)) {
    case 0:  return mc("(" + mc(r()) + Random.index(binaryMathOps) + mc(r()) + ")");
    case 1:  return mc("(" + Random.index(leftUnaryMathOps) + mc(r()) + ")");
    case 2:  return mc("Math." + Random.index(unaryMathFunctions) + "(" + mc(r()) + ")");
    default: return mc("Math." + Random.index(binaryMathFunctions) + "(" + mc(r()) + ", " + mc(r()) + ")");
  }
}
<commit_msg>Apparently bug 997546 was fixed by bug 995817.<commit_after>const NUM_MATH_FUNCTIONS = 6;

var binaryMathOps = [
  " * ", " / ", " % ", " + ", " - ", " << ", " >> ", " >>> ",
  " < ", " > ", " <= ", " >= ",
  " == ", " != ",
  " === ", " !== ",
  " & ", " | ", " ^ ", " && ", " || ",
  " , ",
];

var leftUnaryMathOps = [
  " ! ", " + ", " - ", " ~ ",
];

var unaryMathFunctions = [
  "abs",
  "acos",
  "acosh",
  "asin",
  "asinh",
  "atan",
  "atanh",
  "cbrt",
  "ceil",
  "cos",
  "cosh",
  "exp",
  "expm1",
  // "floor", // avoid breaking rnd.
  "fround",
  "log",
  "log2",
  "log10",
  "log1p",
  // "random", // avoid breaking rnd. avoid non-determinism.
  "round",
  "sign",
  "sin",
  "sinh",
  "sqrt",
  "tan",
  "tanh",
  "trunc",
];

// n-ary functions will also be tested with varying numbers of parameters by makeFunction
var binaryMathFunctions = [
  "atan2",
  "hypot", // n-ary
  "imul",
  "max", // n-ary
  "min", // n-ary
  "pow",
];

function generateMathFunction(d, b, i)
{
  return "(function(x, y) { " + directivePrologue() + "return " + generateMathExpr(d, b, i) + "; })"
}

function generateMathExpr(d, b, i)
{
  // As depth decreases, make it more likely to bottom out
  if (d < rnd(5)) {
    if (rnd(4)) {
      return Random.index(["x", "y"]);
    }
    return Random.index(numericVals);
  }

  if (rnd(500) == 0)
    return makeExpr(d - 1, b);

  function r() { return generateMathExpr(d - 1, b, i); }

  // Frequently, coerce both the inputs and outputs to the same "numeric sub-type"
  // (asm.js formalizes this concept, but JITs may have their own variants)
  var commonCoercion = rnd(10);
  function mc(expr) {
    switch(rnd(3) ? commonCoercion : rnd(10)) {
      case 0: return "(" + " + " + expr + ")";     // f64 (asm.js)
      case 1: return "Math.fround(" + expr + ")";  // f32
      case 2: return "(" + expr + " | 0)";         // i32 (asm.js)
      case 3: return "(" + expr + " >>> 0)";       // u32
      default: return expr;
    }
  }

  if (i > 0 && rnd(10) == 0) {
    // Call a *lower-numbered* mathy function. (This avoids infinite recursion.)
    return mc("mathy" + rnd(i) + "(" + mc(r()) + ", " + mc(r()) + ")");
  }

  if (rnd(20) == 0) {
    return mc("(" + mc(r()) + " ? " + mc(r()) + " : " + mc(r()) + ")");
  }

  switch(rnd(4)) {
    case 0:  return mc("(" + mc(r()) + Random.index(binaryMathOps) + mc(r()) + ")");
    case 1:  return mc("(" + Random.index(leftUnaryMathOps) + mc(r()) + ")");
    case 2:  return mc("Math." + Random.index(unaryMathFunctions) + "(" + mc(r()) + ")");
    default: return mc("Math." + Random.index(binaryMathFunctions) + "(" + mc(r()) + ", " + mc(r()) + ")");
  }
}
<|endoftext|>


<commit_before>define([
	"dojo",//FIXME
	"dijit",//FIXME
	"dijit/registry",
	"dijit/_base/popup",
	"dijit/_editor/_Plugin",
	"dijit/_editor/plugins/LinkDialog",
	"dijit/TooltipDialog",
	"dijit/form/_TextBoxMixin",
	"dijit/form/Button",
	"dijit/form/ValidationTextBox",
	"dijit/form/DropDownButton",
	"dojo/_base/connect",
	"dojo/_base/declare",
	"dojo/_base/sniff",
	"dojox/form/FileUploader", //FIXME: deprecated.  Use Uploader instead
	"dojo/i18n!dojox/editor/plugins/nls/LocalImage"
], function(dojo, dijit, registry, popup, _Plugin, LinkDialog, TooltipDialog,
			_TextBoxMixin, Button, ValidationTextBox, DropDownButton,
			connect, declare, has, FileUploader, messages) {

var LocalImage = dojo.declare("dojox.editor.plugins.LocalImage", LinkDialog.ImgLinkDialog, {
	// summary:
	//		This plugin provides an enhanced image link dialog that
	//		not only insert the online images, but upload the local image files onto
	//		to server then insert them as well.
	// Dependencies:
	//		This plugin depends on dojox.form.FileUploader to upload the images on the local driver.
	//		Do the regression test whenever FileUploader is upgraded.

	// uploadable [public] Boolean
	//		Indicate whether the user can upload a local image file onto the server.
	//		If it is set to true, the Browse button will be available.
	uploadable: false,

	// uploadUrl [public] String
	//		The url targeted for uploading. Both absolute and relative URLs are OK.
	uploadUrl: "",

	// baseImageUrl [public] String
	//		The prefix of the image url on the server.
	//		For example, an image is uploaded and stored at the following location
	//			http://www.myhost.com/images/uploads/test.jpg.
	//		When the image is uploaded, the server returns "uploads/test.jpg" as the
	//		relative path. So the baseImageUrl should be set to "http://www.myhost.com/images/"
	//		so that the client can retrieve the image from the server.
	//		If the image file is located on the same domain as that of the current web page,
	//		baseImageUrl can be a relative path. For example:
	//			baseImageUrl = images/
	//		and the server returns uploads/test.jpg
	//		The complete URL of the image file is images/upload/test.jpg
	baseImageUrl: "",

	// fileMask [public] String
	//		Specify the types of images that are allowed to be uploaded.
	//		Note that the type checking on server is also very important!
	fileMask: "*.jpg;*.jpeg;*.gif;*.png;*.bmp",

	// urlRegExp [protected] String
	//		Used to validate if the input is a valid image URL.
	urlRegExp: "",

	// htmlFieldName [private] htmlFieldName
	htmlFieldName:"uploadedfile",

	// _isLocalFile [private] Boolean
	//		Indicate if a local file is to be uploaded to the server
	//		If false, the text of _urlInput field is regarded as the
	//		URL of the online image
	_isLocalFile: false,

	// _messages [private] Array<String>
	//		Contains i18n strings.
	_messages: "",

	// _cssPrefix [private] String
	//		The prefix of the CSS style
	_cssPrefix: "dijitEditorEilDialog",

	// _closable [private] Boolean
	//		Indicate if the tooltip dialog can be closed. Used to workaround Safari 5 bug
	//		where the file dialog doesn't pop up in modal until after the first click.
	_closable: true,

	// linkDialogTemplate [protected] String
	//		Over-ride for template since this is an enhanced image dialog.
	linkDialogTemplate: [
		"

", //
 breaks the dialog in IE6
		"
${prePopuTextUrl}${prePopuTextBrowse}
",
		"

",
		"${url}",
		"

",
		"<input dojoType='dijit.form.ValidationTextBox' class='dijitEditorEilDialogField'" +
		"regExp='${urlRegExp}' title='${prePopuTextUrl}${prePopuTextBrowse}'  selectOnClick='true' required='true' " +
		"id='${id}_urlInput' name='urlInput' intermediateChanges='true' invalidMessage='${invalidMessage}' " +
		"prePopuText='<${prePopuTextUrl}${prePopuTextBrowse}&gt'>",
		"	",
		"
${browse}
",
		"

",
		"${text}",
		"

",
		"<input dojoType='dijit.form.TextBox' required='false' id='${id}_textInput' " +
		"name='textInput' intermediateChanges='true' selectOnClick='true' class='dijitEditorEilDialogField'>",
		"

",
		"	",
		"

",
		"<button dojoType='dijit.form.Button' id='${id}_setButton'>${set}</button>",
		"

"
	].join(""),

	_initButton: function(){
		// summary:
		//		Override _Plugin._initButton() to initialize DropDownButton and TooltipDialog.
		// tags:
		//		protected
	    	var _this = this;
		this._messages = messages;
		this.tag = "img";
		var dropDown = (this.dropDown = new TooltipDialog({
			title: messages[this.command + "Title"],
			onOpen: function(){
				_this._initialFileUploader();
				_this._onOpenDialog();
				TooltipDialog.prototype.onOpen.apply(this, arguments);
				setTimeout(function(){
					// Auto-select the text if it is not empty
					_TextBoxMixin.selectInputText(_this._urlInput.textbox);
					_this._urlInput.isLoadComplete = true;
				}, 0);
			},
			onClose: function(){
				dojo.disconnect(_this.blurHandler);
				_this.blurHandler = null;
				this.onHide();
			},
			onCancel: function(){
				setTimeout(dojo.hitch(_this, "_onCloseDialog"),0);
			}
		}));

		var label = this.getLabel(this.command),
			className = this.iconClassPrefix + " " + this.iconClassPrefix + this.command.charAt(0).toUpperCase() + this.command.substr(1),
			props = dojo.mixin({
					label: label,
					showLabel: false,
					iconClass: className,
					dropDown: this.dropDown,
					tabIndex: "-1"
				}, this.params || {});

		if(!has('ie')){
			// Workaround for Non-IE problem:
			// Safari 5: After the select-file dialog opens, the first time the user clicks anywhere (even on that dialog)
			// it's treated like a plain click on the page, and the tooltip dialog closes
			// FF & Chrome: the select-file dialog does not block the execution of JS
			props.closeDropDown = function(/*Boolean*/ focus){
				if(_this._closable){
					if(this._opened){
						popup.close(this.dropDown);
						if(focus){ this.focus(); }
						this._opened = false;
						this.state = "";
					}
				}
				setTimeout(function(){ _this._closable = true; }, 10);
			};
		}

		this.button = new DropDownButton(props);

		// Generate the RegExp of the ValidationTextBox from fileMask
		// *.jpg;*.png => /.*\.jpg|.*\.JPG|.*\.png|.*\.PNG/
		var masks = this.fileMask.split(";"),
			temp = "";
		dojo.forEach(masks, function(m){
			m = m.replace(/\./, "\\.").replace(/\*/g, ".*");
			temp += "|" + m + "|" + m.toUpperCase();
		});
		messages.urlRegExp = this.urlRegExp = temp.substring(1);

		if(!this.uploadable){
			messages.prePopuTextBrowse = ".";
		}

		messages.id = registry.getUniqueId(this.editor.id);
		messages.uploadable = this.uploadable ? "inline" : "none";
		this._uniqueId = messages.id;
		this._setContent("
" + dropDown.title + "
" +
			dojo.string.substitute(this.linkDialogTemplate, messages));
		dropDown.startup();

		var urlInput = (this._urlInput = registry.byId(this._uniqueId + "_urlInput"));
		this._textInput = registry.byId(this._uniqueId + "_textInput");
		this._setButton = registry.byId(this._uniqueId + "_setButton");

		if(urlInput){
			var pt = ValidationTextBox.prototype;
			urlInput = dojo.mixin(urlInput, {
				// Indicate if the widget is ready to validate the input text
				isLoadComplete: false,
				isValid: function(isFocused){
					if(this.isLoadComplete){
						return pt.isValid.apply(this, arguments);
					}else{
						return this.get("value").length > 0;
					}
				},
				reset: function(){
					this.isLoadComplete = false;
					pt.reset.apply(this, arguments);
				}
			});

			this.connect(urlInput, "onKeyDown", "_cancelFileUpload");
			this.connect(urlInput, "onChange", "_checkAndFixInput");
		}
		if(this._setButton){
			this.connect(this._setButton, "onClick", "_checkAndSetValue");
		}
		this._connectTagEvents();
	},

	_initialFileUploader: function(){
		// summary:
		//		Initialize the FileUploader and connect up its events
		// tags:
		//		private
		var fup = null,
			_this = this,
			widgetId = _this._uniqueId,
			fUpId = widgetId + "_browse",
			urlInput = _this._urlInput;

		if(_this.uploadable && !_this._fileUploader){
			fup = _this._fileUploader = new FileUploader({
				force: "html", // Noticed that SWF may cause browsers to crash sometimes
				uploadUrl: _this.uploadUrl,
				htmlFieldName: _this.htmlFieldName,
				uploadOnChange: false,
				selectMultipleFiles: false,
				showProgress: true
			}, fUpId);

			// TooltipDialog will call reset on all the widgets contained within it.
			// Have FileUploader be responsive to this call.
			fup.reset = function(){
				_this._isLocalFile = false;
				fup._resetHTML();
			};

			_this.connect(fup, "onClick", function(){
				urlInput.validate(false);
				if(!has('ie')){
					// Firefox, Chrome and Safari have a strange behavior:
					// When the File Upload dialog is open, the browse div (FileUploader) will lose its focus
					// and triggers onBlur event. This event will cause the whole tooltip dialog
					// to be closed when the File Upload dialog is open. The popup dialog should hang up
					// the js execution rather than triggering an event. IE does not have such a problem.
					_this._closable = false;
				}
			});

			_this.connect(fup, "onChange", function(data){
				_this._isLocalFile = true;
				urlInput.set("value", data[0].name); //Single selection
				urlInput.focus();
			});

			_this.connect(fup, "onComplete", function(data){
				var urlPrefix = _this.baseImageUrl;
				urlPrefix = urlPrefix && urlPrefix.charAt(urlPrefix.length - 1) == "/" ? urlPrefix : urlPrefix + "/";
				urlInput.set("value", urlPrefix + data[0].file); //Single selection
				_this._isLocalFile = false;
				_this._setDialogStatus(true);
				_this.setValue(_this.dropDown.get("value"));
			});

			_this.connect(fup, "onError", function(evtObject){
				// summary:
				//		Fires on errors
				console.log("Error occurred when uploading image file!");
				_this._setDialogStatus(true);
			});
		}
	},

	_checkAndFixInput: function(){
		// summray:
		//		Over-ride the original method
		this._setButton.set("disabled", !this._isValid());
	},

	_isValid: function(){
		// summray:
		//		Invalid cases: URL is not ended with the suffix listed
		return this._urlInput.isValid();
	},

	_cancelFileUpload: function(){
		this._fileUploader.reset();
		this._isLocalFile = false;
	},

	_checkAndSetValue: function(){
		// summray:
		//		Determine if a local file is to be uploaded.
		//		If a local file is to be uploaded, do not close the dialog
		//		until the file uploading is finished. Else, insert the image directly into the editor.
		// tags:
		//		private
		if(this._fileUploader && this._isLocalFile){
			this._setDialogStatus(false);
			this._fileUploader.upload();
		}else{
			this.setValue(this.dropDown.get("value"));
		}
	},

	_setDialogStatus: function(/*Boolean*/ value){
		this._urlInput.set("disabled", !value);
		this._textInput.set("disabled", !value);
		this._setButton.set("disabled", !value);
	},

	destroy: function(){
		// summary:
		//		Cleanup of the plugin.
		this.inherited(arguments);
		if(this._fileUploader){
			this._fileUploader.destroy();
			delete this._fileUploader;
		}
	}
});

// Register this plugin.
_Plugin.registry["LocalImage"] = function(args){
	return new LocalImage({
		command: "insertImage",
		uploadable: ("uploadable" in args) ? args.uploadable : false,
		uploadUrl: ("uploadable" in args && "uploadUrl" in args) ? args.uploadUrl : "",
		htmlFieldName: ("uploadable" in args && "htmlFieldName" in args) ? args.htmlFieldName : "uploadedfile",
		baseImageUrl: ("uploadable" in args && "baseImageUrl" in args) ? args.baseImageUrl : "",
		fileMask: ("fileMask" in args) ? args.fileMask : "*.jpg;*.jpeg;*.gif;*.png;*.bmp"
	});
};

return LocalImage;

});
<commit_msg>Fixing issue with backcompat and plugin name.  refs #14437<commit_after>define([
	"dojo",//FIXME
	"dijit",//FIXME
	"dijit/registry",
	"dijit/_base/popup",
	"dijit/_editor/_Plugin",
	"dijit/_editor/plugins/LinkDialog",
	"dijit/TooltipDialog",
	"dijit/form/_TextBoxMixin",
	"dijit/form/Button",
	"dijit/form/ValidationTextBox",
	"dijit/form/DropDownButton",
	"dojo/_base/connect",
	"dojo/_base/declare",
	"dojo/_base/sniff",
	"dojox/form/FileUploader", //FIXME: deprecated.  Use Uploader instead
	"dojo/i18n!dojox/editor/plugins/nls/LocalImage"
], function(dojo, dijit, registry, popup, _Plugin, LinkDialog, TooltipDialog,
			_TextBoxMixin, Button, ValidationTextBox, DropDownButton,
			connect, declare, has, FileUploader, messages) {

var LocalImage = dojo.declare("dojox.editor.plugins.LocalImage", LinkDialog.ImgLinkDialog, {
	// summary:
	//		This plugin provides an enhanced image link dialog that
	//		not only insert the online images, but upload the local image files onto
	//		to server then insert them as well.
	// Dependencies:
	//		This plugin depends on dojox.form.FileUploader to upload the images on the local driver.
	//		Do the regression test whenever FileUploader is upgraded.

	// uploadable [public] Boolean
	//		Indicate whether the user can upload a local image file onto the server.
	//		If it is set to true, the Browse button will be available.
	uploadable: false,

	// uploadUrl [public] String
	//		The url targeted for uploading. Both absolute and relative URLs are OK.
	uploadUrl: "",

	// baseImageUrl [public] String
	//		The prefix of the image url on the server.
	//		For example, an image is uploaded and stored at the following location
	//			http://www.myhost.com/images/uploads/test.jpg.
	//		When the image is uploaded, the server returns "uploads/test.jpg" as the
	//		relative path. So the baseImageUrl should be set to "http://www.myhost.com/images/"
	//		so that the client can retrieve the image from the server.
	//		If the image file is located on the same domain as that of the current web page,
	//		baseImageUrl can be a relative path. For example:
	//			baseImageUrl = images/
	//		and the server returns uploads/test.jpg
	//		The complete URL of the image file is images/upload/test.jpg
	baseImageUrl: "",

	// fileMask [public] String
	//		Specify the types of images that are allowed to be uploaded.
	//		Note that the type checking on server is also very important!
	fileMask: "*.jpg;*.jpeg;*.gif;*.png;*.bmp",

	// urlRegExp [protected] String
	//		Used to validate if the input is a valid image URL.
	urlRegExp: "",

	// htmlFieldName [private] htmlFieldName
	htmlFieldName:"uploadedfile",

	// _isLocalFile [private] Boolean
	//		Indicate if a local file is to be uploaded to the server
	//		If false, the text of _urlInput field is regarded as the
	//		URL of the online image
	_isLocalFile: false,

	// _messages [private] Array<String>
	//		Contains i18n strings.
	_messages: "",

	// _cssPrefix [private] String
	//		The prefix of the CSS style
	_cssPrefix: "dijitEditorEilDialog",

	// _closable [private] Boolean
	//		Indicate if the tooltip dialog can be closed. Used to workaround Safari 5 bug
	//		where the file dialog doesn't pop up in modal until after the first click.
	_closable: true,

	// linkDialogTemplate [protected] String
	//		Over-ride for template since this is an enhanced image dialog.
	linkDialogTemplate: [
		"

", //
 breaks the dialog in IE6
		"
${prePopuTextUrl}${prePopuTextBrowse}
",
		"

",
		"${url}",
		"

",
		"<input dojoType='dijit.form.ValidationTextBox' class='dijitEditorEilDialogField'" +
		"regExp='${urlRegExp}' title='${prePopuTextUrl}${prePopuTextBrowse}'  selectOnClick='true' required='true' " +
		"id='${id}_urlInput' name='urlInput' intermediateChanges='true' invalidMessage='${invalidMessage}' " +
		"prePopuText='<${prePopuTextUrl}${prePopuTextBrowse}&gt'>",
		"	",
		"
${browse}
",
		"

",
		"${text}",
		"

",
		"<input dojoType='dijit.form.TextBox' required='false' id='${id}_textInput' " +
		"name='textInput' intermediateChanges='true' selectOnClick='true' class='dijitEditorEilDialogField'>",
		"

",
		"	",
		"

",
		"<button dojoType='dijit.form.Button' id='${id}_setButton'>${set}</button>",
		"

"
	].join(""),

	_initButton: function(){
		// summary:
		//		Override _Plugin._initButton() to initialize DropDownButton and TooltipDialog.
		// tags:
		//		protected
	    	var _this = this;
		this._messages = messages;
		this.tag = "img";
		var dropDown = (this.dropDown = new TooltipDialog({
			title: messages[this.command + "Title"],
			onOpen: function(){
				_this._initialFileUploader();
				_this._onOpenDialog();
				TooltipDialog.prototype.onOpen.apply(this, arguments);
				setTimeout(function(){
					// Auto-select the text if it is not empty
					_TextBoxMixin.selectInputText(_this._urlInput.textbox);
					_this._urlInput.isLoadComplete = true;
				}, 0);
			},
			onClose: function(){
				dojo.disconnect(_this.blurHandler);
				_this.blurHandler = null;
				this.onHide();
			},
			onCancel: function(){
				setTimeout(dojo.hitch(_this, "_onCloseDialog"),0);
			}
		}));

		var label = this.getLabel(this.command),
			className = this.iconClassPrefix + " " + this.iconClassPrefix + this.command.charAt(0).toUpperCase() + this.command.substr(1),
			props = dojo.mixin({
					label: label,
					showLabel: false,
					iconClass: className,
					dropDown: this.dropDown,
					tabIndex: "-1"
				}, this.params || {});

		if(!has('ie')){
			// Workaround for Non-IE problem:
			// Safari 5: After the select-file dialog opens, the first time the user clicks anywhere (even on that dialog)
			// it's treated like a plain click on the page, and the tooltip dialog closes
			// FF & Chrome: the select-file dialog does not block the execution of JS
			props.closeDropDown = function(/*Boolean*/ focus){
				if(_this._closable){
					if(this._opened){
						popup.close(this.dropDown);
						if(focus){ this.focus(); }
						this._opened = false;
						this.state = "";
					}
				}
				setTimeout(function(){ _this._closable = true; }, 10);
			};
		}

		this.button = new DropDownButton(props);

		// Generate the RegExp of the ValidationTextBox from fileMask
		// *.jpg;*.png => /.*\.jpg|.*\.JPG|.*\.png|.*\.PNG/
		var masks = this.fileMask.split(";"),
			temp = "";
		dojo.forEach(masks, function(m){
			m = m.replace(/\./, "\\.").replace(/\*/g, ".*");
			temp += "|" + m + "|" + m.toUpperCase();
		});
		messages.urlRegExp = this.urlRegExp = temp.substring(1);

		if(!this.uploadable){
			messages.prePopuTextBrowse = ".";
		}

		messages.id = registry.getUniqueId(this.editor.id);
		messages.uploadable = this.uploadable ? "inline" : "none";
		this._uniqueId = messages.id;
		this._setContent("
" + dropDown.title + "
" +
			dojo.string.substitute(this.linkDialogTemplate, messages));
		dropDown.startup();

		var urlInput = (this._urlInput = registry.byId(this._uniqueId + "_urlInput"));
		this._textInput = registry.byId(this._uniqueId + "_textInput");
		this._setButton = registry.byId(this._uniqueId + "_setButton");

		if(urlInput){
			var pt = ValidationTextBox.prototype;
			urlInput = dojo.mixin(urlInput, {
				// Indicate if the widget is ready to validate the input text
				isLoadComplete: false,
				isValid: function(isFocused){
					if(this.isLoadComplete){
						return pt.isValid.apply(this, arguments);
					}else{
						return this.get("value").length > 0;
					}
				},
				reset: function(){
					this.isLoadComplete = false;
					pt.reset.apply(this, arguments);
				}
			});

			this.connect(urlInput, "onKeyDown", "_cancelFileUpload");
			this.connect(urlInput, "onChange", "_checkAndFixInput");
		}
		if(this._setButton){
			this.connect(this._setButton, "onClick", "_checkAndSetValue");
		}
		this._connectTagEvents();
	},

	_initialFileUploader: function(){
		// summary:
		//		Initialize the FileUploader and connect up its events
		// tags:
		//		private
		var fup = null,
			_this = this,
			widgetId = _this._uniqueId,
			fUpId = widgetId + "_browse",
			urlInput = _this._urlInput;

		if(_this.uploadable && !_this._fileUploader){
			fup = _this._fileUploader = new FileUploader({
				force: "html", // Noticed that SWF may cause browsers to crash sometimes
				uploadUrl: _this.uploadUrl,
				htmlFieldName: _this.htmlFieldName,
				uploadOnChange: false,
				selectMultipleFiles: false,
				showProgress: true
			}, fUpId);

			// TooltipDialog will call reset on all the widgets contained within it.
			// Have FileUploader be responsive to this call.
			fup.reset = function(){
				_this._isLocalFile = false;
				fup._resetHTML();
			};

			_this.connect(fup, "onClick", function(){
				urlInput.validate(false);
				if(!has('ie')){
					// Firefox, Chrome and Safari have a strange behavior:
					// When the File Upload dialog is open, the browse div (FileUploader) will lose its focus
					// and triggers onBlur event. This event will cause the whole tooltip dialog
					// to be closed when the File Upload dialog is open. The popup dialog should hang up
					// the js execution rather than triggering an event. IE does not have such a problem.
					_this._closable = false;
				}
			});

			_this.connect(fup, "onChange", function(data){
				_this._isLocalFile = true;
				urlInput.set("value", data[0].name); //Single selection
				urlInput.focus();
			});

			_this.connect(fup, "onComplete", function(data){
				var urlPrefix = _this.baseImageUrl;
				urlPrefix = urlPrefix && urlPrefix.charAt(urlPrefix.length - 1) == "/" ? urlPrefix : urlPrefix + "/";
				urlInput.set("value", urlPrefix + data[0].file); //Single selection
				_this._isLocalFile = false;
				_this._setDialogStatus(true);
				_this.setValue(_this.dropDown.get("value"));
			});

			_this.connect(fup, "onError", function(evtObject){
				// summary:
				//		Fires on errors
				console.log("Error occurred when uploading image file!");
				_this._setDialogStatus(true);
			});
		}
	},

	_checkAndFixInput: function(){
		// summray:
		//		Over-ride the original method
		this._setButton.set("disabled", !this._isValid());
	},

	_isValid: function(){
		// summray:
		//		Invalid cases: URL is not ended with the suffix listed
		return this._urlInput.isValid();
	},

	_cancelFileUpload: function(){
		this._fileUploader.reset();
		this._isLocalFile = false;
	},

	_checkAndSetValue: function(){
		// summray:
		//		Determine if a local file is to be uploaded.
		//		If a local file is to be uploaded, do not close the dialog
		//		until the file uploading is finished. Else, insert the image directly into the editor.
		// tags:
		//		private
		if(this._fileUploader && this._isLocalFile){
			this._setDialogStatus(false);
			this._fileUploader.upload();
		}else{
			this.setValue(this.dropDown.get("value"));
		}
	},

	_setDialogStatus: function(/*Boolean*/ value){
		this._urlInput.set("disabled", !value);
		this._textInput.set("disabled", !value);
		this._setButton.set("disabled", !value);
	},

	destroy: function(){
		// summary:
		//		Cleanup of the plugin.
		this.inherited(arguments);
		if(this._fileUploader){
			this._fileUploader.destroy();
			delete this._fileUploader;
		}
	}
});

var plugin = function(args){
	return new LocalImage({
		command: "insertImage",
		uploadable: ("uploadable" in args) ? args.uploadable : false,
		uploadUrl: ("uploadable" in args && "uploadUrl" in args) ? args.uploadUrl : "",
		htmlFieldName: ("uploadable" in args && "htmlFieldName" in args) ? args.htmlFieldName : "uploadedfile",
		baseImageUrl: ("uploadable" in args && "baseImageUrl" in args) ? args.baseImageUrl : "",
		fileMask: ("fileMask" in args) ? args.fileMask : "*.jpg;*.jpeg;*.gif;*.png;*.bmp"
	});
};

// Register the plugin and some name varients.
_Plugin.registry["LocalImage"] = plugin;
_Plugin.registry["localImage"] = plugin;
_Plugin.registry["localimage"] = plugin;

return LocalImage;

});
<|endoftext|>


<commit_before>exports['Should correctly emit sdam monitoring events for single server'] = {
  metadata: {
    requires: {
      generators: true,
      topology: "single"
    }
  },

  test: function(configuration, test) {
    var Server = configuration.require.Server,
      ObjectId = configuration.require.BSON.ObjectId,
      co = require('co'),
      mockupdb = require('../../../mock');

    // Contain mock server
    var server = null;
    var running = true;
    // Current index for the ismaster
    var currentStep = 0;
    // Primary stop responding
    var stopRespondingPrimary = false;

    // Extend the object
    var extend = function(template, fields) {
      for(var name in template) fields[name] = template[name];
      return fields;
    }

    // Default message fields
    var defaultFields = {
      "ismaster" : true,
      "maxBsonObjectSize" : 16777216,
      "maxMessageSizeBytes" : 48000000,
      "maxWriteBatchSize" : 1000,
      "localTime" : new Date(),
      "maxWireVersion" : 3,
      "minWireVersion" : 0,
      "ok" : 1
    }

    // Primary server states
    var serverIsMaster = [extend(defaultFields, {})];
    var timeoutPromise = function(timeout) {
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          resolve();
        }, timeout);
      });
    }

    // Boot the mock
    co(function*() {
      server = yield mockupdb.createServer(37018, 'localhost');

      // Primary state machine
      co(function*() {
        while(running) {
          var request = yield server.receive();

          // Get the document
          var doc = request.document;
          if(doc.ismaster) {
            request.reply(serverIsMaster[0]);
          }
        }
      });
    });

    // Attempt to connect
    var server = new Server({
      host: 'localhost',
      port: '37018',
      connectionTimeout: 3000,
      socketTimeout: 1000,
      size: 1
    });

    // Results
    var flags = [];
    var id = null;

    // Add event listeners
    server.once('connect', function(_server) {
      // console.log("----------------------------- connect")
      id = _server.id;
      _server.destroy({emitClose:true});
    });

    server.on('serverOpening', function(event) {
      // console.log("----------------------------- serverOpening")
      // console.log(JSON.stringify(event, null, 2))
      flags[0] = event;
    });

    server.on('serverClosed', function(event) {
      // console.log("----------------------------- serverClosed")
      // console.log(JSON.stringify(event, null, 2))
      flags[1] = event;
    });

    server.on('serverDescriptionChanged', function(event) {
      // console.log("----------------------------- serverDescriptionChanged")
      // console.log(JSON.stringify(event, null, 2))
      flags[2] = event;
    });

    server.on('topologyOpening', function(event) {
      // console.log("----------------------------- topologyOpening")
      // console.log(JSON.stringify(event, null, 2))
      flags[3] = event;
    });

    server.on('topologyClosed', function(event) {
      // console.log("----------------------------- topologyClosed")
      // console.log(JSON.stringify(event, null, 2))
      flags[4] = event;
    });

    server.on('topologyDescriptionChanged', function(event) {
      // console.log("----------------------------- topologyDescriptionChanged")
      // console.log(JSON.stringify(event, null, 2))
      flags[5] = event;
    });

    server.on('error', function(){});
    server.on('close', function(){
      setTimeout(function() {
        // console.log("----------------------------------------- 0")
        test.deepEqual({ topologyId: id, address: 'localhost:37018' }, flags[0]);
        // console.log("----------------------------------------- 1")
        test.deepEqual({ topologyId: id, address: 'localhost:37018' }, flags[1]);
        // console.log("----------------------------------------- 2")
        test.deepEqual({ "topologyId": id, "address": "localhost:37018",
          "previousDescription": {
            "address": "localhost:37018",
            "arbiters": [],
            "hosts": [],
            "passives": [],
            "type": "Unknown"
          },
          "newDescription": {
            "address": "localhost:37018",
            "arbiters": [],
            "hosts": [],
            "passives": [],
            "type": "Standalone"
          }
        }, flags[2]);
        // console.log("----------------------------------------- 3")
        // console.dir(flags[0])
        test.deepEqual({ topologyId: id }, flags[3]);
        // console.log("----------------------------------------- 4")
        test.deepEqual({ topologyId: id }, flags[4]);
        // console.log("----------------------------------------- 5")
        test.deepEqual({ "topologyId": id, "address": "localhost:37018",
          "previousDescription": {
            "topologyType": "Unknown",
            "servers": [
              {
                "address": "localhost:37018",
                "arbiters": [],
                "hosts": [],
                "passives": [],
                "type": "Unknown"
              }
            ]
          },
          "newDescription": {
            "topologyType": "Single",
            "servers": [
              {
                "address": "localhost:37018",
                "arbiters": [],
                "hosts": [],
                "passives": [],
                "type": "Standalone"
              }
            ]
          }
        }, flags[5]);
        // console.log("----------------------------------------- 6")
        test.done();
      }, 100);
    });
    server.connect();
  }
}
<commit_msg>test(sdam): add test for emitting correct SDAM server type<commit_after>exports['Should correctly emit sdam monitoring events for single server'] = {
  metadata: {
    requires: {
      generators: true,
      topology: "single"
    }
  },

  test: function(configuration, test) {
    var Server = configuration.require.Server,
      ObjectId = configuration.require.BSON.ObjectId,
      co = require('co'),
      mockupdb = require('../../../mock');

    // Contain mock server
    var server = null;
    var running = true;
    // Current index for the ismaster
    var currentStep = 0;
    // Primary stop responding
    var stopRespondingPrimary = false;

    // Extend the object
    var extend = function(template, fields) {
      for(var name in template) fields[name] = template[name];
      return fields;
    }

    // Default message fields
    var defaultFields = {
      "ismaster" : true,
      "maxBsonObjectSize" : 16777216,
      "maxMessageSizeBytes" : 48000000,
      "maxWriteBatchSize" : 1000,
      "localTime" : new Date(),
      "maxWireVersion" : 3,
      "minWireVersion" : 0,
      "ok" : 1
    }

    // Primary server states
    var serverIsMaster = [extend(defaultFields, {})];
    var timeoutPromise = function(timeout) {
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          resolve();
        }, timeout);
      });
    }

    // Boot the mock
    co(function*() {
      server = yield mockupdb.createServer(37018, 'localhost');

      // Primary state machine
      co(function*() {
        while(running) {
          var request = yield server.receive();

          // Get the document
          var doc = request.document;
          if(doc.ismaster) {
            request.reply(serverIsMaster[0]);
          }
        }
      });
    });

    // Attempt to connect
    var server = new Server({
      host: 'localhost',
      port: '37018',
      connectionTimeout: 3000,
      socketTimeout: 1000,
      size: 1
    });

    // Results
    var flags = [];
    var id = null;

    // Add event listeners
    server.once('connect', function(_server) {
      id = _server.id;
      _server.destroy({emitClose:true});
    });

    server.on('serverOpening', function(event) {
      flags[0] = event;
    });

    server.on('serverClosed', function(event) {
      flags[1] = event;
    });

    server.on('serverDescriptionChanged', function(event) {
      flags[2] = event;
    });

    server.on('topologyOpening', function(event) {
      flags[3] = event;
    });

    server.on('topologyClosed', function(event) {
      flags[4] = event;
    });

    server.on('topologyDescriptionChanged', function(event) {
      flags[5] = event;
    });

    server.on('error', function(){});
    server.on('close', function(){
      setTimeout(function() {
        test.deepEqual({ topologyId: id, address: 'localhost:37018' }, flags[0]);
        test.deepEqual({ topologyId: id, address: 'localhost:37018' }, flags[1]);
        test.deepEqual({ "topologyId": id, "address": "localhost:37018",
          "previousDescription": {
            "address": "localhost:37018",
            "arbiters": [],
            "hosts": [],
            "passives": [],
            "type": "Unknown"
          },
          "newDescription": {
            "address": "localhost:37018",
            "arbiters": [],
            "hosts": [],
            "passives": [],
            "type": "Standalone"
          }
        }, flags[2]);

        test.deepEqual({ topologyId: id }, flags[3]);
        test.deepEqual({ topologyId: id }, flags[4]);
        test.deepEqual({ "topologyId": id, "address": "localhost:37018",
          "previousDescription": {
            "topologyType": "Unknown",
            "servers": [
              {
                "address": "localhost:37018",
                "arbiters": [],
                "hosts": [],
                "passives": [],
                "type": "Unknown"
              }
            ]
          },
          "newDescription": {
            "topologyType": "Single",
            "servers": [
              {
                "address": "localhost:37018",
                "arbiters": [],
                "hosts": [],
                "passives": [],
                "type": "Standalone"
              }
            ]
          }
        }, flags[5]);
        test.done();
      }, 100);
    });

    process.nextTick(function() { server.connect(); });
  }
}

exports['Should correctly emit sdam monitoring events for single server, with correct server type'] = {
  metadata: { requires: { generators: true, topology: "single" } },

  test: function(configuration, test) {
    var Server = configuration.require.Server,
      ObjectId = configuration.require.BSON.ObjectId,
      co = require('co'),
      mockupdb = require('../../../mock');

    // Contain mock server
    var server = null;
    var running = true;
    // Current index for the ismaster
    var currentStep = 0;
    // Primary stop responding
    var stopRespondingPrimary = false;

    // Extend the object
    var extend = function(template, fields) {
      for(var name in template) fields[name] = template[name];
      return fields;
    }

    // Default message fields
    var defaultFields = {
      "ismaster" : true,
      "maxBsonObjectSize" : 16777216,
      "maxMessageSizeBytes" : 48000000,
      "maxWriteBatchSize" : 1000,
      "localTime" : new Date(),
      "maxWireVersion" : 3,
      "minWireVersion" : 0,
      "ok" : 1,
      "hosts": [ "a:27017", "b:27017" ] // <-- this makes it an RSPrimary
    }

    // Primary server states
    var serverIsMaster = [extend(defaultFields, {})];
    var timeoutPromise = function(timeout) {
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          resolve();
        }, timeout);
      });
    }

    // Boot the mock
    co(function*() {
      server = yield mockupdb.createServer(37008, 'localhost');

      // Primary state machine
      co(function*() {
        while(running) {
          var request = yield server.receive();

          // Get the document
          var doc = request.document;
          if(doc.ismaster) {
            request.reply(serverIsMaster[0]);
          }
        }
      });
    });

    // Attempt to connect
    var server = new Server({
      host: 'localhost',
      port: '37008',
      connectionTimeout: 3000,
      socketTimeout: 1000,
      size: 1
    });

    // Results
    var flags = [];
    var id = null;

    // Add event listeners
    server.once('connect', function(_server) {
      id = _server.id;
      _server.destroy({emitClose:true});
    });

    server.on('serverOpening', function(event) {
      flags[0] = event;
    });

    server.on('serverClosed', function(event) {
      flags[1] = event;
    });

    server.on('serverDescriptionChanged', function(event) {
      flags[2] = event;
    });

    server.on('topologyOpening', function(event) {
      flags[3] = event;
    });

    server.on('topologyClosed', function(event) {
      flags[4] = event;
    });

    server.on('topologyDescriptionChanged', function(event) {
      flags[5] = event;
    });

    server.on('error', function(){});
    server.on('close', function(){
      setTimeout(function() {
        test.deepEqual({ topologyId: id, address: 'localhost:37008' }, flags[0]);
        test.deepEqual({ topologyId: id, address: 'localhost:37008' }, flags[1]);
        test.deepEqual({ "topologyId": id, "address": "localhost:37008",
          "previousDescription": {
            "address": "localhost:37008",
            "arbiters": [],
            "hosts": [],
            "passives": [],
            "type": "Unknown"
          },
          "newDescription": {
            "address": "localhost:37008",
            "arbiters": [],
            "hosts": [],
            "passives": [],
            "type": "RSPrimary"
          }
        }, flags[2]);
        test.deepEqual({ topologyId: id }, flags[3]);
        test.deepEqual({ topologyId: id }, flags[4]);
        test.deepEqual({ "topologyId": id, "address": "localhost:37008",
          "previousDescription": {
            "topologyType": "Unknown",
            "servers": [
              {
                "address": "localhost:37008",
                "arbiters": [],
                "hosts": [],
                "passives": [],
                "type": "Unknown"
              }
            ]
          },
          "newDescription": {
            "topologyType": "Single",
            "servers": [
              {
                "address": "localhost:37008",
                "arbiters": [],
                "hosts": [],
                "passives": [],
                "type": "RSPrimary"
              }
            ]
          }
        }, flags[5]);
        test.done();
      }, 100);
    });

    process.nextTick(function() { server.connect(); });
  }
}
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado

/*
 * A bucket that can be used to store spherical objects.  Manages the addition
 * and removal of the spheres, and stacks them as the are added, and manages
 * the stack as spheres are removed.
 *
 * This expects the spheres to have certain properties, please inspect the
 * code to understand the "contract" between the bucket and the spheres.
 *
 * @author John Blanco
 */
define( function ( require ) {

  var Vector2 = require( 'DOT/Vector2' );
  var Inheritance = require( 'PHETCOMMON/util/Inheritance' );
  var Bucket = require( 'PHETCOMMON/model/Bucket' );

  function SphereBucket( options ) {
    Bucket.call( this, options );
    this.particleRadius = options.particleRadius || 10;
    this.yOffset = -this.particleRadius / 2; // Empirically determined, for positioning particles inside the bucket.
    this.particles = [];
  }

  // Inherit from base type.
  Inheritance.inheritPrototype( SphereBucket, Bucket );

  SphereBucket.prototype.addParticleFirstOpen = function ( particle ) {
    particle.position = this.getFirstOpenLocation();
    this.particles.push( particle );
    var thisBucket = this;
    particle.once( 'change:userControlled', function ( m, userControlled ) {
      if ( userControlled ) {
        thisBucket.removeParticle( particle );
      }
    } )
  }

  SphereBucket.prototype.addParticleNearestOpen = function ( particle ) {
    particle.position = this.getNearestOpenLocation( particle.position );
    this.particles.push( particle );
    var thisBucket = this;
    particle.link( 'userControlled', function ( m, userControlled ) {
      if ( userControlled ) {
        thisBucket.removeParticle( particle );
      }
    } )
  }

  SphereBucket.prototype.removeParticle = function ( particle ) {
    if ( this.particles.indexOf( particle ) == -1 ) {
      console.log( "Error: Attempt to remove particle not contained in bucket, ignoring." );
      return;
    }
    this.particles = _.without( this.particles, particle );
    this.relayoutBucketParticles();
  }

  SphereBucket.prototype.isPositionOpen = function ( position ) {
    var positionOpen = true;
    for ( var i = 0; i < this.particles.length; i++ ) {
      var particle = this.particles[ i ];
      if ( particle.position.equals( position ) ) {
        positionOpen = false;
        break;
      }
    }
    return positionOpen;
  }

  SphereBucket.prototype.getFirstOpenLocation = function () {
    var openLocation = Vector2.ZERO;
    var usableWidth = this.size.width - 2 * this.particleRadius;
    var offsetFromBucketEdge = this.particleRadius * 2;
    var numParticlesInLayer = Math.floor( usableWidth / ( this.particleRadius * 2 ) );
    var row = 0;
    var positionInLayer = 0;
    var found = false;
    while ( !found ) {
      var testLocation = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this.particleRadius,
                                      this.getYPositionForLayer( row ) );
      if ( this.isPositionOpen( testLocation ) ) {
        // We found a location that is open.
        openLocation = testLocation;
        found = true;
        continue;
      }
      else {
        positionInLayer++;
        if ( positionInLayer >= numParticlesInLayer ) {
          // Move to the next layer.
          row++;
          positionInLayer = 0;
          numParticlesInLayer--;
          offsetFromBucketEdge += this.particleRadius;
          if ( numParticlesInLayer == 0 ) {
            // This algorithm doesn't handle the situation where
            // more particles are added than can be stacked into
            // a pyramid of the needed size, but so far it hasn't
            // needed to.  If this requirement changes, the
            // algorithm will need to change too.
            numParticlesInLayer = 1;
            offsetFromBucketEdge -= this.particleRadius;
          }
        }
      }
    }
    return openLocation;
  }

  SphereBucket.prototype.getLayerForYPosition = function ( yPosition ) {
    return Math.abs( Math.round( ( yPosition - ( this.y + this.yOffset ) ) / ( this.particleRadius * 2 * 0.866 ) ) );
  }

  /*
   * Get the nearest open location to the provided current location.  This
   * is used for particle stacking.
   *
   * @param location
   * @return
   */
  SphereBucket.prototype.getNearestOpenLocation = function ( position ) {
    // Determine the highest occupied layer.  The bottom layer is 0.
    var highestOccupiedLayer = 0;
    var self = this;
    _.each( this.particles, function ( particle ) {
      var layer = self.getLayerForYPosition( particle.position.y );
      if ( layer > highestOccupiedLayer ) {
        highestOccupiedLayer = layer;
      }
    } );

    // Make a list of all open locations in the occupied layers.
    var openLocations = [];
    var placeableWidth = this.size.width - 2 * this.particleRadius;
    var offsetFromBucketEdge = ( this.size.width - placeableWidth ) / 2 + this.particleRadius;
    var numParticlesInLayer = Math.floor( placeableWidth / ( this.particleRadius * 2 ) );

    // Loop, searching for open positions in the particle stack.
    for ( var layer = 0; layer <= highestOccupiedLayer + 1; layer++ ) {

      // Add all open locations in the current layer.
      for ( var positionInLayer = 0; positionInLayer < numParticlesInLayer; positionInLayer++ ) {
        var testPosition = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this.particleRadius,
                                        this.getYPositionForLayer( layer ) );
        if ( this.isPositionOpen( testPosition ) ) {

          // We found a location that is unoccupied.
          if ( layer == 0 || this.countSupportingParticles( testPosition ) == 2 ) {
            // This is a valid open location.
            openLocations.push( testPosition );
          }
        }
      }

      // Adjust variables for the next layer.
      numParticlesInLayer--;
      offsetFromBucketEdge += this.particleRadius;
      if ( numParticlesInLayer == 0 ) {
        // If the stacking pyramid is full, meaning that there are
        // no locations that are open within it, this algorithm
        // classifies the locations directly above the top of the
        // pyramid as being open.  This would result in a stack
        // of particles with a pyramid base.  So far, this hasn't
        // been a problem, but this limitation may limit
        // reusability of this algorithm.
        numParticlesInLayer = 1;
        offsetFromBucketEdge -= this.particleRadius;
      }
    }

    // Find the closest open location to the provided current location.
    // Only the X-component is used for this determination, because if
    // the Y-component is used the particles often appear to fall sideways
    // when released above the bucket, which just looks weird.
    var closestOpenLocation = openLocations[0] || Vector2.ZERO;

    _.each( openLocations, function ( location ) {
      if ( location.distance( position ) < closestOpenLocation.distance( position ) ) {
        // This location is closer.
        closestOpenLocation = location;
      }
    } )
    return closestOpenLocation;
  }

  SphereBucket.prototype.getYPositionForLayer = function ( layer ) {
    return this.position.y + this.yOffset + layer * this.particleRadius * 2 * 0.866;
  }

  /*
   * Determine whether a particle is 'dangling', i.e. hanging above an open
   * space in the stack of particles.  Dangling particles should fall.
   */
  SphereBucket.prototype.isDangling = function ( particle ) {
    var onBottomRow = particle.position.y === this.position.y + this.yOffset;
    return !onBottomRow && this.countSupportingParticles( particle.position ) < 2;
  }

  SphereBucket.prototype.countSupportingParticles = function ( position ) {
    var count = 0;
    for ( var i = 0; i < this.particles.length; i++ ) {
      p = this.particles[i];
      if ( p.position.y < position.y && // Must be in a lower layer
           p.position.distance( position ) < this.particleRadius * 3 ) {
        // Must be a supporting particle.
        count++;
      }
    }
    return count;
  }

  SphereBucket.prototype.relayoutBucketParticles = function () {
    do {
      for ( var i = 0; i < this.particles.length; i++ ) {
        var particleMoved = false;
        var particle = this.particles[i];
        if ( this.isDangling( particle ) ) {
          particle.position = this.getNearestOpenLocation( particle.position );
          particleMoved = true;
          break;
        }
      }
    } while ( particleMoved );
  }

  return SphereBucket;
} );
<commit_msg>Added method to determine if a particle is contained in the bucket.<commit_after>// Copyright 2002-2013, University of Colorado

/*
 * A bucket that can be used to store spherical objects.  Manages the addition
 * and removal of the spheres, and stacks them as the are added, and manages
 * the stack as spheres are removed.
 *
 * This expects the spheres to have certain properties, please inspect the
 * code to understand the "contract" between the bucket and the spheres.
 *
 * @author John Blanco
 */
define( function ( require ) {

  var Vector2 = require( 'DOT/Vector2' );
  var Inheritance = require( 'PHETCOMMON/util/Inheritance' );
  var Bucket = require( 'PHETCOMMON/model/Bucket' );

  function SphereBucket( options ) {
    Bucket.call( this, options );
    this.particleRadius = options.particleRadius || 10;
    this.yOffset = -this.particleRadius / 2; // Empirically determined, for positioning particles inside the bucket.
    this.particles = [];
  }

  // Inherit from base type.
  Inheritance.inheritPrototype( SphereBucket, Bucket );

  SphereBucket.prototype.addParticleFirstOpen = function ( particle ) {
    particle.position = this.getFirstOpenLocation();
    this.particles.push( particle );
    var thisBucket = this;
    particle.once( 'change:userControlled', function ( m, userControlled ) {
      if ( userControlled ) {
        thisBucket.removeParticle( particle );
      }
    } )
  }

  SphereBucket.prototype.addParticleNearestOpen = function ( particle ) {
    particle.position = this.getNearestOpenLocation( particle.position );
    this.particles.push( particle );
    var thisBucket = this;
    particle.link( 'userControlled', function ( m, userControlled ) {
      if ( userControlled ) {
        thisBucket.removeParticle( particle );
      }
    } )
  }

  SphereBucket.prototype.removeParticle = function ( particle ) {
    if ( this.particles.indexOf( particle ) == -1 ) {
      console.log( "Error: Attempt to remove particle not contained in bucket, ignoring." );
      return;
    }
    this.particles = _.without( this.particles, particle );
    this.relayoutBucketParticles();
  }

  SphereBucket.prototype.isPositionOpen = function ( position ) {
    var positionOpen = true;
    for ( var i = 0; i < this.particles.length; i++ ) {
      var particle = this.particles[ i ];
      if ( particle.position.equals( position ) ) {
        positionOpen = false;
        break;
      }
    }
    return positionOpen;
  }

  SphereBucket.prototype.getFirstOpenLocation = function () {
    var openLocation = Vector2.ZERO;
    var usableWidth = this.size.width - 2 * this.particleRadius;
    var offsetFromBucketEdge = this.particleRadius * 2;
    var numParticlesInLayer = Math.floor( usableWidth / ( this.particleRadius * 2 ) );
    var row = 0;
    var positionInLayer = 0;
    var found = false;
    while ( !found ) {
      var testLocation = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this.particleRadius,
                                      this.getYPositionForLayer( row ) );
      if ( this.isPositionOpen( testLocation ) ) {
        // We found a location that is open.
        openLocation = testLocation;
        found = true;
        continue;
      }
      else {
        positionInLayer++;
        if ( positionInLayer >= numParticlesInLayer ) {
          // Move to the next layer.
          row++;
          positionInLayer = 0;
          numParticlesInLayer--;
          offsetFromBucketEdge += this.particleRadius;
          if ( numParticlesInLayer == 0 ) {
            // This algorithm doesn't handle the situation where
            // more particles are added than can be stacked into
            // a pyramid of the needed size, but so far it hasn't
            // needed to.  If this requirement changes, the
            // algorithm will need to change too.
            numParticlesInLayer = 1;
            offsetFromBucketEdge -= this.particleRadius;
          }
        }
      }
    }
    return openLocation;
  }

  SphereBucket.prototype.getLayerForYPosition = function ( yPosition ) {
    return Math.abs( Math.round( ( yPosition - ( this.y + this.yOffset ) ) / ( this.particleRadius * 2 * 0.866 ) ) );
  }

  /*
   * Get the nearest open location to the provided current location.  This
   * is used for particle stacking.
   *
   * @param location
   * @return
   */
  SphereBucket.prototype.getNearestOpenLocation = function ( position ) {
    // Determine the highest occupied layer.  The bottom layer is 0.
    var highestOccupiedLayer = 0;
    var self = this;
    _.each( this.particles, function ( particle ) {
      var layer = self.getLayerForYPosition( particle.position.y );
      if ( layer > highestOccupiedLayer ) {
        highestOccupiedLayer = layer;
      }
    } );

    // Make a list of all open locations in the occupied layers.
    var openLocations = [];
    var placeableWidth = this.size.width - 2 * this.particleRadius;
    var offsetFromBucketEdge = ( this.size.width - placeableWidth ) / 2 + this.particleRadius;
    var numParticlesInLayer = Math.floor( placeableWidth / ( this.particleRadius * 2 ) );

    // Loop, searching for open positions in the particle stack.
    for ( var layer = 0; layer <= highestOccupiedLayer + 1; layer++ ) {

      // Add all open locations in the current layer.
      for ( var positionInLayer = 0; positionInLayer < numParticlesInLayer; positionInLayer++ ) {
        var testPosition = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this.particleRadius,
                                        this.getYPositionForLayer( layer ) );
        if ( this.isPositionOpen( testPosition ) ) {

          // We found a location that is unoccupied.
          if ( layer == 0 || this.countSupportingParticles( testPosition ) == 2 ) {
            // This is a valid open location.
            openLocations.push( testPosition );
          }
        }
      }

      // Adjust variables for the next layer.
      numParticlesInLayer--;
      offsetFromBucketEdge += this.particleRadius;
      if ( numParticlesInLayer == 0 ) {
        // If the stacking pyramid is full, meaning that there are
        // no locations that are open within it, this algorithm
        // classifies the locations directly above the top of the
        // pyramid as being open.  This would result in a stack
        // of particles with a pyramid base.  So far, this hasn't
        // been a problem, but this limitation may limit
        // reusability of this algorithm.
        numParticlesInLayer = 1;
        offsetFromBucketEdge -= this.particleRadius;
      }
    }

    // Find the closest open location to the provided current location.
    // Only the X-component is used for this determination, because if
    // the Y-component is used the particles often appear to fall sideways
    // when released above the bucket, which just looks weird.
    var closestOpenLocation = openLocations[0] || Vector2.ZERO;

    _.each( openLocations, function ( location ) {
      if ( location.distance( position ) < closestOpenLocation.distance( position ) ) {
        // This location is closer.
        closestOpenLocation = location;
      }
    } )
    return closestOpenLocation;
  }

  SphereBucket.prototype.getYPositionForLayer = function ( layer ) {
    return this.position.y + this.yOffset + layer * this.particleRadius * 2 * 0.866;
  }

  /*
   * Determine whether a particle is 'dangling', i.e. hanging above an open
   * space in the stack of particles.  Dangling particles should fall.
   */
  SphereBucket.prototype.isDangling = function ( particle ) {
    var onBottomRow = particle.position.y === this.position.y + this.yOffset;
    return !onBottomRow && this.countSupportingParticles( particle.position ) < 2;
  }

  SphereBucket.prototype.countSupportingParticles = function ( position ) {
    var count = 0;
    for ( var i = 0; i < this.particles.length; i++ ) {
      p = this.particles[i];
      if ( p.position.y < position.y && // Must be in a lower layer
           p.position.distance( position ) < this.particleRadius * 3 ) {
        // Must be a supporting particle.
        count++;
      }
    }
    return count;
  }

  SphereBucket.prototype.relayoutBucketParticles = function () {
    do {
      for ( var i = 0; i < this.particles.length; i++ ) {
        var particleMoved = false;
        var particle = this.particles[i];
        if ( this.isDangling( particle ) ) {
          particle.position = this.getNearestOpenLocation( particle.position );
          particleMoved = true;
          break;
        }
      }
    } while ( particleMoved );
  }

  SphereBucket.prototype.containsParticle = function ( particle ) {
    return this.particles.indexOf( particle ) != -1;
  }

  return SphereBucket;
} );
<|endoftext|>