From 4c589c0f34dc1a97f8c99d3cb16f903a5f2cbae9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 22 Jun 2015 16:42:56 -0700 Subject: [PATCH 01/16] GUAC-1172: Define ManagedFilesystem type. Handle received filesystem objects. --- .../webapp/app/client/types/ManagedClient.js | 17 ++ .../app/client/types/ManagedFilesystem.js | 263 ++++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClient.js b/guacamole/src/main/webapp/app/client/types/ManagedClient.js index 26fe249b1..2e8a61857 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedClient.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedClient.js @@ -31,6 +31,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', var ManagedClientState = $injector.get('ManagedClientState'); var ManagedDisplay = $injector.get('ManagedDisplay'); var ManagedFileDownload = $injector.get('ManagedFileDownload'); + var ManagedFilesystem = $injector.get('ManagedFilesystem'); var ManagedFileUpload = $injector.get('ManagedFileUpload'); // Required services @@ -119,6 +120,15 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', */ 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 || []; + /** * The current state of the Guacamole client (idle, connecting, * connected, terminated with error, etc.). @@ -382,6 +392,13 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', }); }; + // Handle any received filesystem objects + client.onfilesystem = function fileSystemReceived(object, name) { + $rootScope.$apply(function exposeFilesystem() { + managedClient.filesystems.push(ManagedFilesystem.getInstance(object, name)); + }); + }; + // Manage the client display managedClient.managedDisplay = ManagedDisplay.getInstance(client.getDisplay()); diff --git a/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js b/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js new file mode 100644 index 000000000..2c58d5d74 --- /dev/null +++ b/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js @@ -0,0 +1,263 @@ +/* + * 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 ManagedFilesystem class used by ManagedClient to represent + * available remote filesystems. + */ +angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector', + function defineManagedFilesystem($rootScope, $injector) { + + /** + * 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; + + }; + + /** + * 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, + 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; + + }; + + /** + * 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; + + /** + * Map of all known files containined within this file by name. This is + * only applicable to directories. + * + * @type Object. + */ + this.files = template.files || {}; + + }; + + /** + * All legal type strings for a ManagedFilesystem.File. + * + * @type Object. + */ + 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; + +}]); From 1494f2d46cb3f9c44d11600da2fc96faa70113cd Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 23 Jun 2015 16:34:11 -0700 Subject: [PATCH 02/16] GUAC-1172: Separate sidebar menu style into own class. --- .../webapp/app/client/styles/guac-menu.css | 162 ++++++++++++++++++ .../main/webapp/app/client/styles/menu.css | 151 +--------------- .../webapp/app/client/templates/client.html | 2 +- 3 files changed, 168 insertions(+), 147 deletions(-) create mode 100644 guacamole/src/main/webapp/app/client/styles/guac-menu.css diff --git a/guacamole/src/main/webapp/app/client/styles/guac-menu.css b/guacamole/src/main/webapp/app/client/styles/guac-menu.css new file mode 100644 index 000000000..434749069 --- /dev/null +++ b/guacamole/src/main/webapp/app/client/styles/guac-menu.css @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2015 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. + */ + +#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 #clipboard-settings textarea { + width: 100%; + border: 1px solid #AAA; + -moz-border-radius: 0.25em; + -webkit-border-radius: 0.25em; + -khtml-border-radius: 0.25em; + border-radius: 0.25em; + white-space: pre; + display: block; + font-size: 1em; +} + +#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; +} diff --git a/guacamole/src/main/webapp/app/client/styles/menu.css b/guacamole/src/main/webapp/app/client/styles/menu.css index 107b397ca..a5bc2c43d 100644 --- a/guacamole/src/main/webapp/app/client/styles/menu.css +++ b/guacamole/src/main/webapp/app/client/styles/menu.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 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 @@ -20,7 +20,7 @@ * THE SOFTWARE. */ -#menu { +.menu { overflow: hidden; position: absolute; top: 0; @@ -127,38 +127,6 @@ flex: 0 0 auto; } -#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; - -} - .menu-section h3 { margin: 0; padding: 0; @@ -169,122 +137,13 @@ padding-top: 1em; } -#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; - -} - -#menu .content > * + * { - margin-top: 1em; -} - -#menu, -#menu.closed { +.menu, +.menu.closed { left: -480px; opacity: 0; } -#menu.open { +.menu.open { left: 0px; opacity: 1; } - -#menu #clipboard-settings textarea { - width: 100%; - border: 1px solid #AAA; - -moz-border-radius: 0.25em; - -webkit-border-radius: 0.25em; - -khtml-border-radius: 0.25em; - border-radius: 0.25em; - white-space: pre; - display: block; - font-size: 1em; -} - -#menu #mouse-settings .choice { - text-align: center; -} - -#menu #mouse-settings .choice .figure { - display: inline-block; - vertical-align: middle; - width: 75%; - max-width: 320px; -} - -#menu #keyboard-settings .caption { - font-size: 0.9em; - margin-left: 2em; - margin-right: 2em; -} - -#menu #mouse-settings .figure .caption { - text-align: center; - font-size: 0.9em; -} - -#menu #mouse-settings .figure img { - display: block; - width: 100%; - max-width: 320px; - margin: 1em auto; -} - -#menu #keyboard-settings .figure { - float: right; - max-width: 30%; - margin: 1em; -} - -#menu #keyboard-settings .figure img { - max-width: 100%; -} - -#menu #zoom-settings { - text-align: center; -} - -#menu #zoom-out, -#menu #zoom-in, -#menu #zoom-state { - display: inline-block; - vertical-align: middle; -} - -#menu #zoom-out, -#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; -} - -#menu #zoom-out img, -#menu #zoom-in img { - max-width: 100%; - opacity: 0.5; -} - -#menu #zoom-out:hover, -#menu #zoom-in:hover { - border: 1px solid rgba(0, 0, 0, 1); - background: #CDA; -} - -#menu #zoom-out:hover img, -#menu #zoom-in:hover img { - opacity: 1; -} - -#menu #zoom-state { - font-size: 2em; -} diff --git a/guacamole/src/main/webapp/app/client/templates/client.html b/guacamole/src/main/webapp/app/client/templates/client.html index 2a2a0cde0..134771e94 100644 --- a/guacamole/src/main/webapp/app/client/templates/client.html +++ b/guacamole/src/main/webapp/app/client/templates/client.html @@ -53,7 +53,7 @@ - + + + diff --git a/guacamole/src/main/webapp/images/drive.png b/guacamole/src/main/webapp/images/drive.png new file mode 100644 index 0000000000000000000000000000000000000000..916d58e82521b5a84c5dc70f3211b39016472998 GIT binary patch literal 752 zcmV9?Sy=Qw0?nW1Uas=5-YJdP%+7kNyZ{xB|AG=bIFc*ADC)?dGo%%oq1<= z7X(2N1VIo4K@f!9BU(Xw8+Z-81WGrP&Oc#*cfc#)$4&gf8(;$1438=Bc9o)hXMh5} z0eY3-TVf3C1HXL4$}{LgV4gOk4P$^uzG7bk8bB^pI-94&GG8xz4R}Eq(;uJ=9Ird* zN4DSKRvDjs3-f7$Mc_Nx-1_2}0(XF)NkX3`@R;TH7ZOw8iI337fCgdReOUH^XFft7 z1CIX$42cqOvaVpC*?v9(THxh8+*M_D0myN^GhH| z1EOe^uljC+TmsEzlYBlt_0!W+?^v#*@N_&LmvXsKzKQ Date: Wed, 1 Jul 2015 11:49:11 -0700 Subject: [PATCH 05/16] GUAC-1172: Implement download of specific files via click. --- .../app/client/directives/guacFileBrowser.js | 11 ++++++ .../app/client/templates/guacFileBrowser.html | 2 +- .../app/client/types/ManagedFilesystem.js | 34 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js b/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js index 98147f1dd..9e5e047cc 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js +++ b/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js @@ -97,6 +97,17 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser( }; + /** + * 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); + }; + }] }; diff --git a/guacamole/src/main/webapp/app/client/templates/guacFileBrowser.html b/guacamole/src/main/webapp/app/client/templates/guacFileBrowser.html index dc5bdf18c..1c3a84567 100644 --- a/guacamole/src/main/webapp/app/client/templates/guacFileBrowser.html +++ b/guacamole/src/main/webapp/app/client/templates/guacFileBrowser.html @@ -25,7 +25,7 @@
-
+
{{file.name}}
diff --git a/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js b/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js index 08be2c4d1..265721afb 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js @@ -27,6 +27,10 @@ angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector', function defineManagedFilesystem($rootScope, $injector) { + // Required types + var ManagedFileDownload = $injector.get('ManagedFileDownload'); + var ManagedFileUpload = $injector.get('ManagedFileUpload'); + /** * Object which serves as a surrogate interface, encapsulating a Guacamole * filesystem object while it is active, allowing it to be detached and @@ -182,6 +186,36 @@ angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector' }; + /** + * Downloads the given file from the server using the given Guacamole + * client and filesystem. The file transfer can be monitored through the + * corresponding entry in the downloads array of the given ManagedClient. + * + * @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 and track download + managedClient.downloads.push(ManagedFileDownload.getInstance(stream, mimetype, filename)); + + }); + + }; + /** * A file within a ManagedFilesystem. Each ManagedFilesystem.File provides * sufficient information for retrieval or replacement of the file's From 149fba67370b130110b27bcc8ac03c5fa9c99207 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 1 Jul 2015 15:47:01 -0700 Subject: [PATCH 06/16] GUAC-1172: Implement navigation via breadcrumbs - no more tree. --- .../client/controllers/clientController.js | 40 ++++++++++++++++ .../app/client/directives/guacFileBrowser.js | 18 ++----- .../webapp/app/client/styles/file-browser.css | 10 ++-- .../app/client/styles/filesystem-menu.css | 23 +++++++++ .../webapp/app/client/templates/client.html | 6 +++ .../app/client/templates/guacFileBrowser.html | 27 ++++++----- .../app/client/types/ManagedFilesystem.js | 45 ++++++++++++++---- .../src/main/webapp/images/folder-up.png | Bin 0 -> 819 bytes 8 files changed, 131 insertions(+), 38 deletions(-) create mode 100644 guacamole/src/main/webapp/images/folder-up.png diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index ad55691c8..fbce5cc37 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -28,6 +28,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams // Required types var ManagedClientState = $injector.get('ManagedClientState'); + var ManagedFilesystem = $injector.get('ManagedFilesystem'); var ScrollState = $injector.get('ScrollState'); // Required services @@ -595,6 +596,45 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams return !!$scope.filesystemMenuContents && $scope.menu.shown; }; + /** + * Returns the full path to the given file as an ordered array of parent + * directories. + * + * @param {ManagedFilesystem.File} file + * The file whose full path should be retrieved. + * + * @returns {ManagedFilesystem.File[]} + * An array of directories which make up the hierarchy containing the + * given file, in order of increasing depth. + */ + $scope.getPath = function getPath(file) { + + var path = []; + + // Add all files to path in ascending order of depth + while (file && file.parent) { + path.unshift(file); + file = file.parent; + } + + return path; + + }; + + /** + * Changes the current directory of the given filesystem to the given + * directory. + * + * @param {ManagedFilesystem} filesystem + * The filesystem whose current directory should be changed. + * + * @param {ManagedFilesystem.File} file + * The directory to change to. + */ + $scope.changeDirectory = function changeDirectory(filesystem, file) { + ManagedFilesystem.changeDirectory(filesystem, file); + }; + // Clean up when view destroyed $scope.$on('$destroy', function clientViewDestroyed() { diff --git a/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js b/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js index 9e5e047cc..ba03a9e98 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js +++ b/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js @@ -79,22 +79,14 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser( }; /** - * Toggles the expanded state of the given file between expanded - * and collapsed, showing or hiding the file's children. This is - * only applicable to directories. + * Changes the currently-displayed directory to the given + * directory. * * @param {ManagedFilesystem.File} file - * The file to expand or collapse. + * The directory to change to. */ - $scope.toggleExpanded = function toggleExpanded(file) { - - // Toggle expanded state - file.expanded = !file.expanded; - - // If now expanded, refresh contents - if (file.expanded) - ManagedFilesystem.refresh($scope.filesystem, file); - + $scope.changeDirectory = function changeDirectory(file) { + ManagedFilesystem.changeDirectory($scope.filesystem, file); }; /** diff --git a/guacamole/src/main/webapp/app/client/styles/file-browser.css b/guacamole/src/main/webapp/app/client/styles/file-browser.css index 2c6c923fb..73fce34ad 100644 --- a/guacamole/src/main/webapp/app/client/styles/file-browser.css +++ b/guacamole/src/main/webapp/app/client/styles/file-browser.css @@ -27,10 +27,8 @@ display: none; } -/* Show directory contents if expanded */ - -.file-browser .directory.expanded > .children { - display: block; +.file-browser .list-item .caption { + white-space: nowrap; } /* Directory / file icons */ @@ -43,6 +41,6 @@ background-image: url('images/folder-closed.png'); } -.file-browser .directory.expanded > .caption .icon { - background-image: url('images/folder-open.png'); +.file-browser .directory.previous > .caption .icon { + background-image: url('images/folder-up.png'); } diff --git a/guacamole/src/main/webapp/app/client/styles/filesystem-menu.css b/guacamole/src/main/webapp/app/client/styles/filesystem-menu.css index ce417e042..76ee72ff7 100644 --- a/guacamole/src/main/webapp/app/client/styles/filesystem-menu.css +++ b/guacamole/src/main/webapp/app/client/styles/filesystem-menu.css @@ -46,3 +46,26 @@ -webkit-align-items: center; align-items: center; } + +#filesystem-menu .menu-body { + padding: 0.25em; +} + +#filesystem-menu .header.breadcrumbs { + 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 { + padding: 0.5em; + font-size: 0.8em; + font-weight: bold; +} + +#filesystem-menu .header.breadcrumbs .breadcrumb:hover { + background-color: #CDA; + cursor: pointer; +} diff --git a/guacamole/src/main/webapp/app/client/templates/client.html b/guacamole/src/main/webapp/app/client/templates/client.html index 27ee14937..6493ffef2 100644 --- a/guacamole/src/main/webapp/app/client/templates/client.html +++ b/guacamole/src/main/webapp/app/client/templates/client.html @@ -174,6 +174,12 @@
+ + + -
-
+
+
{{file.name}}
- - -
-
-
- +
+ + +
+ +
+ +
+
diff --git a/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js b/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js index 265721afb..65205def0 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js @@ -68,6 +68,14 @@ angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector' */ this.root = template.root; + /** + * The current directory being viewed or manipulated within the + * filesystem. + * + * @type ManagedFilesystem.File + */ + this.currentDirectory = template.currentDirectory || template.root; + }; /** @@ -140,6 +148,7 @@ angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector' mimetype : mimetypes[name], streamName : name, type : type, + parent : file, name : filename }); @@ -216,6 +225,26 @@ angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector' }; + /** + * 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 @@ -257,6 +286,14 @@ angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector' */ 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. @@ -265,14 +302,6 @@ angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector' */ this.files = template.files || {}; - /** - * Whether this file is currently expanded, exposing any children - * within. This is only applicable to directories. - * - * @type Boolean - */ - this.expanded = template.expanded || false; - }; /** diff --git a/guacamole/src/main/webapp/images/folder-up.png b/guacamole/src/main/webapp/images/folder-up.png new file mode 100644 index 0000000000000000000000000000000000000000..5271b2eb8b3fe7c62a124188ab40cb9243167a7a GIT binary patch literal 819 zcmV-31I+x1P)u>7^n;&7`#EZ&*i^#z&KrQO ztu6BTyz4^uivxm-jNh&$}t13m*ke#i7W z9*?uUyzFvn=cojH2VP7)iay_fxAdi_P17WiNKAcFm4M9DBY4CFTEHiO)zwvBCY@1y z?)T5+GvFP-%F2o_69)E#Y&M%@vsq?lRC{F4{r$bNk7|cUo}m8`w(E4SHzfEbpjxex zOeX!BZen;&MGeD{-Q8Ug5lN*|lS*&LZBHX$Zf;Hv4i5fgW|0z*PN(Jc^z^UPG%^Af z78az{YWS;(MwX|c6;Lb|(huO zkl22&DwT@k(?3*az<|BIJ+7~>+27x1c6Qd$Ge<{9j;^Vk(r&jWHkKVqyX))g$_#Ko z5c2@f0st{hQ?9PAqALK9rKKg?DlifP0Lf%BL+=41BLMn0L1$-Y(G>vwyTHT4!{`ct z{%vrh(TJ{qL90|MVp&#n1pus=FXB0t1B_IrMz{oo| xRW;ZE3X_Y)cP!xJQwO2ZXfzs)Mx)ULkYC)&wuc Date: Thu, 2 Jul 2015 23:08:06 -0700 Subject: [PATCH 07/16] GUAC-1172: Migrate to from ngRepeat to $interpolate for the file browser - Guacamole needs to be easy to $digest ;). --- .../app/client/directives/guacFileBrowser.js | 72 ++++++++++++++++++- .../app/client/templates/guacFileBrowser.html | 43 ++--------- 2 files changed, 78 insertions(+), 37 deletions(-) diff --git a/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js b/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js index ba03a9e98..1f86fb62d 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js +++ b/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js @@ -47,11 +47,22 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser( }, templateUrl: 'app/client/templates/guacFileBrowser.html', - controller: ['$scope', '$injector', function guacFileBrowserController($scope, $injector) { + controller: ['$scope', '$element', '$injector', function guacFileBrowserController($scope, $element, $injector) { // Required types var ManagedFilesystem = $injector.get('ManagedFilesystem'); + // Required services + var $interpolate = $injector.get('$interpolate'); + + /** + * The jQuery-wrapped element representing the contents of the + * current directory within the file browser. + * + * @type Element[] + */ + var currentDirectoryContents = $element.find('.current-directory-contents'); + /** * Returns whether the given file is a normal file. * @@ -100,6 +111,65 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser( ManagedFilesystem.downloadFile($scope.client, $scope.filesystem, file.streamName); }; + /** + * Creates a new element representing the given file and properly + * handling user events, bypassing the overhead incurred through + * use of ngRepeat and related techniques. + * + * @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($interpolate( + + '
' + + '
' + + '
' + + '{{::name}}' + + '
' + + '
' + + )(file)); + + // Change current directory when directories are clicked + if ($scope.isDirectory(file)) { + element.addClass('directory'); + element.on('click', function changeDirectory() { + $scope.changeDirectory(file); + }); + } + + // Initiate downloads when normal files are clicked + else if ($scope.isNormalFile(file)) { + element.addClass('normal-file'); + element.on('click', function downloadFile() { + $scope.downloadFile(file); + }); + } + + return element; + + }; + + // 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 + for (var name in files) { + currentDirectoryContents.append(createFileElement(files[name])); + } + + }); + }] }; diff --git a/guacamole/src/main/webapp/app/client/templates/guacFileBrowser.html b/guacamole/src/main/webapp/app/client/templates/guacFileBrowser.html index 2d3392822..35b26b8b2 100644 --- a/guacamole/src/main/webapp/app/client/templates/guacFileBrowser.html +++ b/guacamole/src/main/webapp/app/client/templates/guacFileBrowser.html @@ -21,43 +21,14 @@ THE SOFTWARE. --> - - - -
- - -
- -
- - -
-
+ +
+
From d265c814e1bed730de7d765ec69ae288b3e17319 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 3 Jul 2015 11:44:51 -0700 Subject: [PATCH 08/16] GUAC-1172: Sort files lexicographically with directories first. --- .../app/client/directives/guacFileBrowser.js | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js b/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js index 1f86fb62d..55323f690 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js +++ b/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js @@ -157,16 +157,55 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser( }; + /** + * 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.} 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); + + }); + + }; + // 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 - for (var name in files) { - currentDirectoryContents.append(createFileElement(files[name])); - } + // Display all files within current directory, sorted + angular.forEach(sortFiles(files), function displayFile(file) { + currentDirectoryContents.append(createFileElement(file)); + }); }); From 6edb954501ae9fcbd5c6ed75272897e088af814d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 3 Jul 2015 11:47:22 -0700 Subject: [PATCH 09/16] GUAC-1172: No whitespace between icon and filename. --- .../src/main/webapp/app/client/templates/guacFileBrowser.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/client/templates/guacFileBrowser.html b/guacamole/src/main/webapp/app/client/templates/guacFileBrowser.html index 35b26b8b2..196a18dcd 100644 --- a/guacamole/src/main/webapp/app/client/templates/guacFileBrowser.html +++ b/guacamole/src/main/webapp/app/client/templates/guacFileBrowser.html @@ -24,7 +24,7 @@ From 94b022b51307e7d84c98c6d3bf8faab9cdbffeec Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 3 Jul 2015 11:55:10 -0700 Subject: [PATCH 10/16] GUAC-1172: Allow file browser breadcrumbs to wrap. --- guacamole/src/main/webapp/app/client/styles/filesystem-menu.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guacamole/src/main/webapp/app/client/styles/filesystem-menu.css b/guacamole/src/main/webapp/app/client/styles/filesystem-menu.css index 76ee72ff7..f8ca075b6 100644 --- a/guacamole/src/main/webapp/app/client/styles/filesystem-menu.css +++ b/guacamole/src/main/webapp/app/client/styles/filesystem-menu.css @@ -52,6 +52,7 @@ } #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; @@ -60,6 +61,7 @@ } #filesystem-menu .header.breadcrumbs .breadcrumb { + display: inline-block; padding: 0.5em; font-size: 0.8em; font-weight: bold; From cfd3710bf2e298b93dbdfefd551a0df12c62acb0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 3 Jul 2015 12:40:11 -0700 Subject: [PATCH 11/16] GUAC-1172: Provide root-level breadcrumb. --- .../app/client/styles/filesystem-menu.css | 26 ++++++++++--------- .../webapp/app/client/templates/client.html | 7 +++-- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/guacamole/src/main/webapp/app/client/styles/filesystem-menu.css b/guacamole/src/main/webapp/app/client/styles/filesystem-menu.css index f8ca075b6..1f51b88b9 100644 --- a/guacamole/src/main/webapp/app/client/styles/filesystem-menu.css +++ b/guacamole/src/main/webapp/app/client/styles/filesystem-menu.css @@ -21,22 +21,10 @@ */ #filesystem-menu .header h2 { - font-size: 1em; font-weight: normal; - padding-top: 0; padding-bottom: 0; - 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; - background-image: url('images/drive.png'); - } #filesystem-menu .header { @@ -71,3 +59,17 @@ 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; +} \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/client/templates/client.html b/guacamole/src/main/webapp/app/client/templates/client.html index 6493ffef2..5b2912c26 100644 --- a/guacamole/src/main/webapp/app/client/templates/client.html +++ b/guacamole/src/main/webapp/app/client/templates/client.html @@ -175,8 +175,11 @@
-
+ +
+ +
+ - - - diff --git a/guacamole/src/main/webapp/app/client/templates/guacFileTransferManager.html b/guacamole/src/main/webapp/app/client/templates/guacFileTransferManager.html index 964294e7c..7de41078a 100644 --- a/guacamole/src/main/webapp/app/client/templates/guacFileTransferManager.html +++ b/guacamole/src/main/webapp/app/client/templates/guacFileTransferManager.html @@ -21,23 +21,21 @@ THE SOFTWARE. --> - -

