mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-31 09:03:21 +00:00 
			
		
		
		
	GUACAMOLE-773: Migrate to NPM for AngularJS portion of webapp build.
This commit is contained in:
		| @@ -1,26 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * The module for authentication and management of tokens. | ||||
|  */ | ||||
| angular.module('auth', [ | ||||
|     'rest', | ||||
|     'storage' | ||||
| ]); | ||||
| @@ -1,398 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A service for authenticating a user against the REST API. | ||||
|  * | ||||
|  * This service broadcasts two events on $rootScope depending on the result of | ||||
|  * authentication operations: 'guacLogin' if authentication was successful and | ||||
|  * a new token was created, and 'guacLogout' if an existing token is being | ||||
|  * destroyed or replaced. Both events will be passed the related token as their | ||||
|  * sole parameter. | ||||
|  * | ||||
|  * If a login attempt results in an existing token being replaced, 'guacLogout' | ||||
|  * will be broadcast first for the token being replaced, followed by | ||||
|  * 'guacLogin' for the new token. | ||||
|  *  | ||||
|  * Failed logins may also result in guacInsufficientCredentials or | ||||
|  * guacInvalidCredentials events, if the provided credentials were rejected for | ||||
|  * being insufficient or invalid respectively. Both events will be provided | ||||
|  * the set of parameters originally given to authenticate() and the error that | ||||
|  * rejected the credentials. The Error object provided will contain set of | ||||
|  * expected credentials returned by the REST endpoint. This set of credentials | ||||
|  * will be in the form of a Field array. | ||||
|  */ | ||||
| angular.module('auth').factory('authenticationService', ['$injector', | ||||
|         function authenticationService($injector) { | ||||
|  | ||||
|     // Required types | ||||
|     var AuthenticationResult = $injector.get('AuthenticationResult'); | ||||
|     var Error                = $injector.get('Error'); | ||||
|  | ||||
|     // Required services | ||||
|     var $rootScope          = $injector.get('$rootScope'); | ||||
|     var localStorageService = $injector.get('localStorageService'); | ||||
|     var requestService      = $injector.get('requestService'); | ||||
|  | ||||
|     var service = {}; | ||||
|  | ||||
|     /** | ||||
|      * The most recent authentication result, or null if no authentication | ||||
|      * result is cached. | ||||
|      * | ||||
|      * @type AuthenticationResult | ||||
|      */ | ||||
|     var cachedResult = null; | ||||
|  | ||||
|     /** | ||||
|      * The unique identifier of the local storage key which stores the result | ||||
|      * of the last authentication attempt. | ||||
|      * | ||||
|      * @type String | ||||
|      */ | ||||
|     var AUTH_STORAGE_KEY = 'GUAC_AUTH'; | ||||
|  | ||||
|     /** | ||||
|      * Retrieves the last successful authentication result. If the user has not | ||||
|      * yet authenticated, the user has logged out, or the last authentication | ||||
|      * attempt failed, null is returned. | ||||
|      * | ||||
|      * @returns {AuthenticationResult} | ||||
|      *     The last successful authentication result, or null if the user is not | ||||
|      *     currently authenticated. | ||||
|      */ | ||||
|     var getAuthenticationResult = function getAuthenticationResult() { | ||||
|  | ||||
|         // Use cached result, if any | ||||
|         if (cachedResult) | ||||
|             return cachedResult; | ||||
|  | ||||
|         // Return explicit null if no auth data is currently stored | ||||
|         var data = localStorageService.getItem(AUTH_STORAGE_KEY); | ||||
|         if (!data) | ||||
|             return null; | ||||
|  | ||||
|         // Update cache and return retrieved auth result | ||||
|         return (cachedResult = new AuthenticationResult(data)); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Stores the given authentication result for future retrieval. The given | ||||
|      * result MUST be the result of the most recent authentication attempt. | ||||
|      * | ||||
|      * @param {AuthenticationResult} data | ||||
|      *     The last successful authentication result, or null if the last | ||||
|      *     authentication attempt failed. | ||||
|      */ | ||||
|     var setAuthenticationResult = function setAuthenticationResult(data) { | ||||
|  | ||||
|         // Clear the currently-stored result if the last attempt failed | ||||
|         if (!data) { | ||||
|             cachedResult = null; | ||||
|             localStorageService.removeItem(AUTH_STORAGE_KEY); | ||||
|         } | ||||
|  | ||||
|         // Otherwise store the authentication attempt directly | ||||
|         else { | ||||
|  | ||||
|             // Always store in cache | ||||
|             cachedResult = data; | ||||
|  | ||||
|             // Persist result past tab/window closure ONLY if not anonymous | ||||
|             if (data.username !== AuthenticationResult.ANONYMOUS_USERNAME) | ||||
|                 localStorageService.setItem(AUTH_STORAGE_KEY, data); | ||||
|  | ||||
|         } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Clears the stored authentication result, if any. If no authentication | ||||
|      * result is currently stored, this function has no effect. | ||||
|      */ | ||||
|     var clearAuthenticationResult = function clearAuthenticationResult() { | ||||
|         setAuthenticationResult(null); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Makes a request to authenticate a user using the token REST API endpoint | ||||
|      * and given arbitrary parameters, returning a promise that succeeds only | ||||
|      * if the authentication operation was successful. The resulting | ||||
|      * authentication data can be retrieved later via getCurrentToken() or | ||||
|      * getCurrentUsername(). | ||||
|      *  | ||||
|      * The provided parameters can be virtually any object, as each property | ||||
|      * will be sent as an HTTP parameter in the authentication request. | ||||
|      * Standard parameters include "username" for the user's username, | ||||
|      * "password" for the user's associated password, and "token" for the | ||||
|      * auth token to check/update. | ||||
|      *  | ||||
|      * If a token is provided, it will be reused if possible. | ||||
|      *  | ||||
|      * @param {Object} parameters  | ||||
|      *     Arbitrary parameters to authenticate with. | ||||
|      * | ||||
|      * @returns {Promise} | ||||
|      *     A promise which succeeds only if the login operation was successful. | ||||
|      */ | ||||
|     service.authenticate = function authenticate(parameters) { | ||||
|  | ||||
|         // Attempt authentication | ||||
|         return requestService({ | ||||
|             method: 'POST', | ||||
|             url: 'api/tokens', | ||||
|             headers: { | ||||
|                 'Content-Type': 'application/x-www-form-urlencoded' | ||||
|             }, | ||||
|             data: $.param(parameters) | ||||
|         }) | ||||
|  | ||||
|         // If authentication succeeds, handle received auth data | ||||
|         .then(function authenticationSuccessful(data) { | ||||
|  | ||||
|             var currentToken = service.getCurrentToken(); | ||||
|  | ||||
|             // If a new token was received, ensure the old token is invalidated, | ||||
|             // if any, and notify listeners of the new token | ||||
|             if (data.authToken !== currentToken) { | ||||
|  | ||||
|                 // If an old token existed, request that the token be revoked | ||||
|                 if (currentToken) { | ||||
|                     service.logout().catch(angular.noop) | ||||
|                 } | ||||
|  | ||||
|                 // Notify of login and new token | ||||
|                 setAuthenticationResult(new AuthenticationResult(data)); | ||||
|                 $rootScope.$broadcast('guacLogin', data.authToken); | ||||
|  | ||||
|             } | ||||
|  | ||||
|             // Update cached authentication result, even if the token remains | ||||
|             // the same | ||||
|             else | ||||
|                 setAuthenticationResult(new AuthenticationResult(data)); | ||||
|  | ||||
|             // Authentication was successful | ||||
|             return data; | ||||
|  | ||||
|         }) | ||||
|  | ||||
|         // If authentication fails, propogate failure to returned promise | ||||
|         ['catch'](requestService.createErrorCallback(function authenticationFailed(error) { | ||||
|  | ||||
|             // Request credentials if provided credentials were invalid | ||||
|             if (error.type === Error.Type.INVALID_CREDENTIALS) | ||||
|                 $rootScope.$broadcast('guacInvalidCredentials', parameters, error); | ||||
|  | ||||
|             // Request more credentials if provided credentials were not enough  | ||||
|             else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS) | ||||
|                 $rootScope.$broadcast('guacInsufficientCredentials', parameters, error); | ||||
|  | ||||
|             // Abort rendering of page if an internal error occurs | ||||
|             else if (error.type === Error.Type.INTERNAL_ERROR) | ||||
|                 $rootScope.$broadcast('guacFatalPageError', error); | ||||
|  | ||||
|             // Authentication failed | ||||
|             throw error; | ||||
|  | ||||
|         })); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Makes a request to update the current auth token, if any, using the | ||||
|      * token REST API endpoint. If the optional parameters object is provided, | ||||
|      * its properties will be included as parameters in the update request. | ||||
|      * This function returns a promise that succeeds only if the authentication | ||||
|      * operation was successful. The resulting authentication data can be | ||||
|      * retrieved later via getCurrentToken() or getCurrentUsername(). | ||||
|      *  | ||||
|      * If there is no current auth token, this function behaves identically to | ||||
|      * authenticate(), and makes a general authentication request. | ||||
|      *  | ||||
|      * @param {Object} [parameters] | ||||
|      *     Arbitrary parameters to authenticate with, if any. | ||||
|      * | ||||
|      * @returns {Promise} | ||||
|      *     A promise which succeeds only if the login operation was successful. | ||||
|      */ | ||||
|     service.updateCurrentToken = function updateCurrentToken(parameters) { | ||||
|  | ||||
|         // HTTP parameters for the authentication request | ||||
|         var httpParameters = {}; | ||||
|  | ||||
|         // Add token parameter if current token is known | ||||
|         var token = service.getCurrentToken(); | ||||
|         if (token) | ||||
|             httpParameters.token = service.getCurrentToken(); | ||||
|  | ||||
|         // Add any additional parameters | ||||
|         if (parameters) | ||||
|             angular.extend(httpParameters, parameters); | ||||
|  | ||||
|         // Make the request | ||||
|         return service.authenticate(httpParameters); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Makes a request to authenticate a user using the token REST API endpoint | ||||
|      * with a username and password, ignoring any currently-stored token,  | ||||
|      * returning a promise that succeeds only if the login operation was | ||||
|      * successful. The resulting authentication data can be retrieved later | ||||
|      * via getCurrentToken() or getCurrentUsername(). | ||||
|      *  | ||||
|      * @param {String} username | ||||
|      *     The username to log in with. | ||||
|      * | ||||
|      * @param {String} password | ||||
|      *     The password to log in with. | ||||
|      * | ||||
|      * @returns {Promise} | ||||
|      *     A promise which succeeds only if the login operation was successful. | ||||
|      */ | ||||
|     service.login = function login(username, password) { | ||||
|         return service.authenticate({ | ||||
|             username: username, | ||||
|             password: password | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Makes a request to logout a user using the login REST API endpoint,  | ||||
|      * returning a promise succeeds only if the logout operation was | ||||
|      * successful. | ||||
|      *  | ||||
|      * @returns {Promise} | ||||
|      *     A promise which succeeds only if the logout operation was | ||||
|      *     successful. | ||||
|      */ | ||||
|     service.logout = function logout() { | ||||
|          | ||||
|         // Clear authentication data | ||||
|         var token = service.getCurrentToken(); | ||||
|         clearAuthenticationResult(); | ||||
|  | ||||
|         // Notify listeners that a token is being destroyed | ||||
|         $rootScope.$broadcast('guacLogout', token); | ||||
|  | ||||
|         // Delete old token | ||||
|         return requestService({ | ||||
|             method: 'DELETE', | ||||
|             url: 'api/tokens/' + token | ||||
|         }); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Returns whether the current user has authenticated anonymously. An | ||||
|      * anonymous user is denoted by the identifier reserved by the Guacamole | ||||
|      * extension API for anonymous users (the empty string). | ||||
|      * | ||||
|      * @returns {Boolean} | ||||
|      *     true if the current user has authenticated anonymously, false | ||||
|      *     otherwise. | ||||
|      */ | ||||
|     service.isAnonymous = function isAnonymous() { | ||||
|         return service.getCurrentUsername() === ''; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Returns the username of the current user. If the current user is not | ||||
|      * logged in, this value may not be valid. | ||||
|      * | ||||
|      * @returns {String} | ||||
|      *     The username of the current user, or null if no authentication data | ||||
|      *     is present. | ||||
|      */ | ||||
|     service.getCurrentUsername = function getCurrentUsername() { | ||||
|  | ||||
|         // Return username, if available | ||||
|         var authData = getAuthenticationResult(); | ||||
|         if (authData) | ||||
|             return authData.username; | ||||
|  | ||||
|         // No auth data present | ||||
|         return null; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Returns the auth token associated with the current user. If the current | ||||
|      * user is not logged in, this token may not be valid. | ||||
|      * | ||||
|      * @returns {String} | ||||
|      *     The auth token associated with the current user, or null if no | ||||
|      *     authentication data is present. | ||||
|      */ | ||||
|     service.getCurrentToken = function getCurrentToken() { | ||||
|  | ||||
|         // Return auth token, if available | ||||
|         var authData = getAuthenticationResult(); | ||||
|         if (authData) | ||||
|             return authData.authToken; | ||||
|  | ||||
|         // No auth data present | ||||
|         return null; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Returns the identifier of the data source that authenticated the current | ||||
|      * user. If the current user is not logged in, this value may not be valid. | ||||
|      * | ||||
|      * @returns {String} | ||||
|      *     The identifier of the data source that authenticated the current | ||||
|      *     user, or null if no authentication data is present. | ||||
|      */ | ||||
|     service.getDataSource = function getDataSource() { | ||||
|  | ||||
|         // Return data source, if available | ||||
|         var authData = getAuthenticationResult(); | ||||
|         if (authData) | ||||
|             return authData.dataSource; | ||||
|  | ||||
|         // No auth data present | ||||
|         return null; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Returns the identifiers of all data sources available to the current | ||||
|      * user. If the current user is not logged in, this value may not be valid. | ||||
|      * | ||||
|      * @returns {String[]} | ||||
|      *     The identifiers of all data sources availble to the current user, | ||||
|      *     or an empty array if no authentication data is present. | ||||
|      */ | ||||
|     service.getAvailableDataSources = function getAvailableDataSources() { | ||||
|  | ||||
|         // Return data sources, if available | ||||
|         var authData = getAuthenticationResult(); | ||||
|         if (authData) | ||||
|             return authData.availableDataSources; | ||||
|  | ||||
|         // No auth data present | ||||
|         return []; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return service; | ||||
| }]); | ||||
| @@ -1,81 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Service which defines the AuthenticationResult class. | ||||
|  */ | ||||
| angular.module('auth').factory('AuthenticationResult', [function defineAuthenticationResult() { | ||||
|              | ||||
|     /** | ||||
|      * The object returned by REST API calls when representing the successful | ||||
|      * result of an authentication attempt. | ||||
|      *  | ||||
|      * @constructor | ||||
|      * @param {AuthenticationResult|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     AuthenticationResult. | ||||
|      */ | ||||
|     var AuthenticationResult = function AuthenticationResult(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The unique token generated for the user that authenticated. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.authToken = template.authToken; | ||||
|  | ||||
|         /** | ||||
|          * The name which uniquely identifies the user that authenticated. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.username = template.username; | ||||
|  | ||||
|         /** | ||||
|          * The unique identifier of the data source which authenticated the | ||||
|          * user. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.dataSource = template.dataSource; | ||||
|  | ||||
|         /** | ||||
|          * The identifiers of all data sources available to the user that | ||||
|          * authenticated. | ||||
|          * | ||||
|          * @type String[] | ||||
|          */ | ||||
|         this.availableDataSources = template.availableDataSources; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The username reserved by the Guacamole extension API for users which have | ||||
|      * authenticated anonymously. | ||||
|      * | ||||
|      * @type String | ||||
|      */ | ||||
|     AuthenticationResult.ANONYMOUS_USERNAME = ''; | ||||
|  | ||||
|     return AuthenticationResult; | ||||
|  | ||||
| }]); | ||||
| @@ -1,34 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * The module for code used to connect to a connection or balancing group. | ||||
|  */ | ||||
| angular.module('client', [ | ||||
|     'auth', | ||||
|     'clipboard', | ||||
|     'element', | ||||
|     'history', | ||||
|     'navigation', | ||||
|     'notification', | ||||
|     'osk', | ||||
|     'rest', | ||||
|     'textInput', | ||||
|     'touch' | ||||
| ]); | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,485 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive for the guacamole client. | ||||
|  */ | ||||
| angular.module('client').directive('guacClient', [function guacClient() { | ||||
|  | ||||
|     return { | ||||
|         // Element only | ||||
|         restrict: 'E', | ||||
|         replace: true, | ||||
|         scope: { | ||||
|  | ||||
|             /** | ||||
|              * The client to display within this guacClient directive. | ||||
|              *  | ||||
|              * @type ManagedClient | ||||
|              */ | ||||
|             client : '=' | ||||
|              | ||||
|         }, | ||||
|         templateUrl: 'app/client/templates/guacClient.html', | ||||
|         controller: ['$scope', '$injector', '$element', function guacClientController($scope, $injector, $element) { | ||||
|     | ||||
|             // Required types | ||||
|             var ManagedClient = $injector.get('ManagedClient'); | ||||
|                  | ||||
|             // Required services | ||||
|             var $window = $injector.get('$window'); | ||||
|                  | ||||
|             /** | ||||
|              * Whether the local, hardware mouse cursor is in use. | ||||
|              *  | ||||
|              * @type Boolean | ||||
|              */ | ||||
|             var localCursor = false; | ||||
|  | ||||
|             /** | ||||
|              * The current Guacamole client instance. | ||||
|              *  | ||||
|              * @type Guacamole.Client  | ||||
|              */ | ||||
|             var client = null; | ||||
|  | ||||
|             /** | ||||
|              * The display of the current Guacamole client instance. | ||||
|              *  | ||||
|              * @type Guacamole.Display | ||||
|              */ | ||||
|             var display = null; | ||||
|  | ||||
|             /** | ||||
|              * The element associated with the display of the current | ||||
|              * Guacamole client instance. | ||||
|              * | ||||
|              * @type Element | ||||
|              */ | ||||
|             var displayElement = null; | ||||
|  | ||||
|             /** | ||||
|              * The element which must contain the Guacamole display element. | ||||
|              * | ||||
|              * @type Element | ||||
|              */ | ||||
|             var displayContainer = $element.find('.display')[0]; | ||||
|  | ||||
|             /** | ||||
|              * The main containing element for the entire directive. | ||||
|              *  | ||||
|              * @type Element | ||||
|              */ | ||||
|             var main = $element[0]; | ||||
|  | ||||
|             /** | ||||
|              * The element which functions as a detector for size changes. | ||||
|              *  | ||||
|              * @type Element | ||||
|              */ | ||||
|             var resizeSensor = $element.find('.resize-sensor')[0]; | ||||
|  | ||||
|             /** | ||||
|              * Guacamole mouse event object, wrapped around the main client | ||||
|              * display. | ||||
|              * | ||||
|              * @type Guacamole.Mouse | ||||
|              */ | ||||
|             var mouse = new Guacamole.Mouse(displayContainer); | ||||
|  | ||||
|             /** | ||||
|              * Guacamole absolute mouse emulation object, wrapped around the | ||||
|              * main client display. | ||||
|              * | ||||
|              * @type Guacamole.Mouse.Touchscreen | ||||
|              */ | ||||
|             var touchScreen = new Guacamole.Mouse.Touchscreen(displayContainer); | ||||
|  | ||||
|             /** | ||||
|              * Guacamole relative mouse emulation object, wrapped around the | ||||
|              * main client display. | ||||
|              * | ||||
|              * @type Guacamole.Mouse.Touchpad | ||||
|              */ | ||||
|             var touchPad = new Guacamole.Mouse.Touchpad(displayContainer); | ||||
|  | ||||
|             /** | ||||
|              * Guacamole touch event handling object, wrapped around the main | ||||
|              * client dislay. | ||||
|              * | ||||
|              * @type Guacamole.Touch | ||||
|              */ | ||||
|             var touch = new Guacamole.Touch(displayContainer); | ||||
|  | ||||
|             /** | ||||
|              * Updates the scale of the attached Guacamole.Client based on current window | ||||
|              * size and "auto-fit" setting. | ||||
|              */ | ||||
|             var updateDisplayScale = function updateDisplayScale() { | ||||
|  | ||||
|                 if (!display) return; | ||||
|  | ||||
|                 // Calculate scale to fit screen | ||||
|                 $scope.client.clientProperties.minScale = Math.min( | ||||
|                     main.offsetWidth  / Math.max(display.getWidth(),  1), | ||||
|                     main.offsetHeight / Math.max(display.getHeight(), 1) | ||||
|                 ); | ||||
|  | ||||
|                 // Calculate appropriate maximum zoom level | ||||
|                 $scope.client.clientProperties.maxScale = Math.max($scope.client.clientProperties.minScale, 3); | ||||
|  | ||||
|                 // Clamp zoom level, maintain auto-fit | ||||
|                 if (display.getScale() < $scope.client.clientProperties.minScale || $scope.client.clientProperties.autoFit) | ||||
|                     $scope.client.clientProperties.scale = $scope.client.clientProperties.minScale; | ||||
|  | ||||
|                 else if (display.getScale() > $scope.client.clientProperties.maxScale) | ||||
|                     $scope.client.clientProperties.scale = $scope.client.clientProperties.maxScale; | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Scrolls the client view such that the mouse cursor is visible. | ||||
|              * | ||||
|              * @param {Guacamole.Mouse.State} mouseState The current mouse | ||||
|              *                                           state. | ||||
|              */ | ||||
|             var scrollToMouse = function scrollToMouse(mouseState) { | ||||
|  | ||||
|                 // Determine mouse position within view | ||||
|                 var mouse_view_x = mouseState.x + displayContainer.offsetLeft - main.scrollLeft; | ||||
|                 var mouse_view_y = mouseState.y + displayContainer.offsetTop  - main.scrollTop; | ||||
|  | ||||
|                 // Determine viewport dimensions | ||||
|                 var view_width  = main.offsetWidth; | ||||
|                 var view_height = main.offsetHeight; | ||||
|  | ||||
|                 // Determine scroll amounts based on mouse position relative to document | ||||
|  | ||||
|                 var scroll_amount_x; | ||||
|                 if (mouse_view_x > view_width) | ||||
|                     scroll_amount_x = mouse_view_x - view_width; | ||||
|                 else if (mouse_view_x < 0) | ||||
|                     scroll_amount_x = mouse_view_x; | ||||
|                 else | ||||
|                     scroll_amount_x = 0; | ||||
|  | ||||
|                 var scroll_amount_y; | ||||
|                 if (mouse_view_y > view_height) | ||||
|                     scroll_amount_y = mouse_view_y - view_height; | ||||
|                 else if (mouse_view_y < 0) | ||||
|                     scroll_amount_y = mouse_view_y; | ||||
|                 else | ||||
|                     scroll_amount_y = 0; | ||||
|  | ||||
|                 // Scroll (if necessary) to keep mouse on screen. | ||||
|                 main.scrollLeft += scroll_amount_x; | ||||
|                 main.scrollTop  += scroll_amount_y; | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Handles a mouse event originating from the user's actual mouse. | ||||
|              * This differs from handleEmulatedMouseEvent() in that the | ||||
|              * software mouse cursor must be shown only if the user's browser | ||||
|              * does not support explicitly setting the hardware mouse cursor. | ||||
|              * | ||||
|              * @param {Guacamole.Mouse.MouseEvent} event | ||||
|              *     The mouse event to handle. | ||||
|              */ | ||||
|             var handleMouseEvent = function handleMouseEvent(event) { | ||||
|  | ||||
|                 // Do not attempt to handle mouse state changes if the client | ||||
|                 // or display are not yet available | ||||
|                 if (!client || !display) | ||||
|                     return; | ||||
|  | ||||
|                 event.stopPropagation(); | ||||
|                 event.preventDefault(); | ||||
|  | ||||
|                 // Send mouse state, show cursor if necessary | ||||
|                 display.showCursor(!localCursor); | ||||
|                 client.sendMouseState(event.state, true); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Handles a mouse event originating from one of Guacamole's mouse | ||||
|              * emulation objects. This differs from handleMouseState() in that | ||||
|              * the software mouse cursor must always be shown (as the emulated | ||||
|              * mouse device will not have its own cursor). | ||||
|              * | ||||
|              * @param {Guacamole.Mouse.MouseEvent} event | ||||
|              *     The mouse event to handle. | ||||
|              */ | ||||
|             var handleEmulatedMouseEvent = function handleEmulatedMouseEvent(event) { | ||||
|  | ||||
|                 // Do not attempt to handle mouse state changes if the client | ||||
|                 // or display are not yet available | ||||
|                 if (!client || !display) | ||||
|                     return; | ||||
|  | ||||
|                 event.stopPropagation(); | ||||
|                 event.preventDefault(); | ||||
|  | ||||
|                 // Ensure software cursor is shown | ||||
|                 display.showCursor(true); | ||||
|  | ||||
|                 // Send mouse state, ensure cursor is visible | ||||
|                 scrollToMouse(event.state); | ||||
|                 client.sendMouseState(event.state, true); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Handles a touch event originating from the user's device. | ||||
|              * | ||||
|              * @param {Guacamole.Touch.Event} touchEvent | ||||
|              *     The touch event. | ||||
|              */ | ||||
|             var handleTouchEvent = function handleTouchEvent(event) { | ||||
|  | ||||
|                 // Do not attempt to handle touch state changes if the client | ||||
|                 // or display are not yet available | ||||
|                 if (!client || !display) | ||||
|                     return; | ||||
|  | ||||
|                 event.preventDefault(); | ||||
|  | ||||
|                 // Send touch state, hiding local cursor | ||||
|                 display.showCursor(false); | ||||
|                 client.sendTouchState(event.state, true); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             // Attach any given managed client | ||||
|             $scope.$watch('client', function attachManagedClient(managedClient) { | ||||
|  | ||||
|                 // Remove any existing display | ||||
|                 displayContainer.innerHTML = ""; | ||||
|  | ||||
|                 // Only proceed if a client is given  | ||||
|                 if (!managedClient) | ||||
|                     return; | ||||
|  | ||||
|                 // Get Guacamole client instance | ||||
|                 client = managedClient.client; | ||||
|  | ||||
|                 // Attach possibly new display | ||||
|                 display = client.getDisplay(); | ||||
|                 display.scale($scope.client.clientProperties.scale); | ||||
|  | ||||
|                 // Add display element | ||||
|                 displayElement = display.getElement(); | ||||
|                 displayContainer.appendChild(displayElement); | ||||
|  | ||||
|                 // Do nothing when the display element is clicked on | ||||
|                 display.getElement().onclick = function(e) { | ||||
|                     e.preventDefault(); | ||||
|                     return false; | ||||
|                 }; | ||||
|  | ||||
|                 // Size of newly-attached client may be different | ||||
|                 $scope.mainElementResized(); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             // Update actual view scrollLeft when scroll properties change | ||||
|             $scope.$watch('client.clientProperties.scrollLeft', function scrollLeftChanged(scrollLeft) { | ||||
|                 main.scrollLeft = scrollLeft; | ||||
|                 $scope.client.clientProperties.scrollLeft = main.scrollLeft; | ||||
|             }); | ||||
|  | ||||
|             // Update actual view scrollTop when scroll properties change | ||||
|             $scope.$watch('client.clientProperties.scrollTop', function scrollTopChanged(scrollTop) { | ||||
|                 main.scrollTop = scrollTop; | ||||
|                 $scope.client.clientProperties.scrollTop = main.scrollTop; | ||||
|             }); | ||||
|  | ||||
|             // Update scale when display is resized | ||||
|             $scope.$watch('client.managedDisplay.size', function setDisplaySize() { | ||||
|                 $scope.$evalAsync(updateDisplayScale); | ||||
|             }); | ||||
|  | ||||
|             // Keep local cursor up-to-date | ||||
|             $scope.$watch('client.managedDisplay.cursor', function setCursor(cursor) { | ||||
|                 if (cursor) | ||||
|                     localCursor = mouse.setCursor(cursor.canvas, cursor.x, cursor.y); | ||||
|             }); | ||||
|  | ||||
|             // Update touch event handling depending on remote multi-touch | ||||
|             // support and mouse emulation mode | ||||
|             $scope.$watchGroup([ | ||||
|                     'client.multiTouchSupport', | ||||
|                     'client.clientProperties.emulateAbsoluteMouse' | ||||
|                 ], function touchBehaviorChanged(emulateAbsoluteMouse) { | ||||
|  | ||||
|                 // Clear existing event handling | ||||
|                 touch.offEach(['touchstart', 'touchmove', 'touchend'], handleTouchEvent); | ||||
|                 touchScreen.offEach(['mousedown', 'mousemove', 'mouseup'], handleEmulatedMouseEvent); | ||||
|                 touchPad.offEach(['mousedown', 'mousemove', 'mouseup'], handleEmulatedMouseEvent); | ||||
|  | ||||
|                 // Directly forward local touch events | ||||
|                 if ($scope.client.multiTouchSupport) | ||||
|                     touch.onEach(['touchstart', 'touchmove', 'touchend'], handleTouchEvent); | ||||
|  | ||||
|                 // Switch to touchscreen if mouse emulation is required and | ||||
|                 // absolute mouse emulation is preferred | ||||
|                 else if ($scope.client.clientProperties.emulateAbsoluteMouse) | ||||
|                     touchScreen.onEach(['mousedown', 'mousemove', 'mouseup'], handleEmulatedMouseEvent); | ||||
|  | ||||
|                 // Use touchpad for mouse emulation if absolute mouse emulation | ||||
|                 // is not preferred | ||||
|                 else | ||||
|                     touchPad.onEach(['mousedown', 'mousemove', 'mouseup'], handleEmulatedMouseEvent); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             // Adjust scale if modified externally | ||||
|             $scope.$watch('client.clientProperties.scale', function changeScale(scale) { | ||||
|  | ||||
|                 // Fix scale within limits | ||||
|                 scale = Math.max(scale, $scope.client.clientProperties.minScale); | ||||
|                 scale = Math.min(scale, $scope.client.clientProperties.maxScale); | ||||
|  | ||||
|                 // If at minimum zoom level, hide scroll bars | ||||
|                 if (scale === $scope.client.clientProperties.minScale) | ||||
|                     main.style.overflow = "hidden"; | ||||
|  | ||||
|                 // If not at minimum zoom level, show scroll bars | ||||
|                 else | ||||
|                     main.style.overflow = "auto"; | ||||
|  | ||||
|                 // Apply scale if client attached | ||||
|                 if (display) | ||||
|                     display.scale(scale); | ||||
|                  | ||||
|                 if (scale !== $scope.client.clientProperties.scale) | ||||
|                     $scope.client.clientProperties.scale = scale; | ||||
|  | ||||
|             }); | ||||
|              | ||||
|             // If autofit is set, the scale should be set to the minimum scale, filling the screen | ||||
|             $scope.$watch('client.clientProperties.autoFit', function changeAutoFit(autoFit) { | ||||
|                 if(autoFit) | ||||
|                     $scope.client.clientProperties.scale = $scope.client.clientProperties.minScale; | ||||
|             }); | ||||
|              | ||||
|             // If the element is resized, attempt to resize client | ||||
|             $scope.mainElementResized = function mainElementResized() { | ||||
|  | ||||
|                 // Send new display size, if changed | ||||
|                 if (client && display) { | ||||
|  | ||||
|                     var pixelDensity = $window.devicePixelRatio || 1; | ||||
|                     var width  = main.offsetWidth  * pixelDensity; | ||||
|                     var height = main.offsetHeight * pixelDensity; | ||||
|  | ||||
|                     if (display.getWidth() !== width || display.getHeight() !== height) | ||||
|                         client.sendSize(width, height); | ||||
|  | ||||
|                 } | ||||
|  | ||||
|                 $scope.$evalAsync(updateDisplayScale); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             // Ensure focus is regained via mousedown before forwarding event | ||||
|             mouse.on('mousedown', document.body.focus.bind(document.body)); | ||||
|  | ||||
|             // Forward all mouse events | ||||
|             mouse.onEach(['mousedown', 'mousemove', 'mouseup'], handleMouseEvent); | ||||
|  | ||||
|             // Hide software cursor when mouse leaves display | ||||
|             mouse.on('mouseout', function() { | ||||
|                 if (!display) return; | ||||
|                 display.showCursor(false); | ||||
|             }); | ||||
|  | ||||
|             // Update remote clipboard if local clipboard changes | ||||
|             $scope.$on('guacClipboard', function onClipboard(event, data) { | ||||
|                 if (client) { | ||||
|                     ManagedClient.setClipboard($scope.client, data); | ||||
|                     $scope.client.clipboardData = data; | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             // Translate local keydown events to remote keydown events if keyboard is enabled | ||||
|             $scope.$on('guacKeydown', function keydownListener(event, keysym, keyboard) { | ||||
|                 if ($scope.client.clientProperties.keyboardEnabled && !event.defaultPrevented) { | ||||
|                     client.sendKeyEvent(1, keysym); | ||||
|                     event.preventDefault(); | ||||
|                 } | ||||
|             }); | ||||
|              | ||||
|             // Translate local keyup events to remote keyup events if keyboard is enabled | ||||
|             $scope.$on('guacKeyup', function keyupListener(event, keysym, keyboard) { | ||||
|                 if ($scope.client.clientProperties.keyboardEnabled && !event.defaultPrevented) { | ||||
|                     client.sendKeyEvent(0, keysym); | ||||
|                     event.preventDefault(); | ||||
|                 }    | ||||
|             }); | ||||
|  | ||||
|             // Universally handle all synthetic keydown events | ||||
|             $scope.$on('guacSyntheticKeydown', function syntheticKeydownListener(event, keysym) { | ||||
|                 client.sendKeyEvent(1, keysym); | ||||
|             }); | ||||
|              | ||||
|             // Universally handle all synthetic keyup events | ||||
|             $scope.$on('guacSyntheticKeyup', function syntheticKeyupListener(event, keysym) { | ||||
|                 client.sendKeyEvent(0, keysym); | ||||
|             }); | ||||
|              | ||||
|             /** | ||||
|              * Ignores the given event. | ||||
|              *  | ||||
|              * @param {Event} e The event to ignore. | ||||
|              */ | ||||
|             function ignoreEvent(e) { | ||||
|                e.preventDefault(); | ||||
|                e.stopPropagation(); | ||||
|             } | ||||
|  | ||||
|             // Handle and ignore dragenter/dragover | ||||
|             displayContainer.addEventListener("dragenter", ignoreEvent, false); | ||||
|             displayContainer.addEventListener("dragover",  ignoreEvent, false); | ||||
|  | ||||
|             // File drop event handler | ||||
|             displayContainer.addEventListener("drop", function(e) { | ||||
|  | ||||
|                 e.preventDefault(); | ||||
|                 e.stopPropagation(); | ||||
|  | ||||
|                 // Ignore file drops if no attached client | ||||
|                 if (!$scope.client) | ||||
|                     return; | ||||
|  | ||||
|                 // Upload each file  | ||||
|                 var files = e.dataTransfer.files; | ||||
|                 for (var i=0; i<files.length; i++) | ||||
|                     ManagedClient.uploadFile($scope.client, files[i]); | ||||
|  | ||||
|             }, false); | ||||
|  | ||||
|             /* | ||||
|              * END CLIENT DIRECTIVE                                            | ||||
|              */ | ||||
|                  | ||||
|         }] | ||||
|     }; | ||||
| }]); | ||||
| @@ -1,170 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A toolbar/panel which displays a list of active Guacamole connections. The | ||||
|  * panel is fixed to the bottom-right corner of its container and can be | ||||
|  * manually hidden/exposed by the user. | ||||
|  */ | ||||
| angular.module('client').directive('guacClientPanel', ['$injector', function guacClientPanel($injector) { | ||||
|  | ||||
|     // Required services | ||||
|     var guacClientManager     = $injector.get('guacClientManager'); | ||||
|     var sessionStorageFactory = $injector.get('sessionStorageFactory'); | ||||
|  | ||||
|     // Required types | ||||
|     var ManagedClientState = $injector.get('ManagedClientState'); | ||||
|  | ||||
|     /** | ||||
|      * Getter/setter for the boolean flag controlling whether the client panel | ||||
|      * is currently hidden. This flag is maintained in session-local storage to | ||||
|      * allow the state of the panel to persist despite navigation within the | ||||
|      * same tab. When hidden, the panel will be collapsed against the right | ||||
|      * side of the container. By default, the panel is visible. | ||||
|      * | ||||
|      * @type Function | ||||
|      */ | ||||
|     var panelHidden = sessionStorageFactory.create(false); | ||||
|  | ||||
|     return { | ||||
|         // Element only | ||||
|         restrict: 'E', | ||||
|         replace: true, | ||||
|         scope: { | ||||
|  | ||||
|             /** | ||||
|              * The ManagedClient instances associated with the active | ||||
|              * connections to be displayed within this panel. | ||||
|              *  | ||||
|              * @type ManagedClient[]|Object.<String, ManagedClient> | ||||
|              */ | ||||
|             clients : '=' | ||||
|  | ||||
|         }, | ||||
|         templateUrl: 'app/client/templates/guacClientPanel.html', | ||||
|         controller: ['$scope', '$element', function guacClientPanelController($scope, $element) { | ||||
|  | ||||
|             /** | ||||
|              * The DOM element containing the scrollable portion of the client | ||||
|              * panel. | ||||
|              * | ||||
|              * @type Element | ||||
|              */ | ||||
|             var scrollableArea = $element.find('.client-panel-connection-list')[0]; | ||||
|  | ||||
|             /** | ||||
|              * On-scope reference to session-local storage of the flag | ||||
|              * controlling whether then panel is hidden. | ||||
|              */ | ||||
|             $scope.panelHidden = panelHidden; | ||||
|  | ||||
|             /** | ||||
|              * Returns whether this panel currently has any clients associated | ||||
|              * with it. | ||||
|              * | ||||
|              * @return {Boolean} | ||||
|              *     true if at least one client is associated with this panel, | ||||
|              *     false otherwise. | ||||
|              */ | ||||
|             $scope.hasClients = function hasClients() { | ||||
|                 return !!_.find($scope.clients, $scope.isManaged); | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns whether the status of the given client has changed in a | ||||
|              * way that requires the user's attention. This may be due to an | ||||
|              * error, or due to a server-initiated disconnect. | ||||
|              * | ||||
|              * @param {ManagedClient} client | ||||
|              *     The client to test. | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if the given client requires the user's attention, | ||||
|              *     false otherwise. | ||||
|              */ | ||||
|             $scope.hasStatusUpdate = function hasStatusUpdate(client) { | ||||
|  | ||||
|                 // Test whether the client has encountered an error | ||||
|                 switch (client.clientState.connectionState) { | ||||
|                     case ManagedClientState.ConnectionState.CONNECTION_ERROR: | ||||
|                     case ManagedClientState.ConnectionState.TUNNEL_ERROR: | ||||
|                     case ManagedClientState.ConnectionState.DISCONNECTED: | ||||
|                         return true; | ||||
|                 } | ||||
|  | ||||
|                 return false; | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns whether the given client is currently being managed by | ||||
|              * the guacClientManager service. | ||||
|              * | ||||
|              * @param {ManagedClient} client | ||||
|              *     The client to test. | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if the given client is being managed by the | ||||
|              *     guacClientManager service, false otherwise. | ||||
|              */ | ||||
|             $scope.isManaged = function isManaged(client) { | ||||
|                 return !!guacClientManager.getManagedClients()[client.id]; | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Initiates an orderly disconnect of the given client. The client | ||||
|              * is removed from management such that attempting to connect to | ||||
|              * the same connection will result in a new connection being | ||||
|              * established, rather than displaying a notification that the | ||||
|              * connection has ended. | ||||
|              * | ||||
|              * @param {type} client | ||||
|              * @returns {undefined} | ||||
|              */ | ||||
|             $scope.disconnect = function disconnect(client) { | ||||
|                 client.client.disconnect(); | ||||
|                 guacClientManager.removeManagedClient(client.id); | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Toggles whether the client panel is currently hidden. | ||||
|              */ | ||||
|             $scope.togglePanel = function togglePanel() { | ||||
|                 panelHidden(!panelHidden()); | ||||
|             }; | ||||
|  | ||||
|             // Override vertical scrolling, scrolling horizontally instead | ||||
|             scrollableArea.addEventListener('wheel', function reorientVerticalScroll(e) { | ||||
|  | ||||
|                 var deltaMultiplier = { | ||||
|                     /* DOM_DELTA_PIXEL */ 0x00: 1, | ||||
|                     /* DOM_DELTA_LINE  */ 0x01: 15, | ||||
|                     /* DOM_DELTA_PAGE  */ 0x02: scrollableArea.offsetWidth | ||||
|                 }; | ||||
|  | ||||
|                 if (e.deltaY) { | ||||
|                     this.scrollLeft += e.deltaY * (deltaMultiplier[e.deltaMode] || deltaMultiplier(0x01)); | ||||
|                     e.preventDefault(); | ||||
|                 } | ||||
|  | ||||
|             }); | ||||
|  | ||||
|         }] | ||||
|     }; | ||||
| }]); | ||||
| @@ -1,289 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive which displays the contents of a filesystem received through the | ||||
|  * Guacamole client. | ||||
|  */ | ||||
| angular.module('client').directive('guacFileBrowser', [function guacFileBrowser() { | ||||
|  | ||||
|     return { | ||||
|         restrict: 'E', | ||||
|         replace: true, | ||||
|         scope: { | ||||
|  | ||||
|             /** | ||||
|              * The client whose file transfers should be managed by this | ||||
|              * directive. | ||||
|              * | ||||
|              * @type ManagedClient | ||||
|              */ | ||||
|             client : '=', | ||||
|  | ||||
|             /** | ||||
|              * @type ManagedFilesystem | ||||
|              */ | ||||
|             filesystem : '=' | ||||
|  | ||||
|         }, | ||||
|  | ||||
|         templateUrl: 'app/client/templates/guacFileBrowser.html', | ||||
|         controller: ['$scope', '$element', '$injector', function guacFileBrowserController($scope, $element, $injector) { | ||||
|  | ||||
|             // Required types | ||||
|             var ManagedFilesystem = $injector.get('ManagedFilesystem'); | ||||
|  | ||||
|             // Required services | ||||
|             var $interpolate     = $injector.get('$interpolate'); | ||||
|             var $templateRequest = $injector.get('$templateRequest'); | ||||
|  | ||||
|             /** | ||||
|              * The jQuery-wrapped element representing the contents of the | ||||
|              * current directory within the file browser. | ||||
|              * | ||||
|              * @type Element[] | ||||
|              */ | ||||
|             var currentDirectoryContents = $element.find('.current-directory-contents'); | ||||
|  | ||||
|             /** | ||||
|              * Statically-cached template HTML used to render each file within | ||||
|              * a directory. Once available, this will be used through | ||||
|              * createFileElement() to generate the DOM elements which make up | ||||
|              * a directory listing. | ||||
|              * | ||||
|              * @type String | ||||
|              */ | ||||
|             var fileTemplate = null; | ||||
|  | ||||
|             /** | ||||
|              * Returns whether the given file is a normal file. | ||||
|              * | ||||
|              * @param {ManagedFilesystem.File} file | ||||
|              *     The file to test. | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if the given file is a normal file, false otherwise. | ||||
|              */ | ||||
|             $scope.isNormalFile = function isNormalFile(file) { | ||||
|                 return file.type === ManagedFilesystem.File.Type.NORMAL; | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns whether the given file is a directory. | ||||
|              * | ||||
|              * @param {ManagedFilesystem.File} file | ||||
|              *     The file to test. | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if the given file is a directory, false otherwise. | ||||
|              */ | ||||
|             $scope.isDirectory = function isDirectory(file) { | ||||
|                 return file.type === ManagedFilesystem.File.Type.DIRECTORY; | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Changes the currently-displayed directory to the given | ||||
|              * directory. | ||||
|              * | ||||
|              * @param {ManagedFilesystem.File} file | ||||
|              *     The directory to change to. | ||||
|              */ | ||||
|             $scope.changeDirectory = function changeDirectory(file) { | ||||
|                 ManagedFilesystem.changeDirectory($scope.filesystem, file); | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Initiates a download of the given file. The progress of the | ||||
|              * download can be observed through guacFileTransferManager. | ||||
|              * | ||||
|              * @param {ManagedFilesystem.File} file | ||||
|              *     The file to download. | ||||
|              */ | ||||
|             $scope.downloadFile = function downloadFile(file) { | ||||
|                 ManagedFilesystem.downloadFile($scope.client, $scope.filesystem, file.streamName); | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Recursively interpolates all text nodes within the DOM tree of | ||||
|              * the given element. All other node types, attributes, etc. will | ||||
|              * be left uninterpolated. | ||||
|              * | ||||
|              * @param {Element} element | ||||
|              *     The element at the root of the DOM tree to be interpolated. | ||||
|              * | ||||
|              * @param {Object} context | ||||
|              *     The evaluation context to use when evaluating expressions | ||||
|              *     embedded in text nodes within the provided element. | ||||
|              */ | ||||
|             var interpolateElement = function interpolateElement(element, context) { | ||||
|  | ||||
|                 // Interpolate the contents of text nodes directly | ||||
|                 if (element.nodeType === Node.TEXT_NODE) | ||||
|                     element.nodeValue = $interpolate(element.nodeValue)(context); | ||||
|  | ||||
|                 // Recursively interpolate the contents of all descendant text | ||||
|                 // nodes | ||||
|                 if (element.hasChildNodes()) { | ||||
|                     var children = element.childNodes; | ||||
|                     for (var i = 0; i < children.length; i++) | ||||
|                         interpolateElement(children[i], context); | ||||
|                 } | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Creates a new element representing the given file and properly | ||||
|              * handling user events, bypassing the overhead incurred through | ||||
|              * use of ngRepeat and related techniques. | ||||
|              * | ||||
|              * Note that this function depends on the availability of the | ||||
|              * statically-cached fileTemplate. | ||||
|              * | ||||
|              * @param {ManagedFilesystem.File} file | ||||
|              *     The file to generate an element for. | ||||
|              * | ||||
|              * @returns {Element[]} | ||||
|              *     A jQuery-wrapped array containing a single DOM element | ||||
|              *     representing the given file. | ||||
|              */ | ||||
|             var createFileElement = function createFileElement(file) { | ||||
|  | ||||
|                 // Create from internal template | ||||
|                 var element = angular.element(fileTemplate); | ||||
|                 interpolateElement(element[0], file); | ||||
|  | ||||
|                 // Double-clicking on unknown file types will do nothing | ||||
|                 var fileAction = function doNothing() {}; | ||||
|  | ||||
|                 // Change current directory when directories are clicked | ||||
|                 if ($scope.isDirectory(file)) { | ||||
|                     element.addClass('directory'); | ||||
|                     fileAction = function changeDirectory() { | ||||
|                         $scope.changeDirectory(file); | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 // Initiate downloads when normal files are clicked | ||||
|                 else if ($scope.isNormalFile(file)) { | ||||
|                     element.addClass('normal-file'); | ||||
|                     fileAction = function downloadFile() { | ||||
|                         $scope.downloadFile(file); | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 // Mark file as focused upon click | ||||
|                 element.on('click', function handleFileClick() { | ||||
|  | ||||
|                     // Fire file-specific action if already focused | ||||
|                     if (element.hasClass('focused')) { | ||||
|                         fileAction(); | ||||
|                         element.removeClass('focused'); | ||||
|                     } | ||||
|  | ||||
|                     // Otherwise mark as focused | ||||
|                     else { | ||||
|                         element.parent().children().removeClass('focused'); | ||||
|                         element.addClass('focused'); | ||||
|                     } | ||||
|  | ||||
|                 }); | ||||
|  | ||||
|                 // Prevent text selection during navigation | ||||
|                 element.on('selectstart', function avoidSelect(e) { | ||||
|                     e.preventDefault(); | ||||
|                     e.stopPropagation(); | ||||
|                 }); | ||||
|  | ||||
|                 return element; | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Sorts the given map of files, returning an array of those files | ||||
|              * grouped by file type (directories first, followed by non- | ||||
|              * directories) and sorted lexicographically. | ||||
|              * | ||||
|              * @param {Object.<String, ManagedFilesystem.File>} files | ||||
|              *     The map of files to sort. | ||||
|              * | ||||
|              * @returns {ManagedFilesystem.File[]} | ||||
|              *     An array of all files in the given map, sorted | ||||
|              *     lexicographically with directories first, followed by non- | ||||
|              *     directories. | ||||
|              */ | ||||
|             var sortFiles = function sortFiles(files) { | ||||
|  | ||||
|                 // Get all given files as an array | ||||
|                 var unsortedFiles = []; | ||||
|                 for (var name in files) | ||||
|                     unsortedFiles.push(files[name]); | ||||
|  | ||||
|                 // Sort files - directories first, followed by all other files | ||||
|                 // sorted by name | ||||
|                 return unsortedFiles.sort(function fileComparator(a, b) { | ||||
|  | ||||
|                     // Directories come before non-directories | ||||
|                     if ($scope.isDirectory(a) && !$scope.isDirectory(b)) | ||||
|                         return -1; | ||||
|  | ||||
|                     // Non-directories come after directories | ||||
|                     if (!$scope.isDirectory(a) && $scope.isDirectory(b)) | ||||
|                         return 1; | ||||
|  | ||||
|                     // All other combinations are sorted by name | ||||
|                     return a.name.localeCompare(b.name); | ||||
|  | ||||
|                 }); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             // Watch directory contents once file template is available | ||||
|             $templateRequest('app/client/templates/file.html').then(function fileTemplateRetrieved(html) { | ||||
|  | ||||
|                 // Store file template statically | ||||
|                 fileTemplate = html; | ||||
|  | ||||
|                 // Update the contents of the file browser whenever the current directory (or its contents) changes | ||||
|                 $scope.$watch('filesystem.currentDirectory.files', function currentDirectoryChanged(files) { | ||||
|  | ||||
|                     // Clear current content | ||||
|                     currentDirectoryContents.html(''); | ||||
|  | ||||
|                     // Display all files within current directory, sorted | ||||
|                     angular.forEach(sortFiles(files), function displayFile(file) { | ||||
|                         currentDirectoryContents.append(createFileElement(file)); | ||||
|                     }); | ||||
|  | ||||
|                 }); | ||||
|  | ||||
|             }, angular.noop); // end retrieve file template | ||||
|  | ||||
|             // Refresh file browser when any upload completes | ||||
|             $scope.$on('guacUploadComplete', function uploadComplete(event, filename) { | ||||
|  | ||||
|                 // Refresh filesystem, if it exists | ||||
|                 if ($scope.filesystem) | ||||
|                     ManagedFilesystem.refresh($scope.filesystem, $scope.filesystem.currentDirectory); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|         }] | ||||
|  | ||||
|     }; | ||||
| }]); | ||||
| @@ -1,234 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Directive which displays an active file transfer, providing links for | ||||
|  * downloads, if applicable. | ||||
|  */ | ||||
| angular.module('client').directive('guacFileTransfer', [function guacFileTransfer() { | ||||
|  | ||||
|     return { | ||||
|         restrict: 'E', | ||||
|         replace: true, | ||||
|         scope: { | ||||
|  | ||||
|             /** | ||||
|              * The file transfer to display. | ||||
|              *  | ||||
|              * @type ManagedFileUpload|ManagedFileDownload | ||||
|              */ | ||||
|             transfer : '=' | ||||
|  | ||||
|         }, | ||||
|  | ||||
|         templateUrl: 'app/client/templates/guacFileTransfer.html', | ||||
|         controller: ['$scope', '$injector', function guacFileTransferController($scope, $injector) { | ||||
|  | ||||
|             // Required types | ||||
|             var ManagedFileTransferState = $injector.get('ManagedFileTransferState'); | ||||
|  | ||||
|             /** | ||||
|              * All upload error codes handled and passed off for translation. | ||||
|              * Any error code not present in this list will be represented by | ||||
|              * the "DEFAULT" translation. | ||||
|              */ | ||||
|             var UPLOAD_ERRORS = { | ||||
|                 0x0100: true, | ||||
|                 0x0201: true, | ||||
|                 0x0202: true, | ||||
|                 0x0203: true, | ||||
|                 0x0204: true, | ||||
|                 0x0205: true, | ||||
|                 0x0301: true, | ||||
|                 0x0303: true, | ||||
|                 0x0308: true, | ||||
|                 0x031D: true | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns the unit string that is most appropriate for the | ||||
|              * number of bytes transferred thus far - either 'gb', 'mb', 'kb', | ||||
|              * or 'b'. | ||||
|              * | ||||
|              * @returns {String} | ||||
|              *     The unit string that is most appropriate for the number of | ||||
|              *     bytes transferred thus far. | ||||
|              */ | ||||
|             $scope.getProgressUnit = function getProgressUnit() { | ||||
|  | ||||
|                 var bytes = $scope.transfer.progress; | ||||
|  | ||||
|                 // Gigabytes | ||||
|                 if (bytes > 1000000000) | ||||
|                     return 'gb'; | ||||
|  | ||||
|                 // Megabytes | ||||
|                 if (bytes > 1000000) | ||||
|                     return 'mb'; | ||||
|  | ||||
|                 // Kilobytes | ||||
|                 if (bytes > 1000) | ||||
|                     return 'kb'; | ||||
|  | ||||
|                 // Bytes | ||||
|                 return 'b'; | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns the amount of data transferred thus far, in the units | ||||
|              * returned by getProgressUnit(). | ||||
|              * | ||||
|              * @returns {Number} | ||||
|              *     The amount of data transferred thus far, in the units | ||||
|              *     returned by getProgressUnit(). | ||||
|              */ | ||||
|             $scope.getProgressValue = function getProgressValue() { | ||||
|  | ||||
|                 var bytes = $scope.transfer.progress; | ||||
|                 if (!bytes) | ||||
|                     return bytes; | ||||
|  | ||||
|                 // Convert bytes to necessary units | ||||
|                 switch ($scope.getProgressUnit()) { | ||||
|  | ||||
|                     // Gigabytes | ||||
|                     case 'gb': | ||||
|                         return (bytes / 1000000000).toFixed(1); | ||||
|  | ||||
|                     // Megabytes | ||||
|                     case 'mb': | ||||
|                         return (bytes / 1000000).toFixed(1); | ||||
|  | ||||
|                     // Kilobytes | ||||
|                     case 'kb': | ||||
|                         return (bytes / 1000).toFixed(1); | ||||
|  | ||||
|                     // Bytes | ||||
|                     case 'b': | ||||
|                     default: | ||||
|                         return bytes; | ||||
|  | ||||
|                 } | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns the percentage of bytes transferred thus far, if the | ||||
|              * overall length of the file is known. | ||||
|              * | ||||
|              * @returns {Number} | ||||
|              *     The percentage of bytes transferred thus far, if the | ||||
|              *     overall length of the file is known. | ||||
|              */ | ||||
|             $scope.getPercentDone = function getPercentDone() { | ||||
|                 return $scope.transfer.progress / $scope.transfer.length * 100; | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Determines whether the associated file transfer is in progress. | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if the file transfer is in progress, false othherwise. | ||||
|              */ | ||||
|             $scope.isInProgress = function isInProgress() { | ||||
|  | ||||
|                 // Not in progress if there is no transfer | ||||
|                 if (!$scope.transfer) | ||||
|                     return false; | ||||
|  | ||||
|                 // Determine in-progress status based on stream state | ||||
|                 switch ($scope.transfer.transferState.streamState) { | ||||
|  | ||||
|                     // IDLE or OPEN file transfers are active | ||||
|                     case ManagedFileTransferState.StreamState.IDLE: | ||||
|                     case ManagedFileTransferState.StreamState.OPEN: | ||||
|                         return true; | ||||
|  | ||||
|                     // All others are not active | ||||
|                     default: | ||||
|                         return false; | ||||
|  | ||||
|                 } | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns whether the file associated with this file transfer can | ||||
|              * be saved locally via a call to save(). | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if a call to save() will result in the file being | ||||
|              *     saved, false otherwise. | ||||
|              */ | ||||
|             $scope.isSavable = function isSavable() { | ||||
|                 return !!$scope.transfer.blob; | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Saves the downloaded file, if any. If this transfer is an upload | ||||
|              * or the download is not yet complete, this function has no | ||||
|              * effect. | ||||
|              */ | ||||
|             $scope.save = function save() { | ||||
|  | ||||
|                 // Ignore if no blob exists | ||||
|                 if (!$scope.transfer.blob) | ||||
|                     return; | ||||
|  | ||||
|                 // Save file | ||||
|                 saveAs($scope.transfer.blob, $scope.transfer.filename);  | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns whether an error has occurred. If an error has occurred, | ||||
|              * the transfer is no longer active, and the text of the error can | ||||
|              * be read from getErrorText(). | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if an error has occurred during transfer, false | ||||
|              *     otherwise. | ||||
|              */ | ||||
|             $scope.hasError = function hasError() { | ||||
|                 return $scope.transfer.transferState.streamState === ManagedFileTransferState.StreamState.ERROR; | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns the text of the current error as a translation string. | ||||
|              * | ||||
|              * @returns {String} | ||||
|              *     The name of the translation string containing the text | ||||
|              *     associated with the current error. | ||||
|              */ | ||||
|             $scope.getErrorText = function getErrorText() { | ||||
|  | ||||
|                 // Determine translation name of error | ||||
|                 var status = $scope.transfer.transferState.statusCode; | ||||
|                 var errorName = (status in UPLOAD_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT"; | ||||
|  | ||||
|                 // Return translation string | ||||
|                 return 'CLIENT.ERROR_UPLOAD_' + errorName; | ||||
|  | ||||
|             }; | ||||
|  | ||||
|         }] // end file transfer controller | ||||
|  | ||||
|     }; | ||||
| }]); | ||||
| @@ -1,91 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Directive which displays all active file transfers. | ||||
|  */ | ||||
| angular.module('client').directive('guacFileTransferManager', [function guacFileTransferManager() { | ||||
|  | ||||
|     return { | ||||
|         restrict: 'E', | ||||
|         replace: true, | ||||
|         scope: { | ||||
|  | ||||
|             /** | ||||
|              * The client whose file transfers should be managed by this | ||||
|              * directive. | ||||
|              *  | ||||
|              * @type ManagerClient | ||||
|              */ | ||||
|             client : '=' | ||||
|  | ||||
|         }, | ||||
|  | ||||
|         templateUrl: 'app/client/templates/guacFileTransferManager.html', | ||||
|         controller: ['$scope', '$injector', function guacFileTransferManagerController($scope, $injector) { | ||||
|  | ||||
|             // Required types | ||||
|             var ManagedFileTransferState = $injector.get('ManagedFileTransferState'); | ||||
|  | ||||
|             /** | ||||
|              * Determines whether the given file transfer state indicates an | ||||
|              * in-progress transfer. | ||||
|              * | ||||
|              * @param {ManagedFileTransferState} transferState | ||||
|              *     The file transfer state to check. | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if the given file transfer state indicates an in- | ||||
|              *     progress transfer, false otherwise. | ||||
|              */ | ||||
|             var isInProgress = function isInProgress(transferState) { | ||||
|                 switch (transferState.streamState) { | ||||
|  | ||||
|                     // IDLE or OPEN file transfers are active | ||||
|                     case ManagedFileTransferState.StreamState.IDLE: | ||||
|                     case ManagedFileTransferState.StreamState.OPEN: | ||||
|                         return true; | ||||
|  | ||||
|                     // All others are not active | ||||
|                     default: | ||||
|                         return false; | ||||
|  | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Removes all file transfers which are not currently in-progress. | ||||
|              */ | ||||
|             $scope.clearCompletedTransfers = function clearCompletedTransfers() { | ||||
|  | ||||
|                 // Nothing to clear if no client attached | ||||
|                 if (!$scope.client) | ||||
|                     return; | ||||
|  | ||||
|                 // Remove completed uploads | ||||
|                 $scope.client.uploads = $scope.client.uploads.filter(function isUploadInProgress(upload) { | ||||
|                     return isInProgress(upload.transferState); | ||||
|                 }); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|         }] | ||||
|  | ||||
|     }; | ||||
| }]); | ||||
| @@ -1,159 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive for displaying a Guacamole client as a non-interactive | ||||
|  * thumbnail. | ||||
|  */ | ||||
| angular.module('client').directive('guacThumbnail', [function guacThumbnail() { | ||||
|  | ||||
|     return { | ||||
|         // Element only | ||||
|         restrict: 'E', | ||||
|         replace: true, | ||||
|         scope: { | ||||
|  | ||||
|             /** | ||||
|              * The client to display within this guacThumbnail directive. | ||||
|              *  | ||||
|              * @type ManagedClient | ||||
|              */ | ||||
|             client : '=' | ||||
|              | ||||
|         }, | ||||
|         templateUrl: 'app/client/templates/guacThumbnail.html', | ||||
|         controller: ['$scope', '$injector', '$element', function guacThumbnailController($scope, $injector, $element) { | ||||
|     | ||||
|             // Required services | ||||
|             var $window = $injector.get('$window'); | ||||
|  | ||||
|             /** | ||||
|              * The optimal thumbnail width, in pixels. | ||||
|              * | ||||
|              * @type Number | ||||
|              */ | ||||
|             var THUMBNAIL_WIDTH = 320; | ||||
|  | ||||
|             /** | ||||
|              * The optimal thumbnail height, in pixels. | ||||
|              * | ||||
|              * @type Number | ||||
|              */ | ||||
|             var THUMBNAIL_HEIGHT = 240; | ||||
|                  | ||||
|             /** | ||||
|              * The display of the current Guacamole client instance. | ||||
|              *  | ||||
|              * @type Guacamole.Display | ||||
|              */ | ||||
|             var display = null; | ||||
|  | ||||
|             /** | ||||
|              * The element associated with the display of the current | ||||
|              * Guacamole client instance. | ||||
|              * | ||||
|              * @type Element | ||||
|              */ | ||||
|             var displayElement = null; | ||||
|  | ||||
|             /** | ||||
|              * The element which must contain the Guacamole display element. | ||||
|              * | ||||
|              * @type Element | ||||
|              */ | ||||
|             var displayContainer = $element.find('.display')[0]; | ||||
|  | ||||
|             /** | ||||
|              * The main containing element for the entire directive. | ||||
|              *  | ||||
|              * @type Element | ||||
|              */ | ||||
|             var main = $element[0]; | ||||
|  | ||||
|             /** | ||||
|              * Updates the scale of the attached Guacamole.Client based on current window | ||||
|              * size and "auto-fit" setting. | ||||
|              */ | ||||
|             $scope.updateDisplayScale = function updateDisplayScale() { | ||||
|  | ||||
|                 if (!display) return; | ||||
|  | ||||
|                 // Fit within available area | ||||
|                 display.scale(Math.min( | ||||
|                     main.offsetWidth  / Math.max(display.getWidth(),  1), | ||||
|                     main.offsetHeight / Math.max(display.getHeight(), 1) | ||||
|                 )); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             // Attach any given managed client | ||||
|             $scope.$watch('client', function attachManagedClient(managedClient) { | ||||
|  | ||||
|                 // Remove any existing display | ||||
|                 displayContainer.innerHTML = ""; | ||||
|  | ||||
|                 // Only proceed if a client is given  | ||||
|                 if (!managedClient) | ||||
|                     return; | ||||
|  | ||||
|                 // Get Guacamole client instance | ||||
|                 var client = managedClient.client; | ||||
|  | ||||
|                 // Attach possibly new display | ||||
|                 display = client.getDisplay(); | ||||
|  | ||||
|                 // Add display element | ||||
|                 displayElement = display.getElement(); | ||||
|                 displayContainer.appendChild(displayElement); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             // Update scale when display is resized | ||||
|             $scope.$watch('client.managedDisplay.size', function setDisplaySize(size) { | ||||
|  | ||||
|                 var width; | ||||
|                 var height; | ||||
|  | ||||
|                 // If no display size yet, assume optimal thumbnail size | ||||
|                 if (!size || size.width === 0 || size.height === 0) { | ||||
|                     width  = THUMBNAIL_WIDTH; | ||||
|                     height = THUMBNAIL_HEIGHT; | ||||
|                 } | ||||
|  | ||||
|                 // Otherwise, generate size that fits within thumbnail bounds | ||||
|                 else { | ||||
|                     var scale = Math.min(THUMBNAIL_WIDTH / size.width, THUMBNAIL_HEIGHT / size.height, 1); | ||||
|                     width  = size.width  * scale; | ||||
|                     height = size.height * scale; | ||||
|                 } | ||||
|                  | ||||
|                 // Generate dummy background image | ||||
|                 var thumbnail = document.createElement("canvas"); | ||||
|                 thumbnail.width  = width; | ||||
|                 thumbnail.height = height; | ||||
|                 $scope.thumbnail = thumbnail.toDataURL("image/png"); | ||||
|  | ||||
|                 // Init display scale | ||||
|                 $scope.$evalAsync($scope.updateDisplayScale); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|         }] | ||||
|     }; | ||||
| }]); | ||||
| @@ -1,112 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive which provides a fullscreen environment for its content. | ||||
|  */ | ||||
| angular.module('client').directive('guacViewport', [function guacViewport() { | ||||
|  | ||||
|     return { | ||||
|         // Element only | ||||
|         restrict: 'E', | ||||
|         scope: {}, | ||||
|         transclude: true, | ||||
|         templateUrl: 'app/client/templates/guacViewport.html', | ||||
|         controller: ['$scope', '$injector', '$element', | ||||
|             function guacViewportController($scope, $injector, $element) { | ||||
|  | ||||
|             // Required services | ||||
|             var $window = $injector.get('$window'); | ||||
|  | ||||
|             /** | ||||
|              * The fullscreen container element. | ||||
|              * | ||||
|              * @type Element | ||||
|              */ | ||||
|             var element = $element.find('.viewport')[0]; | ||||
|  | ||||
|             /** | ||||
|              * The width of the browser viewport when fitVisibleArea() was last | ||||
|              * invoked, in pixels, or null if fitVisibleArea() has not yet been | ||||
|              * called. | ||||
|              * | ||||
|              * @type Number | ||||
|              */ | ||||
|             var lastViewportWidth = null; | ||||
|  | ||||
|             /** | ||||
|              * The height of the browser viewport when fitVisibleArea() was | ||||
|              * last invoked, in pixels, or null if fitVisibleArea() has not yet | ||||
|              * been called. | ||||
|              * | ||||
|              * @type Number | ||||
|              */ | ||||
|             var lastViewportHeight = null; | ||||
|  | ||||
|             /** | ||||
|              * Resizes the container element inside the guacViewport such that | ||||
|              * it exactly fits within the visible area, even if the browser has | ||||
|              * been scrolled. | ||||
|              */ | ||||
|             var fitVisibleArea = function fitVisibleArea() { | ||||
|  | ||||
|                 // Calculate viewport dimensions (this is NOT necessarily the | ||||
|                 // same as 100vw and 100vh, 100%, etc., particularly when the | ||||
|                 // on-screen keyboard of a mobile device pops open) | ||||
|                 var viewportWidth = $window.innerWidth; | ||||
|                 var viewportHeight = $window.innerHeight; | ||||
|  | ||||
|                 // Adjust element width to fit exactly within visible area | ||||
|                 if (viewportWidth !== lastViewportWidth) { | ||||
|                     element.style.width = viewportWidth + 'px'; | ||||
|                     lastViewportWidth = viewportWidth; | ||||
|                 } | ||||
|  | ||||
|                 // Adjust element height to fit exactly within visible area | ||||
|                 if (viewportHeight !== lastViewportHeight) { | ||||
|                     element.style.height = viewportHeight + 'px'; | ||||
|                     lastViewportHeight = viewportHeight; | ||||
|                 } | ||||
|  | ||||
|                 // Scroll element such that its upper-left corner is exactly | ||||
|                 // within the viewport upper-left corner, if not already there | ||||
|                 if (element.scrollLeft || element.scrollTop) { | ||||
|                     $window.scrollTo( | ||||
|                         $window.pageXOffset + element.scrollLeft, | ||||
|                         $window.pageYOffset + element.scrollTop | ||||
|                     ); | ||||
|                 } | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             // Fit container within visible region when window scrolls | ||||
|             $window.addEventListener('scroll', fitVisibleArea); | ||||
|  | ||||
|             // Poll every 10ms, in case scroll event does not fire | ||||
|             var pollArea = $window.setInterval(fitVisibleArea, 10); | ||||
|  | ||||
|             // Clean up on destruction | ||||
|             $scope.$on('$destroy', function destroyViewport() { | ||||
|                 $window.removeEventListener('scroll', fitVisibleArea); | ||||
|                 $window.clearInterval(pollArea); | ||||
|             }); | ||||
|  | ||||
|         }] | ||||
|     }; | ||||
| }]); | ||||
| @@ -1,48 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive which converts between human-readable zoom | ||||
|  * percentage and display scale. | ||||
|  */ | ||||
| angular.module('client').directive('guacZoomCtrl', function guacZoomCtrl() { | ||||
|     return { | ||||
|         restrict: 'A', | ||||
|         require: 'ngModel', | ||||
|         priority: 101, | ||||
|         link: function(scope, element, attrs, ngModel) { | ||||
|  | ||||
|             // Evaluate the ngChange attribute when the model | ||||
|             // changes. | ||||
|             ngModel.$viewChangeListeners.push(function() { | ||||
|                 scope.$eval(attrs.ngChange); | ||||
|             }); | ||||
|  | ||||
|             // When pushing to the menu, mutiply by 100. | ||||
|             ngModel.$formatters.push(function(value) { | ||||
|                 return Math.round(value * 100); | ||||
|             }); | ||||
|             | ||||
|             // When parsing value from menu, divide by 100. | ||||
|             ngModel.$parsers.push(function(value) { | ||||
|                 return Math.round(value) / 100; | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -1,39 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A service for checking browser audio support. | ||||
|  */ | ||||
| angular.module('client').factory('guacAudio', [function guacAudio() { | ||||
|              | ||||
|     /** | ||||
|      * Object describing the UI's level of audio support. | ||||
|      */ | ||||
|     return new (function() { | ||||
|  | ||||
|         /** | ||||
|          * Array of all supported audio mimetypes. | ||||
|          * | ||||
|          * @type String[] | ||||
|          */ | ||||
|         this.supported = Guacamole.AudioPlayer.getSupportedTypes(); | ||||
|  | ||||
|     })(); | ||||
|  | ||||
| }]); | ||||
| @@ -1,169 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A service for managing several active Guacamole clients. | ||||
|  */ | ||||
| angular.module('client').factory('guacClientManager', ['$injector', | ||||
|         function guacClientManager($injector) { | ||||
|  | ||||
|     // Required types | ||||
|     var ManagedClient = $injector.get('ManagedClient'); | ||||
|  | ||||
|     // Required services | ||||
|     var $window               = $injector.get('$window'); | ||||
|     var sessionStorageFactory = $injector.get('sessionStorageFactory'); | ||||
|  | ||||
|     var service = {}; | ||||
|  | ||||
|     /** | ||||
|      * Getter/setter which retrieves or sets the map of all active managed | ||||
|      * clients. Each key is the ID of the connection used by that client. | ||||
|      * | ||||
|      * @type Function | ||||
|      */ | ||||
|     var storedManagedClients = sessionStorageFactory.create({}, function destroyClientStorage() { | ||||
|  | ||||
|         // Disconnect all clients when storage is destroyed | ||||
|         service.clear(); | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     /** | ||||
|      * Returns a map of all active managed clients. Each key is the ID of the | ||||
|      * connection used by that client. | ||||
|      * | ||||
|      * @returns {Object.<String, ManagedClient>} | ||||
|      *     A map of all active managed clients. | ||||
|      */ | ||||
|     service.getManagedClients = function getManagedClients() { | ||||
|         return storedManagedClients(); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Removes the existing ManagedClient associated with the connection having | ||||
|      * the given ID, if any. If no such a ManagedClient already exists, this | ||||
|      * function has no effect. | ||||
|      * | ||||
|      * @param {String} id | ||||
|      *     The ID of the connection whose ManagedClient should be removed. | ||||
|      *  | ||||
|      * @returns {Boolean} | ||||
|      *     true if an existing client was removed, false otherwise. | ||||
|      */ | ||||
|     service.removeManagedClient = function replaceManagedClient(id) { | ||||
|  | ||||
|         var managedClients = storedManagedClients(); | ||||
|  | ||||
|         // Remove client if it exists | ||||
|         if (id in managedClients) { | ||||
|  | ||||
|             // Disconnect and remove | ||||
|             managedClients[id].client.disconnect(); | ||||
|             delete managedClients[id]; | ||||
|  | ||||
|             // A client was removed | ||||
|             return true; | ||||
|  | ||||
|         } | ||||
|  | ||||
|         // No client was removed | ||||
|         return false; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new ManagedClient associated with the connection having the | ||||
|      * given ID. If such a ManagedClient already exists, it is disconnected and | ||||
|      * replaced. | ||||
|      * | ||||
|      * @param {String} id | ||||
|      *     The ID of the connection whose ManagedClient should be retrieved. | ||||
|      *      | ||||
|      * @param {String} [connectionParameters] | ||||
|      *     Any additional HTTP parameters to pass while connecting. This | ||||
|      *     parameter only has an effect if a new connection is established as | ||||
|      *     a result of this function call. | ||||
|      *  | ||||
|      * @returns {ManagedClient} | ||||
|      *     The ManagedClient associated with the connection having the given | ||||
|      *     ID. | ||||
|      */ | ||||
|     service.replaceManagedClient = function replaceManagedClient(id, connectionParameters) { | ||||
|  | ||||
|         // Disconnect any existing client | ||||
|         service.removeManagedClient(id); | ||||
|  | ||||
|         // Set new client | ||||
|         return storedManagedClients()[id] = ManagedClient.getInstance(id, connectionParameters); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Returns the ManagedClient associated with the connection having the | ||||
|      * given ID. If no such ManagedClient exists, a new ManagedClient is | ||||
|      * created. | ||||
|      * | ||||
|      * @param {String} id | ||||
|      *     The ID of the connection whose ManagedClient should be retrieved. | ||||
|      *      | ||||
|      * @param {String} [connectionParameters] | ||||
|      *     Any additional HTTP parameters to pass while connecting. This | ||||
|      *     parameter only has an effect if a new connection is established as | ||||
|      *     a result of this function call. | ||||
|      *  | ||||
|      * @returns {ManagedClient} | ||||
|      *     The ManagedClient associated with the connection having the given | ||||
|      *     ID. | ||||
|      */ | ||||
|     service.getManagedClient = function getManagedClient(id, connectionParameters) { | ||||
|  | ||||
|         var managedClients = storedManagedClients(); | ||||
|  | ||||
|         // Create new managed client if it doesn't already exist | ||||
|         if (!(id in managedClients)) | ||||
|             managedClients[id] = ManagedClient.getInstance(id, connectionParameters); | ||||
|  | ||||
|         // Return existing client | ||||
|         return managedClients[id]; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Disconnects and removes all currently-connected clients. | ||||
|      */ | ||||
|     service.clear = function clear() { | ||||
|  | ||||
|         var managedClients = storedManagedClients(); | ||||
|  | ||||
|         // Disconnect each managed client | ||||
|         for (var id in managedClients) | ||||
|             managedClients[id].client.disconnect(); | ||||
|  | ||||
|         // Clear managed clients | ||||
|         storedManagedClients({}); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     // Disconnect all clients when window is unloaded | ||||
|     $window.addEventListener('unload', service.clear); | ||||
|  | ||||
|     return service; | ||||
|  | ||||
| }]); | ||||
| @@ -1,135 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A service for checking browser image support. | ||||
|  */ | ||||
| angular.module('client').factory('guacImage', ['$injector', function guacImage($injector) { | ||||
|  | ||||
|     // Required services | ||||
|     var $q = $injector.get('$q'); | ||||
|  | ||||
|     var service = {}; | ||||
|  | ||||
|     /** | ||||
|      * Map of possibly-supported image mimetypes to corresponding test images | ||||
|      * encoded with base64. If the image is correctly decoded, it will be a | ||||
|      * single pixel (1x1) image. | ||||
|      * | ||||
|      * @type Object.<String, String> | ||||
|      */ | ||||
|     var testImages = { | ||||
|  | ||||
|         /** | ||||
|          * Test JPEG image, encoded as base64. | ||||
|          */ | ||||
|         'image/jpeg' : | ||||
|             '/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoH' | ||||
|           + 'BwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQME' | ||||
|           + 'BAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU' | ||||
|           + 'FBQUFBQUFBQUFBQUFBT/wAARCAABAAEDAREAAhEBAxEB/8QAFAABAAAAAAAAAAA' | ||||
|           + 'AAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAA' | ||||
|           + 'AAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AVMH/2Q==', | ||||
|  | ||||
|         /** | ||||
|          * Test PNG image, encoded as base64. | ||||
|          */ | ||||
|         'image/png' : | ||||
|             'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX///+nxBvI' | ||||
|           + 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==', | ||||
|  | ||||
|         /** | ||||
|          * Test WebP image, encoded as base64. | ||||
|          */ | ||||
|         'image/webp' : 'UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==' | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Deferred which tracks the progress and ultimate result of all pending | ||||
|      * image format tests. | ||||
|      * | ||||
|      * @type Deferred | ||||
|      */ | ||||
|     var deferredSupportedMimetypes = $q.defer(); | ||||
|  | ||||
|     /** | ||||
|      * Array of all promises associated with pending image tests. Each image | ||||
|      * test promise MUST be guaranteed to resolve and MUST NOT be rejected. | ||||
|      * | ||||
|      * @type Promise[] | ||||
|      */ | ||||
|     var pendingTests = []; | ||||
|  | ||||
|     /** | ||||
|      * The array of supported image formats. This will be gradually populated | ||||
|      * by the various image tests that occur in the background, and will not be | ||||
|      * fully populated until all promises within pendingTests are resolved. | ||||
|      * | ||||
|      * @type String[] | ||||
|      */ | ||||
|     var supported = []; | ||||
|  | ||||
|     /** | ||||
|      * Return a promise which resolves with to an array of image mimetypes | ||||
|      * supported by the browser, once those mimetypes are known. The returned | ||||
|      * promise is guaranteed to resolve successfully. | ||||
|      * | ||||
|      * @returns {Promise.<String[]>} | ||||
|      *     A promise which resolves with an array of image mimetypes supported | ||||
|      *     by the browser. | ||||
|      */ | ||||
|     service.getSupportedMimetypes = function getSupportedMimetypes() { | ||||
|         return deferredSupportedMimetypes.promise; | ||||
|     }; | ||||
|  | ||||
|     // Test each possibly-supported image | ||||
|     angular.forEach(testImages, function testImageSupport(data, mimetype) { | ||||
|  | ||||
|         // Add promise for current image test | ||||
|         var imageTest = $q.defer(); | ||||
|         pendingTests.push(imageTest.promise); | ||||
|  | ||||
|         // Attempt to load image | ||||
|         var image = new Image(); | ||||
|         image.src = 'data:' + mimetype + ';base64,' + data; | ||||
|  | ||||
|         // Store as supported depending on whether load was successful | ||||
|         image.onload = image.onerror = function imageTestComplete() { | ||||
|  | ||||
|             // Image format is supported if successfully decoded | ||||
|             if (image.width === 1 && image.height === 1) | ||||
|                 supported.push(mimetype); | ||||
|  | ||||
|             // Test is complete | ||||
|             imageTest.resolve(); | ||||
|  | ||||
|         }; | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     // When all image tests are complete, resolve promise with list of | ||||
|     // supported formats | ||||
|     $q.all(pendingTests).then(function imageTestsCompleted() { | ||||
|         deferredSupportedMimetypes.resolve(supported); | ||||
|     }); | ||||
|  | ||||
|     return service; | ||||
|  | ||||
| }]); | ||||
| @@ -1,37 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A service for checking browser video support. | ||||
|  */ | ||||
| angular.module('client').factory('guacVideo', [function guacVideo() { | ||||
|             | ||||
|     /** | ||||
|      * Object describing the UI's level of video support. | ||||
|      */ | ||||
|     return new (function() { | ||||
|  | ||||
|         /** | ||||
|          * Array of all supported video mimetypes. | ||||
|          */ | ||||
|         this.supported = Guacamole.VideoPlayer.getSupportedTypes(); | ||||
|  | ||||
|     })(); | ||||
|  | ||||
| }]); | ||||
| @@ -1,129 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| body.client { | ||||
|     background: black; | ||||
|     padding: 0; | ||||
|     margin: 0; | ||||
|     overflow: hidden; | ||||
| } | ||||
|  | ||||
| #preload { | ||||
|     visibility: hidden; | ||||
|     position: absolute; | ||||
|     left: 0; | ||||
|     right: 0; | ||||
|     width: 0; | ||||
|     height: 0; | ||||
|     overflow: hidden; | ||||
| } | ||||
|  | ||||
| .client-view { | ||||
|  | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|  | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|  | ||||
|     font-size: 0px; | ||||
|  | ||||
| } | ||||
|  | ||||
| .client-view-content { | ||||
|  | ||||
|     /* IE10 */ | ||||
|     display: -ms-flexbox; | ||||
|     -ms-flex-align: stretch; | ||||
|     -ms-flex-direction: column; | ||||
|     -ms-flex-pack: end; | ||||
|  | ||||
|     /* Ancient Mozilla */ | ||||
|     display: -moz-box; | ||||
|     -moz-box-align: stretch; | ||||
|     -moz-box-orient: vertical; | ||||
|     -moz-box-pack: end; | ||||
|      | ||||
|     /* Ancient WebKit */ | ||||
|     display: -webkit-box; | ||||
|     -webkit-box-align: stretch; | ||||
|     -webkit-box-orient: vertical; | ||||
|     -webkit-box-pack: end; | ||||
|  | ||||
|     /* Old WebKit */ | ||||
|     display: -webkit-flex; | ||||
|     -webkit-align-items: stretch; | ||||
|     -webkit-flex-direction: column; | ||||
|     -webkit-flex-pack: end; | ||||
|  | ||||
|     /* W3C */ | ||||
|     display: flex; | ||||
|     align-items: stretch; | ||||
|     flex-direction: column; | ||||
|     flex-pack: end; | ||||
|  | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|  | ||||
|     font-size: 12pt; | ||||
|  | ||||
| } | ||||
|  | ||||
| .client-view .client-body { | ||||
|     -ms-flex: 1 1 auto; | ||||
|     -moz-box-flex: 1; | ||||
|     -webkit-box-flex: 1; | ||||
|     -webkit-flex: 1 1 auto; | ||||
|     flex: 1 1 auto; | ||||
|     position: relative; | ||||
| } | ||||
|  | ||||
| .client-view .client-bottom { | ||||
|     -ms-flex: 0 0 auto; | ||||
|     -moz-box-flex: 0; | ||||
|     -webkit-box-flex: 0; | ||||
|     -webkit-flex: 0 0 auto; | ||||
|     flex: 0 0 auto; | ||||
| } | ||||
|  | ||||
| .client-view .client-body .main { | ||||
|  | ||||
|     position: absolute; | ||||
|     left: 0; | ||||
|     top: 0; | ||||
|     right: 0; | ||||
|     bottom: 0; | ||||
|  | ||||
|     width: auto; | ||||
|     height: auto; | ||||
|  | ||||
| } | ||||
|  | ||||
| .client .menu .header h2 { | ||||
|     text-transform: none; | ||||
| } | ||||
|  | ||||
| .client .user-menu .menu-contents li a.disconnect { | ||||
|     background-repeat: no-repeat; | ||||
|     background-size: 1em; | ||||
|     background-position: 0.75em center; | ||||
|     padding-left: 2.5em; | ||||
|     background-image: url('images/x.png'); | ||||
| } | ||||
| @@ -1,57 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| #guac-menu .header h2.connection-select-menu { | ||||
|     overflow: visible; | ||||
| } | ||||
|  | ||||
| .connection-select-menu { | ||||
|     padding: 0; | ||||
|     min-width: 0; | ||||
| } | ||||
|  | ||||
| .connection-select-menu .menu-dropdown { | ||||
|     border: none; | ||||
| } | ||||
|  | ||||
| .connection-select-menu .menu-dropdown .menu-contents { | ||||
|     font-weight: normal; | ||||
|     font-size: 0.8em; | ||||
|     right: auto; | ||||
|     left: 0; | ||||
|     max-width: 100vw; | ||||
|     width: 400px; | ||||
| } | ||||
|  | ||||
| .connection-select-menu .menu-dropdown .menu-contents .filter input { | ||||
|     border-bottom: 1px solid rgba(0,0,0,0.125); | ||||
|     border-left: none; | ||||
| } | ||||
|  | ||||
| .connection-select-menu .menu-dropdown .menu-contents .filter { | ||||
|     margin-bottom: 0.5em; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| .connection-select-menu .menu-dropdown .menu-contents .group-list .caption { | ||||
|     display: inline-block; | ||||
|     width: 100%; | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
| } | ||||
| @@ -1,56 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| #connection-warning { | ||||
|  | ||||
|     position: absolute; | ||||
|     right: 0.25em; | ||||
|     bottom: 0.25em; | ||||
|     z-index: 20; | ||||
|  | ||||
|     width: 3in; | ||||
|     max-width: 100%; | ||||
|     min-height: 1em; | ||||
|  | ||||
|     border-left: 2em solid #FA0; | ||||
|     box-shadow: 1px 1px 2px rgba(0,0,0,0.25); | ||||
|     background: #FFE; | ||||
|     padding: 0.5em 0.75em; | ||||
|     font-size: .8em; | ||||
|  | ||||
| } | ||||
|  | ||||
| #connection-warning::before { | ||||
|  | ||||
|     content: ' '; | ||||
|     display: block; | ||||
|     position: absolute; | ||||
|     left: -2em; | ||||
|     top: 0; | ||||
|  | ||||
|     width: 1.25em; | ||||
|     height: 100%; | ||||
|     margin: 0 0.375em; | ||||
|  | ||||
|     background: url('images/warning.png'); | ||||
|     background-size: contain; | ||||
|     background-position: center; | ||||
|     background-repeat: no-repeat; | ||||
|  | ||||
| } | ||||
| @@ -1,66 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .software-cursor { | ||||
|     cursor: url('images/mouse/blank.gif'),url('images/mouse/blank.cur'),default; | ||||
|     overflow: hidden; | ||||
|     cursor: none; | ||||
| } | ||||
|  | ||||
| .guac-error .software-cursor { | ||||
|     cursor: default; | ||||
| } | ||||
|  | ||||
| div.main { | ||||
|     overflow: auto; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     position: relative; | ||||
|     font-size: 0px; | ||||
| } | ||||
|  | ||||
| div.displayOuter { | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
|     position: absolute; | ||||
|     left: 0; | ||||
|     top: 0; | ||||
|     display: table; | ||||
| } | ||||
|  | ||||
| div.displayMiddle { | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     display: table-cell; | ||||
|     vertical-align: middle; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| div.display { | ||||
|     display: inline-block; | ||||
| } | ||||
|  | ||||
| div.display * { | ||||
|     position: relative; | ||||
| } | ||||
|  | ||||
| div.display > * { | ||||
|     margin-left: auto; | ||||
|     margin-right: auto; | ||||
| } | ||||
| @@ -1,49 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /* Hide directory contents by default */ | ||||
|  | ||||
| .file-browser .directory > .children { | ||||
|     padding-left: 1em; | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| .file-browser .list-item .caption { | ||||
|     white-space: nowrap; | ||||
|     border: 1px solid transparent; | ||||
| } | ||||
|  | ||||
| .file-browser .list-item.focused .caption { | ||||
|     border: 1px dotted rgba(0, 0, 0, 0.5); | ||||
|     background: rgba(204, 221, 170, 0.5); | ||||
| } | ||||
|  | ||||
| /* Directory / file icons */ | ||||
|  | ||||
| .file-browser .normal-file > .caption .icon { | ||||
|     background-image: url('images/file.png'); | ||||
| } | ||||
|  | ||||
| .file-browser .directory > .caption .icon { | ||||
|     background-image: url('images/folder-closed.png'); | ||||
| } | ||||
|  | ||||
| .file-browser .directory.previous > .caption .icon { | ||||
|     background-image: url('images/folder-up.png'); | ||||
| } | ||||
| @@ -1,118 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| #file-transfer-dialog { | ||||
|  | ||||
|     position: absolute; | ||||
|     right: 0; | ||||
|     bottom: 0; | ||||
|     z-index: 20; | ||||
|  | ||||
|     font-size: 0.8em; | ||||
|  | ||||
|     width: 4in; | ||||
|     max-width: 100%; | ||||
|     max-height: 3in; | ||||
|  | ||||
| } | ||||
|  | ||||
| #file-transfer-dialog .transfer-manager { | ||||
|  | ||||
|     /* IE10 */ | ||||
|     display: -ms-flexbox; | ||||
|     -ms-flex-align: stretch; | ||||
|     -ms-flex-direction: column; | ||||
|  | ||||
|     /* Ancient Mozilla */ | ||||
|     display: -moz-box; | ||||
|     -moz-box-align: stretch; | ||||
|     -moz-box-orient: vertical; | ||||
|  | ||||
|     /* Ancient WebKit */ | ||||
|     display: -webkit-box; | ||||
|     -webkit-box-align: stretch; | ||||
|     -webkit-box-orient: vertical; | ||||
|  | ||||
|     /* Old WebKit */ | ||||
|     display: -webkit-flex; | ||||
|     -webkit-align-items: stretch; | ||||
|     -webkit-flex-direction: column; | ||||
|  | ||||
|     /* W3C */ | ||||
|     display: flex; | ||||
|     align-items: stretch; | ||||
|     flex-direction: column; | ||||
|  | ||||
|     max-width: inherit; | ||||
|     max-height: inherit; | ||||
|  | ||||
|     border: 1px solid rgba(0, 0, 0, 0.5); | ||||
|     box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); | ||||
|  | ||||
| } | ||||
|  | ||||
| #file-transfer-dialog .transfer-manager .header { | ||||
|     -ms-flex: 0 0 auto; | ||||
|     -moz-box-flex: 0; | ||||
|     -webkit-box-flex: 0; | ||||
|     -webkit-flex: 0 0 auto; | ||||
|     flex: 0 0 auto; | ||||
| } | ||||
|  | ||||
| #file-transfer-dialog .transfer-manager .transfer-manager-body { | ||||
|  | ||||
|     -ms-flex: 1 1 auto; | ||||
|     -moz-box-flex: 1; | ||||
|     -webkit-box-flex: 1; | ||||
|     -webkit-flex: 1 1 auto; | ||||
|     flex: 1 1 auto; | ||||
|  | ||||
|     overflow: auto; | ||||
|  | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Shrink maximum height if viewport is too small for default 3in dialog. | ||||
|  */ | ||||
| @media all and (max-height: 3in) { | ||||
|  | ||||
|     #file-transfer-dialog { | ||||
|         max-height: 1.5in; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * If viewport is too small for even the 1.5in dialog, fit all available space. | ||||
|  */ | ||||
| @media all and (max-height: 1.5in) { | ||||
|  | ||||
|     #file-transfer-dialog { | ||||
|         height: 100%; | ||||
|     } | ||||
|  | ||||
|     #file-transfer-dialog .transfer-manager { | ||||
|         position: absolute; | ||||
|         left:   0.5em; | ||||
|         top:    0.5em; | ||||
|         right:  0.5em; | ||||
|         bottom: 0.5em; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,72 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| #filesystem-menu .header h2 { | ||||
|     font-size: 1em; | ||||
|     font-weight: normal; | ||||
|     padding-top: 0; | ||||
|     padding-bottom: 0; | ||||
| } | ||||
|  | ||||
| #filesystem-menu .header { | ||||
|     -ms-flex-align:      center; | ||||
|     -moz-box-align:      center; | ||||
|     -webkit-box-align:   center; | ||||
|     -webkit-align-items: center; | ||||
|     align-items:         center; | ||||
| } | ||||
|  | ||||
| #filesystem-menu .menu-body { | ||||
|     padding: 0.25em; | ||||
| } | ||||
|  | ||||
| #filesystem-menu .header.breadcrumbs { | ||||
|     display: block; | ||||
|     background: rgba(0,0,0,0.0125); | ||||
|     border-bottom: 1px solid rgba(0,0,0,0.05); | ||||
|     box-shadow: none; | ||||
|     margin-top: 0; | ||||
|     border-top: none; | ||||
| } | ||||
|  | ||||
| #filesystem-menu .header.breadcrumbs .breadcrumb { | ||||
|     display: inline-block; | ||||
|     padding: 0.5em; | ||||
|     font-size: 0.8em; | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| #filesystem-menu .header.breadcrumbs .breadcrumb:hover { | ||||
|     background-color: #CDA; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| #filesystem-menu .header.breadcrumbs .breadcrumb.root { | ||||
|     background-size:         1.5em 1.5em; | ||||
|     -moz-background-size:    1.5em 1.5em; | ||||
|     -webkit-background-size: 1.5em 1.5em; | ||||
|     -khtml-background-size:  1.5em 1.5em; | ||||
|     background-repeat: no-repeat; | ||||
|     background-position: center center; | ||||
|     background-image: url('images/drive.png'); | ||||
|     width: 2em; | ||||
|     height: 2em; | ||||
|     padding: 0; | ||||
|     vertical-align: middle; | ||||
| } | ||||
| @@ -1,208 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| #guac-menu .content { | ||||
|  | ||||
|     padding: 0; | ||||
|     margin: 0; | ||||
|  | ||||
|     /* IE10 */ | ||||
|     display: -ms-flexbox; | ||||
|     -ms-flex-align: stretch; | ||||
|     -ms-flex-direction: column; | ||||
|  | ||||
|     /* Ancient Mozilla */ | ||||
|     display: -moz-box; | ||||
|     -moz-box-align: stretch; | ||||
|     -moz-box-orient: vertical; | ||||
|  | ||||
|     /* Ancient WebKit */ | ||||
|     display: -webkit-box; | ||||
|     -webkit-box-align: stretch; | ||||
|     -webkit-box-orient: vertical; | ||||
|  | ||||
|     /* Old WebKit */ | ||||
|     display: -webkit-flex; | ||||
|     -webkit-align-items: stretch; | ||||
|     -webkit-flex-direction: column; | ||||
|  | ||||
|     /* W3C */ | ||||
|     display: flex; | ||||
|     align-items: stretch; | ||||
|     flex-direction: column; | ||||
|  | ||||
| } | ||||
|  | ||||
| #guac-menu .content > * { | ||||
|  | ||||
|     margin: 0; | ||||
|  | ||||
|     -ms-flex: 0 0 auto; | ||||
|     -moz-box-flex: 0; | ||||
|     -webkit-box-flex: 0; | ||||
|     -webkit-flex: 0 0 auto; | ||||
|     flex: 0 0 auto; | ||||
|  | ||||
| } | ||||
|  | ||||
| #guac-menu .content > * + * { | ||||
|     margin-top: 1em; | ||||
| } | ||||
|  | ||||
| #guac-menu .header h2 { | ||||
|     white-space: nowrap; | ||||
|     overflow: hidden; | ||||
|     width: 100%; | ||||
|     text-overflow: ellipsis; | ||||
| } | ||||
|  | ||||
| #guac-menu #mouse-settings .choice { | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| #guac-menu #mouse-settings .choice .figure { | ||||
|     display: inline-block; | ||||
|     vertical-align: middle; | ||||
|     width: 75%; | ||||
|     max-width: 320px; | ||||
| } | ||||
|  | ||||
| #guac-menu #keyboard-settings .caption { | ||||
|     font-size: 0.9em; | ||||
|     margin-left: 2em; | ||||
|     margin-right: 2em; | ||||
| } | ||||
|  | ||||
| #guac-menu #mouse-settings .figure .caption { | ||||
|     text-align: center; | ||||
|     font-size: 0.9em; | ||||
| } | ||||
|  | ||||
| #guac-menu #mouse-settings .figure img { | ||||
|     display: block; | ||||
|     width: 100%; | ||||
|     max-width: 320px; | ||||
|     margin: 1em auto; | ||||
| } | ||||
|  | ||||
| #guac-menu #keyboard-settings .figure { | ||||
|     float: right; | ||||
|     max-width: 30%; | ||||
|     margin: 1em; | ||||
| } | ||||
|  | ||||
| #guac-menu #keyboard-settings .figure img { | ||||
|     max-width: 100%; | ||||
| } | ||||
|  | ||||
| #guac-menu #zoom-settings { | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| #guac-menu #zoom-out, | ||||
| #guac-menu #zoom-in, | ||||
| #guac-menu #zoom-state { | ||||
|     display: inline-block; | ||||
|     vertical-align: middle; | ||||
| } | ||||
|  | ||||
| #guac-menu #zoom-out, | ||||
| #guac-menu #zoom-in { | ||||
|     max-width: 3em; | ||||
|     border: 1px solid rgba(0, 0, 0, 0.5); | ||||
|     background: rgba(0, 0, 0, 0.1); | ||||
|     border-radius: 2em; | ||||
|     margin: 0.5em; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| #guac-menu #zoom-out img, | ||||
| #guac-menu #zoom-in img { | ||||
|     max-width: 100%; | ||||
|     opacity: 0.5; | ||||
| } | ||||
|  | ||||
| #guac-menu #zoom-out:hover, | ||||
| #guac-menu #zoom-in:hover { | ||||
|     border: 1px solid rgba(0, 0, 0, 1); | ||||
|     background: #CDA; | ||||
| } | ||||
|  | ||||
| #guac-menu #zoom-out:hover img, | ||||
| #guac-menu #zoom-in:hover img { | ||||
|     opacity: 1; | ||||
| } | ||||
|  | ||||
| #guac-menu #zoom-state { | ||||
|     font-size: 2em; | ||||
| } | ||||
|  | ||||
| #guac-menu #devices .device { | ||||
|  | ||||
|     padding: 1em; | ||||
|     border: 1px solid rgba(0, 0, 0, 0.125); | ||||
|     background: rgba(0, 0, 0, 0.04); | ||||
|  | ||||
|     padding-left: 3.5em; | ||||
|     background-size:         1.5em 1.5em; | ||||
|     -moz-background-size:    1.5em 1.5em; | ||||
|     -webkit-background-size: 1.5em 1.5em; | ||||
|     -khtml-background-size:  1.5em 1.5em; | ||||
|  | ||||
|     background-repeat: no-repeat; | ||||
|     background-position: 1em center; | ||||
|  | ||||
| } | ||||
|  | ||||
| #guac-menu #devices .device:hover { | ||||
|     cursor: pointer; | ||||
|     border-color: black; | ||||
| } | ||||
|  | ||||
| #guac-menu #devices .device.filesystem { | ||||
|     background-image: url('images/drive.png'); | ||||
| } | ||||
|  | ||||
| #guac-menu #share-links { | ||||
|  | ||||
|     padding: 1em; | ||||
|     border: 1px solid rgba(0, 0, 0, 0.125); | ||||
|     background: rgba(0, 0, 0, 0.04); | ||||
|  | ||||
|     font-size: 0.8em; | ||||
|  | ||||
| } | ||||
|  | ||||
| #guac-menu #share-links h3 { | ||||
|     padding-bottom: 0; | ||||
| } | ||||
|  | ||||
| #guac-menu #share-links th { | ||||
|     white-space: nowrap; | ||||
| } | ||||
|  | ||||
| #guac-menu #share-links a[href] { | ||||
|  | ||||
|     display: block; | ||||
|     padding: 0 1em; | ||||
|  | ||||
|     font-family: monospace; | ||||
|     font-weight: bold; | ||||
|  | ||||
| } | ||||
| @@ -1,34 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .keyboard-container { | ||||
|  | ||||
|     text-align: center; | ||||
|  | ||||
|     width: 100%; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|  | ||||
|     border-top: 1px solid black; | ||||
|     background: #222; | ||||
|     opacity: 0.85; | ||||
|  | ||||
|     z-index: 1; | ||||
|  | ||||
| } | ||||
| @@ -1,167 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .menu { | ||||
|     overflow: hidden; | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     height: 100%; | ||||
|     max-width: 100%; | ||||
|     width: 480px; | ||||
|     background: #EEE; | ||||
|     box-shadow: inset -1px 0 2px white, 1px 0 2px black; | ||||
|     z-index: 10; | ||||
|     -webkit-transition: left 0.125s, opacity 0.125s; | ||||
|     -moz-transition: left 0.125s, opacity 0.125s; | ||||
|     -ms-transition: left 0.125s, opacity 0.125s; | ||||
|     -o-transition: left 0.125s, opacity 0.125s; | ||||
|     transition: left 0.125s, opacity 0.125s; | ||||
| } | ||||
|  | ||||
| .menu-content { | ||||
|  | ||||
|     /* IE10 */ | ||||
|     display: -ms-flexbox; | ||||
|     -ms-flex-align: stretch; | ||||
|     -ms-flex-direction: column; | ||||
|  | ||||
|     /* Ancient Mozilla */ | ||||
|     display: -moz-box; | ||||
|     -moz-box-align: stretch; | ||||
|     -moz-box-orient: vertical; | ||||
|      | ||||
|     /* Ancient WebKit */ | ||||
|     display: -webkit-box; | ||||
|     -webkit-box-align: stretch; | ||||
|     -webkit-box-orient: vertical; | ||||
|  | ||||
|     /* Old WebKit */ | ||||
|     display: -webkit-flex; | ||||
|     -webkit-align-items: stretch; | ||||
|     -webkit-flex-direction: column; | ||||
|  | ||||
|     /* W3C */ | ||||
|     display: flex; | ||||
|     align-items: stretch; | ||||
|     flex-direction: column; | ||||
|      | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|  | ||||
| } | ||||
|  | ||||
| .menu-content .header { | ||||
|  | ||||
|     -ms-flex: 0 0 auto; | ||||
|     -moz-box-flex: 0; | ||||
|     -webkit-box-flex: 0; | ||||
|     -webkit-flex: 0 0 auto; | ||||
|     flex: 0 0 auto; | ||||
|  | ||||
|     margin-bottom: 0; | ||||
|      | ||||
| } | ||||
|  | ||||
| .menu-body { | ||||
|  | ||||
|     -ms-flex: 1 1 auto; | ||||
|     -moz-box-flex: 1; | ||||
|     -webkit-box-flex: 1; | ||||
|     -webkit-flex: 1 1 auto; | ||||
|     flex: 1 1 auto; | ||||
|  | ||||
|     padding: 1em; | ||||
|     overflow: auto; | ||||
|  | ||||
|     /* IE10 */ | ||||
|     display: -ms-flexbox; | ||||
|     -ms-flex-align: stretch; | ||||
|     -ms-flex-direction: column; | ||||
|  | ||||
|     /* Ancient Mozilla */ | ||||
|     display: -moz-box; | ||||
|     -moz-box-align: stretch; | ||||
|     -moz-box-orient: vertical; | ||||
|      | ||||
|     /* Ancient WebKit */ | ||||
|     display: -webkit-box; | ||||
|     -webkit-box-align: stretch; | ||||
|     -webkit-box-orient: vertical; | ||||
|  | ||||
|     /* Old WebKit */ | ||||
|     display: -webkit-flex; | ||||
|     -webkit-align-items: stretch; | ||||
|     -webkit-flex-direction: column; | ||||
|  | ||||
|     /* W3C */ | ||||
|     display: flex; | ||||
|     align-items: stretch; | ||||
|     flex-direction: column; | ||||
|  | ||||
| } | ||||
|  | ||||
| .menu-body > * { | ||||
|     -ms-flex: 0 0 auto; | ||||
|     -moz-box-flex: 0; | ||||
|     -webkit-box-flex: 0; | ||||
|     -webkit-flex: 0 0 auto; | ||||
|     flex: 0 0 auto; | ||||
| } | ||||
|  | ||||
| .menu-section h3 { | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     padding-bottom: 1em; | ||||
| } | ||||
|  | ||||
| .menu-section ~ .menu-section h3 { | ||||
|     padding-top: 1em; | ||||
| } | ||||
|  | ||||
| .menu-section input.zoom-ctrl { | ||||
|     width: 2em; | ||||
|     font-size: 1em; | ||||
|     padding: 0; | ||||
|     background: transparent; | ||||
|     border-color: rgba(0, 0, 0, 0.125); | ||||
| } | ||||
|  | ||||
| .menu-section div.zoom-ctrl { | ||||
|     font-size: 1.5em; | ||||
|     display: inline; | ||||
|     align-content: center; | ||||
|     vertical-align: middle; | ||||
| } | ||||
|  | ||||
| .menu-section .zoom-ctrl::-webkit-inner-spin-button, | ||||
| .menu-section .zoom-ctrl::-webkit-outer-spin-button { | ||||
|     -webkit-appearance: none; | ||||
|     margin: 0; | ||||
| } | ||||
|  | ||||
| .menu, | ||||
| .menu.closed { | ||||
|     left: -480px; | ||||
|     opacity: 0; | ||||
| } | ||||
|  | ||||
| .menu.open { | ||||
|     left: 0px; | ||||
|     opacity: 1; | ||||
| } | ||||
| @@ -1,23 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .client .notification .parameters h3, | ||||
| .client .notification .parameters .password-field .toggle-password { | ||||
|     display: none; | ||||
| } | ||||
| @@ -1,206 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| #other-connections .client-panel { | ||||
|  | ||||
|     display: none; | ||||
|     position: absolute; | ||||
|     right: 0; | ||||
|     bottom: 0; | ||||
|  | ||||
|     border: 1px solid rgba(255, 255, 255, 0.25); | ||||
|     background: rgba(0, 0, 0, 0.25); | ||||
|     max-width: 100%; | ||||
|     white-space: nowrap; | ||||
|     transition: max-width 0.125s, width 0.125s; | ||||
|  | ||||
|     /* Render above modal status */ | ||||
|     z-index: 20; | ||||
|  | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel.has-clients { | ||||
|     display: block; | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel.hidden { | ||||
|     max-width: 16px; | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel-handle { | ||||
|  | ||||
|     position: absolute; | ||||
|     left: 0; | ||||
|     bottom: 0; | ||||
|     height: 100%; | ||||
|     width: 16px; | ||||
|     z-index: 1; | ||||
|  | ||||
|     background-color: white; | ||||
|     background-repeat: no-repeat; | ||||
|     background-size: contain; | ||||
|     background-position: center center; | ||||
|     background-image: url(images/arrows/right.png); | ||||
|     opacity: 0.5; | ||||
|  | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel-handle:hover { | ||||
|     opacity: 0.75; | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel.hidden .client-panel-handle { | ||||
|     background-image: url(images/arrows/left.png); | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel-connection-list { | ||||
|  | ||||
|     text-align: right; | ||||
|  | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     padding-left: 16px; | ||||
|  | ||||
|     overflow-x: auto; | ||||
|     overflow-y: hidden; | ||||
|  | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel-connection { | ||||
|  | ||||
|     display: inline-block; | ||||
|     position: relative; | ||||
|  | ||||
|     margin: 0.5em; | ||||
|     border: 1px solid white; | ||||
|     background: black; | ||||
|     box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5); | ||||
|  | ||||
|     opacity: 0.5; | ||||
|     transition: opacity 0.25s; | ||||
|  | ||||
|     max-height: 128px; | ||||
|     overflow: hidden; | ||||
|     vertical-align: middle; | ||||
|  | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel-connection .thumbnail-main img { | ||||
|     max-width: none; | ||||
|     max-height: 128px; | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel-connection a[href]::before { | ||||
|  | ||||
|     display: block; | ||||
|     content: ' '; | ||||
|  | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
|     z-index: 1; | ||||
|  | ||||
|     background: url('images/warning-white.png'); | ||||
|     background-size: 48px; | ||||
|     background-position: center; | ||||
|     background-repeat: no-repeat; | ||||
|     background-color: black; | ||||
|  | ||||
|     opacity: 0; | ||||
|     transition: opacity 0.25s; | ||||
|  | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel-connection.needs-attention a[href]::before { | ||||
|     opacity: 0.75; | ||||
| } | ||||
|  | ||||
| #other-connections button.close-other-connection { | ||||
|  | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     right: 0; | ||||
|     z-index: 2; | ||||
|  | ||||
|     margin: 0; | ||||
|     padding: 4px; | ||||
|     min-width: 0; | ||||
|     border: none; | ||||
|     background: transparent; | ||||
|     box-shadow: none; | ||||
|     text-shadow: none; | ||||
|  | ||||
|     opacity: 0.5; | ||||
|     line-height: 1; | ||||
|  | ||||
| } | ||||
|  | ||||
| #other-connections button.close-other-connection:hover { | ||||
|     opacity: 1; | ||||
| } | ||||
|  | ||||
| #other-connections button.close-other-connection img { | ||||
|     background: #A43; | ||||
|     border-radius: 18px; | ||||
|     max-width: 18px; | ||||
|     padding: 3px; | ||||
| } | ||||
|  | ||||
| #other-connections button.close-other-connection:hover img { | ||||
|     background: #C54; | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel.hidden .client-panel-connection-list { | ||||
|     /* Hide scrollbar when panel is hidden (will be visible through panel | ||||
|      * show/hide button otherwise) */ | ||||
|     overflow-x: hidden; | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel.hidden .client-panel-connection { | ||||
|     /* Hide thumbnails when panel is hidden (will be visible through panel | ||||
|      * show/hide button otherwise) */ | ||||
|     visibility: hidden; | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel-connection .name { | ||||
|  | ||||
|     position: absolute; | ||||
|     padding: 0.25em 0.5em; | ||||
|     left: 0; | ||||
|     right: 0; | ||||
|     bottom: 0; | ||||
|     z-index: 2; | ||||
|  | ||||
|     text-align: left; | ||||
|     color: white; | ||||
|     background: rgba(0, 0, 0, 0.5); | ||||
|     font-size: 0.75em; | ||||
|     font-weight: bold; | ||||
|  | ||||
|     white-space: nowrap; | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
|  | ||||
| } | ||||
|  | ||||
| #other-connections .client-panel-connection:hover { | ||||
|     opacity: 1; | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .share-menu { | ||||
|  | ||||
|     /* IE10 */ | ||||
|     display: -ms-flexbox; | ||||
|     -ms-flex-align: stretch; | ||||
|     -ms-flex-direction: row; | ||||
|  | ||||
|     /* Ancient Mozilla */ | ||||
|     display: -moz-box; | ||||
|     -moz-box-align: stretch; | ||||
|     -moz-box-orient: horizontal; | ||||
|  | ||||
|     /* Ancient WebKit */ | ||||
|     display: -webkit-box; | ||||
|     -webkit-box-align: stretch; | ||||
|     -webkit-box-orient: horizontal; | ||||
|  | ||||
|     /* Old WebKit */ | ||||
|     display: -webkit-flex; | ||||
|     -webkit-align-items: stretch; | ||||
|     -webkit-flex-direction: row; | ||||
|  | ||||
|     /* W3C */ | ||||
|     display: flex; | ||||
|     align-items: stretch; | ||||
|     flex-direction: row; | ||||
|  | ||||
| } | ||||
|  | ||||
| .share-menu .menu-dropdown .menu-title { | ||||
|  | ||||
|     padding-left: 2em; | ||||
|  | ||||
|     background-repeat: no-repeat; | ||||
|     background-size: 1em; | ||||
|     background-position: 0.5em center; | ||||
|     background-image: url('images/share.png'); | ||||
|  | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| div.thumbnail-main { | ||||
|     overflow: hidden; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     position: relative; | ||||
|     font-size: 0px; | ||||
| } | ||||
|  | ||||
| .thumbnail-main img { | ||||
|     max-width: 100%; | ||||
| } | ||||
|  | ||||
| .thumbnail-main .display { | ||||
|     position: absolute; | ||||
|     pointer-events: none; | ||||
| } | ||||
| @@ -1,43 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .transfer-manager { | ||||
|     background: white; | ||||
| } | ||||
|  | ||||
| .transfer-manager .header h2 { | ||||
|     font-size: 1em; | ||||
|     padding-top: 0; | ||||
|     padding-bottom: 0; | ||||
| } | ||||
|  | ||||
| .transfer-manager .header { | ||||
|     margin: 0; | ||||
|     -ms-flex-align:      center; | ||||
|     -moz-box-align:      center; | ||||
|     -webkit-box-align:   center; | ||||
|     -webkit-align-items: center; | ||||
|     align-items:         center; | ||||
| } | ||||
|  | ||||
| .transfer-manager .transfers { | ||||
|     display: table; | ||||
|     padding: 0.25em; | ||||
|     width: 100%; | ||||
| } | ||||
| @@ -1,132 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .transfer { | ||||
|     display: table-row; | ||||
| } | ||||
|  | ||||
| .transfer .transfer-status { | ||||
|     display: table-cell; | ||||
|     padding: 0.25em; | ||||
|     position: relative; | ||||
| } | ||||
|  | ||||
| .transfer .text { | ||||
|     display: table-cell; | ||||
|     text-align: right; | ||||
|     padding: 0.25em | ||||
| } | ||||
|  | ||||
| .transfer .filename { | ||||
|     white-space: nowrap; | ||||
|     text-overflow: ellipsis; | ||||
|     overflow: hidden; | ||||
|     position: relative; | ||||
|     font-family: monospace; | ||||
|     font-weight: bold; | ||||
|     padding: 0.125em; | ||||
| } | ||||
|  | ||||
| @keyframes transfer-progress { | ||||
|     from {background-position: 0px  0px;} | ||||
|     to   {background-position: 64px 0px;} | ||||
| } | ||||
|  | ||||
| @-webkit-keyframes transfer-progress { | ||||
|     from {background-position: 0px  0px;} | ||||
|     to   {background-position: 64px 0px;} | ||||
| } | ||||
|  | ||||
| .transfer .progress { | ||||
|  | ||||
|     width: 100%; | ||||
|     padding: 0.25em; | ||||
|      | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|     bottom: 0; | ||||
|     opacity: 0.25; | ||||
|      | ||||
| } | ||||
|  | ||||
| .transfer.in-progress .progress { | ||||
|  | ||||
|     background-color: #EEE; | ||||
|     background-image: url('images/progress.png'); | ||||
|  | ||||
|     background-size: 16px 16px; | ||||
|     -moz-background-size: 16px 16px; | ||||
|     -webkit-background-size: 16px 16px; | ||||
|     -khtml-background-size: 16px 16px; | ||||
|  | ||||
|     animation-name: transfer-progress; | ||||
|     animation-duration: 2s; | ||||
|     animation-timing-function: linear; | ||||
|     animation-iteration-count: infinite; | ||||
|  | ||||
|     -webkit-animation-name: transfer-progress; | ||||
|     -webkit-animation-duration: 2s; | ||||
|     -webkit-animation-timing-function: linear; | ||||
|     -webkit-animation-iteration-count: infinite; | ||||
|  | ||||
| } | ||||
|  | ||||
| .transfer .progress .bar { | ||||
|     display: none; | ||||
|     background: #A3D655; | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|     height: 100%; | ||||
|     width: 0; | ||||
| } | ||||
|  | ||||
| .transfer.in-progress .progress .bar { | ||||
|     display: initial; | ||||
| } | ||||
|  | ||||
| .transfer.savable { | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| .transfer.savable .filename { | ||||
|     color: blue; | ||||
|     text-decoration: underline; | ||||
| } | ||||
|  | ||||
| .transfer.error { | ||||
|     background: #FDD; | ||||
| } | ||||
|  | ||||
| .transfer.error .text, | ||||
| .transfer.error .progress .bar { | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| .transfer .error-text { | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| .transfer.error .error-text { | ||||
|     display: block; | ||||
|     margin: 0; | ||||
|     margin-top: 0.5em; | ||||
|     width: 100%; | ||||
| } | ||||
| @@ -1,27 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .viewport { | ||||
|     position: absolute; | ||||
|     bottom: 0; | ||||
|     right: 0; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     overflow: hidden; | ||||
| } | ||||
| @@ -1,235 +0,0 @@ | ||||
|  | ||||
| <guac-viewport> | ||||
|  | ||||
|     <!-- Client view --> | ||||
|     <div class="client-view"> | ||||
|         <div class="client-view-content"> | ||||
|  | ||||
|             <!-- Central portion of view --> | ||||
|             <div class="client-body" guac-touch-drag="clientDrag" guac-touch-pinch="clientPinch"> | ||||
|  | ||||
|                 <!-- Client for current connection --> | ||||
|                 <guac-client client="client"></guac-client> | ||||
|  | ||||
|                 <!-- All other active connections --> | ||||
|                 <div id="other-connections"> | ||||
|                     <guac-client-panel clients="otherClients"></guac-client-panel> | ||||
|                 </div> | ||||
|  | ||||
|             </div> | ||||
|  | ||||
|             <!-- Bottom portion of view --> | ||||
|             <div class="client-bottom"> | ||||
|  | ||||
|                 <!-- Text input --> | ||||
|                 <div class="text-input-container" ng-if="showTextInput"> | ||||
|                     <guac-text-input></guac-text-input> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- On-screen keyboard --> | ||||
|                 <div class="keyboard-container" ng-if="showOSK"> | ||||
|                     <guac-osk layout="'CLIENT.URL_OSK_LAYOUT' | translate"></guac-osk> | ||||
|                 </div> | ||||
|  | ||||
|             </div> | ||||
|  | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     <!-- File transfers --> | ||||
|     <div id="file-transfer-dialog" ng-show="hasTransfers()"> | ||||
|         <guac-file-transfer-manager client="client"></guac-file-transfer-manager> | ||||
|     </div> | ||||
|  | ||||
|     <!-- Connection stability warning --> | ||||
|     <div id="connection-warning" ng-show="isConnectionUnstable()"> | ||||
|         {{'CLIENT.TEXT_CLIENT_STATUS_UNSTABLE' | translate}} | ||||
|     </div> | ||||
|  | ||||
|     <!-- Menu --> | ||||
|     <div class="menu" ng-class="{open: menu.shown}" id="guac-menu"> | ||||
|         <div class="menu-content" ng-if="menu.shown"> | ||||
|  | ||||
|             <!-- Stationary header --> | ||||
|             <div class="header"> | ||||
|                 <h2 ng-hide="rootConnectionGroups">{{client.name}}</h2> | ||||
|                 <h2 class="connection-select-menu" ng-show="rootConnectionGroups"> | ||||
|                     <guac-menu menu-title="client.name" interactive="true"> | ||||
|                         <div class="all-connections"> | ||||
|                             <guac-group-list-filter connection-groups="rootConnectionGroups" | ||||
|                                 filtered-connection-groups="filteredRootConnectionGroups" | ||||
|                                 placeholder="'CLIENT.FIELD_PLACEHOLDER_FILTER' | translate" | ||||
|                                 connection-properties="filteredConnectionProperties" | ||||
|                                 connection-group-properties="filteredConnectionGroupProperties"></guac-group-list-filter> | ||||
|                             <guac-group-list | ||||
|                                 connection-groups="filteredRootConnectionGroups" | ||||
|                                 templates="{ | ||||
|                                     'connection'       : 'app/client/templates/connection.html', | ||||
|                                     'connection-group' : 'app/client/templates/connectionGroup.html' | ||||
|                                 }" | ||||
|                                 page-size="10"></guac-group-list> | ||||
|                         </div> | ||||
|                     </guac-menu> | ||||
|                 </h2> | ||||
|                 <div class="share-menu" ng-show="canShareConnection()"> | ||||
|                     <guac-menu menu-title="'CLIENT.ACTION_SHARE' | translate"> | ||||
|                         <ul ng-repeat="sharingProfile in sharingProfiles"> | ||||
|                             <li><a ng-click="share(sharingProfile)">{{sharingProfile.name}}</a></li> | ||||
|                         </ul> | ||||
|                     </guac-menu> | ||||
|                 </div> | ||||
|                 <guac-user-menu local-actions="clientMenuActions"></guac-user-menu> | ||||
|             </div> | ||||
|  | ||||
|             <!-- Scrollable body --> | ||||
|             <div class="menu-body" guac-touch-drag="menuDrag" guac-scroll="menu.scrollState"> | ||||
|  | ||||
|                 <!-- Connection sharing --> | ||||
|                 <div class="menu-section" id="share-links" ng-show="isShared()"> | ||||
|                     <div class="content"> | ||||
|                         <h3>{{'CLIENT.INFO_CONNECTION_SHARED' | translate}}</h3> | ||||
|                         <p class="description" | ||||
|                            translate="CLIENT.HELP_SHARE_LINK" | ||||
|                            translate-values="{LINKS : getShareLinkCount()}"></p> | ||||
|                         <table> | ||||
|                             <tr ng-repeat="link in client.shareLinks | toArray | orderBy: value.name"> | ||||
|                                 <th>{{link.value.name}}</th> | ||||
|                                 <td><a href="{{link.value.href}}" target="_blank">{{link.value.href}}</a></td> | ||||
|                             </tr> | ||||
|                         </table> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Clipboard --> | ||||
|                 <div class="menu-section" id="clipboard-settings"> | ||||
|                     <h3>{{'CLIENT.SECTION_HEADER_CLIPBOARD' | translate}}</h3> | ||||
|                     <div class="content"> | ||||
|                         <p class="description">{{'CLIENT.HELP_CLIPBOARD' | translate}}</p> | ||||
|                         <guac-clipboard data="client.clipboardData"></guac-clipboard> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Devices --> | ||||
|                 <div class="menu-section" id="devices" ng-show="client.filesystems.length"> | ||||
|                     <h3>{{'CLIENT.SECTION_HEADER_DEVICES' | translate}}</h3> | ||||
|                     <div class="content"> | ||||
|                         <div class="device filesystem" ng-repeat="filesystem in client.filesystems" ng-click="showFilesystemMenu(filesystem)"> | ||||
|                             {{filesystem.name}} | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Connection parameters which may be modified while the connection is open --> | ||||
|                 <div class="menu-section connection-parameters" id="connection-settings" ng-show="client.protocol"> | ||||
|                     <guac-form namespace="getProtocolNamespace(client.protocol)" | ||||
|                                content="client.forms" | ||||
|                                model="menu.connectionParameters" | ||||
|                                model-only="true"></guac-form> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Input method --> | ||||
|                 <div class="menu-section" id="keyboard-settings"> | ||||
|                     <h3>{{'CLIENT.SECTION_HEADER_INPUT_METHOD' | translate}}</h3> | ||||
|                     <div class="content"> | ||||
|  | ||||
|                         <!-- No IME --> | ||||
|                         <div class="choice"> | ||||
|                             <label><input id="ime-none" name="input-method" ng-change="closeMenu()" ng-model="menu.inputMethod" type="radio" value="none"/> {{'CLIENT.NAME_INPUT_METHOD_NONE' | translate}}</label> | ||||
|                             <p class="caption"><label for="ime-none">{{'CLIENT.HELP_INPUT_METHOD_NONE' | translate}}</label></p> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- Text input --> | ||||
|                         <div class="choice"> | ||||
|                             <div class="figure"><label for="ime-text"><img src="images/settings/tablet-keys.png" alt=""/></label></div> | ||||
|                             <label><input id="ime-text" name="input-method" ng-change="closeMenu()" ng-model="menu.inputMethod" type="radio" value="text"/> {{'CLIENT.NAME_INPUT_METHOD_TEXT' | translate}}</label> | ||||
|                             <p class="caption"><label for="ime-text">{{'CLIENT.HELP_INPUT_METHOD_TEXT' | translate}} </label></p> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- Guac OSK --> | ||||
|                         <div class="choice"> | ||||
|                             <label><input id="ime-osk" name="input-method" ng-change="closeMenu()" ng-model="menu.inputMethod" type="radio" value="osk"/> {{'CLIENT.NAME_INPUT_METHOD_OSK' | translate}}</label> | ||||
|                             <p class="caption"><label for="ime-osk">{{'CLIENT.HELP_INPUT_METHOD_OSK' | translate}}</label></p> | ||||
|                         </div> | ||||
|  | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Mouse mode --> | ||||
|                 <div class="menu-section" id="mouse-settings" ng-hide="client.multiTouchSupport"> | ||||
|                     <h3>{{'CLIENT.SECTION_HEADER_MOUSE_MODE' | translate}}</h3> | ||||
|                     <div class="content"> | ||||
|                         <p class="description">{{'CLIENT.HELP_MOUSE_MODE' | translate}}</p> | ||||
|  | ||||
|                         <!-- Touchscreen --> | ||||
|                         <div class="choice"> | ||||
|                             <input name="mouse-mode" ng-change="closeMenu()" ng-model="client.clientProperties.emulateAbsoluteMouse" type="radio" ng-value="true" checked="checked" id="absolute"/> | ||||
|                             <div class="figure"> | ||||
|                                 <label for="absolute"><img src="images/settings/touchscreen.png" alt="{{'CLIENT.NAME_MOUSE_MODE_ABSOLUTE' | translate}}"/></label> | ||||
|                                 <p class="caption"><label for="absolute">{{'CLIENT.HELP_MOUSE_MODE_ABSOLUTE' | translate}}</label></p> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- Touchpad --> | ||||
|                         <div class="choice"> | ||||
|                             <input name="mouse-mode" ng-change="closeMenu()" ng-model="client.clientProperties.emulateAbsoluteMouse" type="radio" ng-value="false" id="relative"/> | ||||
|                             <div class="figure"> | ||||
|                                 <label for="relative"><img src="images/settings/touchpad.png" alt="{{'CLIENT.NAME_MOUSE_MODE_RELATIVE' | translate}}"/></label> | ||||
|                                 <p class="caption"><label for="relative">{{'CLIENT.HELP_MOUSE_MODE_RELATIVE' | translate}}</label></p> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Display options --> | ||||
|                 <div class="menu-section" id="display-settings"> | ||||
|                     <h3>{{'CLIENT.SECTION_HEADER_DISPLAY' | translate}}</h3> | ||||
|                     <div class="content"> | ||||
|                         <div id="zoom-settings"> | ||||
|                             <div ng-click="zoomOut()" id="zoom-out"><img src="images/settings/zoom-out.png" alt="-"/></div> | ||||
|                             <div class="zoom-ctrl"> | ||||
|                                 <input type="number" class="zoom-ctrl" guac-zoom-ctrl | ||||
|                                         ng-model="client.clientProperties.scale" | ||||
|                                         ng-model-options="{ updateOn: 'blur submit' }" | ||||
|                                         ng-change="zoomSet()" />% | ||||
|                             </div> | ||||
|                             <div ng-click="zoomIn()" id="zoom-in"><img src="images/settings/zoom-in.png" alt="+"/></div> | ||||
|                         </div> | ||||
|                         <div><label><input ng-model="menu.autoFit" ng-change="changeAutoFit()" ng-disabled="autoFitDisabled()" type="checkbox" id="auto-fit"/> {{'CLIENT.TEXT_ZOOM_AUTO_FIT' | translate}}</label></div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|             </div> | ||||
|  | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     <!-- Filesystem menu --> | ||||
|     <div id="filesystem-menu" class="menu" ng-class="{open: isFilesystemMenuShown()}"> | ||||
|         <div class="menu-content"> | ||||
|  | ||||
|             <!-- Stationary header --> | ||||
|             <div class="header"> | ||||
|                 <h2>{{filesystemMenuContents.name}}</h2> | ||||
|                 <button class="upload button" guac-upload="uploadFiles">{{'CLIENT.ACTION_UPLOAD_FILES' | translate}}</button> | ||||
|                 <button class="back" ng-click="hideFilesystemMenu()">{{'CLIENT.ACTION_NAVIGATE_BACK' | translate}}</button> | ||||
|             </div> | ||||
|  | ||||
|             <!-- Breadcrumbs --> | ||||
|             <div class="header breadcrumbs"><div | ||||
|                     class="breadcrumb root" | ||||
|                     ng-click="changeDirectory(filesystemMenuContents, filesystemMenuContents.root)"></div><div | ||||
|                         class="breadcrumb" | ||||
|                         ng-repeat="file in getPath(filesystemMenuContents.currentDirectory)" | ||||
|                         ng-click="changeDirectory(filesystemMenuContents, file)">{{file.name}}</div> | ||||
|             </div> | ||||
|  | ||||
|             <!-- Scrollable body --> | ||||
|             <div class="menu-body"> | ||||
|                 <guac-file-browser client="client" filesystem="filesystemMenuContents"></guac-file-browser> | ||||
|             </div> | ||||
|  | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
| </guac-viewport> | ||||
| @@ -1,4 +0,0 @@ | ||||
| <a class="connection" ng-href="{{ item.getClientURL() }}"> | ||||
|     <div class="icon type" ng-class="item.protocol"></div> | ||||
|     <span class="name">{{item.name}}</span> | ||||
| </a> | ||||
| @@ -1,4 +0,0 @@ | ||||
| <a class="connection-group" ng-href="{{ item.getClientURL() }}"> | ||||
|     <div ng-show="item.balancing" class="icon type balancer"></div> | ||||
|     <span class="name">{{item.name}}</span> | ||||
| </a> | ||||
| @@ -1,9 +0,0 @@ | ||||
| <div class="list-item"> | ||||
|  | ||||
|     <!-- Filename and icon --> | ||||
|     <div class="caption"> | ||||
|         <div class="icon"></div> | ||||
|         {{::name}} | ||||
|     </div> | ||||
|  | ||||
| </div> | ||||
| @@ -1,13 +0,0 @@ | ||||
| <div class="main" guac-resize="mainElementResized"> | ||||
|  | ||||
|     <!-- Display --> | ||||
|     <div class="displayOuter"> | ||||
|  | ||||
|         <div class="displayMiddle"> | ||||
|             <div class="display software-cursor"> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|     </div> | ||||
|  | ||||
| </div> | ||||
| @@ -1,32 +0,0 @@ | ||||
| <div class="client-panel" | ||||
|      ng-class="{ 'has-clients': hasClients(), 'hidden' : panelHidden() }"> | ||||
|  | ||||
|     <!-- Toggle panel visibility --> | ||||
|     <div class="client-panel-handle" ng-click="togglePanel()"></div> | ||||
|  | ||||
|     <!-- List of connection thumbnails --> | ||||
|     <ul class="client-panel-connection-list"> | ||||
|         <li ng-repeat="client in clients | toArray | orderBy: [ '-value.lastUsed', 'value.title' ]" | ||||
|             ng-class="{ 'needs-attention' : hasStatusUpdate(client.value) }" | ||||
|             ng-show="isManaged(client.value)" | ||||
|             class="client-panel-connection"> | ||||
|  | ||||
|             <!-- Close connection --> | ||||
|             <button class="close-other-connection" ng-click="disconnect(client.value)"> | ||||
|                 <img ng-attr-alt="{{ 'CLIENT.ACTION_DISCONNECT' | translate }}" | ||||
|                      ng-attr-title="{{ 'CLIENT.ACTION_DISCONNECT' | translate }}" | ||||
|                      src="images/x.png"> | ||||
|             </button> | ||||
|  | ||||
|             <!-- Thumbnail --> | ||||
|             <a href="#/client/{{client.value.id}}"> | ||||
|                 <div class="thumbnail"> | ||||
|                     <guac-thumbnail client="client.value"></guac-thumbnail> | ||||
|                 </div> | ||||
|                 <div class="name">{{ client.value.title }}</div> | ||||
|             </a> | ||||
|  | ||||
|         </li> | ||||
|     </ul> | ||||
|  | ||||
| </div> | ||||
| @@ -1,6 +0,0 @@ | ||||
| <div class="file-browser"> | ||||
|  | ||||
|     <!-- Current directory contents --> | ||||
|     <div class="current-directory-contents"></div> | ||||
|  | ||||
| </div> | ||||
| @@ -1,22 +0,0 @@ | ||||
| <div class="transfer" ng-class="{'in-progress': isInProgress(), 'savable': isSavable(), 'error': hasError()}" ng-click="save()"> | ||||
|  | ||||
|     <!-- Overall status of transfer --> | ||||
|     <div class="transfer-status"> | ||||
|  | ||||
|         <!-- Filename and progress bar --> | ||||
|         <div class="filename"> | ||||
|             <div class="progress"><div ng-style="{'width': getPercentDone() + '%'}" class="bar"></div></div> | ||||
|             {{transfer.filename}} | ||||
|         </div> | ||||
|  | ||||
|         <!-- Error text --> | ||||
|         <p class="error-text">{{getErrorText() | translate}}</p> | ||||
|  | ||||
|     </div> | ||||
|  | ||||
|     <!-- Progress/status text --> | ||||
|     <div class="text" | ||||
|          translate="CLIENT.TEXT_FILE_TRANSFER_PROGRESS" | ||||
|          translate-values="{PROGRESS: getProgressValue(), UNIT: getProgressUnit()}"></div> | ||||
|  | ||||
| </div> | ||||
| @@ -1,22 +0,0 @@ | ||||
| <div class="transfer-manager"> | ||||
|  | ||||
|     <!-- File transfer manager header --> | ||||
|     <div class="header"> | ||||
|         <h2>{{'CLIENT.SECTION_HEADER_FILE_TRANSFERS' | translate}}</h2> | ||||
|         <button ng-click="clearCompletedTransfers()">{{'CLIENT.ACTION_CLEAR_COMPLETED_TRANSFERS' | translate}}</button> | ||||
|     </div> | ||||
|  | ||||
|     <!-- Sent/received files --> | ||||
|     <div class="transfer-manager-body"> | ||||
|         <div class="transfers"> | ||||
|             <guac-file-transfer | ||||
|                 transfer="upload" | ||||
|                 ng-repeat="upload in client.uploads"> | ||||
|             </guac-file-transfer><guac-file-transfer | ||||
|                 transfer="download" | ||||
|                 ng-repeat="download in client.downloads"> | ||||
|             </guac-file-transfer> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
| </div> | ||||
| @@ -1,10 +0,0 @@ | ||||
| <div class="thumbnail-main" guac-resize="updateDisplayScale"> | ||||
|  | ||||
|     <!-- Display --> | ||||
|     <div class="display"> | ||||
|     </div> | ||||
|  | ||||
|     <!-- Dummy background thumbnail --> | ||||
|     <img alt="" ng-src="{{thumbnail}}"/> | ||||
|  | ||||
| </div> | ||||
| @@ -1,2 +0,0 @@ | ||||
| <div class="viewport" ng-transclude> | ||||
| </div> | ||||
| @@ -1,106 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A service for generating new guacClient properties objects. | ||||
|  */ | ||||
| angular.module('client').factory('ClientProperties', ['$injector', function defineClientProperties($injector) { | ||||
|  | ||||
|     // Required services | ||||
|     var preferenceService = $injector.get('preferenceService'); | ||||
|          | ||||
|     /** | ||||
|      * Object used for interacting with a guacClient directive. | ||||
|      *  | ||||
|      * @constructor | ||||
|      * @param {ClientProperties|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ClientProperties. | ||||
|      */ | ||||
|     var ClientProperties = function ClientProperties(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * Whether the display should be scaled automatically to fit within the | ||||
|          * available space. | ||||
|          *  | ||||
|          * @type Boolean | ||||
|          */ | ||||
|         this.autoFit = template.autoFit || true; | ||||
|  | ||||
|         /** | ||||
|          * The current scale. If autoFit is true, the effect of setting this | ||||
|          * value is undefined. | ||||
|          *  | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.scale = template.scale || 1; | ||||
|  | ||||
|         /** | ||||
|          * The minimum scale value. | ||||
|          *  | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.minScale = template.minScale || 1; | ||||
|  | ||||
|         /** | ||||
|          * The maximum scale value. | ||||
|          *  | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.maxScale = template.maxScale || 3; | ||||
|  | ||||
|         /** | ||||
|          * Whether or not the client should listen to keyboard events. | ||||
|          *  | ||||
|          * @type Boolean | ||||
|          */ | ||||
|         this.keyboardEnabled = template.keyboardEnabled || true; | ||||
|          | ||||
|         /** | ||||
|          * Whether translation of touch to mouse events should emulate an | ||||
|          * absolute pointer device, or a relative pointer device. | ||||
|          *  | ||||
|          * @type Boolean | ||||
|          */ | ||||
|         this.emulateAbsoluteMouse = template.emulateAbsoluteMouse || preferenceService.preferences.emulateAbsoluteMouse; | ||||
|  | ||||
|         /** | ||||
|          * The relative Y coordinate of the scroll offset of the display within | ||||
|          * the client element. | ||||
|          *  | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.scrollTop = template.scrollTop || 0; | ||||
|  | ||||
|         /** | ||||
|          * The relative X coordinate of the scroll offset of the display within | ||||
|          * the client element. | ||||
|          *  | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.scrollLeft = template.scrollLeft || 0; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return ClientProperties; | ||||
|  | ||||
| }]); | ||||
| @@ -1,152 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the ManagedArgument class used by ManagedClient. | ||||
|  */ | ||||
| angular.module('client').factory('ManagedArgument', ['$q', function defineManagedArgument($q) { | ||||
|  | ||||
|     /** | ||||
|      * Object which represents an argument (connection parameter) which may be | ||||
|      * changed by the user while the connection is open. | ||||
|      *  | ||||
|      * @constructor | ||||
|      * @param {ManagedArgument|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedArgument. | ||||
|      */ | ||||
|     var ManagedArgument = function ManagedArgument(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The name of the connection parameter. | ||||
|          * | ||||
|          * @type {String} | ||||
|          */ | ||||
|         this.name = template.name; | ||||
|  | ||||
|         /** | ||||
|          * The current value of the connection parameter. | ||||
|          * | ||||
|          * @type {String} | ||||
|          */ | ||||
|         this.value = template.value; | ||||
|  | ||||
|         /** | ||||
|          * A valid, open output stream which may be used to apply a new value | ||||
|          * to the connection parameter. | ||||
|          * | ||||
|          * @type {Guacamole.OutputStream} | ||||
|          */ | ||||
|         this.stream = template.stream; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Requests editable access to a given connection parameter, returning a | ||||
|      * promise which is resolved with a ManagedArgument instance that provides | ||||
|      * such access if the parameter is indeed editable. | ||||
|      * | ||||
|      * @param {ManagedClient} managedClient | ||||
|      *     The ManagedClient instance associated with the connection for which | ||||
|      *     an editable version of the connection parameter is being retrieved. | ||||
|      * | ||||
|      * @param {String} name | ||||
|      *     The name of the connection parameter. | ||||
|      * | ||||
|      * @param {String} value | ||||
|      *     The current value of the connection parameter, as received from a | ||||
|      *     prior, inbound "argv" stream. | ||||
|      * | ||||
|      * @returns {Promise.<ManagedArgument>} | ||||
|      *     A promise which is resolved with the new ManagedArgument instance | ||||
|      *     once the requested parameter has been verified as editable. | ||||
|      */ | ||||
|     ManagedArgument.getInstance = function getInstance(managedClient, name, value) { | ||||
|  | ||||
|         var deferred = $q.defer(); | ||||
|  | ||||
|         // Create internal, fully-populated instance of ManagedArgument, to be | ||||
|         // returned only once mutability of the associated connection parameter | ||||
|         // has been verified | ||||
|         var managedArgument = new ManagedArgument({ | ||||
|             name   : name, | ||||
|             value  : value, | ||||
|             stream : managedClient.client.createArgumentValueStream('text/plain', name) | ||||
|         }); | ||||
|  | ||||
|         // The connection parameter is editable only if a successful "ack" is | ||||
|         // received | ||||
|         managedArgument.stream.onack = function ackReceived(status) { | ||||
|             if (status.isError()) | ||||
|                 deferred.reject(status); | ||||
|             else | ||||
|                 deferred.resolve(managedArgument); | ||||
|         }; | ||||
|  | ||||
|         return deferred.promise; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Sets the given editable argument (connection parameter) to the given | ||||
|      * value, updating the behavior of the associated connection in real-time. | ||||
|      * If successful, the ManagedArgument provided cannot be used for future | ||||
|      * calls to setValue() and must be replaced with a new instance. This | ||||
|      * function only has an effect if the new parameter value is different from | ||||
|      * the current value. | ||||
|      * | ||||
|      * @param {ManagedArgument} managedArgument | ||||
|      *     The ManagedArgument instance associated with the connection | ||||
|      *     parameter being modified. | ||||
|      * | ||||
|      * @param {String} value | ||||
|      *     The new value to assign to the connection parameter. | ||||
|      * | ||||
|      * @returns {Boolean} | ||||
|      *     true if the connection parameter was sent and the provided | ||||
|      *     ManagedArgument instance may no longer be used for future setValue() | ||||
|      *     calls, false if the connection parameter was NOT sent as it has not | ||||
|      *     changed. | ||||
|      */ | ||||
|     ManagedArgument.setValue = function setValue(managedArgument, value) { | ||||
|  | ||||
|         // Stream new value only if value has changed | ||||
|         if (value !== managedArgument.value) { | ||||
|  | ||||
|             var writer = new Guacamole.StringWriter(managedArgument.stream); | ||||
|             writer.sendText(value); | ||||
|             writer.sendEnd(); | ||||
|  | ||||
|             // ManagedArgument instance is no longer usable | ||||
|             return true; | ||||
|  | ||||
|         } | ||||
|  | ||||
|         // No parameter value change was attempted and the ManagedArgument | ||||
|         // instance may be reused | ||||
|         return false; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return ManagedArgument; | ||||
|  | ||||
| }]); | ||||
| @@ -1,924 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the ManagedClient class used by the guacClientManager service. | ||||
|  */ | ||||
| angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', | ||||
|     function defineManagedClient($rootScope, $injector) { | ||||
|  | ||||
|     // Required types | ||||
|     var ClientProperties       = $injector.get('ClientProperties'); | ||||
|     var ClientIdentifier       = $injector.get('ClientIdentifier'); | ||||
|     var ClipboardData          = $injector.get('ClipboardData'); | ||||
|     var ManagedArgument        = $injector.get('ManagedArgument'); | ||||
|     var ManagedClientState     = $injector.get('ManagedClientState'); | ||||
|     var ManagedClientThumbnail = $injector.get('ManagedClientThumbnail'); | ||||
|     var ManagedDisplay         = $injector.get('ManagedDisplay'); | ||||
|     var ManagedFilesystem      = $injector.get('ManagedFilesystem'); | ||||
|     var ManagedFileUpload      = $injector.get('ManagedFileUpload'); | ||||
|     var ManagedShareLink       = $injector.get('ManagedShareLink'); | ||||
|  | ||||
|     // Required services | ||||
|     var $document               = $injector.get('$document'); | ||||
|     var $q                      = $injector.get('$q'); | ||||
|     var $rootScope              = $injector.get('$rootScope'); | ||||
|     var $window                 = $injector.get('$window'); | ||||
|     var activeConnectionService = $injector.get('activeConnectionService'); | ||||
|     var authenticationService   = $injector.get('authenticationService'); | ||||
|     var connectionGroupService  = $injector.get('connectionGroupService'); | ||||
|     var connectionService       = $injector.get('connectionService'); | ||||
|     var preferenceService       = $injector.get('preferenceService'); | ||||
|     var requestService          = $injector.get('requestService'); | ||||
|     var schemaService           = $injector.get('schemaService'); | ||||
|     var tunnelService           = $injector.get('tunnelService'); | ||||
|     var guacAudio               = $injector.get('guacAudio'); | ||||
|     var guacHistory             = $injector.get('guacHistory'); | ||||
|     var guacImage               = $injector.get('guacImage'); | ||||
|     var guacVideo               = $injector.get('guacVideo'); | ||||
|  | ||||
|     /** | ||||
|      * The minimum amount of time to wait between updates to the client | ||||
|      * thumbnail, in milliseconds. | ||||
|      * | ||||
|      * @type Number | ||||
|      */ | ||||
|     var THUMBNAIL_UPDATE_FREQUENCY = 5000; | ||||
|  | ||||
|     /** | ||||
|      * Object which serves as a surrogate interface, encapsulating a Guacamole | ||||
|      * client while it is active, allowing it to be detached and reattached | ||||
|      * from different client views. | ||||
|      *  | ||||
|      * @constructor | ||||
|      * @param {ManagedClient|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedClient. | ||||
|      */ | ||||
|     var ManagedClient = function ManagedClient(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The ID of the connection associated with this client. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.id = template.id; | ||||
|  | ||||
|         /** | ||||
|          * The time that the connection was last brought to the foreground of | ||||
|          * the current tab, as the number of milliseconds elapsed since | ||||
|          * midnight of January 1, 1970 UTC. If the connection has not yet been | ||||
|          * viewed, this will be 0. | ||||
|          * | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.lastUsed = template.lastUsed || 0; | ||||
|  | ||||
|         /** | ||||
|          * The actual underlying Guacamole client. | ||||
|          * | ||||
|          * @type Guacamole.Client | ||||
|          */ | ||||
|         this.client = template.client; | ||||
|  | ||||
|         /** | ||||
|          * The tunnel being used by the underlying Guacamole client. | ||||
|          * | ||||
|          * @type Guacamole.Tunnel | ||||
|          */ | ||||
|         this.tunnel = template.tunnel; | ||||
|  | ||||
|         /** | ||||
|          * The display associated with the underlying Guacamole client. | ||||
|          *  | ||||
|          * @type ManagedDisplay | ||||
|          */ | ||||
|         this.managedDisplay = template.managedDisplay; | ||||
|  | ||||
|         /** | ||||
|          * The name returned associated with the connection or connection | ||||
|          * group in use. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.name = template.name; | ||||
|  | ||||
|         /** | ||||
|          * The title which should be displayed as the page title for this | ||||
|          * client. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.title = template.title; | ||||
|  | ||||
|         /** | ||||
|          * The name which uniquely identifies the protocol of the connection in | ||||
|          * use. If the protocol cannot be determined, such as when a connection | ||||
|          * group is in use, this will be null. | ||||
|          * | ||||
|          * @type {String} | ||||
|          */ | ||||
|         this.protocol = template.protocol || null; | ||||
|  | ||||
|         /** | ||||
|          * An array of forms describing all known parameters for the connection | ||||
|          * in use, including those which may not be editable. | ||||
|          * | ||||
|          * @type {Form[]} | ||||
|          */ | ||||
|         this.forms = template.forms || []; | ||||
|  | ||||
|         /** | ||||
|          * The most recently-generated thumbnail for this connection, as | ||||
|          * stored within the local connection history. If no thumbnail is | ||||
|          * stored, this will be null. | ||||
|          * | ||||
|          * @type ManagedClientThumbnail | ||||
|          */ | ||||
|         this.thumbnail = template.thumbnail; | ||||
|  | ||||
|         /** | ||||
|          * The current clipboard contents. | ||||
|          * | ||||
|          * @type ClipboardData | ||||
|          */ | ||||
|         this.clipboardData = template.clipboardData || new ClipboardData({ | ||||
|             type : 'text/plain', | ||||
|             data : '' | ||||
|         }); | ||||
|  | ||||
|         /** | ||||
|          * The current state of all parameters requested by the server via | ||||
|          * "required" instructions, where each object key is the name of a | ||||
|          * requested parameter and each value is the current value entered by | ||||
|          * the user or null if no parameters are currently being requested. | ||||
|          * | ||||
|          * @type Object.<String, String> | ||||
|          */ | ||||
|         this.requiredParameters = null; | ||||
|  | ||||
|         /** | ||||
|          * All uploaded files. As files are uploaded, their progress can be | ||||
|          * observed through the elements of this array. It is intended that | ||||
|          * this array be manipulated externally as needed. | ||||
|          * | ||||
|          * @type ManagedFileUpload[] | ||||
|          */ | ||||
|         this.uploads = template.uploads || []; | ||||
|  | ||||
|         /** | ||||
|          * All currently-exposed filesystems. When the Guacamole server exposes | ||||
|          * a filesystem object, that object will be made available as a | ||||
|          * ManagedFilesystem within this array. | ||||
|          * | ||||
|          * @type ManagedFilesystem[] | ||||
|          */ | ||||
|         this.filesystems = template.filesystems || []; | ||||
|  | ||||
|         /** | ||||
|          * All available share links generated for the this ManagedClient via | ||||
|          * ManagedClient.createShareLink(). Each resulting share link is stored | ||||
|          * under the identifier of its corresponding SharingProfile. | ||||
|          * | ||||
|          * @type Object.<String, ManagedShareLink> | ||||
|          */ | ||||
|         this.shareLinks = template.shareLinks || {}; | ||||
|  | ||||
|         /** | ||||
|          * The number of simultaneous touch contacts supported by the remote | ||||
|          * desktop. Unless explicitly declared otherwise by the remote desktop | ||||
|          * after connecting, this will be 0 (multi-touch unsupported). | ||||
|          * | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.multiTouchSupport = template.multiTouchSupport || 0; | ||||
|  | ||||
|         /** | ||||
|          * The current state of the Guacamole client (idle, connecting, | ||||
|          * connected, terminated with error, etc.). | ||||
|          *  | ||||
|          * @type ManagedClientState | ||||
|          */ | ||||
|         this.clientState = template.clientState || new ManagedClientState(); | ||||
|  | ||||
|         /** | ||||
|          * Properties associated with the display and behavior of the Guacamole | ||||
|          * client. | ||||
|          * | ||||
|          * @type ClientProperties | ||||
|          */ | ||||
|         this.clientProperties = template.clientProperties || new ClientProperties(); | ||||
|  | ||||
|         /** | ||||
|          * All editable arguments (connection parameters), stored by their | ||||
|          * names. Arguments will only be present within this set if their | ||||
|          * current values have been exposed by the server via an inbound "argv" | ||||
|          * stream and the server has confirmed that the value may be changed | ||||
|          * through a successful "ack" to an outbound "argv" stream. | ||||
|          * | ||||
|          * @type {Object.<String, ManagedArgument>} | ||||
|          */ | ||||
|         this.arguments = template.arguments || {}; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The mimetype of audio data to be sent along the Guacamole connection if | ||||
|      * audio input is supported. | ||||
|      * | ||||
|      * @constant | ||||
|      * @type String | ||||
|      */ | ||||
|     ManagedClient.AUDIO_INPUT_MIMETYPE = 'audio/L16;rate=44100,channels=2'; | ||||
|  | ||||
|     /** | ||||
|      * Returns a promise which resolves with the string of connection | ||||
|      * parameters to be passed to the Guacamole client during connection. This | ||||
|      * string generally contains the desired connection ID, display resolution, | ||||
|      * and supported audio/video/image formats. The returned promise is | ||||
|      * guaranteed to resolve successfully. | ||||
|      * | ||||
|      * @param {ClientIdentifier} identifier | ||||
|      *     The identifier representing the connection or group to connect to. | ||||
|      * | ||||
|      * @param {String} [connectionParameters] | ||||
|      *     Any additional HTTP parameters to pass while connecting. | ||||
|      *  | ||||
|      * @returns {Promise.<String>} | ||||
|      *     A promise which resolves with the string of connection parameters to | ||||
|      *     be passed to the Guacamole client, once the string is ready. | ||||
|      */ | ||||
|     var getConnectString = function getConnectString(identifier, connectionParameters) { | ||||
|  | ||||
|         var deferred = $q.defer(); | ||||
|  | ||||
|         // Calculate optimal width/height for display | ||||
|         var pixel_density = $window.devicePixelRatio || 1; | ||||
|         var optimal_dpi = pixel_density * 96; | ||||
|         var optimal_width = $window.innerWidth * pixel_density; | ||||
|         var optimal_height = $window.innerHeight * pixel_density; | ||||
|  | ||||
|         // Build base connect string | ||||
|         var connectString = | ||||
|               "token="             + encodeURIComponent(authenticationService.getCurrentToken()) | ||||
|             + "&GUAC_DATA_SOURCE=" + encodeURIComponent(identifier.dataSource) | ||||
|             + "&GUAC_ID="          + encodeURIComponent(identifier.id) | ||||
|             + "&GUAC_TYPE="        + encodeURIComponent(identifier.type) | ||||
|             + "&GUAC_WIDTH="       + Math.floor(optimal_width) | ||||
|             + "&GUAC_HEIGHT="      + Math.floor(optimal_height) | ||||
|             + "&GUAC_DPI="         + Math.floor(optimal_dpi) | ||||
|             + "&GUAC_TIMEZONE="    + encodeURIComponent(preferenceService.preferences.timezone) | ||||
|             + (connectionParameters ? '&' + connectionParameters : ''); | ||||
|  | ||||
|         // Add audio mimetypes to connect string | ||||
|         guacAudio.supported.forEach(function(mimetype) { | ||||
|             connectString += "&GUAC_AUDIO=" + encodeURIComponent(mimetype); | ||||
|         }); | ||||
|  | ||||
|         // Add video mimetypes to connect string | ||||
|         guacVideo.supported.forEach(function(mimetype) { | ||||
|             connectString += "&GUAC_VIDEO=" + encodeURIComponent(mimetype); | ||||
|         }); | ||||
|  | ||||
|         // Add image mimetypes to connect string | ||||
|         guacImage.getSupportedMimetypes().then(function supportedMimetypesKnown(mimetypes) { | ||||
|  | ||||
|             // Add each image mimetype | ||||
|             angular.forEach(mimetypes, function addImageMimetype(mimetype) { | ||||
|                 connectString += "&GUAC_IMAGE=" + encodeURIComponent(mimetype); | ||||
|             }); | ||||
|  | ||||
|             // Connect string is now ready - nothing else is deferred | ||||
|             deferred.resolve(connectString); | ||||
|  | ||||
|         }); | ||||
|  | ||||
|         return deferred.promise; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Requests the creation of a new audio stream, recorded from the user's | ||||
|      * local audio input device. If audio input is supported by the connection, | ||||
|      * an audio stream will be created which will remain open until the remote | ||||
|      * desktop requests that it be closed. If the audio stream is successfully | ||||
|      * created but is later closed, a new audio stream will automatically be | ||||
|      * established to take its place. The mimetype used for all audio streams | ||||
|      * produced by this function is defined by | ||||
|      * ManagedClient.AUDIO_INPUT_MIMETYPE. | ||||
|      * | ||||
|      * @param {Guacamole.Client} client | ||||
|      *     The Guacamole.Client for which the audio stream is being requested. | ||||
|      */ | ||||
|     var requestAudioStream = function requestAudioStream(client) { | ||||
|  | ||||
|         // Create new audio stream, associating it with an AudioRecorder | ||||
|         var stream = client.createAudioStream(ManagedClient.AUDIO_INPUT_MIMETYPE); | ||||
|         var recorder = Guacamole.AudioRecorder.getInstance(stream, ManagedClient.AUDIO_INPUT_MIMETYPE); | ||||
|  | ||||
|         // If creation of the AudioRecorder failed, simply end the stream | ||||
|         if (!recorder) | ||||
|             stream.sendEnd(); | ||||
|  | ||||
|         // Otherwise, ensure that another audio stream is created after this | ||||
|         // audio stream is closed | ||||
|         else | ||||
|             recorder.onclose = requestAudioStream.bind(this, client); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new ManagedClient, connecting it to the specified connection | ||||
|      * or group. | ||||
|      * | ||||
|      * @param {String} id | ||||
|      *     The ID of the connection or group to connect to. This String must be | ||||
|      *     a valid ClientIdentifier string, as would be generated by | ||||
|      *     ClientIdentifier.toString(). | ||||
|      * | ||||
|      * @param {String} [connectionParameters] | ||||
|      *     Any additional HTTP parameters to pass while connecting. | ||||
|      *  | ||||
|      * @returns {ManagedClient} | ||||
|      *     A new ManagedClient instance which is connected to the connection or | ||||
|      *     connection group having the given ID. | ||||
|      */ | ||||
|     ManagedClient.getInstance = function getInstance(id, connectionParameters) { | ||||
|  | ||||
|         var tunnel; | ||||
|  | ||||
|         // If WebSocket available, try to use it. | ||||
|         if ($window.WebSocket) | ||||
|             tunnel = new Guacamole.ChainedTunnel( | ||||
|                 new Guacamole.WebSocketTunnel('websocket-tunnel'), | ||||
|                 new Guacamole.HTTPTunnel('tunnel') | ||||
|             ); | ||||
|          | ||||
|         // If no WebSocket, then use HTTP. | ||||
|         else | ||||
|             tunnel = new Guacamole.HTTPTunnel('tunnel'); | ||||
|  | ||||
|         // Get new client instance | ||||
|         var client = new Guacamole.Client(tunnel); | ||||
|  | ||||
|         // Associate new managed client with new client and tunnel | ||||
|         var managedClient = new ManagedClient({ | ||||
|             id     : id, | ||||
|             client : client, | ||||
|             tunnel : tunnel | ||||
|         }); | ||||
|  | ||||
|         // Fire events for tunnel errors | ||||
|         tunnel.onerror = function tunnelError(status) { | ||||
|             $rootScope.$apply(function handleTunnelError() { | ||||
|                 ManagedClientState.setConnectionState(managedClient.clientState, | ||||
|                     ManagedClientState.ConnectionState.TUNNEL_ERROR, | ||||
|                     status.code); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Pull protocol-specific information from tunnel once tunnel UUID is | ||||
|         // known | ||||
|         tunnel.onuuid = function tunnelAssignedUUID(uuid) { | ||||
|             tunnelService.getProtocol(uuid).then(function protocolRetrieved(protocol) { | ||||
|                 managedClient.protocol = protocol.name; | ||||
|                 managedClient.forms = protocol.connectionForms; | ||||
|             }, requestService.WARN); | ||||
|         }; | ||||
|  | ||||
|         // Update connection state as tunnel state changes | ||||
|         tunnel.onstatechange = function tunnelStateChanged(state) { | ||||
|             $rootScope.$evalAsync(function updateTunnelState() { | ||||
|                  | ||||
|                 switch (state) { | ||||
|  | ||||
|                     // Connection is being established | ||||
|                     case Guacamole.Tunnel.State.CONNECTING: | ||||
|                         ManagedClientState.setConnectionState(managedClient.clientState, | ||||
|                             ManagedClientState.ConnectionState.CONNECTING); | ||||
|                         break; | ||||
|  | ||||
|                     // Connection is established / no longer unstable | ||||
|                     case Guacamole.Tunnel.State.OPEN: | ||||
|                         ManagedClientState.setTunnelUnstable(managedClient.clientState, false); | ||||
|                         break; | ||||
|  | ||||
|                     // Connection is established but misbehaving | ||||
|                     case Guacamole.Tunnel.State.UNSTABLE: | ||||
|                         ManagedClientState.setTunnelUnstable(managedClient.clientState, true); | ||||
|                         break; | ||||
|  | ||||
|                     // Connection has closed | ||||
|                     case Guacamole.Tunnel.State.CLOSED: | ||||
|                         ManagedClientState.setConnectionState(managedClient.clientState, | ||||
|                             ManagedClientState.ConnectionState.DISCONNECTED); | ||||
|                         break; | ||||
|                      | ||||
|                 } | ||||
|              | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Update connection state as client state changes | ||||
|         client.onstatechange = function clientStateChanged(clientState) { | ||||
|             $rootScope.$evalAsync(function updateClientState() { | ||||
|  | ||||
|                 switch (clientState) { | ||||
|  | ||||
|                     // Idle | ||||
|                     case 0: | ||||
|                         ManagedClientState.setConnectionState(managedClient.clientState, | ||||
|                             ManagedClientState.ConnectionState.IDLE); | ||||
|                         break; | ||||
|  | ||||
|                     // Ignore "connecting" state | ||||
|                     case 1: // Connecting | ||||
|                         break; | ||||
|  | ||||
|                     // Connected + waiting | ||||
|                     case 2: | ||||
|                         ManagedClientState.setConnectionState(managedClient.clientState, | ||||
|                             ManagedClientState.ConnectionState.WAITING); | ||||
|                         break; | ||||
|  | ||||
|                     // Connected | ||||
|                     case 3: | ||||
|                         ManagedClientState.setConnectionState(managedClient.clientState, | ||||
|                             ManagedClientState.ConnectionState.CONNECTED); | ||||
|  | ||||
|                         // Send any clipboard data already provided | ||||
|                         if (managedClient.clipboardData) | ||||
|                             ManagedClient.setClipboard(managedClient, managedClient.clipboardData); | ||||
|  | ||||
|                         // Begin streaming audio input if possible | ||||
|                         requestAudioStream(client); | ||||
|  | ||||
|                         // Update thumbnail with initial display contents | ||||
|                         ManagedClient.updateThumbnail(managedClient); | ||||
|                         break; | ||||
|  | ||||
|                     // Update history when disconnecting | ||||
|                     case 4: // Disconnecting | ||||
|                     case 5: // Disconnected | ||||
|                         ManagedClient.updateThumbnail(managedClient); | ||||
|                         break; | ||||
|  | ||||
|                 } | ||||
|  | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Disconnect and update status when the client receives an error | ||||
|         client.onerror = function clientError(status) { | ||||
|             $rootScope.$apply(function handleClientError() { | ||||
|  | ||||
|                 // Disconnect, if connected | ||||
|                 client.disconnect(); | ||||
|  | ||||
|                 // Update state | ||||
|                 ManagedClientState.setConnectionState(managedClient.clientState, | ||||
|                     ManagedClientState.ConnectionState.CLIENT_ERROR, | ||||
|                     status.code); | ||||
|  | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Automatically update the client thumbnail | ||||
|         client.onsync = function syncReceived() { | ||||
|  | ||||
|             var thumbnail = managedClient.thumbnail; | ||||
|             var timestamp = new Date().getTime(); | ||||
|  | ||||
|             // Update thumbnail if it doesn't exist or is old | ||||
|             if (!thumbnail || timestamp - thumbnail.timestamp >= THUMBNAIL_UPDATE_FREQUENCY) { | ||||
|                 $rootScope.$apply(function updateClientThumbnail() { | ||||
|                     ManagedClient.updateThumbnail(managedClient); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         // Test for argument mutability whenever an argument value is | ||||
|         // received | ||||
|         client.onargv = function clientArgumentValueReceived(stream, mimetype, name) { | ||||
|  | ||||
|             // Ignore arguments which do not use a mimetype currently supported | ||||
|             // by the web application | ||||
|             if (mimetype !== 'text/plain') | ||||
|                 return; | ||||
|  | ||||
|             var reader = new Guacamole.StringReader(stream); | ||||
|  | ||||
|             // Assemble received data into a single string | ||||
|             var value = ''; | ||||
|             reader.ontext = function textReceived(text) { | ||||
|                 value += text; | ||||
|             }; | ||||
|  | ||||
|             // Test mutability once stream is finished, storing the current | ||||
|             // value for the argument only if it is mutable | ||||
|             reader.onend = function textComplete() { | ||||
|                 ManagedArgument.getInstance(managedClient, name, value).then(function argumentIsMutable(argument) { | ||||
|                     managedClient.arguments[name] = argument; | ||||
|                 }, function ignoreImmutableArguments() {}); | ||||
|             }; | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         // Handle any received clipboard data | ||||
|         client.onclipboard = function clientClipboardReceived(stream, mimetype) { | ||||
|  | ||||
|             var reader; | ||||
|  | ||||
|             // If the received data is text, read it as a simple string | ||||
|             if (/^text\//.exec(mimetype)) { | ||||
|  | ||||
|                 reader = new Guacamole.StringReader(stream); | ||||
|  | ||||
|                 // Assemble received data into a single string | ||||
|                 var data = ''; | ||||
|                 reader.ontext = function textReceived(text) { | ||||
|                     data += text; | ||||
|                 }; | ||||
|  | ||||
|                 // Set clipboard contents once stream is finished | ||||
|                 reader.onend = function textComplete() { | ||||
|                     $rootScope.$apply(function updateClipboard() { | ||||
|                         managedClient.clipboardData = new ClipboardData({ | ||||
|                             type : mimetype, | ||||
|                             data : data | ||||
|                         }); | ||||
|                     }); | ||||
|                 }; | ||||
|  | ||||
|             } | ||||
|  | ||||
|             // Otherwise read the clipboard data as a Blob | ||||
|             else { | ||||
|                 reader = new Guacamole.BlobReader(stream, mimetype); | ||||
|                 reader.onend = function blobComplete() { | ||||
|                     $rootScope.$apply(function updateClipboard() { | ||||
|                         managedClient.clipboardData = new ClipboardData({ | ||||
|                             type : mimetype, | ||||
|                             data : reader.getBlob() | ||||
|                         }); | ||||
|                     }); | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         // Update level of multi-touch support when known | ||||
|         client.onmultitouch = function multiTouchSupportDeclared(layer, touches) { | ||||
|             managedClient.multiTouchSupport = touches; | ||||
|         }; | ||||
|  | ||||
|         // Update title when a "name" instruction is received | ||||
|         client.onname = function clientNameReceived(name) { | ||||
|             $rootScope.$apply(function updateClientTitle() { | ||||
|                 managedClient.title = name; | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Handle any received files | ||||
|         client.onfile = function clientFileReceived(stream, mimetype, filename) { | ||||
|             tunnelService.downloadStream(tunnel.uuid, stream, mimetype, filename); | ||||
|         }; | ||||
|  | ||||
|         // Handle any received filesystem objects | ||||
|         client.onfilesystem = function fileSystemReceived(object, name) { | ||||
|             $rootScope.$apply(function exposeFilesystem() { | ||||
|                 managedClient.filesystems.push(ManagedFilesystem.getInstance(object, name)); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Handle any received prompts | ||||
|         client.onrequired = function onrequired(parameters) { | ||||
|             $rootScope.$apply(function promptUser() { | ||||
|                 managedClient.requiredParameters = {}; | ||||
|                 angular.forEach(parameters, function populateParameter(name) { | ||||
|                     managedClient.requiredParameters[name] = ''; | ||||
|                 }); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Manage the client display | ||||
|         managedClient.managedDisplay = ManagedDisplay.getInstance(client.getDisplay()); | ||||
|  | ||||
|         // Parse connection details from ID | ||||
|         var clientIdentifier = ClientIdentifier.fromString(id); | ||||
|  | ||||
|         // Connect the Guacamole client | ||||
|         getConnectString(clientIdentifier, connectionParameters) | ||||
|         .then(function connectClient(connectString) { | ||||
|             client.connect(connectString); | ||||
|         }); | ||||
|  | ||||
|         // If using a connection, pull connection name and protocol information | ||||
|         if (clientIdentifier.type === ClientIdentifier.Types.CONNECTION) { | ||||
|             connectionService.getConnection(clientIdentifier.dataSource, clientIdentifier.id) | ||||
|             .then(function connectionRetrieved(connection) { | ||||
|                 managedClient.name = managedClient.title = connection.name; | ||||
|             }, requestService.WARN); | ||||
|         } | ||||
|          | ||||
|         // If using a connection group, pull connection name | ||||
|         else if (clientIdentifier.type === ClientIdentifier.Types.CONNECTION_GROUP) { | ||||
|             connectionGroupService.getConnectionGroup(clientIdentifier.dataSource, clientIdentifier.id) | ||||
|             .then(function connectionGroupRetrieved(group) { | ||||
|                 managedClient.name = managedClient.title = group.name; | ||||
|             }, requestService.WARN); | ||||
|         } | ||||
|  | ||||
|         // If using an active connection, pull corresponding connection, then | ||||
|         // pull connection name and protocol information from that | ||||
|         else if (clientIdentifier.type === ClientIdentifier.Types.ACTIVE_CONNECTION) { | ||||
|             activeConnectionService.getActiveConnection(clientIdentifier.dataSource, clientIdentifier.id) | ||||
|             .then(function activeConnectionRetrieved(activeConnection) { | ||||
|  | ||||
|                 // Attempt to retrieve connection details only if the | ||||
|                 // underlying connection is known | ||||
|                 if (activeConnection.connectionIdentifier) { | ||||
|                     connectionService.getConnection(clientIdentifier.dataSource, activeConnection.connectionIdentifier) | ||||
|                     .then(function connectionRetrieved(connection) { | ||||
|                         managedClient.name = managedClient.title = connection.name; | ||||
|                     }, requestService.WARN); | ||||
|                 } | ||||
|  | ||||
|             }, requestService.WARN); | ||||
|         } | ||||
|  | ||||
|         return managedClient; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Uploads the given file to the server through the given Guacamole client. | ||||
|      * The file transfer can be monitored through the corresponding entry in | ||||
|      * the uploads array of the given managedClient. | ||||
|      *  | ||||
|      * @param {ManagedClient} managedClient | ||||
|      *     The ManagedClient through which the file is to be uploaded. | ||||
|      *  | ||||
|      * @param {File} file | ||||
|      *     The file to upload. | ||||
|      * | ||||
|      * @param {ManagedFilesystem} [filesystem] | ||||
|      *     The filesystem to upload the file to, if any. If not specified, the | ||||
|      *     file will be sent as a generic Guacamole file stream. | ||||
|      * | ||||
|      * @param {ManagedFilesystem.File} [directory=filesystem.currentDirectory] | ||||
|      *     The directory within the given filesystem to upload the file to. If | ||||
|      *     not specified, but a filesystem is given, the current directory of | ||||
|      *     that filesystem will be used. | ||||
|      */ | ||||
|     ManagedClient.uploadFile = function uploadFile(managedClient, file, filesystem, directory) { | ||||
|  | ||||
|         // Use generic Guacamole file streams by default | ||||
|         var object = null; | ||||
|         var streamName = null; | ||||
|  | ||||
|         // If a filesystem is given, determine the destination object and stream | ||||
|         if (filesystem) { | ||||
|             object = filesystem.object; | ||||
|             streamName = (directory || filesystem.currentDirectory).streamName + '/' + file.name; | ||||
|         } | ||||
|  | ||||
|         // Start and manage file upload | ||||
|         managedClient.uploads.push(ManagedFileUpload.getInstance(managedClient, file, object, streamName)); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Sends the given clipboard data over the given Guacamole client, setting | ||||
|      * the contents of the remote clipboard to the data provided. | ||||
|      * | ||||
|      * @param {ManagedClient} managedClient | ||||
|      *     The ManagedClient over which the given clipboard data is to be sent. | ||||
|      * | ||||
|      * @param {ClipboardData} data | ||||
|      *     The clipboard data to send. | ||||
|      */ | ||||
|     ManagedClient.setClipboard = function setClipboard(managedClient, data) { | ||||
|  | ||||
|         var writer; | ||||
|  | ||||
|         // Create stream with proper mimetype | ||||
|         var stream = managedClient.client.createClipboardStream(data.type); | ||||
|  | ||||
|         // Send data as a string if it is stored as a string | ||||
|         if (typeof data.data === 'string') { | ||||
|             writer = new Guacamole.StringWriter(stream); | ||||
|             writer.sendText(data.data); | ||||
|             writer.sendEnd(); | ||||
|         } | ||||
|  | ||||
|         // Otherwise, assume the data is a File/Blob | ||||
|         else { | ||||
|  | ||||
|             // Write File/Blob asynchronously | ||||
|             writer = new Guacamole.BlobWriter(stream); | ||||
|             writer.oncomplete = function clipboardSent() { | ||||
|                 writer.sendEnd(); | ||||
|             }; | ||||
|  | ||||
|             // Begin sending data | ||||
|             writer.sendBlob(data.data); | ||||
|  | ||||
|         } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Assigns the given value to the connection parameter having the given | ||||
|      * name, updating the behavior of the connection in real-time. If the | ||||
|      * connection parameter is not editable, this function has no effect. | ||||
|      * | ||||
|      * @param {ManagedClient} managedClient | ||||
|      *     The ManagedClient instance associated with the active connection | ||||
|      *     being modified. | ||||
|      * | ||||
|      * @param {String} name | ||||
|      *     The name of the connection parameter to modify. | ||||
|      * | ||||
|      * @param {String} value | ||||
|      *     The value to attempt to assign to the given connection parameter. | ||||
|      */ | ||||
|     ManagedClient.setArgument = function setArgument(managedClient, name, value) { | ||||
|         var managedArgument = managedClient.arguments[name]; | ||||
|         if (managedArgument && ManagedArgument.setValue(managedArgument, value)) | ||||
|             delete managedClient.arguments[name]; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Sends the given connection parameter values using "argv" streams, | ||||
|      * updating the behavior of the connection in real-time if the server is | ||||
|      * expecting or requiring these parameters. | ||||
|      * | ||||
|      * @param {ManagedClient} managedClient | ||||
|      *     The ManagedClient instance associated with the active connection | ||||
|      *     being modified. | ||||
|      * | ||||
|      * @param {Object.<String, String>} values | ||||
|      *     The set of values to attempt to assign to corresponding connection | ||||
|      *     parameters, where each object key is the connection parameter being | ||||
|      *     set. | ||||
|      */ | ||||
|     ManagedClient.sendArguments = function sendArguments(managedClient, values) { | ||||
|         angular.forEach(values, function sendArgument(value, name) { | ||||
|             var stream = managedClient.client.createArgumentValueStream("text/plain", name); | ||||
|             var writer = new Guacamole.StringWriter(stream); | ||||
|             writer.sendText(value); | ||||
|             writer.sendEnd(); | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Retrieves the current values of all editable connection parameters as a | ||||
|      * set of name/value pairs suitable for use as the model of a form which | ||||
|      * edits those parameters. | ||||
|      * | ||||
|      * @param {ManagedClient} client | ||||
|      *     The ManagedClient instance associated with the active connection | ||||
|      *     whose parameter values are being retrieved. | ||||
|      * | ||||
|      * @returns {Object.<String, String>} | ||||
|      *     A new set of name/value pairs containing the current values of all | ||||
|      *     editable parameters. | ||||
|      */ | ||||
|     ManagedClient.getArgumentModel = function getArgumentModel(client) { | ||||
|  | ||||
|         var model = {}; | ||||
|  | ||||
|         angular.forEach(client.arguments, function addModelEntry(managedArgument) { | ||||
|             model[managedArgument.name] = managedArgument.value; | ||||
|         }); | ||||
|  | ||||
|         return model; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Produces a sharing link for the given ManagedClient using the given | ||||
|      * sharing profile. The resulting sharing link, and any required login | ||||
|      * information, can be retrieved from the <code>shareLinks</code> property | ||||
|      * of the given ManagedClient once the various underlying service calls | ||||
|      * succeed. | ||||
|      * | ||||
|      * @param {ManagedClient} client | ||||
|      *     The ManagedClient which will be shared via the generated sharing | ||||
|      *     link. | ||||
|      * | ||||
|      * @param {SharingProfile} sharingProfile | ||||
|      *     The sharing profile to use to generate the sharing link. | ||||
|      * | ||||
|      * @returns {Promise} | ||||
|      *     A Promise which is resolved once the sharing link has been | ||||
|      *     successfully generated, and rejected if generating the link fails. | ||||
|      */ | ||||
|     ManagedClient.createShareLink = function createShareLink(client, sharingProfile) { | ||||
|  | ||||
|         // Retrieve sharing credentials for the sake of generating a share link | ||||
|         var credentialRequest = tunnelService.getSharingCredentials( | ||||
|                 client.tunnel.uuid, sharingProfile.identifier); | ||||
|  | ||||
|         // Add a new share link once the credentials are ready | ||||
|         credentialRequest.then(function sharingCredentialsReceived(sharingCredentials) { | ||||
|             client.shareLinks[sharingProfile.identifier] = | ||||
|                 ManagedShareLink.getInstance(sharingProfile, sharingCredentials); | ||||
|         }, requestService.WARN); | ||||
|  | ||||
|         return credentialRequest; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Returns whether the given ManagedClient is being shared. A ManagedClient | ||||
|      * is shared if it has any associated share links. | ||||
|      * | ||||
|      * @param {ManagedClient} client | ||||
|      *     The ManagedClient to check. | ||||
|      * | ||||
|      * @returns {Boolean} | ||||
|      *     true if the ManagedClient has at least one associated share link, | ||||
|      *     false otherwise. | ||||
|      */ | ||||
|     ManagedClient.isShared = function isShared(client) { | ||||
|  | ||||
|         // The connection is shared if at least one share link exists | ||||
|         for (var dummy in client.shareLinks) | ||||
|             return true; | ||||
|  | ||||
|         // No share links currently exist | ||||
|         return false; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Store the thumbnail of the given managed client within the connection | ||||
|      * history under its associated ID. If the client is not connected, this | ||||
|      * function has no effect. | ||||
|      * | ||||
|      * @param {ManagedClient} managedClient | ||||
|      *     The client whose history entry should be updated. | ||||
|      */ | ||||
|     ManagedClient.updateThumbnail = function updateThumbnail(managedClient) { | ||||
|  | ||||
|         var display = managedClient.client.getDisplay(); | ||||
|  | ||||
|         // Update stored thumbnail of previous connection | ||||
|         if (display && display.getWidth() > 0 && display.getHeight() > 0) { | ||||
|  | ||||
|             // Get screenshot | ||||
|             var canvas = display.flatten(); | ||||
|  | ||||
|             // Calculate scale of thumbnail (max 320x240, max zoom 100%) | ||||
|             var scale = Math.min(320 / canvas.width, 240 / canvas.height, 1); | ||||
|  | ||||
|             // Create thumbnail canvas | ||||
|             var thumbnail = $document[0].createElement("canvas"); | ||||
|             thumbnail.width  = canvas.width*scale; | ||||
|             thumbnail.height = canvas.height*scale; | ||||
|  | ||||
|             // Scale screenshot to thumbnail | ||||
|             var context = thumbnail.getContext("2d"); | ||||
|             context.drawImage(canvas, | ||||
|                 0, 0, canvas.width, canvas.height, | ||||
|                 0, 0, thumbnail.width, thumbnail.height | ||||
|             ); | ||||
|  | ||||
|             // Store updated thumbnail within client | ||||
|             managedClient.thumbnail = new ManagedClientThumbnail({ | ||||
|                 timestamp : new Date().getTime(), | ||||
|                 canvas    : thumbnail | ||||
|             }); | ||||
|  | ||||
|             // Update historical thumbnail | ||||
|             guacHistory.updateThumbnail(managedClient.id, thumbnail.toDataURL("image/png")); | ||||
|  | ||||
|         } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return ManagedClient; | ||||
|  | ||||
| }]); | ||||
| @@ -1,185 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the ManagedClient class used by the guacClientManager service. | ||||
|  */ | ||||
| angular.module('client').factory('ManagedClientState', [function defineManagedClientState() { | ||||
|  | ||||
|     /** | ||||
|      * Object which represents the state of a Guacamole client and its tunnel, | ||||
|      * including any error conditions. | ||||
|      *  | ||||
|      * @constructor | ||||
|      * @param {ManagedClientState|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedClientState. | ||||
|      */ | ||||
|     var ManagedClientState = function ManagedClientState(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The current connection state. Valid values are described by | ||||
|          * ManagedClientState.ConnectionState. | ||||
|          * | ||||
|          * @type String | ||||
|          * @default ManagedClientState.ConnectionState.IDLE | ||||
|          */ | ||||
|         this.connectionState = template.connectionState || ManagedClientState.ConnectionState.IDLE; | ||||
|  | ||||
|         /** | ||||
|          * Whether the network connection used by the tunnel seems unstable. If | ||||
|          * the network connection is unstable, the remote desktop connection | ||||
|          * may perform poorly or disconnect. | ||||
|          * | ||||
|          * @type Boolean | ||||
|          * @default false | ||||
|          */ | ||||
|         this.tunnelUnstable = template.tunnelUnstable || false; | ||||
|  | ||||
|         /** | ||||
|          * The status code of the current error condition, if connectionState | ||||
|          * is CLIENT_ERROR or TUNNEL_ERROR. For all other connectionState | ||||
|          * values, this will be @link{Guacamole.Status.Code.SUCCESS}. | ||||
|          * | ||||
|          * @type Number | ||||
|          * @default Guacamole.Status.Code.SUCCESS | ||||
|          */ | ||||
|         this.statusCode = template.statusCode || Guacamole.Status.Code.SUCCESS; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Valid connection state strings. Each state string is associated with a | ||||
|      * specific state of a Guacamole connection. | ||||
|      */ | ||||
|     ManagedClientState.ConnectionState = { | ||||
|  | ||||
|         /** | ||||
|          * The Guacamole connection has not yet been attempted. | ||||
|          *  | ||||
|          * @type String | ||||
|          */ | ||||
|         IDLE : "IDLE", | ||||
|  | ||||
|         /** | ||||
|          * The Guacamole connection is being established. | ||||
|          *  | ||||
|          * @type String | ||||
|          */ | ||||
|         CONNECTING : "CONNECTING", | ||||
|  | ||||
|         /** | ||||
|          * The Guacamole connection has been successfully established, and the | ||||
|          * client is now waiting for receipt of initial graphical data. | ||||
|          *  | ||||
|          * @type String | ||||
|          */ | ||||
|         WAITING : "WAITING", | ||||
|  | ||||
|         /** | ||||
|          * The Guacamole connection has been successfully established, and | ||||
|          * initial graphical data has been received. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         CONNECTED : "CONNECTED", | ||||
|  | ||||
|         /** | ||||
|          * The Guacamole connection has terminated successfully. No errors are | ||||
|          * indicated. | ||||
|          *  | ||||
|          * @type String | ||||
|          */ | ||||
|         DISCONNECTED : "DISCONNECTED", | ||||
|  | ||||
|         /** | ||||
|          * The Guacamole connection has terminated due to an error reported by | ||||
|          * the client. The associated error code is stored in statusCode. | ||||
|          *  | ||||
|          * @type String | ||||
|          */ | ||||
|         CLIENT_ERROR : "CLIENT_ERROR", | ||||
|  | ||||
|         /** | ||||
|          * The Guacamole connection has terminated due to an error reported by | ||||
|          * the tunnel. The associated error code is stored in statusCode. | ||||
|          *  | ||||
|          * @type String | ||||
|          */ | ||||
|         TUNNEL_ERROR : "TUNNEL_ERROR" | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Sets the current client state and, if given, the associated status code. | ||||
|      * If an error is already represented, this function has no effect. If the | ||||
|      * client state was previously marked as unstable, that flag is implicitly | ||||
|      * cleared. | ||||
|      * | ||||
|      * @param {ManagedClientState} clientState | ||||
|      *     The ManagedClientState to update. | ||||
|      * | ||||
|      * @param {String} connectionState | ||||
|      *     The connection state to assign to the given ManagedClientState, as | ||||
|      *     listed within ManagedClientState.ConnectionState. | ||||
|      *  | ||||
|      * @param {Number} [statusCode] | ||||
|      *     The status code to assign to the given ManagedClientState, if any, | ||||
|      *     as listed within Guacamole.Status.Code. If no status code is | ||||
|      *     specified, the status code of the ManagedClientState is not touched. | ||||
|      */ | ||||
|     ManagedClientState.setConnectionState = function(clientState, connectionState, statusCode) { | ||||
|  | ||||
|         // Do not set state after an error is registered | ||||
|         if (clientState.connectionState === ManagedClientState.ConnectionState.TUNNEL_ERROR | ||||
|          || clientState.connectionState === ManagedClientState.ConnectionState.CLIENT_ERROR) | ||||
|             return; | ||||
|  | ||||
|         // Update connection state | ||||
|         clientState.connectionState = connectionState; | ||||
|         clientState.tunnelUnstable = false; | ||||
|  | ||||
|         // Set status code, if given | ||||
|         if (statusCode) | ||||
|             clientState.statusCode = statusCode; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Updates the given client state, setting whether the underlying tunnel | ||||
|      * is currently unstable. An unstable tunnel is not necessarily | ||||
|      * disconnected, but appears to be misbehaving and may be disconnected. | ||||
|      * | ||||
|      * @param {ManagedClientState} clientState | ||||
|      *     The ManagedClientState to update. | ||||
|      * | ||||
|      * @param {Boolean} unstable | ||||
|      *     Whether the underlying tunnel of the connection currently appears | ||||
|      *     unstable. | ||||
|      */ | ||||
|     ManagedClientState.setTunnelUnstable = function setTunnelUnstable(clientState, unstable) { | ||||
|         clientState.tunnelUnstable = unstable; | ||||
|     }; | ||||
|  | ||||
|     return ManagedClientState; | ||||
|  | ||||
| }]); | ||||
| @@ -1,58 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the ManagedClientThumbnail class used by ManagedClient. | ||||
|  */ | ||||
| angular.module('client').factory('ManagedClientThumbnail', [function defineManagedClientThumbnail() { | ||||
|  | ||||
|     /** | ||||
|      * Object which represents a thumbnail of the Guacamole client display, | ||||
|      * along with the time that the thumbnail was generated. | ||||
|      * | ||||
|      * @constructor | ||||
|      * @param {ManagedClientThumbnail|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedClientThumbnail. | ||||
|      */ | ||||
|     var ManagedClientThumbnail = function ManagedClientThumbnail(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The time that this thumbnail was generated, as the number of | ||||
|          * milliseconds elapsed since midnight of January 1, 1970 UTC. | ||||
|          * | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.timestamp = template.timestamp; | ||||
|  | ||||
|         /** | ||||
|          * The thumbnail of the Guacamole client display. | ||||
|          * | ||||
|          * @type HTMLCanvasElement | ||||
|          */ | ||||
|         this.canvas = template.canvas; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return ManagedClientThumbnail; | ||||
|  | ||||
| }]); | ||||
| @@ -1,174 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the ManagedDisplay class used by the guacClientManager service. | ||||
|  */ | ||||
| angular.module('client').factory('ManagedDisplay', ['$rootScope', | ||||
|     function defineManagedDisplay($rootScope) { | ||||
|  | ||||
|     /** | ||||
|      * Object which serves as a surrogate interface, encapsulating a Guacamole | ||||
|      * display while it is active, allowing it to be detached and reattached | ||||
|      * from different client views. | ||||
|      *  | ||||
|      * @constructor | ||||
|      * @param {ManagedDisplay|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedDisplay. | ||||
|      */ | ||||
|     var ManagedDisplay = function ManagedDisplay(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The underlying Guacamole display. | ||||
|          *  | ||||
|          * @type Guacamole.Display | ||||
|          */ | ||||
|         this.display = template.display; | ||||
|  | ||||
|         /** | ||||
|          * The current size of the Guacamole display. | ||||
|          * | ||||
|          * @type ManagedDisplay.Dimensions | ||||
|          */ | ||||
|         this.size = new ManagedDisplay.Dimensions(template.size); | ||||
|  | ||||
|         /** | ||||
|          * The current mouse cursor, if any. | ||||
|          *  | ||||
|          * @type ManagedDisplay.Cursor | ||||
|          */ | ||||
|         this.cursor = template.cursor; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Object which represents the size of the Guacamole display. | ||||
|      * | ||||
|      * @constructor | ||||
|      * @param {ManagedDisplay.Dimensions|Object} template | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedDisplay.Dimensions. | ||||
|      */ | ||||
|     ManagedDisplay.Dimensions = function Dimensions(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The current width of the Guacamole display, in pixels. | ||||
|          * | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.width = template.width || 0; | ||||
|  | ||||
|         /** | ||||
|          * The current width of the Guacamole display, in pixels. | ||||
|          * | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.height = template.height || 0; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Object which represents a mouse cursor used by the Guacamole display. | ||||
|      * | ||||
|      * @constructor | ||||
|      * @param {ManagedDisplay.Cursor|Object} template | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedDisplay.Cursor. | ||||
|      */ | ||||
|     ManagedDisplay.Cursor = function Cursor(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The actual mouse cursor image. | ||||
|          *  | ||||
|          * @type HTMLCanvasElement | ||||
|          */ | ||||
|         this.canvas = template.canvas; | ||||
|  | ||||
|         /** | ||||
|          * The X coordinate of the cursor hotspot. | ||||
|          *  | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.x = template.x; | ||||
|  | ||||
|         /** | ||||
|          * The Y coordinate of the cursor hotspot. | ||||
|          *  | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.y = template.y; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new ManagedDisplay which represents the current state of the | ||||
|      * given Guacamole display. | ||||
|      *  | ||||
|      * @param {Guacamole.Display} display | ||||
|      *     The Guacamole display to represent. Changes to this display will | ||||
|      *     affect this ManagedDisplay. | ||||
|      * | ||||
|      * @returns {ManagedDisplay} | ||||
|      *     A new ManagedDisplay which represents the current state of the | ||||
|      *     given Guacamole display. | ||||
|      */ | ||||
|     ManagedDisplay.getInstance = function getInstance(display) { | ||||
|  | ||||
|         var managedDisplay = new ManagedDisplay({ | ||||
|             display : display | ||||
|         }); | ||||
|  | ||||
|         // Store changes to display size | ||||
|         display.onresize = function setClientSize() { | ||||
|             $rootScope.$apply(function updateClientSize() { | ||||
|                 managedDisplay.size = new ManagedDisplay.Dimensions({ | ||||
|                     width  : display.getWidth(), | ||||
|                     height : display.getHeight() | ||||
|                 }); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Store changes to display cursor | ||||
|         display.oncursor = function setClientCursor(canvas, x, y) { | ||||
|             $rootScope.$apply(function updateClientCursor() { | ||||
|                 managedDisplay.cursor = new ManagedDisplay.Cursor({ | ||||
|                     canvas : canvas, | ||||
|                     x      : x, | ||||
|                     y      : y | ||||
|                 }); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         return managedDisplay; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return ManagedDisplay; | ||||
|  | ||||
| }]); | ||||
| @@ -1,133 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the ManagedFileTransferState class used by the guacClientManager | ||||
|  * service. | ||||
|  */ | ||||
| angular.module('client').factory('ManagedFileTransferState', [function defineManagedFileTransferState() { | ||||
|  | ||||
|     /** | ||||
|      * Object which represents the state of a Guacamole stream, including any | ||||
|      * error conditions. | ||||
|      *  | ||||
|      * @constructor | ||||
|      * @param {ManagedFileTransferState|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedFileTransferState. | ||||
|      */ | ||||
|     var ManagedFileTransferState = function ManagedFileTransferState(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The current stream state. Valid values are described by | ||||
|          * ManagedFileTransferState.StreamState. | ||||
|          * | ||||
|          * @type String | ||||
|          * @default ManagedFileTransferState.StreamState.IDLE | ||||
|          */ | ||||
|         this.streamState = template.streamState || ManagedFileTransferState.StreamState.IDLE; | ||||
|  | ||||
|         /** | ||||
|          * The status code of the current error condition, if streamState | ||||
|          * is ERROR. For all other streamState values, this will be | ||||
|          * @link{Guacamole.Status.Code.SUCCESS}. | ||||
|          * | ||||
|          * @type Number | ||||
|          * @default Guacamole.Status.Code.SUCCESS | ||||
|          */ | ||||
|         this.statusCode = template.statusCode || Guacamole.Status.Code.SUCCESS; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Valid stream state strings. Each state string is associated with a | ||||
|      * specific state of a Guacamole stream. | ||||
|      */ | ||||
|     ManagedFileTransferState.StreamState = { | ||||
|  | ||||
|         /** | ||||
|          * The stream has not yet been opened. | ||||
|          *  | ||||
|          * @type String | ||||
|          */ | ||||
|         IDLE : "IDLE", | ||||
|  | ||||
|         /** | ||||
|          * The stream has been successfully established. Data can be sent or | ||||
|          * received. | ||||
|          *  | ||||
|          * @type String | ||||
|          */ | ||||
|         OPEN : "OPEN", | ||||
|  | ||||
|         /** | ||||
|          * The stream has terminated successfully. No errors are indicated. | ||||
|          *  | ||||
|          * @type String | ||||
|          */ | ||||
|         CLOSED : "CLOSED", | ||||
|  | ||||
|         /** | ||||
|          * The stream has terminated due to an error. The associated error code | ||||
|          * is stored in statusCode. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         ERROR : "ERROR" | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Sets the current transfer state and, if given, the associated status | ||||
|      * code. If an error is already represented, this function has no effect. | ||||
|      * | ||||
|      * @param {ManagedFileTransferState} transferState | ||||
|      *     The ManagedFileTransferState to update. | ||||
|      * | ||||
|      * @param {String} streamState | ||||
|      *     The stream state to assign to the given ManagedFileTransferState, as | ||||
|      *     listed within ManagedFileTransferState.StreamState. | ||||
|      *  | ||||
|      * @param {Number} [statusCode] | ||||
|      *     The status code to assign to the given ManagedFileTransferState, if | ||||
|      *     any, as listed within Guacamole.Status.Code. If no status code is | ||||
|      *     specified, the status code of the ManagedFileTransferState is not | ||||
|      *     touched. | ||||
|      */ | ||||
|     ManagedFileTransferState.setStreamState = function setStreamState(transferState, streamState, statusCode) { | ||||
|  | ||||
|         // Do not set state after an error is registered | ||||
|         if (transferState.streamState === ManagedFileTransferState.StreamState.ERROR) | ||||
|             return; | ||||
|  | ||||
|         // Update stream state | ||||
|         transferState.streamState = streamState; | ||||
|  | ||||
|         // Set status code, if given | ||||
|         if (statusCode) | ||||
|             transferState.statusCode = statusCode; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return ManagedFileTransferState; | ||||
|  | ||||
| }]); | ||||
| @@ -1,202 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the ManagedFileUpload class used by the guacClientManager service. | ||||
|  */ | ||||
| angular.module('client').factory('ManagedFileUpload', ['$rootScope', '$injector', | ||||
|     function defineManagedFileUpload($rootScope, $injector) { | ||||
|  | ||||
|     // Required types | ||||
|     var Error                    = $injector.get('Error'); | ||||
|     var ManagedFileTransferState = $injector.get('ManagedFileTransferState'); | ||||
|  | ||||
|     // Required services | ||||
|     var requestService = $injector.get('requestService'); | ||||
|     var tunnelService  = $injector.get('tunnelService'); | ||||
|  | ||||
|     /** | ||||
|      * Object which serves as a surrogate interface, encapsulating a Guacamole | ||||
|      * file upload while it is active, allowing it to be detached and | ||||
|      * reattached from different client views. | ||||
|      *  | ||||
|      * @constructor | ||||
|      * @param {ManagedFileUpload|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedFileUpload. | ||||
|      */ | ||||
|     var ManagedFileUpload = function ManagedFileUpload(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The current state of the file transfer stream. | ||||
|          * | ||||
|          * @type ManagedFileTransferState | ||||
|          */ | ||||
|         this.transferState = template.transferState || new ManagedFileTransferState(); | ||||
|  | ||||
|         /** | ||||
|          * The mimetype of the file being transferred. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.mimetype = template.mimetype; | ||||
|  | ||||
|         /** | ||||
|          * The filename of the file being transferred. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.filename = template.filename; | ||||
|  | ||||
|         /** | ||||
|          * The number of bytes transferred so far. | ||||
|          * | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.progress = template.progress; | ||||
|  | ||||
|         /** | ||||
|          * The total number of bytes in the file. | ||||
|          * | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.length = template.length; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new ManagedFileUpload which uploads the given file to the | ||||
|      * server through the given Guacamole client. | ||||
|      *  | ||||
|      * @param {ManagedClient} managedClient | ||||
|      *     The ManagedClient through which the file is to be uploaded. | ||||
|      *  | ||||
|      * @param {File} file | ||||
|      *     The file to upload. | ||||
|      *      | ||||
|      * @param {Object} [object] | ||||
|      *     The object to upload the file to, if any, such as a filesystem | ||||
|      *     object. | ||||
|      * | ||||
|      * @param {String} [streamName] | ||||
|      *     The name of the stream to upload the file to. If an object is given, | ||||
|      *     this must be specified. | ||||
|      * | ||||
|      * @return {ManagedFileUpload} | ||||
|      *     A new ManagedFileUpload object which can be used to track the | ||||
|      *     progress of the upload. | ||||
|      */ | ||||
|     ManagedFileUpload.getInstance = function getInstance(managedClient, file, object, streamName) { | ||||
|  | ||||
|         var managedFileUpload = new ManagedFileUpload(); | ||||
|  | ||||
|         // Pull Guacamole.Tunnel and Guacamole.Client from given ManagedClient | ||||
|         var client = managedClient.client; | ||||
|         var tunnel = managedClient.tunnel; | ||||
|  | ||||
|         // Open file for writing | ||||
|         var stream; | ||||
|         if (!object) | ||||
|             stream = client.createFileStream(file.type, file.name); | ||||
|  | ||||
|         // If object/streamName specified, upload to that instead of a file | ||||
|         // stream | ||||
|         else | ||||
|             stream = object.createOutputStream(file.type, streamName); | ||||
|  | ||||
|         // Notify that the file transfer is pending | ||||
|         $rootScope.$evalAsync(function uploadStreamOpen() { | ||||
|  | ||||
|             // Init managed upload | ||||
|             managedFileUpload.filename = file.name; | ||||
|             managedFileUpload.mimetype = file.type; | ||||
|             managedFileUpload.progress = 0; | ||||
|             managedFileUpload.length   = file.size; | ||||
|  | ||||
|             // Notify that stream is open | ||||
|             ManagedFileTransferState.setStreamState(managedFileUpload.transferState, | ||||
|                 ManagedFileTransferState.StreamState.OPEN); | ||||
|  | ||||
|         }); | ||||
|  | ||||
|         // Upload file once stream is acknowledged | ||||
|         stream.onack = function beginUpload(status) { | ||||
|  | ||||
|             // Notify of any errors from the Guacamole server | ||||
|             if (status.isError()) { | ||||
|                 $rootScope.$apply(function uploadStreamError() { | ||||
|                     ManagedFileTransferState.setStreamState(managedFileUpload.transferState, | ||||
|                         ManagedFileTransferState.StreamState.ERROR, | ||||
|                         status.code); | ||||
|                 }); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // Begin upload | ||||
|             tunnelService.uploadToStream(tunnel.uuid, stream, file, function uploadContinuing(length) { | ||||
|                 $rootScope.$apply(function uploadStreamProgress() { | ||||
|                     managedFileUpload.progress = length; | ||||
|                 }); | ||||
|             }) | ||||
|  | ||||
|             // Notify if upload succeeds | ||||
|             .then(function uploadSuccessful() { | ||||
|  | ||||
|                 // Upload complete | ||||
|                 managedFileUpload.progress = file.size; | ||||
|                 ManagedFileTransferState.setStreamState(managedFileUpload.transferState, | ||||
|                     ManagedFileTransferState.StreamState.CLOSED); | ||||
|  | ||||
|                 // Notify of upload completion | ||||
|                 $rootScope.$broadcast('guacUploadComplete', file.name); | ||||
|  | ||||
|             }, | ||||
|  | ||||
|             // Notify if upload fails | ||||
|             requestService.createErrorCallback(function uploadFailed(error) { | ||||
|  | ||||
|                 // Use provide status code if the error is coming from the stream | ||||
|                 if (error.type === Error.Type.STREAM_ERROR) | ||||
|                     ManagedFileTransferState.setStreamState(managedFileUpload.transferState, | ||||
|                         ManagedFileTransferState.StreamState.ERROR, | ||||
|                         error.statusCode); | ||||
|  | ||||
|                 // Fail with internal error for all other causes | ||||
|                 else | ||||
|                     ManagedFileTransferState.setStreamState(managedFileUpload.transferState, | ||||
|                         ManagedFileTransferState.StreamState.ERROR, | ||||
|                         Guacamole.Status.Code.INTERNAL_ERROR); | ||||
|  | ||||
|             })); | ||||
|  | ||||
|             // Ignore all further acks | ||||
|             stream.onack = null; | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         return managedFileUpload; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return ManagedFileUpload; | ||||
|  | ||||
| }]); | ||||
| @@ -1,330 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the ManagedFilesystem class used by ManagedClient to represent | ||||
|  * available remote filesystems. | ||||
|  */ | ||||
| angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector', | ||||
|     function defineManagedFilesystem($rootScope, $injector) { | ||||
|  | ||||
|     // Required types | ||||
|     var tunnelService = $injector.get('tunnelService'); | ||||
|  | ||||
|     /** | ||||
|      * Object which serves as a surrogate interface, encapsulating a Guacamole | ||||
|      * filesystem object while it is active, allowing it to be detached and | ||||
|      * reattached from different client views. | ||||
|      *  | ||||
|      * @constructor | ||||
|      * @param {ManagedFilesystem|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedFilesystem. | ||||
|      */ | ||||
|     var ManagedFilesystem = function ManagedFilesystem(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The Guacamole filesystem object, as received via a "filesystem" | ||||
|          * instruction. | ||||
|          * | ||||
|          * @type Guacamole.Object | ||||
|          */ | ||||
|         this.object = template.object; | ||||
|  | ||||
|         /** | ||||
|          * The declared, human-readable name of the filesystem | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.name = template.name; | ||||
|  | ||||
|         /** | ||||
|          * The root directory of the filesystem. | ||||
|          * | ||||
|          * @type ManagedFilesystem.File | ||||
|          */ | ||||
|         this.root = template.root; | ||||
|  | ||||
|         /** | ||||
|          * The current directory being viewed or manipulated within the | ||||
|          * filesystem. | ||||
|          * | ||||
|          * @type ManagedFilesystem.File | ||||
|          */ | ||||
|         this.currentDirectory = template.currentDirectory || template.root; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Refreshes the contents of the given file, if that file is a directory. | ||||
|      * Only the immediate children of the file are refreshed. Files further | ||||
|      * down the directory tree are not refreshed. | ||||
|      * | ||||
|      * @param {ManagedFilesystem} filesystem | ||||
|      *     The filesystem associated with the file being refreshed. | ||||
|      * | ||||
|      * @param {ManagedFilesystem.File} file | ||||
|      *     The file being refreshed. | ||||
|      */ | ||||
|     ManagedFilesystem.refresh = function updateDirectory(filesystem, file) { | ||||
|  | ||||
|         // Do not attempt to refresh the contents of directories | ||||
|         if (file.mimetype !== Guacamole.Object.STREAM_INDEX_MIMETYPE) | ||||
|             return; | ||||
|  | ||||
|         // Request contents of given file | ||||
|         filesystem.object.requestInputStream(file.streamName, function handleStream(stream, mimetype) { | ||||
|  | ||||
|             // Ignore stream if mimetype is wrong | ||||
|             if (mimetype !== Guacamole.Object.STREAM_INDEX_MIMETYPE) { | ||||
|                 stream.sendAck('Unexpected mimetype', Guacamole.Status.Code.UNSUPPORTED); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // Signal server that data is ready to be received | ||||
|             stream.sendAck('Ready', Guacamole.Status.Code.SUCCESS); | ||||
|  | ||||
|             // Read stream as JSON | ||||
|             var reader = new Guacamole.JSONReader(stream); | ||||
|  | ||||
|             // Acknowledge received JSON blobs | ||||
|             reader.onprogress = function onprogress() { | ||||
|                 stream.sendAck("Received", Guacamole.Status.Code.SUCCESS); | ||||
|             }; | ||||
|  | ||||
|             // Reset contents of directory | ||||
|             reader.onend = function jsonReady() { | ||||
|                 $rootScope.$evalAsync(function updateFileContents() { | ||||
|  | ||||
|                     // Empty contents | ||||
|                     file.files = {}; | ||||
|  | ||||
|                     // Determine the expected filename prefix of each stream | ||||
|                     var expectedPrefix = file.streamName; | ||||
|                     if (expectedPrefix.charAt(expectedPrefix.length - 1) !== '/') | ||||
|                         expectedPrefix += '/'; | ||||
|  | ||||
|                     // For each received stream name | ||||
|                     var mimetypes = reader.getJSON(); | ||||
|                     for (var name in mimetypes) { | ||||
|  | ||||
|                         // Assert prefix is correct | ||||
|                         if (name.substring(0, expectedPrefix.length) !== expectedPrefix) | ||||
|                             continue; | ||||
|  | ||||
|                         // Extract filename from stream name | ||||
|                         var filename = name.substring(expectedPrefix.length); | ||||
|  | ||||
|                         // Deduce type from mimetype | ||||
|                         var type = ManagedFilesystem.File.Type.NORMAL; | ||||
|                         if (mimetypes[name] === Guacamole.Object.STREAM_INDEX_MIMETYPE) | ||||
|                             type = ManagedFilesystem.File.Type.DIRECTORY; | ||||
|  | ||||
|                         // Add file entry | ||||
|                         file.files[filename] = new ManagedFilesystem.File({ | ||||
|                             mimetype   : mimetypes[name], | ||||
|                             streamName : name, | ||||
|                             type       : type, | ||||
|                             parent     : file, | ||||
|                             name       : filename | ||||
|                         }); | ||||
|  | ||||
|                     } | ||||
|  | ||||
|                 }); | ||||
|             }; | ||||
|  | ||||
|         }); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new ManagedFilesystem instance from the given Guacamole.Object | ||||
|      * and human-readable name. Upon creation, a request to populate the | ||||
|      * contents of the root directory will be automatically dispatched. | ||||
|      * | ||||
|      * @param {Guacamole.Object} object | ||||
|      *     The Guacamole.Object defining the filesystem. | ||||
|      * | ||||
|      * @param {String} name | ||||
|      *     A human-readable name for the filesystem. | ||||
|      * | ||||
|      * @returns {ManagedFilesystem} | ||||
|      *     The newly-created ManagedFilesystem. | ||||
|      */ | ||||
|     ManagedFilesystem.getInstance = function getInstance(object, name) { | ||||
|  | ||||
|         // Init new filesystem object | ||||
|         var managedFilesystem = new ManagedFilesystem({ | ||||
|             object : object, | ||||
|             name   : name, | ||||
|             root   : new ManagedFilesystem.File({ | ||||
|                 mimetype   : Guacamole.Object.STREAM_INDEX_MIMETYPE, | ||||
|                 streamName : Guacamole.Object.ROOT_STREAM, | ||||
|                 type       : ManagedFilesystem.File.Type.DIRECTORY | ||||
|             }) | ||||
|         }); | ||||
|  | ||||
|         // Retrieve contents of root | ||||
|         ManagedFilesystem.refresh(managedFilesystem, managedFilesystem.root); | ||||
|  | ||||
|         return managedFilesystem; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Downloads the given file from the server using the given Guacamole | ||||
|      * client and filesystem. The browser will automatically start the | ||||
|      * download upon completion of this function. | ||||
|      * | ||||
|      * @param {ManagedClient} managedClient | ||||
|      *     The ManagedClient from which the file is to be downloaded. | ||||
|      * | ||||
|      * @param {ManagedFilesystem} managedFilesystem | ||||
|      *     The ManagedFilesystem from which the file is to be downloaded. Any | ||||
|      *     path information provided must be relative to this filesystem. | ||||
|      * | ||||
|      * @param {String} path | ||||
|      *     The full, absolute path of the file to download. | ||||
|      */ | ||||
|     ManagedFilesystem.downloadFile = function downloadFile(managedClient, managedFilesystem, path) { | ||||
|  | ||||
|         // Request download | ||||
|         managedFilesystem.object.requestInputStream(path, function downloadStreamReceived(stream, mimetype) { | ||||
|  | ||||
|             // Parse filename from string | ||||
|             var filename = path.match(/(.*[\\/])?(.*)/)[2]; | ||||
|  | ||||
|             // Start download | ||||
|             tunnelService.downloadStream(managedClient.tunnel.uuid, stream, mimetype, filename); | ||||
|  | ||||
|         }); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Changes the current directory of the given filesystem, automatically | ||||
|      * refreshing the contents of that directory. | ||||
|      * | ||||
|      * @param {ManagedFilesystem} filesystem | ||||
|      *     The filesystem whose current directory should be changed. | ||||
|      * | ||||
|      * @param {ManagedFilesystem.File} file | ||||
|      *     The directory to change to. | ||||
|      */ | ||||
|     ManagedFilesystem.changeDirectory = function changeDirectory(filesystem, file) { | ||||
|  | ||||
|         // Refresh contents | ||||
|         ManagedFilesystem.refresh(filesystem, file); | ||||
|  | ||||
|         // Set current directory | ||||
|         filesystem.currentDirectory = file; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * A file within a ManagedFilesystem. Each ManagedFilesystem.File provides | ||||
|      * sufficient information for retrieval or replacement of the file's | ||||
|      * contents, as well as the file's name and type. | ||||
|      * | ||||
|      * @param {ManagedFilesystem|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedFilesystem.File. | ||||
|      */ | ||||
|     ManagedFilesystem.File = function File(template) { | ||||
|  | ||||
|         /** | ||||
|          * The mimetype of the data contained within this file. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.mimetype = template.mimetype; | ||||
|  | ||||
|         /** | ||||
|          * The name of the stream representing this files contents within its | ||||
|          * associated filesystem object. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.streamName = template.streamName; | ||||
|  | ||||
|         /** | ||||
|          * The type of this file. All legal file type strings are defined | ||||
|          * within ManagedFilesystem.File.Type. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.type = template.type; | ||||
|  | ||||
|         /** | ||||
|          * The name of this file. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.name = template.name; | ||||
|  | ||||
|         /** | ||||
|          * The parent directory of this file. In the case of the root | ||||
|          * directory, this will be null. | ||||
|          * | ||||
|          * @type ManagedFilesystem.File | ||||
|          */ | ||||
|         this.parent = template.parent; | ||||
|  | ||||
|         /** | ||||
|          * Map of all known files containined within this file by name. This is | ||||
|          * only applicable to directories. | ||||
|          * | ||||
|          * @type Object.<String, ManagedFilesystem.File> | ||||
|          */ | ||||
|         this.files = template.files || {}; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * All legal type strings for a ManagedFilesystem.File. | ||||
|      * | ||||
|      * @type Object.<String, String> | ||||
|      */ | ||||
|     ManagedFilesystem.File.Type = { | ||||
|  | ||||
|         /** | ||||
|          * A normal file. As ManagedFilesystem does not currently represent any | ||||
|          * other non-directory types of files, like symbolic links, this type | ||||
|          * string may be used for any non-directory file. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         NORMAL : 'NORMAL', | ||||
|  | ||||
|         /** | ||||
|          * A directory. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         DIRECTORY : 'DIRECTORY' | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return ManagedFilesystem; | ||||
|  | ||||
| }]); | ||||
| @@ -1,105 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the ManagedShareLink class used by ManagedClient to represent | ||||
|  * generated connection sharing links. | ||||
|  */ | ||||
| angular.module('client').factory('ManagedShareLink', ['$injector', | ||||
|     function defineManagedShareLink($injector) { | ||||
|  | ||||
|     // Required types | ||||
|     var UserCredentials = $injector.get('UserCredentials'); | ||||
|  | ||||
|     /** | ||||
|      * Object which represents a link which can be used to gain access to an | ||||
|      * active Guacamole connection. | ||||
|      *  | ||||
|      * @constructor | ||||
|      * @param {ManagedShareLink|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedShareLink. | ||||
|      */ | ||||
|     var ManagedShareLink = function ManagedShareLink(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The human-readable display name of this share link. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.name = template.name; | ||||
|  | ||||
|         /** | ||||
|          * The actual URL of the link which can be used to access the shared | ||||
|          * connection. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.href = template.href; | ||||
|  | ||||
|         /** | ||||
|          * The sharing profile which was used to generate the share link. | ||||
|          * | ||||
|          * @type SharingProfile | ||||
|          */ | ||||
|         this.sharingProfile = template.sharingProfile; | ||||
|  | ||||
|         /** | ||||
|          * The credentials from which the share link was derived. | ||||
|          * | ||||
|          * @type UserCredentials | ||||
|          */ | ||||
|         this.sharingCredentials = template.sharingCredentials; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new ManagedShareLink from a set of UserCredentials and the | ||||
|      * SharingProfile which was used to generate those UserCredentials. | ||||
|      *  | ||||
|      * @param {SharingProfile} sharingProfile | ||||
|      *     The SharingProfile which was used, via the REST API, to generate the | ||||
|      *     given UserCredentials. | ||||
|      *  | ||||
|      * @param {UserCredentials} sharingCredentials | ||||
|      *     The UserCredentials object returned by the REST API in response to a | ||||
|      *     request to share a connection using the given SharingProfile. | ||||
|      * | ||||
|      * @return {ManagedShareLink} | ||||
|      *     A new ManagedShareLink object can be used to access the connection | ||||
|      *     shared via the given SharingProfile and resulting UserCredentials. | ||||
|      */ | ||||
|     ManagedShareLink.getInstance = function getInstance(sharingProfile, sharingCredentials) { | ||||
|  | ||||
|         // Generate new share link using the given profile and credentials | ||||
|         return new ManagedShareLink({ | ||||
|             'name'               : sharingProfile.name, | ||||
|             'href'               : UserCredentials.getLink(sharingCredentials), | ||||
|             'sharingProfile'     : sharingProfile, | ||||
|             'sharingCredentials' : sharingCredentials | ||||
|         }); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return ManagedShareLink; | ||||
|  | ||||
| }]); | ||||
| @@ -1,23 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * The module for code used to manipulate/observe the clipboard. | ||||
|  */ | ||||
| angular.module('clipboard', []); | ||||
| @@ -1,108 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive provides an editor whose contents are exposed via a | ||||
|  * ClipboardData object via the "data" attribute. If this data should also be | ||||
|  * synced to the local clipboard, or sent via a connected Guacamole client | ||||
|  * using a "guacClipboard" event, it is up to external code to do so. | ||||
|  */ | ||||
| angular.module('clipboard').directive('guacClipboard', ['$injector', | ||||
|     function guacClipboard($injector) { | ||||
|  | ||||
|     // Required types | ||||
|     var ClipboardData = $injector.get('ClipboardData'); | ||||
|  | ||||
|     /** | ||||
|      * Configuration object for the guacClipboard directive. | ||||
|      * | ||||
|      * @type Object.<String, Object> | ||||
|      */ | ||||
|     var config = { | ||||
|         restrict    : 'E', | ||||
|         replace     : true, | ||||
|         templateUrl : 'app/clipboard/templates/guacClipboard.html' | ||||
|     }; | ||||
|  | ||||
|     // Scope properties exposed by the guacClipboard directive | ||||
|     config.scope = { | ||||
|  | ||||
|         /** | ||||
|          * The data to display within the field provided by this directive. This | ||||
|          * data will modified or replaced when the user manually alters the | ||||
|          * contents of the field. | ||||
|          * | ||||
|          * @type ClipboardData | ||||
|          */ | ||||
|         data : '=' | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     // guacClipboard directive controller | ||||
|     config.controller = ['$scope', '$injector', '$element', | ||||
|             function guacClipboardController($scope, $injector, $element) { | ||||
|  | ||||
|         /** | ||||
|          * The DOM element which will contain the clipboard contents within the | ||||
|          * user interface provided by this directive. | ||||
|          * | ||||
|          * @type Element | ||||
|          */ | ||||
|         var element = $element[0]; | ||||
|  | ||||
|         /** | ||||
|          * Rereads the contents of the clipboard field, updating the | ||||
|          * ClipboardData object on the scope as necessary. The type of data | ||||
|          * stored within the ClipboardData object will be heuristically | ||||
|          * determined from the HTML contents of the clipboard field. | ||||
|          */ | ||||
|         var updateClipboardData = function updateClipboardData() { | ||||
|  | ||||
|             // Read contents of clipboard textarea | ||||
|             $scope.$evalAsync(function assignClipboardText() { | ||||
|                 $scope.data = new ClipboardData({ | ||||
|                     type : 'text/plain', | ||||
|                     data : element.value | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         // Update the internally-stored clipboard data when events are fired | ||||
|         // that indicate the clipboard field may have been changed | ||||
|         element.addEventListener('input', updateClipboardData); | ||||
|         element.addEventListener('change', updateClipboardData); | ||||
|  | ||||
|         // Watch clipboard for new data, updating the clipboard textarea as | ||||
|         // necessary | ||||
|         $scope.$watch('data', function clipboardDataChanged(data) { | ||||
|  | ||||
|             // If the clipboard data is a string, render it as text | ||||
|             if (typeof data.data === 'string') | ||||
|                 element.value = data.data; | ||||
|  | ||||
|             // Ignore other data types for now | ||||
|  | ||||
|         }); // end $scope.data watch | ||||
|  | ||||
|     }]; | ||||
|  | ||||
|     return config; | ||||
|  | ||||
| }]); | ||||
| @@ -1,553 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A service for accessing local clipboard data. | ||||
|  */ | ||||
| angular.module('clipboard').factory('clipboardService', ['$injector', | ||||
|         function clipboardService($injector) { | ||||
|  | ||||
|     // Get required services | ||||
|     var $q      = $injector.get('$q'); | ||||
|     var $window = $injector.get('$window'); | ||||
|  | ||||
|     // Required types | ||||
|     var ClipboardData = $injector.get('ClipboardData'); | ||||
|  | ||||
|     var service = {}; | ||||
|  | ||||
|     /** | ||||
|      * The amount of time to wait before actually serving a request to read | ||||
|      * clipboard data, in milliseconds. Providing a reasonable delay between | ||||
|      * request and read attempt allows the cut/copy operation to settle, in | ||||
|      * case the data we are anticipating to be present is not actually present | ||||
|      * in the clipboard yet. | ||||
|      * | ||||
|      * @constant | ||||
|      * @type Number | ||||
|      */ | ||||
|     var CLIPBOARD_READ_DELAY = 100; | ||||
|  | ||||
|     /** | ||||
|      * The promise associated with the current pending clipboard read attempt. | ||||
|      * If no clipboard read is active, this will be null. | ||||
|      * | ||||
|      * @type Promise.<ClipboardData> | ||||
|      */ | ||||
|     var pendingRead = null; | ||||
|  | ||||
|     /** | ||||
|      * Reference to the window.document object. | ||||
|      * | ||||
|      * @private | ||||
|      * @type HTMLDocument | ||||
|      */ | ||||
|     var document = $window.document; | ||||
|  | ||||
|     /** | ||||
|      * The textarea that will be used to hold the local clipboard contents. | ||||
|      * | ||||
|      * @type Element | ||||
|      */ | ||||
|     var clipboardContent = document.createElement('textarea'); | ||||
|  | ||||
|     // Ensure clipboard target is selectable but not visible | ||||
|     clipboardContent.className = 'clipboard-service-target'; | ||||
|  | ||||
|     // Add clipboard target to DOM | ||||
|     document.body.appendChild(clipboardContent); | ||||
|  | ||||
|     /** | ||||
|      * Stops the propogation of the given event through the DOM tree. This is | ||||
|      * identical to invoking stopPropogation() on the event directly, except | ||||
|      * that this function is usable as an event handler itself. | ||||
|      * | ||||
|      * @param {Event} e | ||||
|      *     The event whose propogation through the DOM tree should be stopped. | ||||
|      */ | ||||
|     var stopEventPropagation = function stopEventPropagation(e) { | ||||
|         e.stopPropagation(); | ||||
|     }; | ||||
|  | ||||
|     // Prevent events generated due to execCommand() from disturbing external things | ||||
|     clipboardContent.addEventListener('cut',   stopEventPropagation); | ||||
|     clipboardContent.addEventListener('copy',  stopEventPropagation); | ||||
|     clipboardContent.addEventListener('paste', stopEventPropagation); | ||||
|     clipboardContent.addEventListener('input', stopEventPropagation); | ||||
|  | ||||
|     /** | ||||
|      * A stack of past node selection ranges. A range convering the nodes | ||||
|      * currently selected within the document can be pushed onto this stack | ||||
|      * with pushSelection(), and the most recently pushed selection can be | ||||
|      * popped off the stack (and thus re-selected) with popSelection(). | ||||
|      * | ||||
|      * @type Range[] | ||||
|      */ | ||||
|     var selectionStack = []; | ||||
|  | ||||
|     /** | ||||
|      * Pushes the current selection range to the selection stack such that it | ||||
|      * can later be restored with popSelection(). | ||||
|      */ | ||||
|     var pushSelection = function pushSelection() { | ||||
|  | ||||
|         // Add a range representing the current selection to the stack | ||||
|         var selection = $window.getSelection(); | ||||
|         if (selection.getRangeAt && selection.rangeCount) | ||||
|             selectionStack.push(selection.getRangeAt(0)); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Pops a selection range off the selection stack restoring the document's | ||||
|      * previous selection state. The selection range will be the most recent | ||||
|      * selection range pushed by pushSelection(). If there are no selection | ||||
|      * ranges currently on the stack, this function has no effect. | ||||
|      */ | ||||
|     var popSelection = function popSelection() { | ||||
|  | ||||
|         // Pull one selection range from the stack | ||||
|         var range = selectionStack.pop(); | ||||
|         if (!range) | ||||
|             return; | ||||
|  | ||||
|         // Replace any current selection with the retrieved selection | ||||
|         var selection = $window.getSelection(); | ||||
|         selection.removeAllRanges(); | ||||
|         selection.addRange(range); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Selects all nodes within the given element. This will replace the | ||||
|      * current selection with a new selection range that covers the element's | ||||
|      * contents. If the original selection should be preserved, use | ||||
|      * pushSelection() and popSelection(). | ||||
|      * | ||||
|      * @param {Element} element | ||||
|      *     The element whose contents should be selected. | ||||
|      */ | ||||
|     var selectAll = function selectAll(element) { | ||||
|  | ||||
|         // Use the select() function defined for input elements, if available | ||||
|         if (element.select) | ||||
|             element.select(); | ||||
|  | ||||
|         // Fallback to manual manipulation of the selection | ||||
|         else { | ||||
|  | ||||
|             // Generate a range which selects all nodes within the given element | ||||
|             var range = document.createRange(); | ||||
|             range.selectNodeContents(element); | ||||
|  | ||||
|             // Replace any current selection with the generated range | ||||
|             var selection = $window.getSelection(); | ||||
|             selection.removeAllRanges(); | ||||
|             selection.addRange(range); | ||||
|  | ||||
|         } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Sets the local clipboard, if possible, to the given text. | ||||
|      * | ||||
|      * @param {ClipboardData} data | ||||
|      *     The data to assign to the local clipboard should be set. | ||||
|      * | ||||
|      * @return {Promise} | ||||
|      *     A promise that will resolve if setting the clipboard was successful, | ||||
|      *     and will reject if it failed. | ||||
|      */ | ||||
|     service.setLocalClipboard = function setLocalClipboard(data) { | ||||
|  | ||||
|         var deferred = $q.defer(); | ||||
|  | ||||
|         try { | ||||
|  | ||||
|             // Attempt to read the clipboard using the Asynchronous Clipboard | ||||
|             // API, if it's available | ||||
|             if (navigator.clipboard && navigator.clipboard.writeText) { | ||||
|                 if (data.type === 'text/plain') { | ||||
|                     navigator.clipboard.writeText(data.data).then(deferred.resolve, deferred.reject); | ||||
|                     return deferred.promise; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         // Ignore any hard failures to use Asynchronous Clipboard API, falling | ||||
|         // back to traditional document.execCommand() | ||||
|         catch (ignore) {} | ||||
|  | ||||
|         // Track the originally-focused element prior to changing focus | ||||
|         var originalElement = document.activeElement; | ||||
|         pushSelection(); | ||||
|  | ||||
|         // Copy the given value into the clipboard DOM element | ||||
|         if (typeof data.data === 'string') | ||||
|             clipboardContent.value = data.data; | ||||
|         else { | ||||
|             clipboardContent.innerHTML = ''; | ||||
|             var img = document.createElement('img'); | ||||
|             img.src = URL.createObjectURL(data.data); | ||||
|             clipboardContent.appendChild(img); | ||||
|         } | ||||
|  | ||||
|         // Select all data within the clipboard target | ||||
|         clipboardContent.focus(); | ||||
|         selectAll(clipboardContent); | ||||
|  | ||||
|         // Attempt to copy data from clipboard element into local clipboard | ||||
|         if (document.execCommand('copy')) | ||||
|             deferred.resolve(); | ||||
|         else | ||||
|             deferred.reject(); | ||||
|  | ||||
|         // Unfocus the clipboard DOM event to avoid mobile keyboard opening, | ||||
|         // restoring whichever element was originally focused | ||||
|         clipboardContent.blur(); | ||||
|         originalElement.focus(); | ||||
|         popSelection(); | ||||
|  | ||||
|         return deferred.promise; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Parses the given data URL, returning its decoded contents as a new Blob. | ||||
|      * If the URL is not a valid data URL, null will be returned instead. | ||||
|      * | ||||
|      * @param {String} url | ||||
|      *     The data URL to parse. | ||||
|      * | ||||
|      * @returns {Blob} | ||||
|      *     A new Blob containing the decoded contents of the data URL, or null | ||||
|      *     if the URL is not a valid data URL. | ||||
|      */ | ||||
|     service.parseDataURL = function parseDataURL(url) { | ||||
|  | ||||
|         // Parse given string as a data URL | ||||
|         var result = /^data:([^;]*);base64,([a-zA-Z0-9+/]*[=]*)$/.exec(url); | ||||
|         if (!result) | ||||
|             return null; | ||||
|  | ||||
|         // Pull the mimetype and base64 contents of the data URL | ||||
|         var type = result[1]; | ||||
|         var data = $window.atob(result[2]); | ||||
|  | ||||
|         // Convert the decoded binary string into a typed array | ||||
|         var buffer = new Uint8Array(data.length); | ||||
|         for (var i = 0; i < data.length; i++) | ||||
|             buffer[i] = data.charCodeAt(i); | ||||
|  | ||||
|         // Produce a proper blob containing the data and type provided in | ||||
|         // the data URL | ||||
|         return new Blob([buffer], { type : type }); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Returns the content of the given element as plain, unformatted text, | ||||
|      * preserving only individual characters and newlines. Formatting, images, | ||||
|      * etc. are not taken into account. | ||||
|      * | ||||
|      * @param {Element} element | ||||
|      *     The element whose text content should be returned. | ||||
|      * | ||||
|      * @returns {String} | ||||
|      *     The plain text contents of the given element, including newlines and | ||||
|      *     spacing but otherwise without any formatting. | ||||
|      */ | ||||
|     service.getTextContent = function getTextContent(element) { | ||||
|  | ||||
|         var blocks = []; | ||||
|         var currentBlock = ''; | ||||
|  | ||||
|         // For each child of the given element | ||||
|         var current = element.firstChild; | ||||
|         while (current) { | ||||
|  | ||||
|             // Simply append the content of any text nodes | ||||
|             if (current.nodeType === Node.TEXT_NODE) | ||||
|                 currentBlock += current.nodeValue; | ||||
|  | ||||
|             // Render <br> as a newline character | ||||
|             else if (current.nodeName === 'BR') | ||||
|                 currentBlock += '\n'; | ||||
|  | ||||
|             // Render <img> as alt text, if available | ||||
|             else if (current.nodeName === 'IMG') | ||||
|                 currentBlock += current.getAttribute('alt') || ''; | ||||
|  | ||||
|             // For all other nodes, handling depends on whether they are | ||||
|             // block-level elements | ||||
|             else { | ||||
|  | ||||
|                 // If we are entering a new block context, start a new block if | ||||
|                 // the current block is non-empty | ||||
|                 if (currentBlock.length && $window.getComputedStyle(current).display === 'block') { | ||||
|  | ||||
|                     // Trim trailing newline (would otherwise inflate the line count by 1) | ||||
|                     if (currentBlock.substring(currentBlock.length - 1) === '\n') | ||||
|                         currentBlock = currentBlock.substring(0, currentBlock.length - 1); | ||||
|  | ||||
|                     // Finish current block and start a new block | ||||
|                     blocks.push(currentBlock); | ||||
|                     currentBlock = ''; | ||||
|  | ||||
|                 } | ||||
|  | ||||
|                 // Append the content of the current element to the current block | ||||
|                 currentBlock += service.getTextContent(current); | ||||
|  | ||||
|             } | ||||
|  | ||||
|             current = current.nextSibling; | ||||
|  | ||||
|         } | ||||
|  | ||||
|         // Add any in-progress block | ||||
|         if (currentBlock.length) | ||||
|             blocks.push(currentBlock); | ||||
|  | ||||
|         // Combine all non-empty blocks, separated by newlines | ||||
|         return blocks.join('\n'); | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Replaces the current text content of the given element with the given | ||||
|      * text. To avoid affecting the position of the cursor within an editable | ||||
|      * element, or firing unnecessary DOM modification events, the underlying | ||||
|      * <code>textContent</code> property of the element is only touched if | ||||
|      * doing so would actually change the text. | ||||
|      * | ||||
|      * @param {Element} element | ||||
|      *     The element whose text content should be changed. | ||||
|      * | ||||
|      * @param {String} text | ||||
|      *     The text content to assign to the given element. | ||||
|      */ | ||||
|     service.setTextContent = function setTextContent(element, text) { | ||||
|  | ||||
|         // Strip out any images | ||||
|         $(element).find('img').remove(); | ||||
|  | ||||
|         // Reset text content only if doing so will actually change the content | ||||
|         if (service.getTextContent(element) !== text) | ||||
|             element.textContent = text; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Returns the URL of the single image within the given element, if the | ||||
|      * element truly contains only one child and that child is an image. If the | ||||
|      * content of the element is mixed or not an image, null is returned. | ||||
|      * | ||||
|      * @param {Element} element | ||||
|      *     The element whose image content should be retrieved. | ||||
|      * | ||||
|      * @returns {String} | ||||
|      *     The URL of the image contained within the given element, if that | ||||
|      *     element contains only a single child element which happens to be an | ||||
|      *     image, or null if the content of the element is not purely an image. | ||||
|      */ | ||||
|     service.getImageContent = function getImageContent(element) { | ||||
|  | ||||
|         // Return the source of the single child element, if it is an image | ||||
|         var firstChild = element.firstChild; | ||||
|         if (firstChild && firstChild.nodeName === 'IMG' && !firstChild.nextSibling) | ||||
|             return firstChild.getAttribute('src'); | ||||
|  | ||||
|         // Otherwise, the content of this element is not simply an image | ||||
|         return null; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Replaces the current contents of the given element with a single image | ||||
|      * having the given URL. To avoid affecting the position of the cursor | ||||
|      * within an editable element, or firing unnecessary DOM modification | ||||
|      * events, the content of the element is only touched if doing so would | ||||
|      * actually change content. | ||||
|      * | ||||
|      * @param {Element} element | ||||
|      *     The element whose image content should be changed. | ||||
|      * | ||||
|      * @param {String} url | ||||
|      *     The URL of the image which should be assigned as the contents of the | ||||
|      *     given element. | ||||
|      */ | ||||
|     service.setImageContent = function setImageContent(element, url) { | ||||
|  | ||||
|         // Retrieve the URL of the current image contents, if any | ||||
|         var currentImage = service.getImageContent(element); | ||||
|  | ||||
|         // If the current contents are not the given image (or not an image | ||||
|         // at all), reassign the contents | ||||
|         if (currentImage !== url) { | ||||
|  | ||||
|             // Clear current contents | ||||
|             element.innerHTML = ''; | ||||
|  | ||||
|             // Add a new image as the sole contents of the element | ||||
|             var img = document.createElement('img'); | ||||
|             img.src = url; | ||||
|             element.appendChild(img); | ||||
|  | ||||
|         } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Get the current value of the local clipboard. | ||||
|      * | ||||
|      * @return {Promise.<ClipboardData>} | ||||
|      *     A promise that will resolve with the contents of the local clipboard | ||||
|      *     if getting the clipboard was successful, and will reject if it | ||||
|      *     failed. | ||||
|      */ | ||||
|     service.getLocalClipboard = function getLocalClipboard() { | ||||
|  | ||||
|         // If the clipboard is already being read, do not overlap the read | ||||
|         // attempts; instead share the result across all requests | ||||
|         if (pendingRead) | ||||
|             return pendingRead; | ||||
|  | ||||
|         var deferred = $q.defer(); | ||||
|  | ||||
|         try { | ||||
|  | ||||
|             // Attempt to read the clipboard using the Asynchronous Clipboard | ||||
|             // API, if it's available | ||||
|             if (navigator.clipboard && navigator.clipboard.readText) { | ||||
|  | ||||
|                 navigator.clipboard.readText().then(function textRead(text) { | ||||
|                     deferred.resolve(new ClipboardData({ | ||||
|                         type : 'text/plain', | ||||
|                         data : text | ||||
|                     })); | ||||
|                 }, deferred.reject); | ||||
|  | ||||
|                 return deferred.promise; | ||||
|  | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         // Ignore any hard failures to use Asynchronous Clipboard API, falling | ||||
|         // back to traditional document.execCommand() | ||||
|         catch (ignore) {} | ||||
|  | ||||
|         // Track the originally-focused element prior to changing focus | ||||
|         var originalElement = document.activeElement; | ||||
|  | ||||
|         /** | ||||
|          * Attempts to paste the clipboard contents into the | ||||
|          * currently-focused element. The promise related to the current | ||||
|          * attempt to read the clipboard will be resolved or rejected | ||||
|          * depending on whether the attempt to paste succeeds. | ||||
|          */ | ||||
|         var performPaste = function performPaste() { | ||||
|  | ||||
|             // Attempt paste local clipboard into clipboard DOM element | ||||
|             if (document.execCommand('paste')) { | ||||
|  | ||||
|                 // If the pasted data is a single image, resolve with a blob | ||||
|                 // containing that image | ||||
|                 var currentImage = service.getImageContent(clipboardContent); | ||||
|                 if (currentImage) { | ||||
|  | ||||
|                     // Convert the image's data URL into a blob | ||||
|                     var blob = service.parseDataURL(currentImage); | ||||
|                     if (blob) { | ||||
|                         deferred.resolve(new ClipboardData({ | ||||
|                             type : blob.type, | ||||
|                             data : blob | ||||
|                         })); | ||||
|                     } | ||||
|  | ||||
|                     // Reject if conversion fails | ||||
|                     else | ||||
|                         deferred.reject(); | ||||
|  | ||||
|                 } // end if clipboard is an image | ||||
|  | ||||
|                 // Otherwise, assume the clipboard contains plain text | ||||
|                 else | ||||
|                     deferred.resolve(new ClipboardData({ | ||||
|                         type : 'text/plain', | ||||
|                         data : clipboardContent.value | ||||
|                     })); | ||||
|  | ||||
|             } | ||||
|  | ||||
|             // Otherwise, reading from the clipboard has failed | ||||
|             else | ||||
|                 deferred.reject(); | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         // Mark read attempt as in progress, cleaning up event listener and | ||||
|         // selection once the paste attempt has completed | ||||
|         pendingRead = deferred.promise['finally'](function cleanupReadAttempt() { | ||||
|  | ||||
|             // Do not use future changes in focus | ||||
|             clipboardContent.removeEventListener('focus', performPaste); | ||||
|  | ||||
|             // Unfocus the clipboard DOM event to avoid mobile keyboard opening, | ||||
|             // restoring whichever element was originally focused | ||||
|             clipboardContent.blur(); | ||||
|             originalElement.focus(); | ||||
|             popSelection(); | ||||
|  | ||||
|             // No read is pending any longer | ||||
|             pendingRead = null; | ||||
|  | ||||
|         }); | ||||
|  | ||||
|         // Wait for the next event queue run before attempting to read | ||||
|         // clipboard data (in case the copy/cut has not yet completed) | ||||
|         $window.setTimeout(function deferredClipboardRead() { | ||||
|  | ||||
|             pushSelection(); | ||||
|  | ||||
|             // Ensure clipboard element is blurred (and that the "focus" event | ||||
|             // will fire) | ||||
|             clipboardContent.blur(); | ||||
|             clipboardContent.addEventListener('focus', performPaste); | ||||
|  | ||||
|             // Clear and select the clipboard DOM element | ||||
|             clipboardContent.value = ''; | ||||
|             clipboardContent.focus(); | ||||
|             selectAll(clipboardContent); | ||||
|  | ||||
|             // If focus failed to be set, we cannot read the clipboard | ||||
|             if (document.activeElement !== clipboardContent) | ||||
|                 deferred.reject(); | ||||
|  | ||||
|         }, CLIPBOARD_READ_DELAY); | ||||
|  | ||||
|         return pendingRead; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return service; | ||||
|  | ||||
| }]); | ||||
| @@ -1,61 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .clipboard, .clipboard-service-target { | ||||
|     background: white; | ||||
| } | ||||
|  | ||||
| .clipboard  { | ||||
|     position: relative; | ||||
|     border: 1px solid #AAA; | ||||
|     -moz-border-radius: 0.25em; | ||||
|     -webkit-border-radius: 0.25em; | ||||
|     -khtml-border-radius: 0.25em; | ||||
|     border-radius: 0.25em; | ||||
|     width: 100%; | ||||
|     height: 2in; | ||||
|     white-space: pre; | ||||
|     font-size: 1em; | ||||
|     overflow: auto; | ||||
|     padding: 0.25em; | ||||
| } | ||||
|  | ||||
| .clipboard p, | ||||
| .clipboard div { | ||||
|     margin: 0; | ||||
| } | ||||
|  | ||||
| .clipboard img { | ||||
|     max-width: 100%; | ||||
|     max-height: 100%; | ||||
|     display: block; | ||||
|     margin: 0 auto; | ||||
|     border: 1px solid black; | ||||
|     background: url('images/checker.png'); | ||||
| } | ||||
|  | ||||
| .clipboard-service-target { | ||||
|     position: fixed; | ||||
|     left: -1em; | ||||
|     right: -1em; | ||||
|     width: 1em; | ||||
|     height: 1em; | ||||
|     white-space: pre; | ||||
|     overflow: hidden; | ||||
| } | ||||
| @@ -1 +0,0 @@ | ||||
| <textarea class="clipboard"></textarea> | ||||
| @@ -1,59 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the ClipboardData class used for interchange between the | ||||
|  * guacClipboard directive, clipboardService service, etc. | ||||
|  */ | ||||
| angular.module('clipboard').factory('ClipboardData', [function defineClipboardData() { | ||||
|  | ||||
|     /** | ||||
|      * Arbitrary data which can be contained by the clipboard. | ||||
|      * | ||||
|      * @constructor | ||||
|      * @param {ClipboardData|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ClipboardData. | ||||
|      */ | ||||
|     var ClipboardData = function ClipboardData(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The mimetype of the data currently stored within the clipboard. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.type = template.type || 'text/plain'; | ||||
|  | ||||
|         /** | ||||
|          * The data currently stored within the clipboard. Depending on the | ||||
|          * nature of the stored data, this may be either a String, a Blob, or a | ||||
|          * File. | ||||
|          * | ||||
|          * @type String|Blob|File | ||||
|          */ | ||||
|         this.data = template.data || ''; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return ClipboardData; | ||||
|  | ||||
| }]); | ||||
| @@ -1,63 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive which allows elements to be manually focused / blurred. | ||||
|  */ | ||||
| angular.module('element').directive('guacFocus', ['$injector', function guacFocus($injector) { | ||||
|  | ||||
|     // Required services | ||||
|     var $parse   = $injector.get('$parse'); | ||||
|     var $timeout = $injector.get('$timeout'); | ||||
|  | ||||
|     return { | ||||
|         restrict: 'A', | ||||
|  | ||||
|         link: function linkGuacFocus($scope, $element, $attrs) { | ||||
|  | ||||
|             /** | ||||
|              * Whether the element associated with this directive should be | ||||
|              * focussed. | ||||
|              * | ||||
|              * @type Boolean | ||||
|              */ | ||||
|             var guacFocus = $parse($attrs.guacFocus); | ||||
|  | ||||
|             /** | ||||
|              * The element which will be focused / blurred. | ||||
|              * | ||||
|              * @type Element | ||||
|              */ | ||||
|             var element = $element[0]; | ||||
|  | ||||
|             // Set/unset focus depending on value of guacFocus | ||||
|             $scope.$watch(guacFocus, function updateFocus(value) { | ||||
|                 $timeout(function updateFocusAfterRender() { | ||||
|                     if (value) | ||||
|                         element.focus(); | ||||
|                     else | ||||
|                         element.blur(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|         } // end guacFocus link function | ||||
|  | ||||
|     }; | ||||
|  | ||||
| }]); | ||||
| @@ -1,59 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive which stores a marker which refers to a specific element, | ||||
|  * allowing that element to be scrolled into view when desired. | ||||
|  */ | ||||
| angular.module('element').directive('guacMarker', ['$injector', function guacMarker($injector) { | ||||
|  | ||||
|     // Required types | ||||
|     var Marker = $injector.get('Marker'); | ||||
|  | ||||
|     // Required services | ||||
|     var $parse = $injector.get('$parse'); | ||||
|  | ||||
|     return { | ||||
|         restrict: 'A', | ||||
|  | ||||
|         link: function linkGuacMarker($scope, $element, $attrs) { | ||||
|  | ||||
|             /** | ||||
|              * The property in which a new Marker should be stored. The new | ||||
|              * Marker will refer to the element associated with this directive. | ||||
|              * | ||||
|              * @type Marker | ||||
|              */ | ||||
|             var guacMarker = $parse($attrs.guacMarker); | ||||
|  | ||||
|             /** | ||||
|              * The element to associate with the new Marker. | ||||
|              * | ||||
|              * @type Element | ||||
|              */ | ||||
|             var element = $element[0]; | ||||
|  | ||||
|             // Assign new marker | ||||
|             guacMarker.assign($scope, new Marker(element)); | ||||
|  | ||||
|         } | ||||
|  | ||||
|     }; | ||||
|  | ||||
| }]); | ||||
| @@ -1,114 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive which calls a given callback when its associated element is | ||||
|  * resized. This will modify the internal DOM tree of the associated element, | ||||
|  * and the associated element MUST have position (for example, | ||||
|  * "position: relative"). | ||||
|  */ | ||||
| angular.module('element').directive('guacResize', ['$document', function guacResize($document) { | ||||
|  | ||||
|     return { | ||||
|         restrict: 'A', | ||||
|  | ||||
|         link: function linkGuacResize($scope, $element, $attrs) { | ||||
|  | ||||
|             /** | ||||
|              * The function to call whenever the associated element is | ||||
|              * resized. The function will be passed the width and height of | ||||
|              * the element, in pixels. | ||||
|              * | ||||
|              * @type Function  | ||||
|              */ | ||||
|             var guacResize = $scope.$eval($attrs.guacResize); | ||||
|  | ||||
|             /** | ||||
|              * The element which will monitored for size changes. | ||||
|              * | ||||
|              * @type Element | ||||
|              */ | ||||
|             var element = $element[0]; | ||||
|  | ||||
|             /** | ||||
|              * The resize sensor - an HTML object element. | ||||
|              * | ||||
|              * @type HTMLObjectElement | ||||
|              */ | ||||
|             var resizeSensor = $document[0].createElement('object'); | ||||
|  | ||||
|             /** | ||||
|              * The width of the associated element, in pixels. | ||||
|              * | ||||
|              * @type Number | ||||
|              */ | ||||
|             var lastWidth = element.offsetWidth; | ||||
|  | ||||
|             /** | ||||
|              * The height of the associated element, in pixels. | ||||
|              * | ||||
|              * @type Number | ||||
|              */ | ||||
|             var lastHeight = element.offsetHeight; | ||||
|  | ||||
|             /** | ||||
|              * Checks whether the size of the associated element has changed | ||||
|              * and, if so, calls the resize callback with the new width and | ||||
|              * height as parameters. | ||||
|              */ | ||||
|             var checkSize = function checkSize() { | ||||
|  | ||||
|                 // Call callback only if size actually changed | ||||
|                 if (element.offsetWidth !== lastWidth | ||||
|                  || element.offsetHeight !== lastHeight) { | ||||
|  | ||||
|                     // Call resize callback, if defined | ||||
|                     if (guacResize) { | ||||
|                         $scope.$evalAsync(function elementSizeChanged() { | ||||
|                             guacResize(element.offsetWidth, element.offsetHeight); | ||||
|                         }); | ||||
|                     } | ||||
|  | ||||
|                     // Update stored size | ||||
|                     lastWidth  = element.offsetWidth; | ||||
|                     lastHeight = element.offsetHeight; | ||||
|  | ||||
|                  } | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             // Register event listener once window object exists | ||||
|             resizeSensor.onload = function resizeSensorReady() { | ||||
|                 resizeSensor.contentDocument.defaultView.addEventListener('resize', checkSize); | ||||
|                 checkSize(); | ||||
|             }; | ||||
|  | ||||
|             // Load blank contents | ||||
|             resizeSensor.className = 'resize-sensor'; | ||||
|             resizeSensor.type      = 'text/html'; | ||||
|             resizeSensor.data      = 'app/element/templates/blank.html'; | ||||
|  | ||||
|             // Add resize sensor to associated element | ||||
|             element.insertBefore(resizeSensor, element.firstChild); | ||||
|  | ||||
|         } // end guacResize link function | ||||
|  | ||||
|     }; | ||||
|  | ||||
| }]); | ||||
| @@ -1,82 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive which allows elements to be manually scrolled, and for their | ||||
|  * scroll state to be observed. | ||||
|  */ | ||||
| angular.module('element').directive('guacScroll', [function guacScroll() { | ||||
|  | ||||
|     return { | ||||
|         restrict: 'A', | ||||
|  | ||||
|         link: function linkGuacScroll($scope, $element, $attrs) { | ||||
|  | ||||
|             /** | ||||
|              * The current scroll state of the element. | ||||
|              * | ||||
|              * @type ScrollState | ||||
|              */ | ||||
|             var guacScroll = $scope.$eval($attrs.guacScroll); | ||||
|  | ||||
|             /** | ||||
|              * The element which is being scrolled, or monitored for changes | ||||
|              * in scroll. | ||||
|              * | ||||
|              * @type Element | ||||
|              */ | ||||
|             var element = $element[0]; | ||||
|  | ||||
|             /** | ||||
|              * Returns the current left edge of the scrolling rectangle. | ||||
|              * | ||||
|              * @returns {Number} | ||||
|              *     The current left edge of the scrolling rectangle. | ||||
|              */ | ||||
|             var getScrollLeft = function getScrollLeft() { | ||||
|                 return guacScroll.left; | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns the current top edge of the scrolling rectangle. | ||||
|              * | ||||
|              * @returns {Number} | ||||
|              *     The current top edge of the scrolling rectangle. | ||||
|              */ | ||||
|             var getScrollTop = function getScrollTop() { | ||||
|                 return guacScroll.top; | ||||
|             }; | ||||
|  | ||||
|             // Update underlying scrollLeft property when left changes | ||||
|             $scope.$watch(getScrollLeft, function scrollLeftChanged(left) { | ||||
|                 element.scrollLeft = left; | ||||
|                 guacScroll.left = element.scrollLeft; | ||||
|             }); | ||||
|  | ||||
|             // Update underlying scrollTop property when top changes | ||||
|             $scope.$watch(getScrollTop, function scrollTopChanged(top) { | ||||
|                 element.scrollTop = top; | ||||
|                 guacScroll.top = element.scrollTop; | ||||
|             }); | ||||
|  | ||||
|         } // end guacScroll link function | ||||
|  | ||||
|     }; | ||||
|  | ||||
| }]); | ||||
| @@ -1,92 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive which allows multiple files to be uploaded. Clicking on the | ||||
|  * associated element will result in a file selector dialog, which then calls | ||||
|  * the provided callback function with any chosen files. | ||||
|  */ | ||||
| angular.module('element').directive('guacUpload', ['$document', function guacUpload($document) { | ||||
|  | ||||
|     return { | ||||
|         restrict: 'A', | ||||
|  | ||||
|         link: function linkGuacUpload($scope, $element, $attrs) { | ||||
|  | ||||
|             /** | ||||
|              * The function to call whenever files are chosen. The callback is | ||||
|              * provided a single parameter: the FileList containing all chosen | ||||
|              * files. | ||||
|              * | ||||
|              * @type Function  | ||||
|              */ | ||||
|             var guacUpload = $scope.$eval($attrs.guacUpload); | ||||
|  | ||||
|             /** | ||||
|              * The element which will register the drag gesture. | ||||
|              * | ||||
|              * @type Element | ||||
|              */ | ||||
|             var element = $element[0]; | ||||
|  | ||||
|             /** | ||||
|              * Internal form, containing a single file input element. | ||||
|              * | ||||
|              * @type HTMLFormElement | ||||
|              */ | ||||
|             var form = $document[0].createElement('form'); | ||||
|  | ||||
|             /** | ||||
|              * Internal file input element. | ||||
|              * | ||||
|              * @type HTMLInputElement | ||||
|              */ | ||||
|             var input = $document[0].createElement('input'); | ||||
|  | ||||
|             // Init input element | ||||
|             input.type = 'file'; | ||||
|             input.multiple = true; | ||||
|  | ||||
|             // Add input element to internal form | ||||
|             form.appendChild(input); | ||||
|  | ||||
|             // Notify of any chosen files | ||||
|             input.addEventListener('change', function filesSelected() { | ||||
|                 $scope.$apply(function setSelectedFiles() { | ||||
|  | ||||
|                     // Only set chosen files selection is not canceled | ||||
|                     if (guacUpload && input.files.length > 0) | ||||
|                         guacUpload(input.files); | ||||
|  | ||||
|                     // Reset selection | ||||
|                     form.reset(); | ||||
|  | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // Open file chooser when element is clicked | ||||
|             element.addEventListener('click', function elementClicked() { | ||||
|                 input.click(); | ||||
|             }); | ||||
|  | ||||
|         } // end guacUpload link function | ||||
|  | ||||
|     }; | ||||
|  | ||||
| }]); | ||||
| @@ -1,24 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Module for manipulating element state, such as focus or scroll position, as | ||||
|  * well as handling browser events. | ||||
|  */ | ||||
| angular.module('element', []); | ||||
| @@ -1,30 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .resize-sensor { | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
|     position: absolute; | ||||
|     left: 0; | ||||
|     top: 0; | ||||
|     overflow: hidden; | ||||
|     border: none; | ||||
|     opacity: 0; | ||||
|     z-index: -1; | ||||
| } | ||||
| @@ -1,8 +0,0 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|     <head> | ||||
|         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||||
|         <title>_</title> | ||||
|     </head> | ||||
|     <body></body> | ||||
| </html> | ||||
| @@ -1,47 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the Marker class definition. | ||||
|  */ | ||||
| angular.module('element').factory('Marker', [function defineMarker() { | ||||
|  | ||||
|     /** | ||||
|      * Creates a new Marker which allows its associated element to be scolled | ||||
|      * into view as desired. | ||||
|      * | ||||
|      * @constructor | ||||
|      * @param {Element} element | ||||
|      *     The element to associate with this marker. | ||||
|      */ | ||||
|     var Marker = function Marker(element) { | ||||
|  | ||||
|         /** | ||||
|          * Scrolls scrollable elements, or the window, as needed to bring the | ||||
|          * element associated with this marker into view. | ||||
|          */ | ||||
|         this.scrollIntoView = function scrollIntoView() { | ||||
|             element.scrollIntoView(); | ||||
|         }; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return Marker; | ||||
|  | ||||
| }]); | ||||
| @@ -1,60 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the ScrollState class definition. | ||||
|  */ | ||||
| angular.module('element').factory('ScrollState', [function defineScrollState() { | ||||
|  | ||||
|     /** | ||||
|      * Creates a new ScrollState, representing the current scroll position of | ||||
|      * an arbitrary element. This constructor initializes the properties of the | ||||
|      * new ScrollState with the corresponding properties of the given template. | ||||
|      * | ||||
|      * @constructor | ||||
|      * @param {ScrollState|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ScrollState. | ||||
|      */ | ||||
|     var ScrollState = function ScrollState(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The left edge of the view rectangle within the scrollable area. This | ||||
|          * value naturally increases as the user scrolls right. | ||||
|          * | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.left = template.left || 0; | ||||
|  | ||||
|         /** | ||||
|          * The top edge of the view rectangle within the scrollable area. This | ||||
|          * value naturally increases as the user scrolls down. | ||||
|          * | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.top = template.top || 0; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return ScrollState; | ||||
|  | ||||
| }]); | ||||
| @@ -1,37 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Controller for checkbox fields. | ||||
|  */ | ||||
| angular.module('form').controller('checkboxFieldController', ['$scope', | ||||
|     function checkboxFieldController($scope) { | ||||
|  | ||||
|     // Update typed value when model is changed | ||||
|     $scope.$watch('model', function modelChanged(model) { | ||||
|         $scope.typedValue = (model === $scope.field.options[0]); | ||||
|     }); | ||||
|  | ||||
|     // Update string value in model when typed value is changed | ||||
|     $scope.$watch('typedValue', function typedValueChanged(typedValue) { | ||||
|         $scope.model = (typedValue ? $scope.field.options[0] : ''); | ||||
|     }); | ||||
|  | ||||
| }]); | ||||
| @@ -1,89 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Controller for date fields. | ||||
|  */ | ||||
| angular.module('form').controller('dateFieldController', ['$scope', '$injector', | ||||
|     function dateFieldController($scope, $injector) { | ||||
|  | ||||
|     // Required services | ||||
|     var $filter = $injector.get('$filter'); | ||||
|  | ||||
|     /** | ||||
|      * Options which dictate the behavior of the input field model, as defined | ||||
|      * by https://docs.angularjs.org/api/ng/directive/ngModelOptions | ||||
|      * | ||||
|      * @type Object.<String, String> | ||||
|      */ | ||||
|     $scope.modelOptions = { | ||||
|  | ||||
|         /** | ||||
|          * Space-delimited list of events on which the model will be updated. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         updateOn : 'blur', | ||||
|  | ||||
|         /** | ||||
|          * The time zone to use when reading/writing the Date object of the | ||||
|          * model. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         timezone : 'UTC' | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Parses the date components of the given string into a Date with only the | ||||
|      * date components set. The resulting Date will be in the UTC timezone, | ||||
|      * with the time left as midnight. The input string must be in the format | ||||
|      * YYYY-MM-DD (zero-padded). | ||||
|      * | ||||
|      * @param {String} str | ||||
|      *     The date string to parse. | ||||
|      * | ||||
|      * @returns {Date} | ||||
|      *     A Date object, in the UTC timezone, with only the date components | ||||
|      *     set. | ||||
|      */ | ||||
|     var parseDate = function parseDate(str) { | ||||
|  | ||||
|         // Parse date, return blank if invalid | ||||
|         var parsedDate = new Date(str + 'T00:00Z'); | ||||
|         if (isNaN(parsedDate.getTime())) | ||||
|             return null; | ||||
|  | ||||
|         return parsedDate; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     // Update typed value when model is changed | ||||
|     $scope.$watch('model', function modelChanged(model) { | ||||
|         $scope.typedValue = (model ? parseDate(model) : null); | ||||
|     }); | ||||
|  | ||||
|     // Update string value in model when typed value is changed | ||||
|     $scope.$watch('typedValue', function typedValueChanged(typedValue) { | ||||
|         $scope.model = (typedValue ? $filter('date')(typedValue, 'yyyy-MM-dd', 'UTC') : ''); | ||||
|     }); | ||||
|  | ||||
| }]); | ||||
| @@ -1,52 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Controller for the language field type. The language field type allows the | ||||
|  * user to select a language from the set of languages supported by the | ||||
|  * Guacamole web application. | ||||
|  */ | ||||
| angular.module('form').controller('languageFieldController', ['$scope', '$injector', | ||||
|     function languageFieldController($scope, $injector) { | ||||
|  | ||||
|     // Required services | ||||
|     var languageService = $injector.get('languageService'); | ||||
|     var requestService  = $injector.get('requestService'); | ||||
|  | ||||
|     /** | ||||
|      * A map of all available language keys to their human-readable | ||||
|      * names. | ||||
|      * | ||||
|      * @type Object.<String, String> | ||||
|      */ | ||||
|     $scope.languages = null; | ||||
|  | ||||
|     // Retrieve defined languages | ||||
|     languageService.getLanguages().then(function languagesRetrieved(languages) { | ||||
|         $scope.languages = languages; | ||||
|     }, requestService.DIE); | ||||
|  | ||||
|     // Interpret undefined/null as empty string | ||||
|     $scope.$watch('model', function setModel(model) { | ||||
|         if (!model && model !== '') | ||||
|             $scope.model = ''; | ||||
|     }); | ||||
|  | ||||
| }]); | ||||
| @@ -1,37 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Controller for number fields. | ||||
|  */ | ||||
| angular.module('form').controller('numberFieldController', ['$scope', | ||||
|     function numberFieldController($scope) { | ||||
|  | ||||
|     // Update typed value when model is changed | ||||
|     $scope.$watch('model', function modelChanged(model) { | ||||
|         $scope.typedValue = (model ? Number(model) : null); | ||||
|     }); | ||||
|  | ||||
|     // Update string value in model when typed value is changed | ||||
|     $scope.$watch('typedValue', function typedValueChanged(typedValue) { | ||||
|         $scope.model = ((typedValue || typedValue === 0) ? typedValue.toString() : ''); | ||||
|     }); | ||||
|  | ||||
| }]); | ||||
| @@ -1,72 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Controller for password fields. | ||||
|  */ | ||||
| angular.module('form').controller('passwordFieldController', ['$scope', | ||||
|     function passwordFieldController($scope) { | ||||
|  | ||||
|     /** | ||||
|      * The type to use for the input field. By default, the input field will | ||||
|      * have the type 'password', and thus will be masked. | ||||
|      * | ||||
|      * @type String | ||||
|      * @default 'password' | ||||
|      */ | ||||
|     $scope.passwordInputType = 'password'; | ||||
|  | ||||
|     /** | ||||
|      * Returns a string which describes the action the next call to | ||||
|      * togglePassword() will have. | ||||
|      * | ||||
|      * @return {String} | ||||
|      *     A string which describes the action the next call to | ||||
|      *     togglePassword() will have. | ||||
|      */ | ||||
|     $scope.getTogglePasswordHelpText = function getTogglePasswordHelpText() { | ||||
|  | ||||
|         // If password is hidden, togglePassword() will show the password | ||||
|         if ($scope.passwordInputType === 'password') | ||||
|             return 'FORM.HELP_SHOW_PASSWORD'; | ||||
|  | ||||
|         // If password is shown, togglePassword() will hide the password | ||||
|         return 'FORM.HELP_HIDE_PASSWORD'; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Toggles visibility of the field contents, if this field is a | ||||
|      * password field. Initially, password contents are masked | ||||
|      * (invisible). | ||||
|      */ | ||||
|     $scope.togglePassword = function togglePassword() { | ||||
|  | ||||
|         // If password is hidden, show the password | ||||
|         if ($scope.passwordInputType === 'password') | ||||
|             $scope.passwordInputType = 'text'; | ||||
|  | ||||
|         // If password is shown, hide the password | ||||
|         else | ||||
|             $scope.passwordInputType = 'password'; | ||||
|  | ||||
|     }; | ||||
|  | ||||
| }]); | ||||
| @@ -1,32 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Controller for the redirect field, which redirects the user to the provided | ||||
|  * URL. | ||||
|  */ | ||||
| angular.module('form').controller('redirectFieldController', ['$scope','$window', | ||||
|     function redirectFieldController($scope,$window) { | ||||
|  | ||||
|     /** | ||||
|      * Redirect the user to the provided URL. | ||||
|      */ | ||||
|     $window.location.href = $scope.field.redirectUrl; | ||||
|  | ||||
| }]); | ||||
| @@ -1,33 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Controller for select fields. | ||||
|  */ | ||||
| angular.module('form').controller('selectFieldController', ['$scope', '$injector', | ||||
|     function selectFieldController($scope, $injector) { | ||||
|  | ||||
|     // Interpret undefined/null as empty string | ||||
|     $scope.$watch('model', function setModel(model) { | ||||
|         if (!model && model !== '') | ||||
|             $scope.model = ''; | ||||
|     }); | ||||
|  | ||||
| }]); | ||||
| @@ -1,142 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Controller for terminal color scheme fields. | ||||
|  */ | ||||
| angular.module('form').controller('terminalColorSchemeFieldController', ['$scope', '$injector', | ||||
|     function terminalColorSchemeFieldController($scope, $injector) { | ||||
|  | ||||
|     // Required types | ||||
|     var ColorScheme = $injector.get('ColorScheme'); | ||||
|  | ||||
|     /** | ||||
|      * The currently selected color scheme. If a pre-defined color scheme is | ||||
|      * selected, this will be the connection parameter value associated with | ||||
|      * that color scheme. If a custom color scheme is selected, this will be | ||||
|      * the string "custom". | ||||
|      * | ||||
|      * @type String | ||||
|      */ | ||||
|     $scope.selectedColorScheme = ''; | ||||
|  | ||||
|     /** | ||||
|      * The current custom color scheme, if a custom color scheme has been | ||||
|      * specified. If no custom color scheme has yet been specified, this will | ||||
|      * be a ColorScheme instance that has been initialized to the default | ||||
|      * colors. | ||||
|      * | ||||
|      * @type ColorScheme | ||||
|      */ | ||||
|     $scope.customColorScheme = new ColorScheme(); | ||||
|  | ||||
|     /** | ||||
|      * The array of colors to include within the color picker as pre-defined | ||||
|      * options for convenience. | ||||
|      * | ||||
|      * @type String[] | ||||
|      */ | ||||
|     $scope.defaultPalette = new ColorScheme().colors; | ||||
|  | ||||
|     /** | ||||
|      * Whether the raw details of the custom color scheme should be shown. By | ||||
|      * default, such details are hidden. | ||||
|      * | ||||
|      * @type Boolean | ||||
|      */ | ||||
|     $scope.detailsShown = false; | ||||
|  | ||||
|     /** | ||||
|      * The palette indices of all colors which are considered low-intensity. | ||||
|      * | ||||
|      * @type Number[] | ||||
|      */ | ||||
|     $scope.lowIntensity = [ 0, 1, 2, 3, 4, 5, 6, 7 ]; | ||||
|  | ||||
|     /** | ||||
|      * The palette indices of all colors which are considered high-intensity. | ||||
|      * | ||||
|      * @type Number[] | ||||
|      */ | ||||
|     $scope.highIntensity = [ 8, 9, 10, 11, 12, 13, 14, 15 ]; | ||||
|  | ||||
|     /** | ||||
|      * The string value which is assigned to selectedColorScheme if a custom | ||||
|      * color scheme is selected. | ||||
|      * | ||||
|      * @constant | ||||
|      * @type String | ||||
|      */ | ||||
|     var CUSTOM_COLOR_SCHEME = 'custom'; | ||||
|  | ||||
|     /** | ||||
|      * Returns whether a custom color scheme has been selected. | ||||
|      * | ||||
|      * @returns {Boolean} | ||||
|      *     true if a custom color scheme has been selected, false otherwise. | ||||
|      */ | ||||
|     $scope.isCustom = function isCustom() { | ||||
|         return $scope.selectedColorScheme === CUSTOM_COLOR_SCHEME; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Shows the raw details of the custom color scheme. If the details are | ||||
|      * already shown, this function has no effect. | ||||
|      */ | ||||
|     $scope.showDetails = function showDetails() { | ||||
|         $scope.detailsShown = true; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Hides the raw details of the custom color scheme. If the details are | ||||
|      * already hidden, this function has no effect. | ||||
|      */ | ||||
|     $scope.hideDetails = function hideDetails() { | ||||
|         $scope.detailsShown = false; | ||||
|     }; | ||||
|  | ||||
|     // Keep selected color scheme and custom color scheme in sync with changes | ||||
|     // to model | ||||
|     $scope.$watch('model', function modelChanged(model) { | ||||
|         if ($scope.selectedColorScheme === CUSTOM_COLOR_SCHEME || (model && !_.includes($scope.field.options, model))) { | ||||
|             $scope.customColorScheme = ColorScheme.fromString(model); | ||||
|             $scope.selectedColorScheme = CUSTOM_COLOR_SCHEME; | ||||
|         } | ||||
|         else | ||||
|             $scope.selectedColorScheme = model || ''; | ||||
|     }); | ||||
|  | ||||
|     // Keep model in sync with changes to selected color scheme | ||||
|     $scope.$watch('selectedColorScheme', function selectedColorSchemeChanged(selectedColorScheme) { | ||||
|         if (!selectedColorScheme) | ||||
|             $scope.model = ''; | ||||
|         else if (selectedColorScheme === CUSTOM_COLOR_SCHEME) | ||||
|             $scope.model = ColorScheme.toString($scope.customColorScheme); | ||||
|         else | ||||
|             $scope.model = selectedColorScheme; | ||||
|     }); | ||||
|  | ||||
|     // Keep model in sync with changes to custom color scheme | ||||
|     $scope.$watch('customColorScheme', function customColorSchemeChanged(customColorScheme) { | ||||
|         if ($scope.selectedColorScheme === CUSTOM_COLOR_SCHEME) | ||||
|             $scope.model = ColorScheme.toString(customColorScheme); | ||||
|     }, true); | ||||
|  | ||||
| }]); | ||||
| @@ -1,40 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Controller for text fields. | ||||
|  */ | ||||
| angular.module('form').controller('textFieldController', ['$scope', '$injector', | ||||
|     function textFieldController($scope, $injector) { | ||||
|  | ||||
|     /** | ||||
|      * The ID of the datalist element that should be associated with the text | ||||
|      * field, providing a set of known-good values. If no such values are | ||||
|      * defined, this will be null. | ||||
|      * | ||||
|      * @type String | ||||
|      */ | ||||
|     $scope.dataListId = null; | ||||
|  | ||||
|     // Generate unique ID for datalist, if applicable | ||||
|     if ($scope.field.options && $scope.field.options.length) | ||||
|         $scope.dataListId = $scope.fieldId + '-datalist'; | ||||
|  | ||||
| }]); | ||||
| @@ -1,89 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Controller for time fields. | ||||
|  */ | ||||
| angular.module('form').controller('timeFieldController', ['$scope', '$injector', | ||||
|     function timeFieldController($scope, $injector) { | ||||
|  | ||||
|     // Required services | ||||
|     var $filter = $injector.get('$filter'); | ||||
|  | ||||
|     /** | ||||
|      * Options which dictate the behavior of the input field model, as defined | ||||
|      * by https://docs.angularjs.org/api/ng/directive/ngModelOptions | ||||
|      * | ||||
|      * @type Object.<String, String> | ||||
|      */ | ||||
|     $scope.modelOptions = { | ||||
|  | ||||
|         /** | ||||
|          * Space-delimited list of events on which the model will be updated. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         updateOn : 'blur', | ||||
|  | ||||
|         /** | ||||
|          * The time zone to use when reading/writing the Date object of the | ||||
|          * model. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         timezone : 'UTC' | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Parses the time components of the given string into a Date with only the | ||||
|      * time components set. The resulting Date will be in the UTC timezone, | ||||
|      * with the date left as 1970-01-01. The input string must be in the format | ||||
|      * HH:MM:SS (zero-padded, 24-hour). | ||||
|      * | ||||
|      * @param {String} str | ||||
|      *     The time string to parse. | ||||
|      * | ||||
|      * @returns {Date} | ||||
|      *     A Date object, in the UTC timezone, with only the time components | ||||
|      *     set. | ||||
|      */ | ||||
|     var parseTime = function parseTime(str) { | ||||
|  | ||||
|         // Parse time, return blank if invalid | ||||
|         var parsedDate = new Date('1970-01-01T' + str + 'Z'); | ||||
|         if (isNaN(parsedDate.getTime())) | ||||
|             return null; | ||||
|          | ||||
|         return parsedDate; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     // Update typed value when model is changed | ||||
|     $scope.$watch('model', function modelChanged(model) { | ||||
|         $scope.typedValue = (model ? parseTime(model) : null); | ||||
|     }); | ||||
|  | ||||
|     // Update string value in model when typed value is changed | ||||
|     $scope.$watch('typedValue', function typedValueChanged(typedValue) { | ||||
|         $scope.model = (typedValue ? $filter('date')(typedValue, 'HH:mm:ss', 'UTC') : ''); | ||||
|     }); | ||||
|  | ||||
| }]); | ||||
| @@ -1,708 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Controller for time zone fields. Time zone fields use IANA time zone | ||||
|  * database identifiers as the standard representation for each supported time | ||||
|  * zone. These identifiers are also legal Java time zone IDs. | ||||
|  */ | ||||
| angular.module('form').controller('timeZoneFieldController', ['$scope', '$injector', | ||||
|     function timeZoneFieldController($scope, $injector) { | ||||
|  | ||||
|     /** | ||||
|      * Map of time zone regions to the map of all time zone name/ID pairs | ||||
|      * within those regions. | ||||
|      * | ||||
|      * @type Object.<String, Object.<String, String>> | ||||
|      */ | ||||
|     $scope.timeZones = { | ||||
|  | ||||
|         "Africa" : { | ||||
|             "Abidjan"       : "Africa/Abidjan", | ||||
|             "Accra"         : "Africa/Accra", | ||||
|             "Addis Ababa"   : "Africa/Addis_Ababa", | ||||
|             "Algiers"       : "Africa/Algiers", | ||||
|             "Asmara"        : "Africa/Asmara", | ||||
|             "Asmera"        : "Africa/Asmera", | ||||
|             "Bamako"        : "Africa/Bamako", | ||||
|             "Bangui"        : "Africa/Bangui", | ||||
|             "Banjul"        : "Africa/Banjul", | ||||
|             "Bissau"        : "Africa/Bissau", | ||||
|             "Blantyre"      : "Africa/Blantyre", | ||||
|             "Brazzaville"   : "Africa/Brazzaville", | ||||
|             "Bujumbura"     : "Africa/Bujumbura", | ||||
|             "Cairo"         : "Africa/Cairo", | ||||
|             "Casablanca"    : "Africa/Casablanca", | ||||
|             "Ceuta"         : "Africa/Ceuta", | ||||
|             "Conakry"       : "Africa/Conakry", | ||||
|             "Dakar"         : "Africa/Dakar", | ||||
|             "Dar es Salaam" : "Africa/Dar_es_Salaam", | ||||
|             "Djibouti"      : "Africa/Djibouti", | ||||
|             "Douala"        : "Africa/Douala", | ||||
|             "El Aaiun"      : "Africa/El_Aaiun", | ||||
|             "Freetown"      : "Africa/Freetown", | ||||
|             "Gaborone"      : "Africa/Gaborone", | ||||
|             "Harare"        : "Africa/Harare", | ||||
|             "Johannesburg"  : "Africa/Johannesburg", | ||||
|             "Juba"          : "Africa/Juba", | ||||
|             "Kampala"       : "Africa/Kampala", | ||||
|             "Khartoum"      : "Africa/Khartoum", | ||||
|             "Kigali"        : "Africa/Kigali", | ||||
|             "Kinshasa"      : "Africa/Kinshasa", | ||||
|             "Lagos"         : "Africa/Lagos", | ||||
|             "Libreville"    : "Africa/Libreville", | ||||
|             "Lome"          : "Africa/Lome", | ||||
|             "Luanda"        : "Africa/Luanda", | ||||
|             "Lubumbashi"    : "Africa/Lubumbashi", | ||||
|             "Lusaka"        : "Africa/Lusaka", | ||||
|             "Malabo"        : "Africa/Malabo", | ||||
|             "Maputo"        : "Africa/Maputo", | ||||
|             "Maseru"        : "Africa/Maseru", | ||||
|             "Mbabane"       : "Africa/Mbabane", | ||||
|             "Mogadishu"     : "Africa/Mogadishu", | ||||
|             "Monrovia"      : "Africa/Monrovia", | ||||
|             "Nairobi"       : "Africa/Nairobi", | ||||
|             "Ndjamena"      : "Africa/Ndjamena", | ||||
|             "Niamey"        : "Africa/Niamey", | ||||
|             "Nouakchott"    : "Africa/Nouakchott", | ||||
|             "Ouagadougou"   : "Africa/Ouagadougou", | ||||
|             "Porto-Novo"    : "Africa/Porto-Novo", | ||||
|             "Sao Tome"      : "Africa/Sao_Tome", | ||||
|             "Timbuktu"      : "Africa/Timbuktu", | ||||
|             "Tripoli"       : "Africa/Tripoli", | ||||
|             "Tunis"         : "Africa/Tunis", | ||||
|             "Windhoek"      : "Africa/Windhoek" | ||||
|         }, | ||||
|  | ||||
|         "America" : { | ||||
|             "Adak"                           : "America/Adak", | ||||
|             "Anchorage"                      : "America/Anchorage", | ||||
|             "Anguilla"                       : "America/Anguilla", | ||||
|             "Antigua"                        : "America/Antigua", | ||||
|             "Araguaina"                      : "America/Araguaina", | ||||
|             "Argentina / Buenos Aires"       : "America/Argentina/Buenos_Aires", | ||||
|             "Argentina / Catamarca"          : "America/Argentina/Catamarca", | ||||
|             "Argentina / Comodoro Rivadavia" : "America/Argentina/ComodRivadavia", | ||||
|             "Argentina / Cordoba"            : "America/Argentina/Cordoba", | ||||
|             "Argentina / Jujuy"              : "America/Argentina/Jujuy", | ||||
|             "Argentina / La Rioja"           : "America/Argentina/La_Rioja", | ||||
|             "Argentina / Mendoza"            : "America/Argentina/Mendoza", | ||||
|             "Argentina / Rio Gallegos"       : "America/Argentina/Rio_Gallegos", | ||||
|             "Argentina / Salta"              : "America/Argentina/Salta", | ||||
|             "Argentina / San Juan"           : "America/Argentina/San_Juan", | ||||
|             "Argentina / San Luis"           : "America/Argentina/San_Luis", | ||||
|             "Argentina / Tucuman"            : "America/Argentina/Tucuman", | ||||
|             "Argentina / Ushuaia"            : "America/Argentina/Ushuaia", | ||||
|             "Aruba"                          : "America/Aruba", | ||||
|             "Asuncion"                       : "America/Asuncion", | ||||
|             "Atikokan"                       : "America/Atikokan", | ||||
|             "Atka"                           : "America/Atka", | ||||
|             "Bahia"                          : "America/Bahia", | ||||
|             "Bahia Banderas"                 : "America/Bahia_Banderas", | ||||
|             "Barbados"                       : "America/Barbados", | ||||
|             "Belem"                          : "America/Belem", | ||||
|             "Belize"                         : "America/Belize", | ||||
|             "Blanc-Sablon"                   : "America/Blanc-Sablon", | ||||
|             "Boa Vista"                      : "America/Boa_Vista", | ||||
|             "Bogota"                         : "America/Bogota", | ||||
|             "Boise"                          : "America/Boise", | ||||
|             "Buenos Aires"                   : "America/Buenos_Aires", | ||||
|             "Cambridge Bay"                  : "America/Cambridge_Bay", | ||||
|             "Campo Grande"                   : "America/Campo_Grande", | ||||
|             "Cancun"                         : "America/Cancun", | ||||
|             "Caracas"                        : "America/Caracas", | ||||
|             "Catamarca"                      : "America/Catamarca", | ||||
|             "Cayenne"                        : "America/Cayenne", | ||||
|             "Cayman"                         : "America/Cayman", | ||||
|             "Chicago"                        : "America/Chicago", | ||||
|             "Chihuahua"                      : "America/Chihuahua", | ||||
|             "Coral Harbour"                  : "America/Coral_Harbour", | ||||
|             "Cordoba"                        : "America/Cordoba", | ||||
|             "Costa Rica"                     : "America/Costa_Rica", | ||||
|             "Creston"                        : "America/Creston", | ||||
|             "Cuiaba"                         : "America/Cuiaba", | ||||
|             "Curacao"                        : "America/Curacao", | ||||
|             "Danmarkshavn"                   : "America/Danmarkshavn", | ||||
|             "Dawson"                         : "America/Dawson", | ||||
|             "Dawson Creek"                   : "America/Dawson_Creek", | ||||
|             "Denver"                         : "America/Denver", | ||||
|             "Detroit"                        : "America/Detroit", | ||||
|             "Dominica"                       : "America/Dominica", | ||||
|             "Edmonton"                       : "America/Edmonton", | ||||
|             "Eirunepe"                       : "America/Eirunepe", | ||||
|             "El Salvador"                    : "America/El_Salvador", | ||||
|             "Ensenada"                       : "America/Ensenada", | ||||
|             "Fort Wayne"                     : "America/Fort_Wayne", | ||||
|             "Fortaleza"                      : "America/Fortaleza", | ||||
|             "Glace Bay"                      : "America/Glace_Bay", | ||||
|             "Godthab"                        : "America/Godthab", | ||||
|             "Goose Bay"                      : "America/Goose_Bay", | ||||
|             "Grand Turk"                     : "America/Grand_Turk", | ||||
|             "Grenada"                        : "America/Grenada", | ||||
|             "Guadeloupe"                     : "America/Guadeloupe", | ||||
|             "Guatemala"                      : "America/Guatemala", | ||||
|             "Guayaquil"                      : "America/Guayaquil", | ||||
|             "Guyana"                         : "America/Guyana", | ||||
|             "Halifax"                        : "America/Halifax", | ||||
|             "Havana"                         : "America/Havana", | ||||
|             "Hermosillo"                     : "America/Hermosillo", | ||||
|             "Indiana / Indianapolis"         : "America/Indiana/Indianapolis", | ||||
|             "Indiana / Knox"                 : "America/Indiana/Knox", | ||||
|             "Indiana / Marengo"              : "America/Indiana/Marengo", | ||||
|             "Indiana / Petersburg"           : "America/Indiana/Petersburg", | ||||
|             "Indiana / Tell City"            : "America/Indiana/Tell_City", | ||||
|             "Indiana / Vevay"                : "America/Indiana/Vevay", | ||||
|             "Indiana / Vincennes"            : "America/Indiana/Vincennes", | ||||
|             "Indiana / Winamac"              : "America/Indiana/Winamac", | ||||
|             "Indianapolis"                   : "America/Indianapolis", | ||||
|             "Inuvik"                         : "America/Inuvik", | ||||
|             "Iqaluit"                        : "America/Iqaluit", | ||||
|             "Jamaica"                        : "America/Jamaica", | ||||
|             "Jujuy"                          : "America/Jujuy", | ||||
|             "Juneau"                         : "America/Juneau", | ||||
|             "Kentucky / Louisville"          : "America/Kentucky/Louisville", | ||||
|             "Kentucky / Monticello"          : "America/Kentucky/Monticello", | ||||
|             "Kralendijk"                     : "America/Kralendijk", | ||||
|             "La Paz"                         : "America/La_Paz", | ||||
|             "Lima"                           : "America/Lima", | ||||
|             "Los Angeles"                    : "America/Los_Angeles", | ||||
|             "Louisville"                     : "America/Louisville", | ||||
|             "Lower Princes"                  : "America/Lower_Princes", | ||||
|             "Maceio"                         : "America/Maceio", | ||||
|             "Managua"                        : "America/Managua", | ||||
|             "Manaus"                         : "America/Manaus", | ||||
|             "Marigot"                        : "America/Marigot", | ||||
|             "Martinique"                     : "America/Martinique", | ||||
|             "Matamoros"                      : "America/Matamoros", | ||||
|             "Mazatlan"                       : "America/Mazatlan", | ||||
|             "Mendoza"                        : "America/Mendoza", | ||||
|             "Menominee"                      : "America/Menominee", | ||||
|             "Merida"                         : "America/Merida", | ||||
|             "Metlakatla"                     : "America/Metlakatla", | ||||
|             "Mexico City"                    : "America/Mexico_City", | ||||
|             "Miquelon"                       : "America/Miquelon", | ||||
|             "Moncton"                        : "America/Moncton", | ||||
|             "Monterrey"                      : "America/Monterrey", | ||||
|             "Montevideo"                     : "America/Montevideo", | ||||
|             "Montreal"                       : "America/Montreal", | ||||
|             "Montserrat"                     : "America/Montserrat", | ||||
|             "Nassau"                         : "America/Nassau", | ||||
|             "New York"                       : "America/New_York", | ||||
|             "Nipigon"                        : "America/Nipigon", | ||||
|             "Nome"                           : "America/Nome", | ||||
|             "Noronha"                        : "America/Noronha", | ||||
|             "North Dakota / Beulah"          : "America/North_Dakota/Beulah", | ||||
|             "North Dakota / Center"          : "America/North_Dakota/Center", | ||||
|             "North Dakota / New Salem"       : "America/North_Dakota/New_Salem", | ||||
|             "Ojinaga"                        : "America/Ojinaga", | ||||
|             "Panama"                         : "America/Panama", | ||||
|             "Pangnirtung"                    : "America/Pangnirtung", | ||||
|             "Paramaribo"                     : "America/Paramaribo", | ||||
|             "Phoenix"                        : "America/Phoenix", | ||||
|             "Port-au-Prince"                 : "America/Port-au-Prince", | ||||
|             "Port of Spain"                  : "America/Port_of_Spain", | ||||
|             "Porto Acre"                     : "America/Porto_Acre", | ||||
|             "Porto Velho"                    : "America/Porto_Velho", | ||||
|             "Puerto Rico"                    : "America/Puerto_Rico", | ||||
|             "Rainy River"                    : "America/Rainy_River", | ||||
|             "Rankin Inlet"                   : "America/Rankin_Inlet", | ||||
|             "Recife"                         : "America/Recife", | ||||
|             "Regina"                         : "America/Regina", | ||||
|             "Resolute"                       : "America/Resolute", | ||||
|             "Rio Branco"                     : "America/Rio_Branco", | ||||
|             "Rosario"                        : "America/Rosario", | ||||
|             "Santa Isabel"                   : "America/Santa_Isabel", | ||||
|             "Santarem"                       : "America/Santarem", | ||||
|             "Santiago"                       : "America/Santiago", | ||||
|             "Santo Domingo"                  : "America/Santo_Domingo", | ||||
|             "Sao Paulo"                      : "America/Sao_Paulo", | ||||
|             "Scoresbysund"                   : "America/Scoresbysund", | ||||
|             "Shiprock"                       : "America/Shiprock", | ||||
|             "Sitka"                          : "America/Sitka", | ||||
|             "St. Barthelemy"                 : "America/St_Barthelemy", | ||||
|             "St. Johns"                      : "America/St_Johns", | ||||
|             "St. Kitts"                      : "America/St_Kitts", | ||||
|             "St. Lucia"                      : "America/St_Lucia", | ||||
|             "St. Thomas"                     : "America/St_Thomas", | ||||
|             "St. Vincent"                    : "America/St_Vincent", | ||||
|             "Swift Current"                  : "America/Swift_Current", | ||||
|             "Tegucigalpa"                    : "America/Tegucigalpa", | ||||
|             "Thule"                          : "America/Thule", | ||||
|             "Thunder Bay"                    : "America/Thunder_Bay", | ||||
|             "Tijuana"                        : "America/Tijuana", | ||||
|             "Toronto"                        : "America/Toronto", | ||||
|             "Tortola"                        : "America/Tortola", | ||||
|             "Vancouver"                      : "America/Vancouver", | ||||
|             "Virgin"                         : "America/Virgin", | ||||
|             "Whitehorse"                     : "America/Whitehorse", | ||||
|             "Winnipeg"                       : "America/Winnipeg", | ||||
|             "Yakutat"                        : "America/Yakutat", | ||||
|             "Yellowknife"                    : "America/Yellowknife" | ||||
|         }, | ||||
|  | ||||
|         "Antarctica" : { | ||||
|             "Casey"            : "Antarctica/Casey", | ||||
|             "Davis"            : "Antarctica/Davis", | ||||
|             "Dumont d'Urville" : "Antarctica/DumontDUrville", | ||||
|             "Macquarie"        : "Antarctica/Macquarie", | ||||
|             "Mawson"           : "Antarctica/Mawson", | ||||
|             "McMurdo"          : "Antarctica/McMurdo", | ||||
|             "Palmer"           : "Antarctica/Palmer", | ||||
|             "Rothera"          : "Antarctica/Rothera", | ||||
|             "South Pole"       : "Antarctica/South_Pole", | ||||
|             "Syowa"            : "Antarctica/Syowa", | ||||
|             "Troll"            : "Antarctica/Troll", | ||||
|             "Vostok"           : "Antarctica/Vostok" | ||||
|         }, | ||||
|  | ||||
|         "Arctic" : { | ||||
|             "Longyearbyen" : "Arctic/Longyearbyen" | ||||
|         }, | ||||
|  | ||||
|         "Asia" : { | ||||
|             "Aden"          : "Asia/Aden", | ||||
|             "Almaty"        : "Asia/Almaty", | ||||
|             "Amman"         : "Asia/Amman", | ||||
|             "Anadyr"        : "Asia/Anadyr", | ||||
|             "Aqtau"         : "Asia/Aqtau", | ||||
|             "Aqtobe"        : "Asia/Aqtobe", | ||||
|             "Ashgabat"      : "Asia/Ashgabat", | ||||
|             "Ashkhabad"     : "Asia/Ashkhabad", | ||||
|             "Baghdad"       : "Asia/Baghdad", | ||||
|             "Bahrain"       : "Asia/Bahrain", | ||||
|             "Baku"          : "Asia/Baku", | ||||
|             "Bangkok"       : "Asia/Bangkok", | ||||
|             "Beirut"        : "Asia/Beirut", | ||||
|             "Bishkek"       : "Asia/Bishkek", | ||||
|             "Brunei"        : "Asia/Brunei", | ||||
|             "Calcutta"      : "Asia/Calcutta", | ||||
|             "Chita"         : "Asia/Chita", | ||||
|             "Choibalsan"    : "Asia/Choibalsan", | ||||
|             "Chongqing"     : "Asia/Chongqing", | ||||
|             "Colombo"       : "Asia/Colombo", | ||||
|             "Dacca"         : "Asia/Dacca", | ||||
|             "Damascus"      : "Asia/Damascus", | ||||
|             "Dhaka"         : "Asia/Dhaka", | ||||
|             "Dili"          : "Asia/Dili", | ||||
|             "Dubai"         : "Asia/Dubai", | ||||
|             "Dushanbe"      : "Asia/Dushanbe", | ||||
|             "Gaza"          : "Asia/Gaza", | ||||
|             "Harbin"        : "Asia/Harbin", | ||||
|             "Hebron"        : "Asia/Hebron", | ||||
|             "Ho Chi Minh"   : "Asia/Ho_Chi_Minh", | ||||
|             "Hong Kong"     : "Asia/Hong_Kong", | ||||
|             "Hovd"          : "Asia/Hovd", | ||||
|             "Irkutsk"       : "Asia/Irkutsk", | ||||
|             "Istanbul"      : "Asia/Istanbul", | ||||
|             "Jakarta"       : "Asia/Jakarta", | ||||
|             "Jayapura"      : "Asia/Jayapura", | ||||
|             "Jerusalem"     : "Asia/Jerusalem", | ||||
|             "Kabul"         : "Asia/Kabul", | ||||
|             "Kamchatka"     : "Asia/Kamchatka", | ||||
|             "Karachi"       : "Asia/Karachi", | ||||
|             "Kashgar"       : "Asia/Kashgar", | ||||
|             "Kathmandu"     : "Asia/Kathmandu", | ||||
|             "Katmandu"      : "Asia/Katmandu", | ||||
|             "Khandyga"      : "Asia/Khandyga", | ||||
|             "Kolkata"       : "Asia/Kolkata", | ||||
|             "Krasnoyarsk"   : "Asia/Krasnoyarsk", | ||||
|             "Kuala Lumpur"  : "Asia/Kuala_Lumpur", | ||||
|             "Kuching"       : "Asia/Kuching", | ||||
|             "Kuwait"        : "Asia/Kuwait", | ||||
|             "Macao"         : "Asia/Macao", | ||||
|             "Macau"         : "Asia/Macau", | ||||
|             "Magadan"       : "Asia/Magadan", | ||||
|             "Makassar"      : "Asia/Makassar", | ||||
|             "Manila"        : "Asia/Manila", | ||||
|             "Muscat"        : "Asia/Muscat", | ||||
|             "Nicosia"       : "Asia/Nicosia", | ||||
|             "Novokuznetsk"  : "Asia/Novokuznetsk", | ||||
|             "Novosibirsk"   : "Asia/Novosibirsk", | ||||
|             "Omsk"          : "Asia/Omsk", | ||||
|             "Oral"          : "Asia/Oral", | ||||
|             "Phnom Penh"    : "Asia/Phnom_Penh", | ||||
|             "Pontianak"     : "Asia/Pontianak", | ||||
|             "Pyongyang"     : "Asia/Pyongyang", | ||||
|             "Qatar"         : "Asia/Qatar", | ||||
|             "Qyzylorda"     : "Asia/Qyzylorda", | ||||
|             "Rangoon"       : "Asia/Rangoon", | ||||
|             "Riyadh"        : "Asia/Riyadh", | ||||
|             "Saigon"        : "Asia/Saigon", | ||||
|             "Sakhalin"      : "Asia/Sakhalin", | ||||
|             "Samarkand"     : "Asia/Samarkand", | ||||
|             "Seoul"         : "Asia/Seoul", | ||||
|             "Shanghai"      : "Asia/Shanghai", | ||||
|             "Singapore"     : "Asia/Singapore", | ||||
|             "Srednekolymsk" : "Asia/Srednekolymsk", | ||||
|             "Taipei"        : "Asia/Taipei", | ||||
|             "Tashkent"      : "Asia/Tashkent", | ||||
|             "Tbilisi"       : "Asia/Tbilisi", | ||||
|             "Tehran"        : "Asia/Tehran", | ||||
|             "Tel Aviv"      : "Asia/Tel_Aviv", | ||||
|             "Thimbu"        : "Asia/Thimbu", | ||||
|             "Thimphu"       : "Asia/Thimphu", | ||||
|             "Tokyo"         : "Asia/Tokyo", | ||||
|             "Ujung Pandang" : "Asia/Ujung_Pandang", | ||||
|             "Ulaanbaatar"   : "Asia/Ulaanbaatar", | ||||
|             "Ulan Bator"    : "Asia/Ulan_Bator", | ||||
|             "Urumqi"        : "Asia/Urumqi", | ||||
|             "Ust-Nera"      : "Asia/Ust-Nera", | ||||
|             "Vientiane"     : "Asia/Vientiane", | ||||
|             "Vladivostok"   : "Asia/Vladivostok", | ||||
|             "Yakutsk"       : "Asia/Yakutsk", | ||||
|             "Yekaterinburg" : "Asia/Yekaterinburg", | ||||
|             "Yerevan"       : "Asia/Yerevan" | ||||
|         }, | ||||
|  | ||||
|         "Atlantic" : { | ||||
|             "Azores"        : "Atlantic/Azores", | ||||
|             "Bermuda"       : "Atlantic/Bermuda", | ||||
|             "Canary"        : "Atlantic/Canary", | ||||
|             "Cape Verde"    : "Atlantic/Cape_Verde", | ||||
|             "Faeroe"        : "Atlantic/Faeroe", | ||||
|             "Faroe"         : "Atlantic/Faroe", | ||||
|             "Jan Mayen"     : "Atlantic/Jan_Mayen", | ||||
|             "Madeira"       : "Atlantic/Madeira", | ||||
|             "Reykjavik"     : "Atlantic/Reykjavik", | ||||
|             "South Georgia" : "Atlantic/South_Georgia", | ||||
|             "St. Helena"    : "Atlantic/St_Helena", | ||||
|             "Stanley"       : "Atlantic/Stanley" | ||||
|         }, | ||||
|  | ||||
|         "Australia" : { | ||||
|             "Adelaide"    : "Australia/Adelaide", | ||||
|             "Brisbane"    : "Australia/Brisbane", | ||||
|             "Broken Hill" : "Australia/Broken_Hill", | ||||
|             "Canberra"    : "Australia/Canberra", | ||||
|             "Currie"      : "Australia/Currie", | ||||
|             "Darwin"      : "Australia/Darwin", | ||||
|             "Eucla"       : "Australia/Eucla", | ||||
|             "Hobart"      : "Australia/Hobart", | ||||
|             "Lindeman"    : "Australia/Lindeman", | ||||
|             "Lord Howe"   : "Australia/Lord_Howe", | ||||
|             "Melbourne"   : "Australia/Melbourne", | ||||
|             "North"       : "Australia/North", | ||||
|             "Perth"       : "Australia/Perth", | ||||
|             "Queensland"  : "Australia/Queensland", | ||||
|             "South"       : "Australia/South", | ||||
|             "Sydney"      : "Australia/Sydney", | ||||
|             "Tasmania"    : "Australia/Tasmania", | ||||
|             "Victoria"    : "Australia/Victoria", | ||||
|             "West"        : "Australia/West", | ||||
|             "Yancowinna"  : "Australia/Yancowinna" | ||||
|         }, | ||||
|  | ||||
|         "Brazil" : { | ||||
|             "Acre"                : "Brazil/Acre", | ||||
|             "Fernando de Noronha" : "Brazil/DeNoronha", | ||||
|             "East"                : "Brazil/East", | ||||
|             "West"                : "Brazil/West" | ||||
|         }, | ||||
|  | ||||
|         "Canada" : { | ||||
|             "Atlantic"          : "Canada/Atlantic", | ||||
|             "Central"           : "Canada/Central", | ||||
|             "Eastern"           : "Canada/Eastern", | ||||
|             "Mountain"          : "Canada/Mountain", | ||||
|             "Newfoundland"      : "Canada/Newfoundland", | ||||
|             "Pacific"           : "Canada/Pacific", | ||||
|             "Saskatchewan"      : "Canada/Saskatchewan", | ||||
|             "Yukon"             : "Canada/Yukon" | ||||
|         }, | ||||
|  | ||||
|         "Chile" : { | ||||
|             "Continental"   : "Chile/Continental", | ||||
|             "Easter Island" : "Chile/EasterIsland" | ||||
|         }, | ||||
|  | ||||
|         "Europe" : { | ||||
|             "Amsterdam"   : "Europe/Amsterdam", | ||||
|             "Andorra"     : "Europe/Andorra", | ||||
|             "Athens"      : "Europe/Athens", | ||||
|             "Belfast"     : "Europe/Belfast", | ||||
|             "Belgrade"    : "Europe/Belgrade", | ||||
|             "Berlin"      : "Europe/Berlin", | ||||
|             "Bratislava"  : "Europe/Bratislava", | ||||
|             "Brussels"    : "Europe/Brussels", | ||||
|             "Bucharest"   : "Europe/Bucharest", | ||||
|             "Budapest"    : "Europe/Budapest", | ||||
|             "Busingen"    : "Europe/Busingen", | ||||
|             "Chisinau"    : "Europe/Chisinau", | ||||
|             "Copenhagen"  : "Europe/Copenhagen", | ||||
|             "Dublin"      : "Europe/Dublin", | ||||
|             "Gibraltar"   : "Europe/Gibraltar", | ||||
|             "Guernsey"    : "Europe/Guernsey", | ||||
|             "Helsinki"    : "Europe/Helsinki", | ||||
|             "Isle of Man" : "Europe/Isle_of_Man", | ||||
|             "Istanbul"    : "Europe/Istanbul", | ||||
|             "Jersey"      : "Europe/Jersey", | ||||
|             "Kaliningrad" : "Europe/Kaliningrad", | ||||
|             "Kiev"        : "Europe/Kiev", | ||||
|             "Lisbon"      : "Europe/Lisbon", | ||||
|             "Ljubljana"   : "Europe/Ljubljana", | ||||
|             "London"      : "Europe/London", | ||||
|             "Luxembourg"  : "Europe/Luxembourg", | ||||
|             "Madrid"      : "Europe/Madrid", | ||||
|             "Malta"       : "Europe/Malta", | ||||
|             "Mariehamn"   : "Europe/Mariehamn", | ||||
|             "Minsk"       : "Europe/Minsk", | ||||
|             "Monaco"      : "Europe/Monaco", | ||||
|             "Moscow"      : "Europe/Moscow", | ||||
|             "Nicosia"     : "Europe/Nicosia", | ||||
|             "Oslo"        : "Europe/Oslo", | ||||
|             "Paris"       : "Europe/Paris", | ||||
|             "Podgorica"   : "Europe/Podgorica", | ||||
|             "Prague"      : "Europe/Prague", | ||||
|             "Riga"        : "Europe/Riga", | ||||
|             "Rome"        : "Europe/Rome", | ||||
|             "Samara"      : "Europe/Samara", | ||||
|             "San Marino"  : "Europe/San_Marino", | ||||
|             "Sarajevo"    : "Europe/Sarajevo", | ||||
|             "Simferopol"  : "Europe/Simferopol", | ||||
|             "Skopje"      : "Europe/Skopje", | ||||
|             "Sofia"       : "Europe/Sofia", | ||||
|             "Stockholm"   : "Europe/Stockholm", | ||||
|             "Tallinn"     : "Europe/Tallinn", | ||||
|             "Tirane"      : "Europe/Tirane", | ||||
|             "Tiraspol"    : "Europe/Tiraspol", | ||||
|             "Uzhgorod"    : "Europe/Uzhgorod", | ||||
|             "Vaduz"       : "Europe/Vaduz", | ||||
|             "Vatican"     : "Europe/Vatican", | ||||
|             "Vienna"      : "Europe/Vienna", | ||||
|             "Vilnius"     : "Europe/Vilnius", | ||||
|             "Volgograd"   : "Europe/Volgograd", | ||||
|             "Warsaw"      : "Europe/Warsaw", | ||||
|             "Zagreb"      : "Europe/Zagreb", | ||||
|             "Zaporozhye"  : "Europe/Zaporozhye", | ||||
|             "Zurich"      : "Europe/Zurich" | ||||
|         }, | ||||
|  | ||||
|         "GMT" : { | ||||
|             "GMT-14" : "Etc/GMT-14", | ||||
|             "GMT-13" : "Etc/GMT-13", | ||||
|             "GMT-12" : "Etc/GMT-12", | ||||
|             "GMT-11" : "Etc/GMT-11", | ||||
|             "GMT-10" : "Etc/GMT-10", | ||||
|             "GMT-9"  : "Etc/GMT-9", | ||||
|             "GMT-8"  : "Etc/GMT-8", | ||||
|             "GMT-7"  : "Etc/GMT-7", | ||||
|             "GMT-6"  : "Etc/GMT-6", | ||||
|             "GMT-5"  : "Etc/GMT-5", | ||||
|             "GMT-4"  : "Etc/GMT-4", | ||||
|             "GMT-3"  : "Etc/GMT-3", | ||||
|             "GMT-2"  : "Etc/GMT-2", | ||||
|             "GMT-1"  : "Etc/GMT-1", | ||||
|             "GMT+0"  : "Etc/GMT+0", | ||||
|             "GMT+1"  : "Etc/GMT+1", | ||||
|             "GMT+2"  : "Etc/GMT+2", | ||||
|             "GMT+3"  : "Etc/GMT+3", | ||||
|             "GMT+4"  : "Etc/GMT+4", | ||||
|             "GMT+5"  : "Etc/GMT+5", | ||||
|             "GMT+6"  : "Etc/GMT+6", | ||||
|             "GMT+7"  : "Etc/GMT+7", | ||||
|             "GMT+8"  : "Etc/GMT+8", | ||||
|             "GMT+9"  : "Etc/GMT+9", | ||||
|             "GMT+10" : "Etc/GMT+10", | ||||
|             "GMT+11" : "Etc/GMT+11", | ||||
|             "GMT+12" : "Etc/GMT+12" | ||||
|         }, | ||||
|  | ||||
|         "Indian" : { | ||||
|             "Antananarivo" : "Indian/Antananarivo", | ||||
|             "Chagos"       : "Indian/Chagos", | ||||
|             "Christmas"    : "Indian/Christmas", | ||||
|             "Cocos"        : "Indian/Cocos", | ||||
|             "Comoro"       : "Indian/Comoro", | ||||
|             "Kerguelen"    : "Indian/Kerguelen", | ||||
|             "Mahe"         : "Indian/Mahe", | ||||
|             "Maldives"     : "Indian/Maldives", | ||||
|             "Mauritius"    : "Indian/Mauritius", | ||||
|             "Mayotte"      : "Indian/Mayotte", | ||||
|             "Reunion"      : "Indian/Reunion" | ||||
|         }, | ||||
|  | ||||
|         "Mexico" : { | ||||
|             "Baja Norte" : "Mexico/BajaNorte", | ||||
|             "Baja Sur"   : "Mexico/BajaSur", | ||||
|             "General"    : "Mexico/General" | ||||
|         }, | ||||
|  | ||||
|         "Pacific" : { | ||||
|             "Apia"         : "Pacific/Apia", | ||||
|             "Auckland"     : "Pacific/Auckland", | ||||
|             "Bougainville" : "Pacific/Bougainville", | ||||
|             "Chatham"      : "Pacific/Chatham", | ||||
|             "Chuuk"        : "Pacific/Chuuk", | ||||
|             "Easter"       : "Pacific/Easter", | ||||
|             "Efate"        : "Pacific/Efate", | ||||
|             "Enderbury"    : "Pacific/Enderbury", | ||||
|             "Fakaofo"      : "Pacific/Fakaofo", | ||||
|             "Fiji"         : "Pacific/Fiji", | ||||
|             "Funafuti"     : "Pacific/Funafuti", | ||||
|             "Galapagos"    : "Pacific/Galapagos", | ||||
|             "Gambier"      : "Pacific/Gambier", | ||||
|             "Guadalcanal"  : "Pacific/Guadalcanal", | ||||
|             "Guam"         : "Pacific/Guam", | ||||
|             "Honolulu"     : "Pacific/Honolulu", | ||||
|             "Johnston"     : "Pacific/Johnston", | ||||
|             "Kiritimati"   : "Pacific/Kiritimati", | ||||
|             "Kosrae"       : "Pacific/Kosrae", | ||||
|             "Kwajalein"    : "Pacific/Kwajalein", | ||||
|             "Majuro"       : "Pacific/Majuro", | ||||
|             "Marquesas"    : "Pacific/Marquesas", | ||||
|             "Midway"       : "Pacific/Midway", | ||||
|             "Nauru"        : "Pacific/Nauru", | ||||
|             "Niue"         : "Pacific/Niue", | ||||
|             "Norfolk"      : "Pacific/Norfolk", | ||||
|             "Noumea"       : "Pacific/Noumea", | ||||
|             "Pago Pago"    : "Pacific/Pago_Pago", | ||||
|             "Palau"        : "Pacific/Palau", | ||||
|             "Pitcairn"     : "Pacific/Pitcairn", | ||||
|             "Pohnpei"      : "Pacific/Pohnpei", | ||||
|             "Ponape"       : "Pacific/Ponape", | ||||
|             "Port Moresby" : "Pacific/Port_Moresby", | ||||
|             "Rarotonga"    : "Pacific/Rarotonga", | ||||
|             "Saipan"       : "Pacific/Saipan", | ||||
|             "Samoa"        : "Pacific/Samoa", | ||||
|             "Tahiti"       : "Pacific/Tahiti", | ||||
|             "Tarawa"       : "Pacific/Tarawa", | ||||
|             "Tongatapu"    : "Pacific/Tongatapu", | ||||
|             "Truk"         : "Pacific/Truk", | ||||
|             "Wake"         : "Pacific/Wake", | ||||
|             "Wallis"       : "Pacific/Wallis", | ||||
|             "Yap"          : "Pacific/Yap" | ||||
|         } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * All selectable regions. | ||||
|      * | ||||
|      * @type String[] | ||||
|      */ | ||||
|     $scope.regions = (function collectRegions() { | ||||
|  | ||||
|         // Start with blank entry | ||||
|         var regions = [ '' ]; | ||||
|  | ||||
|         // Add each available region | ||||
|         for (var region in $scope.timeZones) | ||||
|             regions.push(region); | ||||
|  | ||||
|         return regions; | ||||
|  | ||||
|     })(); | ||||
|  | ||||
|     /** | ||||
|      * Direct mapping of all time zone IDs to the region containing that ID. | ||||
|      * | ||||
|      * @type Object.<String, String> | ||||
|      */ | ||||
|     var timeZoneRegions = (function mapRegions() { | ||||
|  | ||||
|         var regions = {}; | ||||
|  | ||||
|         // For each available region | ||||
|         for (var region in $scope.timeZones) { | ||||
|  | ||||
|             // Get time zones within that region | ||||
|             var timeZonesInRegion = $scope.timeZones[region]; | ||||
|  | ||||
|             // For each of those time zones | ||||
|             for (var timeZoneName in timeZonesInRegion) { | ||||
|  | ||||
|                 // Get corresponding ID | ||||
|                 var timeZoneID = timeZonesInRegion[timeZoneName]; | ||||
|  | ||||
|                 // Store region in map | ||||
|                 regions[timeZoneID] = region; | ||||
|  | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         return regions; | ||||
|  | ||||
|     })(); | ||||
|  | ||||
|     /** | ||||
|      * Map of regions to the currently selected time zone for that region. | ||||
|      * Initially, all regions will be set to default selections (the first | ||||
|      * time zone, sorted lexicographically). | ||||
|      * | ||||
|      * @type Object.<String, String> | ||||
|      */ | ||||
|     var selectedTimeZone = (function produceDefaultTimeZones() { | ||||
|  | ||||
|         var defaultTimeZone = {}; | ||||
|  | ||||
|         // For each available region | ||||
|         for (var region in $scope.timeZones) { | ||||
|  | ||||
|             // Get time zones within that region | ||||
|             var timeZonesInRegion = $scope.timeZones[region]; | ||||
|  | ||||
|             // No default initially | ||||
|             var defaultZoneName = null; | ||||
|             var defaultZoneID = null; | ||||
|  | ||||
|             // For each of those time zones | ||||
|             for (var timeZoneName in timeZonesInRegion) { | ||||
|  | ||||
|                 // Get corresponding ID | ||||
|                 var timeZoneID = timeZonesInRegion[timeZoneName]; | ||||
|  | ||||
|                 // Set as default if earlier than existing default | ||||
|                 if (!defaultZoneName || timeZoneName < defaultZoneName) { | ||||
|                     defaultZoneName = timeZoneName; | ||||
|                     defaultZoneID = timeZoneID; | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|  | ||||
|             // Store default zone | ||||
|             defaultTimeZone[region] = defaultZoneID; | ||||
|  | ||||
|         } | ||||
|  | ||||
|         return defaultTimeZone; | ||||
|  | ||||
|     })(); | ||||
|  | ||||
|     /** | ||||
|      * The name of the region currently selected. The selected region narrows | ||||
|      * which time zones are selectable. | ||||
|      * | ||||
|      * @type String | ||||
|      */ | ||||
|     $scope.region = ''; | ||||
|  | ||||
|     // Ensure corresponding region is selected | ||||
|     $scope.$watch('model', function setModel(model) { | ||||
|         $scope.region = timeZoneRegions[model] || ''; | ||||
|         selectedTimeZone[$scope.region] = model; | ||||
|     }); | ||||
|  | ||||
|     // Restore time zone selection when region changes | ||||
|     $scope.$watch('region', function restoreSelection(region) { | ||||
|         $scope.model = selectedTimeZone[region] || null; | ||||
|     }); | ||||
|  | ||||
| }]); | ||||
| @@ -1,255 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * A directive that allows editing of a collection of fields. | ||||
|  */ | ||||
| angular.module('form').directive('guacForm', [function form() { | ||||
|  | ||||
|     return { | ||||
|         // Element only | ||||
|         restrict: 'E', | ||||
|         replace: true, | ||||
|         scope: { | ||||
|  | ||||
|             /** | ||||
|              * The translation namespace of the translation strings that will | ||||
|              * be generated for all fields. This namespace is absolutely | ||||
|              * required. If this namespace is omitted, all generated | ||||
|              * translation strings will be placed within the MISSING_NAMESPACE | ||||
|              * namespace, as a warning. | ||||
|              * | ||||
|              * @type String | ||||
|              */ | ||||
|             namespace : '=', | ||||
|  | ||||
|             /** | ||||
|              * The form content to display. This may be a form, an array of | ||||
|              * forms, or a simple array of fields. | ||||
|              * | ||||
|              * @type Form[]|Form|Field[]|Field | ||||
|              */ | ||||
|             content : '=', | ||||
|  | ||||
|             /** | ||||
|              * The object which will receive all field values. Each field value | ||||
|              * will be assigned to the property of this object having the same | ||||
|              * name. | ||||
|              * | ||||
|              * @type Object.<String, String> | ||||
|              */ | ||||
|             model : '=', | ||||
|  | ||||
|             /** | ||||
|              * Whether the contents of the form should be restricted to those | ||||
|              * fields/forms which match properties defined within the given | ||||
|              * model object. By default, all fields will be shown. | ||||
|              * | ||||
|              * @type Boolean | ||||
|              */ | ||||
|             modelOnly : '=', | ||||
|  | ||||
|             /** | ||||
|              * Whether the contents of the form should be rendered as disabled. | ||||
|              * By default, form fields are enabled. | ||||
|              * | ||||
|              * @type Boolean | ||||
|              */ | ||||
|             disabled : '=', | ||||
|  | ||||
|             /** | ||||
|              * The name of the field to be focused, if any. | ||||
|              * | ||||
|              * @type String | ||||
|              */ | ||||
|             focused : '=' | ||||
|  | ||||
|         }, | ||||
|         templateUrl: 'app/form/templates/form.html', | ||||
|         controller: ['$scope', '$injector', function formController($scope, $injector) { | ||||
|  | ||||
|             // Required services | ||||
|             var translationStringService = $injector.get('translationStringService'); | ||||
|  | ||||
|             /** | ||||
|              * The array of all forms to display. | ||||
|              * | ||||
|              * @type Form[] | ||||
|              */ | ||||
|             $scope.forms = []; | ||||
|  | ||||
|             /** | ||||
|              * The object which will receive all field values. Normally, this | ||||
|              * will be the object provided within the "model" attribute. If | ||||
|              * no such object has been provided, a blank model will be used | ||||
|              * instead as a placeholder, such that the fields of this form | ||||
|              * will have something to bind to. | ||||
|              * | ||||
|              * @type Object.<String, String> | ||||
|              */ | ||||
|             $scope.values = {}; | ||||
|  | ||||
|             /** | ||||
|              * Produces the translation string for the section header of the | ||||
|              * given form. The translation string will be of the form: | ||||
|              * | ||||
|              * <code>NAMESPACE.SECTION_HEADER_NAME<code> | ||||
|              * | ||||
|              * where <code>NAMESPACE</code> is the namespace provided to the | ||||
|              * directive and <code>NAME</code> is the form name transformed | ||||
|              * via translationStringService.canonicalize(). | ||||
|              * | ||||
|              * @param {Form} form | ||||
|              *     The form for which to produce the translation string. | ||||
|              * | ||||
|              * @returns {String} | ||||
|              *     The translation string which produces the translated header | ||||
|              *     of the form. | ||||
|              */ | ||||
|             $scope.getSectionHeader = function getSectionHeader(form) { | ||||
|  | ||||
|                 // If no form, or no name, then no header | ||||
|                 if (!form || !form.name) | ||||
|                     return ''; | ||||
|  | ||||
|                 return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE') | ||||
|                         + '.SECTION_HEADER_' + translationStringService.canonicalize(form.name); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Determines whether the given object is a form, under the | ||||
|              * assumption that the object is either a form or a field. | ||||
|              * | ||||
|              * @param {Form|Field} obj | ||||
|              *     The object to test. | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if the given object appears to be a form, false | ||||
|              *     otherwise. | ||||
|              */ | ||||
|             var isForm = function isForm(obj) { | ||||
|                 return !!('name' in obj && 'fields' in obj); | ||||
|             }; | ||||
|  | ||||
|             // Produce set of forms from any given content | ||||
|             $scope.$watch('content', function setContent(content) { | ||||
|  | ||||
|                 // If no content provided, there are no forms | ||||
|                 if (!content) { | ||||
|                     $scope.forms = []; | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 // Ensure content is an array | ||||
|                 if (!angular.isArray(content)) | ||||
|                     content = [content]; | ||||
|  | ||||
|                 // If content is an array of fields, convert to an array of forms | ||||
|                 if (content.length && !isForm(content[0])) { | ||||
|                     content = [{ | ||||
|                         fields : content | ||||
|                     }]; | ||||
|                 } | ||||
|  | ||||
|                 // Content is now an array of forms | ||||
|                 $scope.forms = content; | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             // Update string value and re-assign to model when field is changed | ||||
|             $scope.$watch('model', function setModel(model) { | ||||
|  | ||||
|                 // Assign new model only if provided | ||||
|                 if (model) | ||||
|                     $scope.values = model; | ||||
|  | ||||
|                 // Otherwise, use blank model | ||||
|                 else | ||||
|                     $scope.values = {}; | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             /** | ||||
|              * Returns whether the given field should be focused or not. | ||||
|              * | ||||
|              * @param {Field} field | ||||
|              *     The field to check. | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if the given field should be focused, false otherwise. | ||||
|              */ | ||||
|             $scope.isFocused = function isFocused(field) { | ||||
|                 return field && (field.name === $scope.focused); | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns whether the given field should be displayed to the | ||||
|              * current user. | ||||
|              * | ||||
|              * @param {Field} field | ||||
|              *     The field to check. | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if the given field should be visible, false otherwise. | ||||
|              */ | ||||
|             $scope.isVisible = function isVisible(field) { | ||||
|  | ||||
|                 // All fields are visible if contents are not restricted to | ||||
|                 // model properties only | ||||
|                 if (!$scope.modelOnly) | ||||
|                     return true; | ||||
|  | ||||
|                 // Otherwise, fields are only visible if they are present | ||||
|                 // within the model | ||||
|                 return field && (field.name in $scope.values); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns whether at least one of the given fields should be | ||||
|              * displayed to the current user. | ||||
|              * | ||||
|              * @param {Field[]} fields | ||||
|              *     The array of fields to check. | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if at least one field within the given array should be | ||||
|              *     visible, false otherwise. | ||||
|              */ | ||||
|             $scope.containsVisible = function containsVisible(fields) { | ||||
|  | ||||
|                 // If fields are defined, check whether at least one is visible | ||||
|                 if (fields) { | ||||
|                     for (var i = 0; i < fields.length; i++) { | ||||
|                         if ($scope.isVisible(fields[i])) | ||||
|                             return true; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // Otherwise, there are no visible fields | ||||
|                 return false; | ||||
|  | ||||
|             }; | ||||
|  | ||||
|         }] // end controller | ||||
|     }; | ||||
|  | ||||
| }]); | ||||
| @@ -1,188 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * A directive that allows editing of a field. | ||||
|  */ | ||||
| angular.module('form').directive('guacFormField', [function formField() { | ||||
|      | ||||
|     return { | ||||
|         // Element only | ||||
|         restrict: 'E', | ||||
|         replace: true, | ||||
|         scope: { | ||||
|  | ||||
|             /** | ||||
|              * The translation namespace of the translation strings that will | ||||
|              * be generated for this field. This namespace is absolutely | ||||
|              * required. If this namespace is omitted, all generated | ||||
|              * translation strings will be placed within the MISSING_NAMESPACE | ||||
|              * namespace, as a warning. | ||||
|              * | ||||
|              * @type String | ||||
|              */ | ||||
|             namespace : '=', | ||||
|  | ||||
|             /** | ||||
|              * The field to display. | ||||
|              * | ||||
|              * @type Field | ||||
|              */ | ||||
|             field : '=', | ||||
|  | ||||
|             /** | ||||
|              * The property which contains this fields current value. When this | ||||
|              * field changes, the property will be updated accordingly. | ||||
|              * | ||||
|              * @type String | ||||
|              */ | ||||
|             model : '=', | ||||
|  | ||||
|             /** | ||||
|              * Whether this field should be rendered as disabled. By default, | ||||
|              * form fields are enabled. | ||||
|              * | ||||
|              * @type Boolean | ||||
|              */ | ||||
|             disabled : '=', | ||||
|  | ||||
|             /** | ||||
|              * Whether this field should be focused. | ||||
|              * | ||||
|              * @type Boolean | ||||
|              */ | ||||
|             focused : '=' | ||||
|  | ||||
|         }, | ||||
|         templateUrl: 'app/form/templates/formField.html', | ||||
|         controller: ['$scope', '$injector', '$element', function formFieldController($scope, $injector, $element) { | ||||
|  | ||||
|             // Required services | ||||
|             var $log                     = $injector.get('$log'); | ||||
|             var formService              = $injector.get('formService'); | ||||
|             var translationStringService = $injector.get('translationStringService'); | ||||
|  | ||||
|             /** | ||||
|              * The element which should contain any compiled field content. The | ||||
|              * actual content of a field is dynamically determined by its type. | ||||
|              * | ||||
|              * @type Element[] | ||||
|              */ | ||||
|             var fieldContent = $element.find('.form-field'); | ||||
|  | ||||
|             /** | ||||
|              * An ID value which is reasonably likely to be unique relative to | ||||
|              * other elements on the page. This ID should be used to associate | ||||
|              * the relevant input element with the label provided by the | ||||
|              * guacFormField directive, if there is such an input element. | ||||
|              * | ||||
|              * @type String | ||||
|              */ | ||||
|             $scope.fieldId = 'guac-field-XXXXXXXXXXXXXXXX'.replace(/X/g, function getRandomCharacter() { | ||||
|                 return Math.floor(Math.random() * 36).toString(36); | ||||
|             }) + '-' + new Date().getTime().toString(36); | ||||
|  | ||||
|             /** | ||||
|              * Produces the translation string for the header of the current | ||||
|              * field. The translation string will be of the form: | ||||
|              * | ||||
|              * <code>NAMESPACE.FIELD_HEADER_NAME<code> | ||||
|              * | ||||
|              * where <code>NAMESPACE</code> is the namespace provided to the | ||||
|              * directive and <code>NAME</code> is the field name transformed | ||||
|              * via translationStringService.canonicalize(). | ||||
|              * | ||||
|              * @returns {String} | ||||
|              *     The translation string which produces the translated header | ||||
|              *     of the field. | ||||
|              */ | ||||
|             $scope.getFieldHeader = function getFieldHeader() { | ||||
|  | ||||
|                 // If no field, or no name, then no header | ||||
|                 if (!$scope.field || !$scope.field.name) | ||||
|                     return ''; | ||||
|  | ||||
|                 return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE') | ||||
|                         + '.FIELD_HEADER_' + translationStringService.canonicalize($scope.field.name); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Produces the translation string for the given field option | ||||
|              * value. The translation string will be of the form: | ||||
|              * | ||||
|              * <code>NAMESPACE.FIELD_OPTION_NAME_VALUE<code> | ||||
|              * | ||||
|              * where <code>NAMESPACE</code> is the namespace provided to the | ||||
|              * directive, <code>NAME</code> is the field name transformed | ||||
|              * via translationStringService.canonicalize(), and | ||||
|              * <code>VALUE</code> is the option value transformed via | ||||
|              * translationStringService.canonicalize() | ||||
|              * | ||||
|              * @param {String} value | ||||
|              *     The name of the option value. | ||||
|              * | ||||
|              * @returns {String} | ||||
|              *     The translation string which produces the translated name of the | ||||
|              *     value specified. | ||||
|              */ | ||||
|             $scope.getFieldOption = function getFieldOption(value) { | ||||
|  | ||||
|                 // If no field, or no value, then no corresponding translation string | ||||
|                 if (!$scope.field || !$scope.field.name) | ||||
|                     return ''; | ||||
|  | ||||
|                 return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE') | ||||
|                         + '.FIELD_OPTION_' + translationStringService.canonicalize($scope.field.name) | ||||
|                         + '_'              + translationStringService.canonicalize(value || 'EMPTY'); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Returns whether the current field should be displayed. | ||||
|              * | ||||
|              * @returns {Boolean} | ||||
|              *     true if the current field should be displayed, false | ||||
|              *     otherwise. | ||||
|              */ | ||||
|             $scope.isFieldVisible = function isFieldVisible() { | ||||
|                 return fieldContent[0].hasChildNodes(); | ||||
|             }; | ||||
|  | ||||
|             // Update field contents when field definition is changed | ||||
|             $scope.$watch('field', function setField(field) { | ||||
|  | ||||
|                 // Reset contents | ||||
|                 fieldContent.innerHTML = ''; | ||||
|  | ||||
|                 // Append field content | ||||
|                 if (field) { | ||||
|                     formService.insertFieldElement(fieldContent[0], | ||||
|                         field.type, $scope)['catch'](function fieldCreationFailed() { | ||||
|                             $log.warn('Failed to retrieve field with type "' + field.type + '"'); | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|             }); | ||||
|  | ||||
|         }] // end controller | ||||
|     }; | ||||
|      | ||||
| }]); | ||||
| @@ -1,117 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive which implements a color input field. If the underlying color | ||||
|  * picker implementation cannot be used due to a lack of browser support, this | ||||
|  * directive will become read-only, functioning essentially as a color preview. | ||||
|  * | ||||
|  * @see colorPickerService | ||||
|  */ | ||||
| angular.module('form').directive('guacInputColor', [function guacInputColor() { | ||||
|  | ||||
|     var config = { | ||||
|         restrict: 'E', | ||||
|         replace: true, | ||||
|         templateUrl: 'app/form/templates/guacInputColor.html', | ||||
|         transclude: true | ||||
|     }; | ||||
|  | ||||
|     config.scope = { | ||||
|  | ||||
|         /** | ||||
|          * The current selected color value, in standard 6-digit hexadecimal | ||||
|          * RGB notation. When the user selects a different color using this | ||||
|          * directive, this value will updated accordingly. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         model: '=', | ||||
|  | ||||
|         /** | ||||
|          * An optional array of colors to include within the color picker as a | ||||
|          * convenient selection of pre-defined colors. The colors within the | ||||
|          * array must be in standard 6-digit hexadecimal RGB notation. | ||||
|          * | ||||
|          * @type String[] | ||||
|          */ | ||||
|         palette: '=' | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     config.controller = ['$scope', '$element', '$injector', | ||||
|         function guacInputColorController($scope, $element, $injector) { | ||||
|  | ||||
|         // Required services | ||||
|         var colorPickerService = $injector.get('colorPickerService'); | ||||
|  | ||||
|         /** | ||||
|          * @borrows colorPickerService.isAvailable() | ||||
|          */ | ||||
|         $scope.isColorPickerAvailable = colorPickerService.isAvailable; | ||||
|  | ||||
|         /** | ||||
|          * Returns whether the color currently selected is "dark" in the sense | ||||
|          * that the color white will have higher contrast against it than the | ||||
|          * color black. | ||||
|          * | ||||
|          * @returns {Boolean} | ||||
|          *     true if the currently selected color is relatively dark (white | ||||
|          *     text would provide better contrast than black), false otherwise. | ||||
|          */ | ||||
|         $scope.isDark = function isDark() { | ||||
|  | ||||
|             // Assume not dark if color is invalid or undefined | ||||
|             var rgb = $scope.model && /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec($scope.model); | ||||
|             if (!rgb) | ||||
|                 return false; | ||||
|  | ||||
|             // Parse color component values as hexadecimal | ||||
|             var red = parseInt(rgb[1], 16); | ||||
|             var green = parseInt(rgb[2], 16); | ||||
|             var blue = parseInt(rgb[3], 16); | ||||
|  | ||||
|             // Convert RGB to luminance in HSL space (as defined by the | ||||
|             // relative luminance formula given by the W3C for accessibility) | ||||
|             var luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue; | ||||
|  | ||||
|             // Consider the background to be dark if white text over that | ||||
|             // background would provide better contrast than black | ||||
|             return luminance <= 153; // 153 is the component value 0.6 converted from 0-1 to the 0-255 range | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Prompts the user to choose a color by displaying a color selection | ||||
|          * dialog. If the user chooses a color, this directive's model is | ||||
|          * automatically updated. If the user cancels the dialog, the model is | ||||
|          * left untouched. | ||||
|          */ | ||||
|         $scope.selectColor = function selectColor() { | ||||
|             colorPickerService.selectColor($element[0], $scope.model, $scope.palette) | ||||
|             .then(function colorSelected(color) { | ||||
|                 $scope.model = color; | ||||
|             }, angular.noop); | ||||
|         }; | ||||
|  | ||||
|     }]; | ||||
|  | ||||
|     return config; | ||||
|  | ||||
| }]); | ||||
| @@ -1,80 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive which modifies the parsing and formatting of ngModel when used | ||||
|  * on an HTML5 date input field, relaxing the otherwise strict parsing and | ||||
|  * validation behavior. The behavior of this directive for other input elements | ||||
|  * is undefined. | ||||
|  */ | ||||
| angular.module('form').directive('guacLenientDate', ['$injector', | ||||
|     function guacLenientDate($injector) { | ||||
|  | ||||
|     // Required services | ||||
|     var $filter = $injector.get('$filter'); | ||||
|  | ||||
|     /** | ||||
|      * Directive configuration object. | ||||
|      * | ||||
|      * @type Object.<String, Object> | ||||
|      */ | ||||
|     var config = { | ||||
|         restrict : 'A', | ||||
|         require  : 'ngModel' | ||||
|     }; | ||||
|  | ||||
|     // Linking function | ||||
|     config.link = function linkGuacLenientDate($scope, $element, $attrs, ngModel) { | ||||
|  | ||||
|         // Parse date strings leniently | ||||
|         ngModel.$parsers = [function parse(viewValue) { | ||||
|  | ||||
|             // If blank, return null | ||||
|             if (!viewValue) | ||||
|                 return null; | ||||
|  | ||||
|             // Match basic date pattern | ||||
|             var match = /([0-9]*)(?:-([0-9]*)(?:-([0-9]*))?)?/.exec(viewValue); | ||||
|             if (!match) | ||||
|                 return null; | ||||
|  | ||||
|             // Determine year, month, and day based on pattern | ||||
|             var year  = parseInt(match[1] || '0') || new Date().getFullYear(); | ||||
|             var month = parseInt(match[2] || '0') || 1; | ||||
|             var day   = parseInt(match[3] || '0') || 1; | ||||
|  | ||||
|             // Convert to Date object | ||||
|             var parsedDate = new Date(Date.UTC(year, month - 1, day)); | ||||
|             if (isNaN(parsedDate.getTime())) | ||||
|                 return null; | ||||
|  | ||||
|             return parsedDate; | ||||
|  | ||||
|         }]; | ||||
|  | ||||
|         // Format date strings as "yyyy-MM-dd" | ||||
|         ngModel.$formatters = [function format(modelValue) { | ||||
|             return modelValue ? $filter('date')(modelValue, 'yyyy-MM-dd', 'UTC') : ''; | ||||
|         }]; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return config; | ||||
|  | ||||
| }]); | ||||
| @@ -1,100 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A directive which modifies the parsing and formatting of ngModel when used | ||||
|  * on an HTML5 time input field, relaxing the otherwise strict parsing and | ||||
|  * validation behavior. The behavior of this directive for other input elements | ||||
|  * is undefined. | ||||
|  */ | ||||
| angular.module('form').directive('guacLenientTime', ['$injector', | ||||
|     function guacLenientTime($injector) { | ||||
|  | ||||
|     // Required services | ||||
|     var $filter = $injector.get('$filter'); | ||||
|  | ||||
|     /** | ||||
|      * Directive configuration object. | ||||
|      * | ||||
|      * @type Object.<String, Object> | ||||
|      */ | ||||
|     var config = { | ||||
|         restrict : 'A', | ||||
|         require  : 'ngModel' | ||||
|     }; | ||||
|  | ||||
|     // Linking function | ||||
|     config.link = function linkGuacLenientTIme($scope, $element, $attrs, ngModel) { | ||||
|  | ||||
|         // Parse time strings leniently | ||||
|         ngModel.$parsers = [function parse(viewValue) { | ||||
|  | ||||
|             // If blank, return null | ||||
|             if (!viewValue) | ||||
|                 return null; | ||||
|  | ||||
|             // Match basic time pattern | ||||
|             var match = /([0-9]*)(?::([0-9]*)(?::([0-9]*))?)?(?:\s*(a|p))?/.exec(viewValue.toLowerCase()); | ||||
|             if (!match) | ||||
|                 return null; | ||||
|  | ||||
|             // Determine hour, minute, and second based on pattern | ||||
|             var hour   = parseInt(match[1] || '0'); | ||||
|             var minute = parseInt(match[2] || '0'); | ||||
|             var second = parseInt(match[3] || '0'); | ||||
|  | ||||
|             // Handle AM/PM | ||||
|             if (match[4]) { | ||||
|  | ||||
|                 // Interpret 12 AM as 00:00 and 12 PM as 12:00 | ||||
|                 if (hour === 12) | ||||
|                     hour = 0; | ||||
|  | ||||
|                 // Increment hour to evening if PM | ||||
|                 if (match[4] === 'p') | ||||
|                     hour += 12; | ||||
|  | ||||
|             } | ||||
|  | ||||
|             // Wrap seconds and minutes into minutes and hours | ||||
|             minute += second / 60; second %= 60; | ||||
|             hour   += minute / 60; minute %= 60; | ||||
|  | ||||
|             // Constrain hours to 0 - 23 | ||||
|             hour %= 24; | ||||
|  | ||||
|             // Convert to Date object | ||||
|             var parsedDate = new Date(Date.UTC(1970, 0, 1, hour, minute, second)); | ||||
|             if (isNaN(parsedDate.getTime())) | ||||
|                 return null; | ||||
|  | ||||
|             return parsedDate; | ||||
|  | ||||
|         }]; | ||||
|  | ||||
|         // Format time strings as "HH:mm:ss" | ||||
|         ngModel.$formatters = [function format(modelValue) { | ||||
|             return modelValue ? $filter('date')(modelValue, 'HH:mm:ss', 'UTC') : ''; | ||||
|         }]; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return config; | ||||
|  | ||||
| }]); | ||||
| @@ -1,26 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Module for displaying dynamic forms. | ||||
|  */ | ||||
| angular.module('form', [ | ||||
|     'locale', | ||||
|     'rest' | ||||
| ]); | ||||
| @@ -1,268 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A service for prompting the user to choose a color using the "Pickr" color | ||||
|  * picker. As the Pickr color picker might not be available if the JavaScript | ||||
|  * features it requires are not supported by the browser (Internet Explorer), | ||||
|  * the isAvailable() function should be used to test for usability. | ||||
|  */ | ||||
| angular.module('form').provider('colorPickerService', function colorPickerServiceProvider() { | ||||
|  | ||||
|     /** | ||||
|      * A singleton instance of the "Pickr" color picker, shared by all users of | ||||
|      * this service. Pickr does not initialize synchronously, nor is it | ||||
|      * supported by all browsers. If Pickr is not yet initialized, or is | ||||
|      * unsupported, this will be null. | ||||
|      * | ||||
|      * @type {Pickr} | ||||
|      */ | ||||
|     var pickr = null; | ||||
|  | ||||
|     /** | ||||
|      * Whether Pickr has completed initialization. | ||||
|      * | ||||
|      * @type {Boolean} | ||||
|      */ | ||||
|     var pickrInitComplete = false; | ||||
|  | ||||
|     /** | ||||
|      * The HTML element to provide to Pickr as the root element. | ||||
|      * | ||||
|      * @type {HTMLDivElement} | ||||
|      */ | ||||
|     var pickerContainer = document.createElement('div'); | ||||
|     pickerContainer.className = 'shared-color-picker'; | ||||
|  | ||||
|     /** | ||||
|      * An instance of Deferred which represents an active request for the | ||||
|      * user to choose a color. The promise associated with the Deferred will | ||||
|      * be resolved with the chosen color once a color is chosen, and rejected | ||||
|      * if the request is cancelled or Pickr is not available. If no request is | ||||
|      * active, this will be null. | ||||
|      * | ||||
|      * @type {Deferred} | ||||
|      */ | ||||
|     var activeRequest = null; | ||||
|  | ||||
|     /** | ||||
|      * Resolves the current active request with the given color value. If no | ||||
|      * color value is provided, the active request is rejected. If no request | ||||
|      * is active, this function has no effect. | ||||
|      * | ||||
|      * @param {String} [color] | ||||
|      *     The color value to resolve the active request with. | ||||
|      */ | ||||
|     var completeActiveRequest = function completeActiveRequest(color) { | ||||
|         if (activeRequest) { | ||||
|  | ||||
|             // Hide color picker, if shown | ||||
|             pickr.hide(); | ||||
|  | ||||
|             // Resolve/reject active request depending on value provided | ||||
|             if (color) | ||||
|                 activeRequest.resolve(color); | ||||
|             else | ||||
|                 activeRequest.reject(); | ||||
|  | ||||
|             // No active request | ||||
|             activeRequest = null; | ||||
|  | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     try { | ||||
|         pickr = Pickr.create({ | ||||
|  | ||||
|             // Bind color picker to the container element | ||||
|             el : pickerContainer, | ||||
|  | ||||
|             // Wrap color picker dialog in Guacamole-specific class for | ||||
|             // sake of additional styling | ||||
|             appClass : 'guac-input-color-picker', | ||||
|  | ||||
|             'default' : '#000000', | ||||
|  | ||||
|             // Display color details as hex | ||||
|             defaultRepresentation : 'HEX', | ||||
|  | ||||
|             // Use "monolith" theme, as a nice balance between "nano" (does | ||||
|             // not work in Internet Explorer) and "classic" (too big) | ||||
|             theme : 'monolith', | ||||
|  | ||||
|             // Leverage the container element as the button which shows the | ||||
|             // picker, relying on our own styling for that button | ||||
|             useAsButton  : true, | ||||
|             appendToBody : true, | ||||
|  | ||||
|             // Do not include opacity controls | ||||
|             lockOpacity : true, | ||||
|  | ||||
|             // Include a selection of palette entries for convenience and | ||||
|             // reference | ||||
|             swatches : [], | ||||
|  | ||||
|             components: { | ||||
|  | ||||
|                 // Include hue and color preview controls | ||||
|                 preview : true, | ||||
|                 hue     : true, | ||||
|  | ||||
|                 // Display only a text color input field and the save and | ||||
|                 // cancel buttons (no clear button) | ||||
|                 interaction: { | ||||
|                     input  : true, | ||||
|                     save   : true, | ||||
|                     cancel : true | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|  | ||||
|         }); | ||||
|  | ||||
|         // Hide color picker after user clicks "cancel" | ||||
|         pickr.on('cancel', function colorChangeCanceled() { | ||||
|             completeActiveRequest(); | ||||
|         }); | ||||
|  | ||||
|         // Keep model in sync with changes to the color picker | ||||
|         pickr.on('save', function colorChanged(color) { | ||||
|             completeActiveRequest(color.toHEXA().toString()); | ||||
|             activeRequest = null; | ||||
|         }); | ||||
|  | ||||
|         // Keep color picker in sync with changes to the model | ||||
|         pickr.on('init', function pickrReady() { | ||||
|             pickrInitComplete = true; | ||||
|         }); | ||||
|     } | ||||
|     catch (e) { | ||||
|         // If the "Pickr" color picker cannot be loaded (Internet Explorer), | ||||
|         // the available flag will remain set to false | ||||
|     } | ||||
|  | ||||
|     // Factory method required by provider | ||||
|     this.$get = ['$injector', function colorPickerServiceFactory($injector) { | ||||
|  | ||||
|         // Required services | ||||
|         var $q         = $injector.get('$q'); | ||||
|         var $translate = $injector.get('$translate'); | ||||
|  | ||||
|         var service = {}; | ||||
|  | ||||
|         /** | ||||
|          * Promise which is resolved when Pickr initialization has completed | ||||
|          * and rejected if Pickr cannot be used. | ||||
|          * | ||||
|          * @type {Promise} | ||||
|          */ | ||||
|         var pickrPromise = (function getPickr() { | ||||
|  | ||||
|             var deferred = $q.defer(); | ||||
|  | ||||
|             // Resolve promise when Pickr has completed initialization | ||||
|             if (pickrInitComplete) | ||||
|                 deferred.resolve(); | ||||
|             else if (pickr) | ||||
|                 pickr.on('init', deferred.resolve); | ||||
|  | ||||
|             // Reject promise if Pickr cannot be used at all | ||||
|             else | ||||
|                 deferred.reject(); | ||||
|  | ||||
|             return deferred.promise; | ||||
|  | ||||
|         })(); | ||||
|  | ||||
|         /** | ||||
|          * Returns whether the underlying color picker (Pickr) can be used by | ||||
|          * calling selectColor(). If the browser cannot support the color | ||||
|          * picker, false is returned. | ||||
|          * | ||||
|          * @returns {Boolean} | ||||
|          *     true if the underlying color picker can be used by calling | ||||
|          *     selectColor(), false otherwise. | ||||
|          */ | ||||
|         service.isAvailable = function isAvailable() { | ||||
|             return !!pickr; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Prompts the user to choose a color, returning the color chosen via a | ||||
|          * Promise. | ||||
|          * | ||||
|          * @param {Element} element | ||||
|          *     The element that the user interacted with to indicate their | ||||
|          *     desire to choose a color. | ||||
|          * | ||||
|          * @param {String} current | ||||
|          *     The color that should be selected by default, in standard | ||||
|          *     6-digit hexadecimal RGB format, including "#" prefix. | ||||
|          * | ||||
|          * @param {String[]} [palette] | ||||
|          *     An array of color choices which should be exposed to the user | ||||
|          *     within the color chooser for convenience. Each color must be in | ||||
|          *     standard 6-digit hexadecimal RGB format, including "#" prefix. | ||||
|          * | ||||
|          * @returns {Promise.<String>} | ||||
|          *     A Promise which is resolved with the color chosen by the user, | ||||
|          *     in standard 6-digit hexadecimal RGB format with "#" prefix, and | ||||
|          *     rejected if the selection operation was cancelled or the color | ||||
|          *     picker cannot be used. | ||||
|          */ | ||||
|         service.selectColor = function selectColor(element, current, palette) { | ||||
|  | ||||
|             // Show picker once the relevant translation strings have been | ||||
|             // retrieved and Pickr is ready for use | ||||
|             return $q.all({ | ||||
|                 'saveString'   : $translate('APP.ACTION_SAVE'), | ||||
|                 'cancelString' : $translate('APP.ACTION_CANCEL'), | ||||
|                 'pickr'        : pickrPromise | ||||
|             }).then(function dependenciesReady(deps) { | ||||
|  | ||||
|                 // Cancel any active request | ||||
|                 completeActiveRequest(); | ||||
|  | ||||
|                 // Reset state of color picker to provided parameters | ||||
|                 pickr.setColor(current); | ||||
|                 element.appendChild(pickerContainer); | ||||
|  | ||||
|                 // Assign translated strings to button text | ||||
|                 var pickrRoot = pickr.getRoot(); | ||||
|                 pickrRoot.interaction.save.value = deps.saveString; | ||||
|                 pickrRoot.interaction.cancel.value = deps.cancelString; | ||||
|  | ||||
|                 // Replace all color swatches with the palette of colors given | ||||
|                 while (pickr.removeSwatch(0)) {} | ||||
|                 angular.forEach(palette, pickr.addSwatch.bind(pickr)); | ||||
|  | ||||
|                 // Show color picker and wait for user to complete selection | ||||
|                 activeRequest = $q.defer(); | ||||
|                 pickr.show(); | ||||
|                 return activeRequest.promise; | ||||
|  | ||||
|             }); | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         return service; | ||||
|  | ||||
|     }]; | ||||
|  | ||||
| }); | ||||
| @@ -1,381 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * A service for maintaining form-related metadata and linking that data to | ||||
|  * corresponding controllers and templates. | ||||
|  */ | ||||
| angular.module('form').provider('formService', function formServiceProvider() { | ||||
|  | ||||
|     /** | ||||
|      * Reference to the provider itself. | ||||
|      * | ||||
|      * @type formServiceProvider | ||||
|      */ | ||||
|     var provider = this; | ||||
|  | ||||
|     /** | ||||
|      * Map of all registered field type definitions by name. | ||||
|      * | ||||
|      * @type Object.<String, FieldType> | ||||
|      */ | ||||
|     this.fieldTypes = { | ||||
|  | ||||
|         /** | ||||
|          * Text field type. | ||||
|          * | ||||
|          * @see {@link Field.Type.TEXT} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'TEXT' : { | ||||
|             module      : 'form', | ||||
|             controller  : 'textFieldController', | ||||
|             templateUrl : 'app/form/templates/textField.html' | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Email address field type. | ||||
|          * | ||||
|          * @see {@link Field.Type.EMAIL} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'EMAIL' : { | ||||
|             templateUrl : 'app/form/templates/emailField.html' | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Numeric field type. | ||||
|          * | ||||
|          * @see {@link Field.Type.NUMERIC} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'NUMERIC' : { | ||||
|             module      : 'form', | ||||
|             controller  : 'numberFieldController', | ||||
|             templateUrl : 'app/form/templates/numberField.html' | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Boolean field type. | ||||
|          * | ||||
|          * @see {@link Field.Type.BOOLEAN} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'BOOLEAN' : { | ||||
|             module      : 'form', | ||||
|             controller  : 'checkboxFieldController', | ||||
|             templateUrl : 'app/form/templates/checkboxField.html' | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Username field type. Identical in principle to a text field, but may | ||||
|          * have different semantics. | ||||
|          * | ||||
|          * @see {@link Field.Type.USERNAME} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'USERNAME' : { | ||||
|             templateUrl : 'app/form/templates/textField.html' | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Password field type. Similar to a text field, but the contents of | ||||
|          * the field are masked. | ||||
|          * | ||||
|          * @see {@link Field.Type.PASSWORD} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'PASSWORD' : { | ||||
|             module      : 'form', | ||||
|             controller  : 'passwordFieldController', | ||||
|             templateUrl : 'app/form/templates/passwordField.html' | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Enumerated field type. The user is presented a finite list of values | ||||
|          * to choose from. | ||||
|          * | ||||
|          * @see {@link Field.Type.ENUM} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'ENUM' : { | ||||
|             module      : 'form', | ||||
|             controller  : 'selectFieldController', | ||||
|             templateUrl : 'app/form/templates/selectField.html' | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Multiline field type. The user may enter multiple lines of text. | ||||
|          * | ||||
|          * @see {@link Field.Type.MULTILINE} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'MULTILINE' : { | ||||
|             templateUrl : 'app/form/templates/textAreaField.html' | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Field type which allows selection of languages. The languages | ||||
|          * displayed are the set of languages supported by the Guacamole web | ||||
|          * application. Legal values are valid language IDs, as dictated by | ||||
|          * the filenames of Guacamole's available translations. | ||||
|          * | ||||
|          * @see {@link Field.Type.LANGUAGE} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'LANGUAGE' : { | ||||
|             module      : 'form', | ||||
|             controller  : 'languageFieldController', | ||||
|             templateUrl : 'app/form/templates/languageField.html' | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Field type which allows selection of time zones. | ||||
|          * | ||||
|          * @see {@link Field.Type.TIMEZONE} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'TIMEZONE' : { | ||||
|             module      : 'form', | ||||
|             controller  : 'timeZoneFieldController', | ||||
|             templateUrl : 'app/form/templates/timeZoneField.html' | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Field type which allows selection of individual dates. | ||||
|          * | ||||
|          * @see {@link Field.Type.DATE} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'DATE' : { | ||||
|             module      : 'form', | ||||
|             controller  : 'dateFieldController', | ||||
|             templateUrl : 'app/form/templates/dateField.html' | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Field type which allows selection of times of day. | ||||
|          * | ||||
|          * @see {@link Field.Type.TIME} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'TIME' : { | ||||
|             module      : 'form', | ||||
|             controller  : 'timeFieldController', | ||||
|             templateUrl : 'app/form/templates/timeField.html' | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Field type which allows selection of color schemes accepted by the | ||||
|          * Guacamole server terminal emulator and protocols which leverage it. | ||||
|          * | ||||
|          * @see {@link Field.Type.TERMINAL_COLOR_SCHEME} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'TERMINAL_COLOR_SCHEME' : { | ||||
|             module      : 'form', | ||||
|             controller  : 'terminalColorSchemeFieldController', | ||||
|             templateUrl : 'app/form/templates/terminalColorSchemeField.html' | ||||
|         }, | ||||
|          | ||||
|         /** | ||||
|          * Field type that supports redirecting the client browser to another | ||||
|          * URL. | ||||
|          *  | ||||
|          * @see {@link Field.Type.REDIRECT} | ||||
|          * @type FieldType | ||||
|          */ | ||||
|         'REDIRECT' : { | ||||
|             module      : 'form', | ||||
|             controller  : 'redirectFieldController', | ||||
|             templateUrl : 'app/form/templates/redirectField.html' | ||||
|         } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Registers a new field type under the given name. | ||||
|      * | ||||
|      * @param {String} fieldTypeName | ||||
|      *     The name which uniquely identifies the field type being registered. | ||||
|      * | ||||
|      * @param {FieldType} fieldType | ||||
|      *     The field type definition to associate with the given name. | ||||
|      */ | ||||
|     this.registerFieldType = function registerFieldType(fieldTypeName, fieldType) { | ||||
|  | ||||
|         // Store field type | ||||
|         provider.fieldTypes[fieldTypeName] = fieldType; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     // Factory method required by provider | ||||
|     this.$get = ['$injector', function formServiceFactory($injector) { | ||||
|  | ||||
|         // Required services | ||||
|         var $compile         = $injector.get('$compile'); | ||||
|         var $q               = $injector.get('$q'); | ||||
|         var $templateRequest = $injector.get('$templateRequest'); | ||||
|  | ||||
|         /** | ||||
|          * Map of module name to the injector instance created for that module. | ||||
|          * | ||||
|          * @type {Object.<String, injector>} | ||||
|          */ | ||||
|         var injectors = {}; | ||||
|  | ||||
|         var service = {}; | ||||
|  | ||||
|         service.fieldTypes = provider.fieldTypes; | ||||
|  | ||||
|         /** | ||||
|          * Given the name of a module, returns an injector instance which | ||||
|          * injects dependencies within that module. A new injector may be | ||||
|          * created and initialized if no such injector has yet been requested. | ||||
|          * If the injector available to formService already includes the | ||||
|          * requested module, that injector will simply be returned. | ||||
|          * | ||||
|          * @param {String} module | ||||
|          *     The name of the module to produce an injector for. | ||||
|          * | ||||
|          * @returns {injector} | ||||
|          *     An injector instance which injects dependencies for the given | ||||
|          *     module. | ||||
|          */ | ||||
|         var getInjector = function getInjector(module) { | ||||
|  | ||||
|             // Use the formService's injector if possible | ||||
|             if ($injector.modules[module]) | ||||
|                 return $injector; | ||||
|  | ||||
|             // If the formService's injector does not include the requested | ||||
|             // module, create the necessary injector, reusing that injector for | ||||
|             // future calls | ||||
|             injectors[module] = injectors[module] || angular.injector(['ng', module]); | ||||
|             return injectors[module]; | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Compiles and links the field associated with the given name to the given | ||||
|          * scope, producing a distinct and independent DOM Element which functions | ||||
|          * as an instance of that field. The scope object provided must include at | ||||
|          * least the following properties: | ||||
|          * | ||||
|          * namespace: | ||||
|          *     A String which defines the unique namespace associated the | ||||
|          *     translation strings used by the form using a field of this type. | ||||
|          * | ||||
|          * fieldId: | ||||
|          *     A String value which is reasonably likely to be unique and may | ||||
|          *     be used to associate the main element of the field with its | ||||
|          *     label. | ||||
|          * | ||||
|          * field: | ||||
|          *     The Field object that is being rendered, representing a field of | ||||
|          *     this type. | ||||
|          * | ||||
|          * model: | ||||
|          *     The current String value of the field, if any. | ||||
|          * | ||||
|          * disabled: | ||||
|          *     A boolean value which is true if the field should be disabled. | ||||
|          *     If false or undefined, the field should be enabled. | ||||
|          * | ||||
|          * @param {Element} fieldContainer | ||||
|          *     The DOM Element whose contents should be replaced with the | ||||
|          *     compiled field template. | ||||
|          * | ||||
|          * @param {String} fieldTypeName | ||||
|          *     The name of the field type defining the nature of the element to be | ||||
|          *     created. | ||||
|          * | ||||
|          * @param {Object} scope | ||||
|          *     The scope to which the new element will be linked. | ||||
|          * | ||||
|          * @return {Promise.<Element>} | ||||
|          *     A Promise which resolves to the compiled Element. If an error occurs | ||||
|          *     while retrieving the field type, this Promise will be rejected. | ||||
|          */ | ||||
|         service.insertFieldElement = function insertFieldElement(fieldContainer, | ||||
|             fieldTypeName, scope) { | ||||
|  | ||||
|             // Ensure field type is defined | ||||
|             var fieldType = provider.fieldTypes[fieldTypeName]; | ||||
|             if (!fieldType) | ||||
|                 return $q.reject(); | ||||
|  | ||||
|             var templateRequest; | ||||
|  | ||||
|             // Use raw HTML template if provided | ||||
|             if (fieldType.template) { | ||||
|                 var deferredTemplate = $q.defer(); | ||||
|                 deferredTemplate.resolve(fieldType.template); | ||||
|                 templateRequest = deferredTemplate.promise; | ||||
|             } | ||||
|  | ||||
|             // If no raw HTML template is provided, retrieve template from URL | ||||
|             else if (fieldType.templateUrl) | ||||
|                 templateRequest = $templateRequest(fieldType.templateUrl); | ||||
|  | ||||
|             // Otherwise, use empty template | ||||
|             else { | ||||
|                 var emptyTemplate= $q.defer(); | ||||
|                 emptyTemplate.resolve(''); | ||||
|                 templateRequest = emptyTemplate.promise; | ||||
|             } | ||||
|  | ||||
|             // Defer compilation of template pending successful retrieval | ||||
|             var compiledTemplate = $q.defer(); | ||||
|  | ||||
|             // Resolve with compiled HTML upon success | ||||
|             templateRequest.then(function templateRetrieved(html) { | ||||
|  | ||||
|                 // Insert template into DOM | ||||
|                 fieldContainer.innerHTML = html; | ||||
|  | ||||
|                 // Populate scope using defined controller | ||||
|                 if (fieldType.module && fieldType.controller) { | ||||
|                     var $controller = getInjector(fieldType.module).get('$controller'); | ||||
|                     $controller(fieldType.controller, { | ||||
|                         '$scope'   : scope, | ||||
|                         '$element' : angular.element(fieldContainer.childNodes) | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 // Compile DOM with populated scope | ||||
|                 compiledTemplate.resolve($compile(fieldContainer.childNodes)(scope)); | ||||
|  | ||||
|             }) | ||||
|  | ||||
|             // Reject on failure | ||||
|             ['catch'](function templateError() { | ||||
|                 compiledTemplate.reject(); | ||||
|             }); | ||||
|  | ||||
|             // Return promise which resolves to the compiled template | ||||
|             return compiledTemplate.promise; | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         return service; | ||||
|  | ||||
|     }]; | ||||
|  | ||||
| }); | ||||
| @@ -1,47 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /* Keep toggle-password icon on same line */ | ||||
| .form-field .password-field { | ||||
|     white-space: nowrap; | ||||
| } | ||||
|  | ||||
| /* Generic 1x1em icon/button */ | ||||
| .form-field .password-field .icon.toggle-password { | ||||
|  | ||||
|     display: inline-block; | ||||
|     opacity: 0.5; | ||||
|     cursor: default; | ||||
|  | ||||
|     background-repeat: no-repeat; | ||||
|     background-size: 1em; | ||||
|     width: 1em; | ||||
|     height: 1em; | ||||
|  | ||||
| } | ||||
|  | ||||
| /* Icon for unmasking passwords */ | ||||
| .form-field .password-field input[type=password] ~ .icon.toggle-password { | ||||
|     background-image: url('images/action-icons/guac-show-pass.png'); | ||||
| } | ||||
|  | ||||
| /* Icon for masking passwords */ | ||||
| .form-field .password-field input[type=text] ~ .icon.toggle-password { | ||||
|     background-image: url('images/action-icons/guac-hide-pass.png'); | ||||
| } | ||||
| @@ -1,24 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .form table.fields th { | ||||
|     text-align: left; | ||||
|     font-weight: normal; | ||||
|     padding-right: 1em; | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .redirect-field-container { | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
|     position: fixed; | ||||
|     left: 0; | ||||
|     top: 0; | ||||
|     display: table; | ||||
|     background: white; | ||||
| } | ||||
|  | ||||
| .redirect-field { | ||||
|     width: 100%; | ||||
|     display: table-cell; | ||||
|     vertical-align: middle; | ||||
|     text-align: center; | ||||
| } | ||||
| @@ -1,158 +0,0 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| .terminal-color-scheme-field { | ||||
|     max-width: 320px; | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field select { | ||||
|     width: 100%; | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field .custom-color-scheme { | ||||
|     background: #EEE; | ||||
|     padding: 0.5em; | ||||
|     border: 1px solid silver; | ||||
|     border-spacing: 0; | ||||
|     margin-top: -2px; | ||||
|     width: 100%; | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field .custom-color-scheme-section  { | ||||
|     display: -ms-flexbox; | ||||
|     display: -moz-box; | ||||
|     display: -webkit-box; | ||||
|     display: -webkit-flex; | ||||
|     display: flex; | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field .guac-input-color { | ||||
|  | ||||
|     display: block; | ||||
|     margin: 2px; | ||||
|     width: 1.5em; | ||||
|     height: 1.5em; | ||||
|     min-width: 1.25em; | ||||
|     border-radius: 0.15em; | ||||
|     line-height: 1.5em; | ||||
|     text-align: center; | ||||
|     font-size: 0.75em; | ||||
|     cursor: pointer; | ||||
|     color: black; | ||||
|  | ||||
|     -ms-flex: 1; | ||||
|     -moz-box-flex: 1; | ||||
|     -webkit-box-flex: 1; | ||||
|     -webkit-flex: 1; | ||||
|     flex: 1; | ||||
|  | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field .guac-input-color.read-only { | ||||
|     cursor: not-allowed; | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field .guac-input-color.dark { | ||||
|     color: white; | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field .palette .guac-input-color { | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| /* Hide palette numbers unless color scheme details are visible */ | ||||
| .terminal-color-scheme-field.custom-color-scheme-details-hidden .custom-color-scheme .palette .guac-input-color { | ||||
|     color: transparent; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Custom color scheme details header | ||||
|  */ | ||||
|  | ||||
| .terminal-color-scheme-field .custom-color-scheme-details-header { | ||||
|     font-size: 0.8em; | ||||
|     margin: 0.5em 0; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field .custom-color-scheme-details-header::before { | ||||
|     content: '▸ '; | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field.custom-color-scheme-details-visible .custom-color-scheme-details-header::before { | ||||
|     content: '▾ '; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Details show/hide link | ||||
|  */ | ||||
|  | ||||
| /* Render show/hide as a link */ | ||||
| .terminal-color-scheme-field .custom-color-scheme-hide-details, | ||||
| .terminal-color-scheme-field .custom-color-scheme-show-details { | ||||
|     color: blue; | ||||
|     text-decoration: underline; | ||||
|     cursor: pointer; | ||||
|     margin: 0 0.25em; | ||||
|     font-weight: normal; | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field .custom-color-scheme-hide-details { | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field.custom-color-scheme-details-visible .custom-color-scheme-hide-details { | ||||
|     display: inline; | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field.custom-color-scheme-details-visible .custom-color-scheme-show-details { | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Color scheme details | ||||
|  */ | ||||
|  | ||||
| .terminal-color-scheme-field .custom-color-scheme-details { | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| .terminal-color-scheme-field.custom-color-scheme-details-visible .custom-color-scheme-details { | ||||
|     display: block; | ||||
|     width: 100%; | ||||
|     margin: 0.5em 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Color picker | ||||
|  */ | ||||
|  | ||||
| /* Increase width of color picker to allow two even rows of eight color | ||||
|  * swatches */ | ||||
| .guac-input-color-picker[data-theme="monolith"] { | ||||
|     width: 16.25em; | ||||
| } | ||||
|  | ||||
| /* Remove Guacamole-specific styles inherited from the generic button rules */ | ||||
| .guac-input-color-picker[data-theme="monolith"] button { | ||||
|     min-width: 0; | ||||
|     padding: 0; | ||||
|     margin: 0; | ||||
|     box-shadow: none; | ||||
| } | ||||
| @@ -1,7 +0,0 @@ | ||||
| <input type="checkbox" | ||||
|        ng-attr-id="{{ fieldId }}" | ||||
|        ng-disabled="disabled" | ||||
|        ng-model="typedValue" | ||||
|        guac-focus="focused" | ||||
|        autocorrect="off" | ||||
|        autocapitalize="off"/> | ||||
| @@ -1,12 +0,0 @@ | ||||
| <div class="date-field"> | ||||
|     <input type="date" | ||||
|            ng-disabled="disabled" | ||||
|            ng-attr-id="{{ fieldId }}" | ||||
|            ng-model="typedValue" | ||||
|            ng-model-options="modelOptions" | ||||
|            guac-lenient-date | ||||
|            guac-focus="focused" | ||||
|            placeholder="{{'FORM.FIELD_PLACEHOLDER_DATE' | translate}}" | ||||
|            autocorrect="off" | ||||
|            autocapitalize="off"/> | ||||
| </div> | ||||
| @@ -1,11 +0,0 @@ | ||||
| <div class="email-field"> | ||||
|     <input type="email" | ||||
|            ng-disabled="disabled" | ||||
|            ng-attr-id="{{ fieldId }}" | ||||
|            ng-model="model" | ||||
|            ng-hide="readOnly" | ||||
|            guac-focus="focused" | ||||
|            autocorrect="off" | ||||
|            autocapitalize="off"/> | ||||
|     <a href="mailto:{{model}}" ng-show="readOnly">{{model}}</a> | ||||
| </div> | ||||
| @@ -1,19 +0,0 @@ | ||||
| <div class="form-group"> | ||||
|     <div ng-repeat="form in forms" class="form" | ||||
|          ng-show="containsVisible(form.fields)"> | ||||
|  | ||||
|         <!-- Form name --> | ||||
|         <h3 ng-show="form.name">{{getSectionHeader(form) | translate}}</h3> | ||||
|  | ||||
|         <!-- All fields in form --> | ||||
|         <div class="fields"> | ||||
|             <guac-form-field ng-repeat="field in form.fields" namespace="namespace" | ||||
|                              ng-if="isVisible(field)" | ||||
|                              data-disabled="disabled" | ||||
|                              focused="isFocused(field)" | ||||
|                              field="field" | ||||
|                              model="values[field.name]"></guac-form-field> | ||||
|         </div> | ||||
|  | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,11 +0,0 @@ | ||||
| <div class="labeled-field" ng-class="{empty: !model}" ng-show="isFieldVisible()"> | ||||
|  | ||||
|     <!-- Field header --> | ||||
|     <div class="field-header"> | ||||
|         <label ng-attr-for="{{ fieldId }}">{{getFieldHeader() | translate}}</label> | ||||
|     </div> | ||||
|  | ||||
|     <!-- Field content --> | ||||
|     <div class="form-field"></div> | ||||
|  | ||||
| </div> | ||||
| @@ -1,11 +0,0 @@ | ||||
| <div class="guac-input-color" | ||||
|      ng-class="{ | ||||
|          'dark' : isDark(), | ||||
|          'read-only' : !isColorPickerAvailable() | ||||
|      }" | ||||
|      ng-click="selectColor()" | ||||
|      ng-style="{ | ||||
|         'background-color' : model | ||||
|      }"> | ||||
|     <ng-transclude></ng-transclude> | ||||
| </div> | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user