From 06828df960f487424c7ac3640c3608a63f263321 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 1 Jan 2015 18:48:51 -0800 Subject: [PATCH 01/11] GUAC-963: Add directive for selecting files for upload via arbitrary elements. --- .../app/element/directives/guacUpload.js | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 guacamole/src/main/webapp/app/element/directives/guacUpload.js diff --git a/guacamole/src/main/webapp/app/element/directives/guacUpload.js b/guacamole/src/main/webapp/app/element/directives/guacUpload.js new file mode 100644 index 000000000..8dde18e60 --- /dev/null +++ b/guacamole/src/main/webapp/app/element/directives/guacUpload.js @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * 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) { + + /** + * Angular expression which evaluates to the function to call + * whenever files are chosen. The callback is provided a single + * parameter: the FileList containing all chosen files. + * + * @type String + */ + var guacUpload = $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() { + + var callback = $scope.$eval(guacUpload); + + // Only set chosen files selection is not canceled + if (callback && input.files.length > 0) + callback(input.files); + + // Reset selection + form.reset(); + + }); + }); + + // Open file chooser when element is clicked + element.addEventListener('click', function elementClicked() { + input.click(); + }); + + } // end guacUpload link function + + }; + +}]); From 0caa3b0161916ff9e51853a7f63d987f1a1301bf Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 1 Jan 2015 19:06:04 -0800 Subject: [PATCH 02/11] GUAC-963: Only send clipboard data it's a string. --- .../main/webapp/app/client/controllers/clientController.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index 63a35bdc8..a788cdccf 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -333,10 +333,10 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams }); - $scope.$watch('menuShown', function setKeyboardEnabled(menuShown, menuShownPreviousState) { + $scope.$watch('menuShown', function menuVisibilityChanged(menuShown, menuShownPreviousState) { // Send clipboard data if menu is hidden - if (!menuShown && menuShownPreviousState) + if (!menuShown && menuShownPreviousState && angular.isString($scope.client.clipboardData)) $scope.$broadcast('guacClipboard', 'text/plain', $scope.client.clipboardData); // Disable client keyboard if the menu is shown From e055bf6254d29adce01f921e0b1ab3f0ced7d7c4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 1 Jan 2015 19:09:29 -0800 Subject: [PATCH 03/11] GUAC-963: Add initial implementation of file transfer manager. Display file transfers within guac menu. --- .../app/client/directives/guacFileTransfer.js | 51 +++++++ .../directives/guacFileTransferManager.js | 137 ++++++++++++++++++ .../app/client/styles/transfer-manager.css | 41 ++++++ .../webapp/app/client/styles/transfer.css | 91 ++++++++++++ .../webapp/app/client/templates/client.html | 18 ++- .../client/templates/guacFileTransfer.html | 33 +++++ .../templates/guacFileTransferManager.html | 43 ++++++ .../src/main/webapp/translations/en_US.json | 29 ++-- 8 files changed, 424 insertions(+), 19 deletions(-) create mode 100644 guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js create mode 100644 guacamole/src/main/webapp/app/client/directives/guacFileTransferManager.js create mode 100644 guacamole/src/main/webapp/app/client/styles/transfer-manager.css create mode 100644 guacamole/src/main/webapp/app/client/styles/transfer.css create mode 100644 guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html create mode 100644 guacamole/src/main/webapp/app/client/templates/guacFileTransferManager.html diff --git a/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js b/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js new file mode 100644 index 000000000..87b7ae949 --- /dev/null +++ b/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * 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) { + + /* STUB */ + + }] + + }; +}]); diff --git a/guacamole/src/main/webapp/app/client/directives/guacFileTransferManager.js b/guacamole/src/main/webapp/app/client/directives/guacFileTransferManager.js new file mode 100644 index 000000000..d49e94288 --- /dev/null +++ b/guacamole/src/main/webapp/app/client/directives/guacFileTransferManager.js @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * 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 ManagedClient = $injector.get('ManagedClient'); + var ManagedFileTransferState = $injector.get('ManagedFileTransferState'); + + /** + * Determines whether the attached client has associated file + * transfers, regardless of those file transfers' state. + * + * @returns {Boolean} + * true if there are any file transfers associated with the + * attached client, false otherise. + */ + $scope.hasTransfers = function hasTransfers() { + + // There are no file transfers if there is no client + if (!$scope.client) + return false; + + return !!($scope.client.uploads.length || $scope.client.downloads.length); + + }; + + /** + * 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); + }); + + // Remove completed downloads + $scope.client.downloads = $scope.client.downloads.filter(function isDownloadInProgress(download) { + return isInProgress(download.transferState); + }); + + }; + + /** + * Begins a file upload through the attached Guacamole client for + * each file in the given FileList. + * + * @param {FileList} files + * The files to upload. + */ + $scope.uploadFiles = function uploadFiles(files) { + + // Ignore file uploads if no attached client + if (!$scope.client) + return; + + // Upload each file + for (var i = 0; i < files.length; i++) + ManagedClient.uploadFile($scope.client, files[i]); + + }; + + }] + + }; +}]); diff --git a/guacamole/src/main/webapp/app/client/styles/transfer-manager.css b/guacamole/src/main/webapp/app/client/styles/transfer-manager.css new file mode 100644 index 000000000..e2b9c5f0d --- /dev/null +++ b/guacamole/src/main/webapp/app/client/styles/transfer-manager.css @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +.transfer-manager .action-buttons { + text-align: center; +} + +.transfer-manager .no-transfers { + + color: rgba(255, 255, 255, 0.5); + text-shadow: -1px -1px rgba(0, 0, 0, 0.5); + opacity: 0.5; + + font-size: 2em; + font-weight: bolder; + text-align: center; + +} + +.transfer-manager .transfer { + margin: 1em; +} diff --git a/guacamole/src/main/webapp/app/client/styles/transfer.css b/guacamole/src/main/webapp/app/client/styles/transfer.css new file mode 100644 index 000000000..d69df0d4e --- /dev/null +++ b/guacamole/src/main/webapp/app/client/styles/transfer.css @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +.transfer { + position: relative; + padding: 0.5em; + font-size: 0.75em; +} + +.transfer .filename { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + width: 100%; + margin-bottom: 0.5em; +} + +.transfer .text { + position: relative; + text-align: center; + margin-top: 0.5em; +} + +@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%; + background: #C2C2C2 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; + + padding: 0.25em; + + border: 1px solid gray; + + position: absolute; + top: 0; + left: 0; + bottom: 0; + opacity: 0.25; + +} + +.transfer .progress .bar { + background: #A3D655; + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 0; +} diff --git a/guacamole/src/main/webapp/app/client/templates/client.html b/guacamole/src/main/webapp/app/client/templates/client.html index 8a071ed53..cabe77362 100644 --- a/guacamole/src/main/webapp/app/client/templates/client.html +++ b/guacamole/src/main/webapp/app/client/templates/client.html @@ -60,13 +60,21 @@ {{'CLIENT.ACTION_DISCONNECT' | translate}}

{{client.name}}

- + +

{{'CLIENT.SECTION_HEADER_CLIPBOARD' | translate}}

{{'CLIENT.HELP_CLIPBOARD' | translate}}

+ +

{{'CLIENT.SECTION_HEADER_FILE_TRANSFERS' | translate}}

+
+ +
+ +

{{'CLIENT.SECTION_HEADER_INPUT_METHOD' | translate}}

@@ -91,6 +99,7 @@
+

{{'CLIENT.SECTION_HEADER_MOUSE_MODE' | translate}}

{{'CLIENT.HELP_MOUSE_MODE' | translate}}

@@ -115,6 +124,7 @@
+

{{'CLIENT.SECTION_HEADER_DISPLAY' | translate}}

@@ -124,11 +134,5 @@
- - - -
- -
diff --git a/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html b/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html new file mode 100644 index 000000000..89a1e76a0 --- /dev/null +++ b/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html @@ -0,0 +1,33 @@ +
+ + + +
{{transfer.filename}}
+ + +
{{transfer.progress}}
+ + +
+ +
diff --git a/guacamole/src/main/webapp/app/client/templates/guacFileTransferManager.html b/guacamole/src/main/webapp/app/client/templates/guacFileTransferManager.html new file mode 100644 index 000000000..964294e7c --- /dev/null +++ b/guacamole/src/main/webapp/app/client/templates/guacFileTransferManager.html @@ -0,0 +1,43 @@ +
+ + + +

{{'CLIENT.INFO_NO_FILE_TRANSFERS' | translate}}

+ + +
+ +
+ + +
+ +
+ + + + +
diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index cc0fee696..8d0998ee1 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -17,11 +17,13 @@ "CLIENT" : { - "ACTION_RECONNECT" : "Reconnect", - "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", - "ACTION_DISCONNECT" : "Disconnect", - "ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK", - "ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE", + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_CLEAR_COMPLETED_TRANSFERS" : "Clear Completed Transfers", + "ACTION_DISCONNECT" : "Disconnect", + "ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK", + "ACTION_RECONNECT" : "Reconnect", + "ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE", + "ACTION_UPLOAD_FILES" : "Upload Files", "DIALOG_HEADER_CONNECTING" : "Connecting", "DIALOG_HEADER_CONNECTION_ERROR" : "Connection Error", @@ -69,6 +71,8 @@ "HELP_MOUSE_MODE_ABSOLUTE" : "Tap to click. The click occurs at the location of the touch.", "HELP_MOUSE_MODE_RELATIVE" : "Drag to move the mouse pointer and tap to click. The click occurs at the location of the pointer.", + "INFO_NO_FILE_TRANSFERS" : "No file transfers.", + "NAME_INPUT_METHOD_NONE" : "None", "NAME_INPUT_METHOD_OSK" : "On-screen keyboard", "NAME_INPUT_METHOD_TEXT" : "Text input", @@ -79,10 +83,11 @@ "NAME_MOUSE_MODE_ABSOLUTE" : "Touchscreen", "NAME_MOUSE_MODE_RELATIVE" : "Touchpad", - "SECTION_HEADER_CLIPBOARD" : "Clipboard", - "SECTION_HEADER_INPUT_METHOD" : "Input method", - "SECTION_HEADER_DISPLAY" : "Display", - "SECTION_HEADER_MOUSE_MODE" : "Mouse emulation mode", + "SECTION_HEADER_CLIPBOARD" : "Clipboard", + "SECTION_HEADER_DISPLAY" : "Display", + "SECTION_HEADER_FILE_TRANSFERS" : "File Transfers", + "SECTION_HEADER_INPUT_METHOD" : "Input method", + "SECTION_HEADER_MOUSE_MODE" : "Mouse emulation mode", "TEXT_ZOOM_AUTO_FIT" : "Automatically fit to browser window", "TEXT_CLIENT_STATUS_IDLE" : "Idle.", @@ -101,10 +106,10 @@ "ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT", "ACTION_MANAGE" : "@:APP.ACTION_MANAGE", - "INFO_NO_RECENT_CONNECTIONS" : "No recent Connections.", + "INFO_NO_RECENT_CONNECTIONS" : "No recent connections.", - "SECTION_HEADER_RECENT_CONNECTIONS" : "Recent Connections", - "SECTION_HEADER_ALL_CONNECTIONS" : "All Connections" + "SECTION_HEADER_ALL_CONNECTIONS" : "All Connections", + "SECTION_HEADER_RECENT_CONNECTIONS" : "Recent Connections" }, From a538999856543748dfed2b1b2bf5a8092bbec360 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 2 Jan 2015 00:50:37 -0800 Subject: [PATCH 04/11] GUAC-963: Display progress with units for file transfer. Only display moving bars when file transfer is in-progress. --- .../client/controllers/clientController.js | 54 --------- .../app/client/directives/guacFileTransfer.js | 113 +++++++++++++++++- .../webapp/app/client/styles/transfer.css | 28 +++-- .../client/templates/guacFileTransfer.html | 6 +- 4 files changed, 131 insertions(+), 70 deletions(-) diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index a788cdccf..eb0dc795b 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -505,60 +505,6 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams }; - /** - * Returns a progress object, as required by $scope.addNotification(), which - * contains the given number of bytes as an appropriate combination of - * progress value and associated unit. - * - * @param {String} text - * The translation string to associate with the progress object - * returned. - * - * @param {Number} bytes The number of bytes. - * @param {Number} [length] The file length, in bytes, if known. - * - * @returns {Object} - * A progress object, as required by $scope.addNotification(). - */ - var getFileProgress = function getFileProgress(text, bytes, length) { - - // Gigabytes - if (bytes > 1000000000) - return { - text : text, - value : (bytes / 1000000000).toFixed(1), - ratio : bytes / length, - unit : "gb" - }; - - // Megabytes - if (bytes > 1000000) - return { - text : text, - value : (bytes / 1000000).toFixed(1), - ratio : bytes / length, - unit : "mb" - }; - - // Kilobytes - if (bytes > 1000) - return { - text : text, - value : (bytes / 1000).toFixed(1), - ratio : bytes / length, - unit : "kb" - }; - - // Bytes - return { - text : text, - value : bytes, - ratio : bytes / length, - unit : "b" - }; - - }; - // Clean up when view destroyed $scope.$on('$destroy', function clientViewDestroyed() { diff --git a/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js b/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js index 87b7ae949..aad794844 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js +++ b/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js @@ -43,9 +43,118 @@ angular.module('client').directive('guacFileTransfer', [function guacFileTransfe templateUrl: 'app/client/templates/guacFileTransfer.html', controller: ['$scope', '$injector', function guacFileTransferController($scope, $injector) { - /* STUB */ + // Required types + var ManagedFileTransferState = $injector.get('ManagedFileTransferState'); - }] + /** + * 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; + + } + + }; + + }] // end file transfer controller }; }]); diff --git a/guacamole/src/main/webapp/app/client/styles/transfer.css b/guacamole/src/main/webapp/app/client/styles/transfer.css index d69df0d4e..a903e234e 100644 --- a/guacamole/src/main/webapp/app/client/styles/transfer.css +++ b/guacamole/src/main/webapp/app/client/styles/transfer.css @@ -53,7 +53,23 @@ .transfer .progress { width: 100%; - background: #C2C2C2 url('images/progress.png'); + background: #C2C2C2; + padding: 0.25em; + + border: 1px solid gray; + + position: absolute; + top: 0; + left: 0; + bottom: 0; + opacity: 0.25; + +} + +.transfer.in-progress .progress { + + background-image: url('images/progress.png'); + background-size: 16px 16px; -moz-background-size: 16px 16px; -webkit-background-size: 16px 16px; @@ -69,16 +85,6 @@ -webkit-animation-timing-function: linear; -webkit-animation-iteration-count: infinite; - padding: 0.25em; - - border: 1px solid gray; - - position: absolute; - top: 0; - left: 0; - bottom: 0; - opacity: 0.25; - } .transfer .progress .bar { diff --git a/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html b/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html index 89a1e76a0..170753cbc 100644 --- a/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html +++ b/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html @@ -1,4 +1,4 @@ -
+
-
{{transfer.progress}}
+
{{'CLIENT.TEXT_FILE_TRANSFER_PROGRESS' | translate:'{PROGRESS: getProgressValue(), UNIT: getProgressUnit()}'}}
-
+
From ba99316b5089ea1f445fe8172fb8e91317469f99 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 2 Jan 2015 01:45:41 -0800 Subject: [PATCH 05/11] GUAC-963: Allow files with blobs to be saved. Style savable transfers differently. --- .../app/client/directives/guacFileTransfer.js | 28 +++++++++++++++++++ .../webapp/app/client/styles/transfer.css | 13 +++++++++ .../client/templates/guacFileTransfer.html | 2 +- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js b/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js index aad794844..246eeda12 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js +++ b/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js @@ -154,6 +154,34 @@ angular.module('client').directive('guacFileTransfer', [function guacFileTransfe }; + /** + * 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); + + }; + }] // end file transfer controller }; diff --git a/guacamole/src/main/webapp/app/client/styles/transfer.css b/guacamole/src/main/webapp/app/client/styles/transfer.css index a903e234e..3bf953093 100644 --- a/guacamole/src/main/webapp/app/client/styles/transfer.css +++ b/guacamole/src/main/webapp/app/client/styles/transfer.css @@ -95,3 +95,16 @@ height: 100%; width: 0; } + +.savable.transfer { + cursor: pointer; +} + +.savable.transfer:hover .progress { + border-color: black; +} + +.savable.transfer .filename { + color: blue; + text-decoration: underline; +} diff --git a/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html b/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html index 170753cbc..4a3ab7da7 100644 --- a/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html +++ b/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html @@ -1,4 +1,4 @@ -
+
-

{{'CLIENT.SECTION_HEADER_FILE_TRANSFERS' | translate}}

+

{{'CLIENT.SECTION_HEADER_FILE_TRANSFERS' | translate}}

diff --git a/guacamole/src/main/webapp/app/element/directives/guacMarker.js b/guacamole/src/main/webapp/app/element/directives/guacMarker.js new file mode 100644 index 000000000..444cf6814 --- /dev/null +++ b/guacamole/src/main/webapp/app/element/directives/guacMarker.js @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * 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)); + + } + + }; + +}]); diff --git a/guacamole/src/main/webapp/app/element/types/Marker.js b/guacamole/src/main/webapp/app/element/types/Marker.js new file mode 100644 index 000000000..c3cada482 --- /dev/null +++ b/guacamole/src/main/webapp/app/element/types/Marker.js @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * 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; + +}]); From 664e90c53c731f4fcd0c27f6f50c568db77314fb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 2 Jan 2015 02:35:52 -0800 Subject: [PATCH 08/11] GUAC-963: Fix comment. --- guacamole/src/main/webapp/app/element/directives/guacFocus.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/element/directives/guacFocus.js b/guacamole/src/main/webapp/app/element/directives/guacFocus.js index 2331bc0e1..b8f8d6cdd 100644 --- a/guacamole/src/main/webapp/app/element/directives/guacFocus.js +++ b/guacamole/src/main/webapp/app/element/directives/guacFocus.js @@ -39,7 +39,7 @@ angular.module('element').directive('guacFocus', ['$parse', function guacFocus($ var guacFocus = $parse($attrs.guacFocus); /** - * The element which will register the drag gesture. + * The element which will be focused / blurred. * * @type Element */ From a899a1a02f8f29eb5ee810409b3fd249d60d91a7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 2 Jan 2015 14:18:37 -0800 Subject: [PATCH 09/11] GUAC-963: Add support for file transfer errors. --- .../client/controllers/clientController.js | 18 ------- .../app/client/directives/guacFileTransfer.js | 49 +++++++++++++++++++ .../webapp/app/client/styles/transfer.css | 19 +++++++ .../client/templates/guacFileTransfer.html | 5 +- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index 44f6ce7f6..41d6fd0ad 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -112,24 +112,6 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams 0x031D: true }; - /** - * 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 - }; - /** * All error codes for which automatic reconnection is appropriate when a * tunnel error occurs. diff --git a/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js b/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js index 246eeda12..0c232035e 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js +++ b/guacamole/src/main/webapp/app/client/directives/guacFileTransfer.js @@ -46,6 +46,24 @@ angular.module('client').directive('guacFileTransfer', [function guacFileTransfe // 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', @@ -182,6 +200,37 @@ angular.module('client').directive('guacFileTransfer', [function guacFileTransfe }; + /** + * 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 }; diff --git a/guacamole/src/main/webapp/app/client/styles/transfer.css b/guacamole/src/main/webapp/app/client/styles/transfer.css index 3bf953093..a927ce083 100644 --- a/guacamole/src/main/webapp/app/client/styles/transfer.css +++ b/guacamole/src/main/webapp/app/client/styles/transfer.css @@ -32,6 +32,8 @@ overflow: hidden; width: 100%; margin-bottom: 0.5em; + font-family: monospace; + font-weight: bold; } .transfer .text { @@ -108,3 +110,20 @@ color: blue; text-decoration: underline; } + +.error.transfer { + background: #FDD; +} + +.error.transfer .progress { + border-color: rgba(0, 0, 0, 0.125); +} + +.error.transfer .text, +.error.transfer .progress .bar { + display: none; +} + +.error-text { + margin-bottom: 0; +} diff --git a/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html b/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html index 4a3ab7da7..26cb1dee7 100644 --- a/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html +++ b/guacamole/src/main/webapp/app/client/templates/guacFileTransfer.html @@ -1,4 +1,4 @@ -
+
+ +

{{getErrorText() | translate}}

+
From bce22709522bd62c6e014443cf25fcf56ba21064 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 2 Jan 2015 20:26:52 -0800 Subject: [PATCH 10/11] GUAC-963: Simplify guacUpload directive - $eval() only at link time. --- .../webapp/app/element/directives/guacUpload.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/guacamole/src/main/webapp/app/element/directives/guacUpload.js b/guacamole/src/main/webapp/app/element/directives/guacUpload.js index 8dde18e60..b235e0d2b 100644 --- a/guacamole/src/main/webapp/app/element/directives/guacUpload.js +++ b/guacamole/src/main/webapp/app/element/directives/guacUpload.js @@ -33,13 +33,13 @@ angular.module('element').directive('guacUpload', ['$document', function guacUpl link: function linkGuacUpload($scope, $element, $attrs) { /** - * Angular expression which evaluates to the function to call - * whenever files are chosen. The callback is provided a single - * parameter: the FileList containing all chosen files. + * The function to call whenever files are chosen. The callback is + * provided a single parameter: the FileList containing all chosen + * files. * - * @type String + * @type Function */ - var guacUpload = $attrs.guacUpload; + var guacUpload = $scope.$eval($attrs.guacUpload); /** * The element which will register the drag gesture. @@ -73,11 +73,9 @@ angular.module('element').directive('guacUpload', ['$document', function guacUpl input.addEventListener('change', function filesSelected() { $scope.$apply(function setSelectedFiles() { - var callback = $scope.$eval(guacUpload); - // Only set chosen files selection is not canceled - if (callback && input.files.length > 0) - callback(input.files); + if (guacUpload && input.files.length > 0) + guacUpload(input.files); // Reset selection form.reset(); From 30002b2160b363bba29b22ed5055d3ce695dec15 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 2 Jan 2015 20:39:21 -0800 Subject: [PATCH 11/11] GUAC-963: Properly initialize clipboardData. --- .../src/main/webapp/app/client/controllers/clientController.js | 2 +- guacamole/src/main/webapp/app/client/types/ManagedClient.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index 41d6fd0ad..c24eaaacc 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -318,7 +318,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams $scope.$watch('menuShown', function menuVisibilityChanged(menuShown, menuShownPreviousState) { // Send clipboard data if menu is hidden - if (!menuShown && menuShownPreviousState && angular.isString($scope.client.clipboardData)) + if (!menuShown && menuShownPreviousState) $scope.$broadcast('guacClipboard', 'text/plain', $scope.client.clipboardData); // Disable client keyboard if the menu is shown diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClient.js b/guacamole/src/main/webapp/app/client/types/ManagedClient.js index 8c53d7b68..d73b49981 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedClient.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedClient.js @@ -99,7 +99,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', * * @type String */ - this.clipboardData = template.clipboardData; + this.clipboardData = template.clipboardData || ''; /** * All downloaded files. As files are downloaded, their progress can be