{{'CLIENT.INFO_NO_FILE_TRANSFERS' | translate}}

- - -
- + +
+

{{'CLIENT.SECTION_HEADER_FILE_TRANSFERS' | translate}}

+
- -
- -
- - - diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index a301e2689..6b15d2a47 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -41,7 +41,7 @@ "CLIENT" : { "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", - "ACTION_CLEAR_COMPLETED_TRANSFERS" : "Clear Completed Transfers", + "ACTION_CLEAR_COMPLETED_TRANSFERS" : "Clear", "ACTION_DISCONNECT" : "Disconnect", "ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK", "ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME", diff --git a/guacamole/src/main/webapp/translations/fr.json b/guacamole/src/main/webapp/translations/fr.json index a623ed7b2..e1f5f1908 100644 --- a/guacamole/src/main/webapp/translations/fr.json +++ b/guacamole/src/main/webapp/translations/fr.json @@ -39,7 +39,7 @@ "CLIENT" : { "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", - "ACTION_CLEAR_COMPLETED_TRANSFERS" : "Vider transferts terminés", + "ACTION_CLEAR_COMPLETED_TRANSFERS" : "Vider", "ACTION_DISCONNECT" : "Déconnecter", "ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK", "ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME", diff --git a/guacamole/src/main/webapp/translations/nl.json b/guacamole/src/main/webapp/translations/nl.json index 5470d2868..40021f140 100644 --- a/guacamole/src/main/webapp/translations/nl.json +++ b/guacamole/src/main/webapp/translations/nl.json @@ -41,7 +41,7 @@ "CLIENT" : { "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", - "ACTION_CLEAR_COMPLETED_TRANSFERS" : "Wis lijst Voltooide Overdrachten", + "ACTION_CLEAR_COMPLETED_TRANSFERS" : "Wis lijst", "ACTION_DISCONNECT" : "Verbreek Verbinding", "ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK", "ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME", diff --git a/guacamole/src/main/webapp/translations/ru.json b/guacamole/src/main/webapp/translations/ru.json index a5c2fa5ae..9d2def73e 100644 --- a/guacamole/src/main/webapp/translations/ru.json +++ b/guacamole/src/main/webapp/translations/ru.json @@ -40,7 +40,7 @@ "CLIENT" : { "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", - "ACTION_CLEAR_COMPLETED_TRANSFERS" : "Очистить завершенные загрузки", + "ACTION_CLEAR_COMPLETED_TRANSFERS" : "Очистить", "ACTION_DISCONNECT" : "Отключиться", "ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK", "ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME", From 05eb8da0ec2b4fcebfd249c9f71b320a768604ca Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 5 Jul 2015 13:42:56 -0700 Subject: [PATCH 15/16] GUAC-1172: Use template for files within the file browser. --- .../app/client/directives/guacFileBrowser.js | 51 ++++++++++++------- .../webapp/app/client/templates/file.html | 30 +++++++++++ 2 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 guacamole/src/main/webapp/app/client/templates/file.html diff --git a/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js b/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js index 55323f690..d612dfdfc 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js +++ b/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js @@ -53,7 +53,8 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser( var ManagedFilesystem = $injector.get('ManagedFilesystem'); // Required services - var $interpolate = $injector.get('$interpolate'); + var $interpolate = $injector.get('$interpolate'); + var $templateRequest = $injector.get('$templateRequest'); /** * The jQuery-wrapped element representing the contents of the @@ -63,6 +64,16 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser( */ 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. * @@ -116,6 +127,9 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser( * 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. * @@ -126,16 +140,7 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser( var createFileElement = function createFileElement(file) { // Create from internal template - var element = angular.element($interpolate( - - '
' - + '
' - + '
' - + '{{::name}}' - + '
' - + '
' - - )(file)); + var element = angular.element($interpolate(fileTemplate)(file)); // Change current directory when directories are clicked if ($scope.isDirectory(file)) { @@ -196,18 +201,26 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser( }; - // Update the contents of the file browser whenever the current directory (or its contents) changes - $scope.$watch('filesystem.currentDirectory.files', function currentDirectoryChanged(files) { + // Watch directory contents once file template is available + $templateRequest('app/client/templates/file.html').then(function fileTemplateRetrieved(html) { - // Clear current content - currentDirectoryContents.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)); + }); - // Display all files within current directory, sorted - angular.forEach(sortFiles(files), function displayFile(file) { - currentDirectoryContents.append(createFileElement(file)); }); - }); + }); // end retrieve file template }] diff --git a/guacamole/src/main/webapp/app/client/templates/file.html b/guacamole/src/main/webapp/app/client/templates/file.html new file mode 100644 index 000000000..2bd86ea34 --- /dev/null +++ b/guacamole/src/main/webapp/app/client/templates/file.html @@ -0,0 +1,30 @@ +
+ + + +
+
+ {{::name}} +
+ +
From a7366e88b3f65ae0ef21711d9edbc4bbab13263c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 5 Jul 2015 13:45:10 -0700 Subject: [PATCH 16/16] GUAC-1172: Use $templateRequest now that we have it (Angular 1.3.x) - no need to manually check/populate $templateCache. --- .../webapp/app/form/services/formService.js | 54 ++----------------- 1 file changed, 4 insertions(+), 50 deletions(-) diff --git a/guacamole/src/main/webapp/app/form/services/formService.js b/guacamole/src/main/webapp/app/form/services/formService.js index e8a1045b3..b598347ab 100644 --- a/guacamole/src/main/webapp/app/form/services/formService.js +++ b/guacamole/src/main/webapp/app/form/services/formService.js @@ -143,60 +143,14 @@ angular.module('form').provider('formService', function formServiceProvider() { this.$get = ['$injector', function formServiceFactory($injector) { // Required services - var $compile = $injector.get('$compile'); - var $http = $injector.get('$http'); - var $q = $injector.get('$q'); - var $templateCache = $injector.get('$templateCache'); + var $compile = $injector.get('$compile'); + var $q = $injector.get('$q'); + var $templateRequest = $injector.get('$templateRequest'); var service = {}; service.fieldTypes = provider.fieldTypes; - /** - * Returns a Promise which resolves with the HTML contents of the - * template at the given URL. The template contents will be retrieved from - * the $templateCache if possible. - * - * @param {String} url - * The URL of the template to retrieve. - * - * @returns {Promise.} - * A Promise which resolves with the HTML contents of the template at - * the given URL. - */ - var templateRequest = function templateRequest(url) { - - // Pull template from cache if present - var template = $templateCache.get(url); - if (template) - return $q.when(template); - - // Defer retrieval of template - var templateContent = $q.defer(); - - // Retrieve template manually - $http({ - method : 'GET', - url : url, - cache : true - }) - - // Upon success, resolve promise and update template cache - .success(function templateRetrieved(html) { - $templateCache.put(url, html); - templateContent.resolve(html); - }) - - // Fail if template cannot be retrieved - .error(function templateError() { - templateContent.reject(); - }); - - // Return promise which will resolve with the retrieved template - return templateContent.promise; - - }; - /** * Compiles and links the field associated with the given name to the given * scope, producing a distinct and independent DOM Element which functions @@ -249,7 +203,7 @@ angular.module('form').provider('formService', function formServiceProvider() { else { // Attempt to retrieve template HTML - templateRequest(fieldType.templateUrl) + $templateRequest(fieldType.templateUrl) // Resolve with compiled HTML upon success .then(function templateRetrieved(html) {