mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 21:27:40 +00:00
Merge pull request #206 from glyptodon/file-browser
GUAC-1172: Implement file browsing UI
This commit is contained in:
@@ -27,7 +27,9 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
function clientController($scope, $routeParams, $injector) {
|
||||
|
||||
// Required types
|
||||
var ManagedClient = $injector.get('ManagedClient');
|
||||
var ManagedClientState = $injector.get('ManagedClientState');
|
||||
var ManagedFilesystem = $injector.get('ManagedFilesystem');
|
||||
var ScrollState = $injector.get('ScrollState');
|
||||
|
||||
// Required services
|
||||
@@ -407,17 +409,6 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
$scope.page.title = name;
|
||||
});
|
||||
|
||||
// Show file transfer section of menu if new file transfers have started
|
||||
$scope.$watch('client.uploads.length + client.downloads.length', function transfersChanged(count, oldCount) {
|
||||
|
||||
// Show menu and scroll file transfer into view
|
||||
if (count > oldCount) {
|
||||
$scope.menu.shown = true;
|
||||
$scope.menu.fileTransferMarker.scrollIntoView();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Show status dialog when connection status changes
|
||||
$scope.$watch('client.clientState.connectionState', function clientStateChanged(connectionState) {
|
||||
|
||||
@@ -558,6 +549,119 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
// Set client-specific menu actions
|
||||
$scope.clientMenuActions = [ DISCONNECT_MENU_ACTION ];
|
||||
|
||||
/**
|
||||
* The currently-visible filesystem within the filesystem menu, if the
|
||||
* filesystem menu is open. If no filesystem is currently visible, this
|
||||
* will be null.
|
||||
*
|
||||
* @type ManagedFilesystem
|
||||
*/
|
||||
$scope.filesystemMenuContents = null;
|
||||
|
||||
/**
|
||||
* Hides the filesystem menu.
|
||||
*/
|
||||
$scope.hideFilesystemMenu = function hideFilesystemMenu() {
|
||||
$scope.filesystemMenuContents = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the filesystem menu, displaying the contents of the given
|
||||
* filesystem within it.
|
||||
*
|
||||
* @param {ManagedFilesystem} filesystem
|
||||
* The filesystem to show within the filesystem menu.
|
||||
*/
|
||||
$scope.showFilesystemMenu = function showFilesystemMenu(filesystem) {
|
||||
$scope.filesystemMenuContents = filesystem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the filesystem menu should be visible.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the filesystem menu is shown, false otherwise.
|
||||
*/
|
||||
$scope.isFilesystemMenuShown = function isFilesystemMenuShown() {
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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], $scope.filesystemMenuContents);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
};
|
||||
|
||||
// Clean up when view destroyed
|
||||
$scope.$on('$destroy', function clientViewDestroyed() {
|
||||
|
||||
|
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A directive which displays the contents of a filesystem received through the
|
||||
* Guacamole client.
|
||||
*/
|
||||
angular.module('client').directive('guacFileBrowser', [function guacFileBrowser() {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
scope: {
|
||||
|
||||
/**
|
||||
* The client whose file transfers should be managed by this
|
||||
* directive.
|
||||
*
|
||||
* @type ManagedClient
|
||||
*/
|
||||
client : '=',
|
||||
|
||||
/**
|
||||
* @type ManagedFilesystem
|
||||
*/
|
||||
filesystem : '='
|
||||
|
||||
},
|
||||
|
||||
templateUrl: 'app/client/templates/guacFileBrowser.html',
|
||||
controller: ['$scope', '$element', '$injector', function guacFileBrowserController($scope, $element, $injector) {
|
||||
|
||||
// Required types
|
||||
var ManagedFilesystem = $injector.get('ManagedFilesystem');
|
||||
|
||||
// Required services
|
||||
var $interpolate = $injector.get('$interpolate');
|
||||
var $templateRequest = $injector.get('$templateRequest');
|
||||
|
||||
/**
|
||||
* The jQuery-wrapped element representing the contents of the
|
||||
* current directory within the file browser.
|
||||
*
|
||||
* @type Element[]
|
||||
*/
|
||||
var currentDirectoryContents = $element.find('.current-directory-contents');
|
||||
|
||||
/**
|
||||
* Statically-cached template HTML used to render each file within
|
||||
* a directory. Once available, this will be used through
|
||||
* createFileElement() to generate the DOM elements which make up
|
||||
* a directory listing.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
var fileTemplate = null;
|
||||
|
||||
/**
|
||||
* Returns whether the given file is a normal file.
|
||||
*
|
||||
* @param {ManagedFilesystem.File} file
|
||||
* The file to test.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the given file is a normal file, false otherwise.
|
||||
*/
|
||||
$scope.isNormalFile = function isNormalFile(file) {
|
||||
return file.type === ManagedFilesystem.File.Type.NORMAL;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the given file is a directory.
|
||||
*
|
||||
* @param {ManagedFilesystem.File} file
|
||||
* The file to test.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the given file is a directory, false otherwise.
|
||||
*/
|
||||
$scope.isDirectory = function isDirectory(file) {
|
||||
return file.type === ManagedFilesystem.File.Type.DIRECTORY;
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the currently-displayed directory to the given
|
||||
* directory.
|
||||
*
|
||||
* @param {ManagedFilesystem.File} file
|
||||
* The directory to change to.
|
||||
*/
|
||||
$scope.changeDirectory = function changeDirectory(file) {
|
||||
ManagedFilesystem.changeDirectory($scope.filesystem, file);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initiates a download of the given file. The progress of the
|
||||
* download can be observed through guacFileTransferManager.
|
||||
*
|
||||
* @param {ManagedFilesystem.File} file
|
||||
* The file to download.
|
||||
*/
|
||||
$scope.downloadFile = function downloadFile(file) {
|
||||
ManagedFilesystem.downloadFile($scope.client, $scope.filesystem, file.streamName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new element representing the given file and properly
|
||||
* handling user events, bypassing the overhead incurred through
|
||||
* use of ngRepeat and related techniques.
|
||||
*
|
||||
* Note that this function depends on the availability of the
|
||||
* statically-cached fileTemplate.
|
||||
*
|
||||
* @param {ManagedFilesystem.File} file
|
||||
* The file to generate an element for.
|
||||
*
|
||||
* @returns {Element[]}
|
||||
* A jQuery-wrapped array containing a single DOM element
|
||||
* representing the given file.
|
||||
*/
|
||||
var createFileElement = function createFileElement(file) {
|
||||
|
||||
// Create from internal template
|
||||
var element = angular.element($interpolate(fileTemplate)(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;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Sorts the given map of files, returning an array of those files
|
||||
* grouped by file type (directories first, followed by non-
|
||||
* directories) and sorted lexicographically.
|
||||
*
|
||||
* @param {Object.<String, ManagedFilesystem.File>} files
|
||||
* The map of files to sort.
|
||||
*
|
||||
* @returns {ManagedFilesystem.File[]}
|
||||
* An array of all files in the given map, sorted
|
||||
* lexicographically with directories first, followed by non-
|
||||
* directories.
|
||||
*/
|
||||
var sortFiles = function sortFiles(files) {
|
||||
|
||||
// Get all given files as an array
|
||||
var unsortedFiles = [];
|
||||
for (var name in files)
|
||||
unsortedFiles.push(files[name]);
|
||||
|
||||
// Sort files - directories first, followed by all other files
|
||||
// sorted by name
|
||||
return unsortedFiles.sort(function fileComparator(a, b) {
|
||||
|
||||
// Directories come before non-directories
|
||||
if ($scope.isDirectory(a) && !$scope.isDirectory(b))
|
||||
return -1;
|
||||
|
||||
// Non-directories come after directories
|
||||
if (!$scope.isDirectory(a) && $scope.isDirectory(b))
|
||||
return 1;
|
||||
|
||||
// All other combinations are sorted by name
|
||||
return a.name.localeCompare(b.name);
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// Watch directory contents once file template is available
|
||||
$templateRequest('app/client/templates/file.html').then(function fileTemplateRetrieved(html) {
|
||||
|
||||
// Store file template statically
|
||||
fileTemplate = html;
|
||||
|
||||
// Update the contents of the file browser whenever the current directory (or its contents) changes
|
||||
$scope.$watch('filesystem.currentDirectory.files', function currentDirectoryChanged(files) {
|
||||
|
||||
// Clear current content
|
||||
currentDirectoryContents.html('');
|
||||
|
||||
// Display all files within current directory, sorted
|
||||
angular.forEach(sortFiles(files), function displayFile(file) {
|
||||
currentDirectoryContents.append(createFileElement(file));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}); // end retrieve file template
|
||||
|
||||
}]
|
||||
|
||||
};
|
||||
}]);
|
@@ -44,27 +44,8 @@ angular.module('client').directive('guacFileTransferManager', [function guacFile
|
||||
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.
|
||||
@@ -112,25 +93,6 @@ angular.module('client').directive('guacFileTransferManager', [function guacFile
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
|
||||
};
|
||||
|
||||
}]
|
||||
|
||||
};
|
||||
|
46
guacamole/src/main/webapp/app/client/styles/file-browser.css
Normal file
46
guacamole/src/main/webapp/app/client/styles/file-browser.css
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* Hide directory contents by default */
|
||||
|
||||
.file-browser .directory > .children {
|
||||
padding-left: 1em;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-browser .list-item .caption {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Directory / file icons */
|
||||
|
||||
.file-browser .normal-file > .caption .icon {
|
||||
background-image: url('images/file.png');
|
||||
}
|
||||
|
||||
.file-browser .directory > .caption .icon {
|
||||
background-image: url('images/folder-closed.png');
|
||||
}
|
||||
|
||||
.file-browser .directory.previous > .caption .icon {
|
||||
background-image: url('images/folder-up.png');
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#file-transfer-dialog {
|
||||
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
font-size: 0.8em;
|
||||
padding: 0.5em;
|
||||
|
||||
width: 4in;
|
||||
max-width: 100%;
|
||||
|
||||
}
|
||||
|
||||
#file-transfer-dialog .transfer-manager {
|
||||
border: 1px solid rgba(0, 0, 0, 0.125);
|
||||
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.125);
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#filesystem-menu .header h2 {
|
||||
font-size: 1em;
|
||||
font-weight: normal;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#filesystem-menu .header {
|
||||
-ms-flex-align: center;
|
||||
-moz-box-align: center;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#filesystem-menu .menu-body {
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
#filesystem-menu .header.breadcrumbs {
|
||||
display: block;
|
||||
background: rgba(0,0,0,0.0125);
|
||||
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||
box-shadow: none;
|
||||
margin-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
#filesystem-menu .header.breadcrumbs .breadcrumb {
|
||||
display: inline-block;
|
||||
padding: 0.5em;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#filesystem-menu .header.breadcrumbs .breadcrumb:hover {
|
||||
background-color: #CDA;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#filesystem-menu .header.breadcrumbs .breadcrumb.root {
|
||||
background-size: 1.5em 1.5em;
|
||||
-moz-background-size: 1.5em 1.5em;
|
||||
-webkit-background-size: 1.5em 1.5em;
|
||||
-khtml-background-size: 1.5em 1.5em;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-image: url('images/drive.png');
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
}
|
188
guacamole/src/main/webapp/app/client/styles/guac-menu.css
Normal file
188
guacamole/src/main/webapp/app/client/styles/guac-menu.css
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
#guac-menu #devices .device {
|
||||
|
||||
padding: 1em;
|
||||
border: 1px solid rgba(0, 0, 0, 0.125);
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
|
||||
padding-left: 3.5em;
|
||||
background-size: 1.5em 1.5em;
|
||||
-moz-background-size: 1.5em 1.5em;
|
||||
-webkit-background-size: 1.5em 1.5em;
|
||||
-khtml-background-size: 1.5em 1.5em;
|
||||
|
||||
background-repeat: no-repeat;
|
||||
background-position: 1em center;
|
||||
|
||||
}
|
||||
|
||||
#guac-menu #devices .device:hover {
|
||||
cursor: pointer;
|
||||
border-color: black;
|
||||
}
|
||||
|
||||
#guac-menu #devices .device.filesystem {
|
||||
background-image: url('images/drive.png');
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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,22 +20,27 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
.transfer-manager .action-buttons {
|
||||
text-align: center;
|
||||
.transfer-manager {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.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 .header h2 {
|
||||
font-size: 1em;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.transfer-manager .transfer {
|
||||
margin: 1em;
|
||||
.transfer-manager .header {
|
||||
margin: 0;
|
||||
-ms-flex-align: center;
|
||||
-moz-box-align: center;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.transfer-manager .transfers {
|
||||
display: table;
|
||||
padding: 0.25em;
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -21,25 +21,29 @@
|
||||
*/
|
||||
|
||||
.transfer {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.transfer .transfer-status {
|
||||
display: table-cell;
|
||||
padding: 0.25em;
|
||||
position: relative;
|
||||
padding: 0.5em;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.transfer .text {
|
||||
display: table-cell;
|
||||
text-align: right;
|
||||
padding: 0.25em
|
||||
}
|
||||
|
||||
.transfer .filename {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
margin-bottom: 0.5em;
|
||||
position: relative;
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.transfer .text {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin-top: 0.5em;
|
||||
padding: 0.125em;
|
||||
}
|
||||
|
||||
@keyframes transfer-progress {
|
||||
@@ -55,11 +59,8 @@
|
||||
.transfer .progress {
|
||||
|
||||
width: 100%;
|
||||
background: #C2C2C2;
|
||||
padding: 0.25em;
|
||||
|
||||
border: 1px solid gray;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -70,6 +71,7 @@
|
||||
|
||||
.transfer.in-progress .progress {
|
||||
|
||||
background-color: #EEE;
|
||||
background-image: url('images/progress.png');
|
||||
|
||||
background-size: 16px 16px;
|
||||
@@ -90,6 +92,7 @@
|
||||
}
|
||||
|
||||
.transfer .progress .bar {
|
||||
display: none;
|
||||
background: #A3D655;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -98,32 +101,35 @@
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.savable.transfer {
|
||||
.transfer.in-progress .progress .bar {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.transfer.savable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.savable.transfer:hover .progress {
|
||||
border-color: black;
|
||||
}
|
||||
|
||||
.savable.transfer .filename {
|
||||
.transfer.savable .filename {
|
||||
color: blue;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.error.transfer {
|
||||
.transfer.error {
|
||||
background: #FDD;
|
||||
}
|
||||
|
||||
.error.transfer .progress {
|
||||
border-color: rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.error.transfer .text,
|
||||
.error.transfer .progress .bar {
|
||||
.transfer.error .text,
|
||||
.transfer.error .progress .bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
margin-bottom: 0;
|
||||
.transfer .error-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.transfer.error .error-text {
|
||||
display: block;
|
||||
margin: 0;
|
||||
margin-top: 0.5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -52,8 +52,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- File transfers -->
|
||||
<div id="file-transfer-dialog" ng-show="hasTransfers()">
|
||||
<guac-file-transfer-manager client="client"></guac-file-transfer-manager>
|
||||
</div>
|
||||
|
||||
<!-- Menu -->
|
||||
<div ng-class="{open: menu.shown}" id="menu">
|
||||
<div class="menu" ng-class="{open: menu.shown}" id="guac-menu">
|
||||
<div class="menu-content">
|
||||
|
||||
<!-- Stationary header -->
|
||||
@@ -74,11 +79,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- File transfers -->
|
||||
<div class="menu-section" id="file-transfers">
|
||||
<h3 guac-marker="menu.fileTransferMarker">{{'CLIENT.SECTION_HEADER_FILE_TRANSFERS' | translate}}</h3>
|
||||
<!-- Devices -->
|
||||
<div class="menu-section" id="devices" ng-show="client.filesystems.length">
|
||||
<h3>{{'CLIENT.SECTION_HEADER_DEVICES' | translate}}</h3>
|
||||
<div class="content">
|
||||
<guac-file-transfer-manager client="client"></guac-file-transfer-manager>
|
||||
<div class="device filesystem" ng-repeat="filesystem in client.filesystems" ng-click="showFilesystemMenu(filesystem)">
|
||||
{{filesystem.name}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -154,4 +161,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filesystem menu -->
|
||||
<div id="filesystem-menu" class="menu" ng-class="{open: isFilesystemMenuShown()}">
|
||||
<div class="menu-content">
|
||||
|
||||
<!-- Stationary header -->
|
||||
<div class="header">
|
||||
<h2>{{filesystemMenuContents.name}}</h2>
|
||||
<button class="upload button" guac-upload="uploadFiles">{{'CLIENT.ACTION_UPLOAD_FILES' | translate}}</button>
|
||||
<button class="back" ng-click="hideFilesystemMenu()">{{'CLIENT.ACTION_NAVIGATE_BACK' | translate}}</button>
|
||||
</div>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<div class="header breadcrumbs"><div
|
||||
class="breadcrumb root"
|
||||
ng-click="changeDirectory(filesystemMenuContents, filesystemMenuContents.root)"></div><div
|
||||
class="breadcrumb"
|
||||
ng-repeat="file in getPath(filesystemMenuContents.currentDirectory)"
|
||||
ng-click="changeDirectory(filesystemMenuContents, file)">{{file.name}}</div>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable body -->
|
||||
<div class="menu-body">
|
||||
<guac-file-browser client="client" filesystem="filesystemMenuContents"></guac-file-browser>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</guac-viewport>
|
||||
|
30
guacamole/src/main/webapp/app/client/templates/file.html
Normal file
30
guacamole/src/main/webapp/app/client/templates/file.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<div class="list-item">
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<!-- Filename and icon -->
|
||||
<div class="caption">
|
||||
<div class="icon"></div>
|
||||
{{::name}}
|
||||
</div>
|
||||
|
||||
</div>
|
@@ -0,0 +1,34 @@
|
||||
<div class="file-browser">
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<!-- Parent directory -->
|
||||
<div class="list-item directory previous" ng-show="filesystem.currentDirectory.parent">
|
||||
<div class="caption" ng-click="changeDirectory(filesystem.currentDirectory.parent)">
|
||||
<div class="icon"></div>..
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current directory contents -->
|
||||
<div class="current-directory-contents"></div>
|
||||
|
||||
</div>
|
@@ -21,18 +21,23 @@
|
||||
THE SOFTWARE.
|
||||
-->
|
||||
|
||||
<!-- Filename -->
|
||||
<div class="filename">{{transfer.filename}}</div>
|
||||
<!-- Overall status of transfer -->
|
||||
<div class="transfer-status">
|
||||
|
||||
<!-- Filename and progress bar -->
|
||||
<div class="filename">
|
||||
<div class="progress"><div ng-style="{'width': getPercentDone() + '%'}" class="bar"></div></div>
|
||||
{{transfer.filename}}
|
||||
</div>
|
||||
|
||||
<!-- Error text -->
|
||||
<p class="error-text">{{getErrorText() | translate}}</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Progress/status text -->
|
||||
<div class="text"
|
||||
translate="CLIENT.TEXT_FILE_TRANSFER_PROGRESS"
|
||||
translate-values="{PROGRESS: getProgressValue(), UNIT: getProgressUnit()}"></div>
|
||||
|
||||
<!-- Progress bar -->
|
||||
<div class="progress"><div ng-style="{'width': getPercentDone() + '%'}" class="bar"></div></div>
|
||||
|
||||
<!-- Error text -->
|
||||
<p class="error-text" ng-show="hasError()">{{getErrorText() | translate}}</p>
|
||||
|
||||
</div>
|
||||
|
@@ -21,23 +21,21 @@
|
||||
THE SOFTWARE.
|
||||
-->
|
||||
|
||||
<!-- No transfers currently present -->
|
||||
<p class="no-transfers" ng-hide="hasTransfers()">{{'CLIENT.INFO_NO_FILE_TRANSFERS' | translate}}</p>
|
||||
|
||||
<!-- Sent files -->
|
||||
<div ng-repeat="upload in client.uploads">
|
||||
<guac-file-transfer transfer="upload"></guac-file-transfer>
|
||||
<!-- File transfer manager header -->
|
||||
<div class="header">
|
||||
<h2>{{'CLIENT.SECTION_HEADER_FILE_TRANSFERS' | translate}}</h2>
|
||||
<button ng-click="clearCompletedTransfers()">{{'CLIENT.ACTION_CLEAR_COMPLETED_TRANSFERS' | translate}}</button>
|
||||
</div>
|
||||
|
||||
<!-- Received files -->
|
||||
<div ng-repeat="download in client.downloads">
|
||||
<guac-file-transfer transfer="download"></guac-file-transfer>
|
||||
</div>
|
||||
|
||||
<!-- Form buttons -->
|
||||
<div class="action-buttons">
|
||||
<a class="upload button" guac-upload="uploadFiles">{{'CLIENT.ACTION_UPLOAD_FILES' | translate}}</a>
|
||||
<a class="button" ng-click="clearCompletedTransfers()">{{'CLIENT.ACTION_CLEAR_COMPLETED_TRANSFERS' | translate}}</a>
|
||||
<!-- Sent/received files files -->
|
||||
<div class="transfers">
|
||||
<guac-file-transfer
|
||||
transfer="upload"
|
||||
ng-repeat="upload in client.uploads">
|
||||
</guac-file-transfer><guac-file-transfer
|
||||
transfer="download"
|
||||
ng-repeat="download in client.downloads">
|
||||
</guac-file-transfer>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@@ -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());
|
||||
|
||||
@@ -421,9 +438,31 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
||||
*
|
||||
* @param {File} file
|
||||
* The file to upload.
|
||||
*
|
||||
* @param {ManagedFilesystem} [filesystem]
|
||||
* The filesystem to upload the file to, if any. If not specified, the
|
||||
* file will be sent as a generic Guacamole file stream.
|
||||
*
|
||||
* @param {ManagedFilesystem.File} [directory=filesystem.currentDirectory]
|
||||
* The directory within the given filesystem to upload the file to. If
|
||||
* not specified, but a filesystem is given, the current directory of
|
||||
* that filesystem will be used.
|
||||
*/
|
||||
ManagedClient.uploadFile = function uploadFile(managedClient, file) {
|
||||
managedClient.uploads.push(ManagedFileUpload.getInstance(managedClient.client, file));
|
||||
ManagedClient.uploadFile = function uploadFile(managedClient, file, filesystem, directory) {
|
||||
|
||||
// Use generic Guacamole file streams by default
|
||||
var object = null;
|
||||
var streamName = null;
|
||||
|
||||
// If a filesystem is given, determine the destination object and stream
|
||||
if (filesystem) {
|
||||
object = filesystem.object;
|
||||
streamName = (directory || filesystem.currentDirectory).streamName + '/' + file.name;
|
||||
}
|
||||
|
||||
// Start and manage file upload
|
||||
managedClient.uploads.push(ManagedFileUpload.getInstance(managedClient.client, file, object, streamName));
|
||||
|
||||
};
|
||||
|
||||
return ManagedClient;
|
||||
|
@@ -124,11 +124,19 @@ angular.module('client').factory('ManagedFileUpload', ['$rootScope', '$injector'
|
||||
* @param {File} file
|
||||
* The file to upload.
|
||||
*
|
||||
* @param {Object} [object]
|
||||
* The object to upload the file to, if any, such as a filesystem
|
||||
* object.
|
||||
*
|
||||
* @param {String} [streamName]
|
||||
* The name of the stream to upload the file to. If an object is given,
|
||||
* this must be specified.
|
||||
*
|
||||
* @return {ManagedFileUpload}
|
||||
* A new ManagedFileUpload object which can be used to track the
|
||||
* progress of the upload.
|
||||
*/
|
||||
ManagedFileUpload.getInstance = function getInstance(client, file) {
|
||||
ManagedFileUpload.getInstance = function getInstance(client, file, object, streamName) {
|
||||
|
||||
var managedFileUpload = new ManagedFileUpload();
|
||||
|
||||
@@ -137,7 +145,14 @@ angular.module('client').factory('ManagedFileUpload', ['$rootScope', '$injector'
|
||||
reader.onloadend = function fileContentsLoaded() {
|
||||
|
||||
// Open file for writing
|
||||
var stream = client.createFileStream(file.type, file.name);
|
||||
var stream;
|
||||
if (!object)
|
||||
stream = client.createFileStream(file.type, file.name);
|
||||
|
||||
// If object/streamName specified, upload to that instead of a file
|
||||
// stream
|
||||
else
|
||||
stream = object.createOutputStream(file.type, streamName);
|
||||
|
||||
var valid = true;
|
||||
var bytes = new Uint8Array(reader.result);
|
||||
|
334
guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js
Normal file
334
guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js
Normal file
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* 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) {
|
||||
|
||||
// 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
|
||||
* reattached from different client views.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ManagedFilesystem|Object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* ManagedFilesystem.
|
||||
*/
|
||||
var ManagedFilesystem = function ManagedFilesystem(template) {
|
||||
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* The Guacamole filesystem object, as received via a "filesystem"
|
||||
* instruction.
|
||||
*
|
||||
* @type Guacamole.Object
|
||||
*/
|
||||
this.object = template.object;
|
||||
|
||||
/**
|
||||
* The declared, human-readable name of the filesystem
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.name = template.name;
|
||||
|
||||
/**
|
||||
* The root directory of the filesystem.
|
||||
*
|
||||
* @type ManagedFilesystem.File
|
||||
*/
|
||||
this.root = template.root;
|
||||
|
||||
/**
|
||||
* The current directory being viewed or manipulated within the
|
||||
* filesystem.
|
||||
*
|
||||
* @type ManagedFilesystem.File
|
||||
*/
|
||||
this.currentDirectory = template.currentDirectory || template.root;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Refreshes the contents of the given file, if that file is a directory.
|
||||
* Only the immediate children of the file are refreshed. Files further
|
||||
* down the directory tree are not refreshed.
|
||||
*
|
||||
* @param {ManagedFilesystem} filesystem
|
||||
* The filesystem associated with the file being refreshed.
|
||||
*
|
||||
* @param {ManagedFilesystem.File} file
|
||||
* The file being refreshed.
|
||||
*/
|
||||
ManagedFilesystem.refresh = function updateDirectory(filesystem, file) {
|
||||
|
||||
// Do not attempt to refresh the contents of directories
|
||||
if (file.mimetype !== Guacamole.Object.STREAM_INDEX_MIMETYPE)
|
||||
return;
|
||||
|
||||
// Request contents of given file
|
||||
filesystem.object.requestInputStream(file.streamName, function handleStream(stream, mimetype) {
|
||||
|
||||
// Ignore stream if mimetype is wrong
|
||||
if (mimetype !== Guacamole.Object.STREAM_INDEX_MIMETYPE) {
|
||||
stream.sendAck('Unexpected mimetype', Guacamole.Status.Code.UNSUPPORTED);
|
||||
return;
|
||||
}
|
||||
|
||||
// Signal server that data is ready to be received
|
||||
stream.sendAck('Ready', Guacamole.Status.Code.SUCCESS);
|
||||
|
||||
// Read stream as JSON
|
||||
var reader = new Guacamole.JSONReader(stream);
|
||||
|
||||
// Acknowledge received JSON blobs
|
||||
reader.onprogress = function onprogress() {
|
||||
stream.sendAck("Received", Guacamole.Status.Code.SUCCESS);
|
||||
};
|
||||
|
||||
// Reset contents of directory
|
||||
reader.onend = function jsonReady() {
|
||||
$rootScope.$evalAsync(function updateFileContents() {
|
||||
|
||||
// Empty contents
|
||||
file.files = {};
|
||||
|
||||
// Determine the expected filename prefix of each stream
|
||||
var expectedPrefix = file.streamName;
|
||||
if (expectedPrefix.charAt(expectedPrefix.length - 1) !== '/')
|
||||
expectedPrefix += '/';
|
||||
|
||||
// For each received stream name
|
||||
var mimetypes = reader.getJSON();
|
||||
for (var name in mimetypes) {
|
||||
|
||||
// Assert prefix is correct
|
||||
if (name.substring(0, expectedPrefix.length) !== expectedPrefix)
|
||||
continue;
|
||||
|
||||
// Extract filename from stream name
|
||||
var filename = name.substring(expectedPrefix.length);
|
||||
|
||||
// Deduce type from mimetype
|
||||
var type = ManagedFilesystem.File.Type.NORMAL;
|
||||
if (mimetypes[name] === Guacamole.Object.STREAM_INDEX_MIMETYPE)
|
||||
type = ManagedFilesystem.File.Type.DIRECTORY;
|
||||
|
||||
// Add file entry
|
||||
file.files[filename] = new ManagedFilesystem.File({
|
||||
mimetype : mimetypes[name],
|
||||
streamName : name,
|
||||
type : type,
|
||||
parent : file,
|
||||
name : filename
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new ManagedFilesystem instance from the given Guacamole.Object
|
||||
* and human-readable name. Upon creation, a request to populate the
|
||||
* contents of the root directory will be automatically dispatched.
|
||||
*
|
||||
* @param {Guacamole.Object} object
|
||||
* The Guacamole.Object defining the filesystem.
|
||||
*
|
||||
* @param {String} name
|
||||
* A human-readable name for the filesystem.
|
||||
*
|
||||
* @returns {ManagedFilesystem}
|
||||
* The newly-created ManagedFilesystem.
|
||||
*/
|
||||
ManagedFilesystem.getInstance = function getInstance(object, name) {
|
||||
|
||||
// Init new filesystem object
|
||||
var managedFilesystem = new ManagedFilesystem({
|
||||
object : object,
|
||||
name : name,
|
||||
root : new ManagedFilesystem.File({
|
||||
mimetype : Guacamole.Object.STREAM_INDEX_MIMETYPE,
|
||||
streamName : Guacamole.Object.ROOT_STREAM,
|
||||
type : ManagedFilesystem.File.Type.DIRECTORY
|
||||
})
|
||||
});
|
||||
|
||||
// Retrieve contents of root
|
||||
ManagedFilesystem.refresh(managedFilesystem, managedFilesystem.root);
|
||||
|
||||
return managedFilesystem;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Downloads the given file from the server using the given Guacamole
|
||||
* client and filesystem. The 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));
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the current directory of the given filesystem, automatically
|
||||
* refreshing the contents of that directory.
|
||||
*
|
||||
* @param {ManagedFilesystem} filesystem
|
||||
* The filesystem whose current directory should be changed.
|
||||
*
|
||||
* @param {ManagedFilesystem.File} file
|
||||
* The directory to change to.
|
||||
*/
|
||||
ManagedFilesystem.changeDirectory = function changeDirectory(filesystem, file) {
|
||||
|
||||
// Refresh contents
|
||||
ManagedFilesystem.refresh(filesystem, file);
|
||||
|
||||
// Set current directory
|
||||
filesystem.currentDirectory = file;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A file within a ManagedFilesystem. Each ManagedFilesystem.File provides
|
||||
* sufficient information for retrieval or replacement of the file's
|
||||
* contents, as well as the file's name and type.
|
||||
*
|
||||
* @param {ManagedFilesystem|Object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* ManagedFilesystem.File.
|
||||
*/
|
||||
ManagedFilesystem.File = function File(template) {
|
||||
|
||||
/**
|
||||
* The mimetype of the data contained within this file.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.mimetype = template.mimetype;
|
||||
|
||||
/**
|
||||
* The name of the stream representing this files contents within its
|
||||
* associated filesystem object.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.streamName = template.streamName;
|
||||
|
||||
/**
|
||||
* The type of this file. All legal file type strings are defined
|
||||
* within ManagedFilesystem.File.Type.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.type = template.type;
|
||||
|
||||
/**
|
||||
* The name of this file.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.name = template.name;
|
||||
|
||||
/**
|
||||
* The parent directory of this file. In the case of the root
|
||||
* directory, this will be null.
|
||||
*
|
||||
* @type ManagedFilesystem.File
|
||||
*/
|
||||
this.parent = template.parent;
|
||||
|
||||
/**
|
||||
* Map of all known files containined within this file by name. This is
|
||||
* only applicable to directories.
|
||||
*
|
||||
* @type Object.<String, ManagedFilesystem.File>
|
||||
*/
|
||||
this.files = template.files || {};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* All legal type strings for a ManagedFilesystem.File.
|
||||
*
|
||||
* @type Object.<String, String>
|
||||
*/
|
||||
ManagedFilesystem.File.Type = {
|
||||
|
||||
/**
|
||||
* A normal file. As ManagedFilesystem does not currently represent any
|
||||
* other non-directory types of files, like symbolic links, this type
|
||||
* string may be used for any non-directory file.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
NORMAL : 'NORMAL',
|
||||
|
||||
/**
|
||||
* A directory.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
DIRECTORY : 'DIRECTORY'
|
||||
|
||||
};
|
||||
|
||||
return ManagedFilesystem;
|
||||
|
||||
}]);
|
@@ -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.<String>}
|
||||
* 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) {
|
||||
|
BIN
guacamole/src/main/webapp/images/drive.png
Normal file
BIN
guacamole/src/main/webapp/images/drive.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 752 B |
BIN
guacamole/src/main/webapp/images/file.png
Normal file
BIN
guacamole/src/main/webapp/images/file.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 471 B |
BIN
guacamole/src/main/webapp/images/folder-closed.png
Normal file
BIN
guacamole/src/main/webapp/images/folder-closed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 487 B |
BIN
guacamole/src/main/webapp/images/folder-open.png
Normal file
BIN
guacamole/src/main/webapp/images/folder-open.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 803 B |
BIN
guacamole/src/main/webapp/images/folder-up.png
Normal file
BIN
guacamole/src/main/webapp/images/folder-up.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 819 B |
@@ -41,8 +41,9 @@
|
||||
"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",
|
||||
"ACTION_RECONNECT" : "Reconnect",
|
||||
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
|
||||
@@ -106,6 +107,7 @@
|
||||
"NAME_MOUSE_MODE_RELATIVE" : "Touchpad",
|
||||
|
||||
"SECTION_HEADER_CLIPBOARD" : "Clipboard",
|
||||
"SECTION_HEADER_DEVICES" : "Devices",
|
||||
"SECTION_HEADER_DISPLAY" : "Display",
|
||||
"SECTION_HEADER_FILE_TRANSFERS" : "File Transfers",
|
||||
"SECTION_HEADER_INPUT_METHOD" : "Input method",
|
||||
|
@@ -39,8 +39,9 @@
|
||||
"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",
|
||||
"ACTION_RECONNECT" : "Reconnecter",
|
||||
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
|
||||
|
@@ -41,8 +41,9 @@
|
||||
"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",
|
||||
"ACTION_RECONNECT" : "Verbind Opnieuw",
|
||||
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
|
||||
|
@@ -40,8 +40,9 @@
|
||||
"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",
|
||||
"ACTION_RECONNECT" : "Переподключиться",
|
||||
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
|
||||
|
Reference in New Issue
Block a user