mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-08 06:01:22 +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) {
|
function clientController($scope, $routeParams, $injector) {
|
||||||
|
|
||||||
// Required types
|
// Required types
|
||||||
|
var ManagedClient = $injector.get('ManagedClient');
|
||||||
var ManagedClientState = $injector.get('ManagedClientState');
|
var ManagedClientState = $injector.get('ManagedClientState');
|
||||||
|
var ManagedFilesystem = $injector.get('ManagedFilesystem');
|
||||||
var ScrollState = $injector.get('ScrollState');
|
var ScrollState = $injector.get('ScrollState');
|
||||||
|
|
||||||
// Required services
|
// Required services
|
||||||
@@ -407,17 +409,6 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
$scope.page.title = name;
|
$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
|
// Show status dialog when connection status changes
|
||||||
$scope.$watch('client.clientState.connectionState', function clientStateChanged(connectionState) {
|
$scope.$watch('client.clientState.connectionState', function clientStateChanged(connectionState) {
|
||||||
|
|
||||||
@@ -558,6 +549,119 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
// Set client-specific menu actions
|
// Set client-specific menu actions
|
||||||
$scope.clientMenuActions = [ DISCONNECT_MENU_ACTION ];
|
$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
|
// Clean up when view destroyed
|
||||||
$scope.$on('$destroy', function clientViewDestroyed() {
|
$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) {
|
controller: ['$scope', '$injector', function guacFileTransferManagerController($scope, $injector) {
|
||||||
|
|
||||||
// Required types
|
// Required types
|
||||||
var ManagedClient = $injector.get('ManagedClient');
|
|
||||||
var ManagedFileTransferState = $injector.get('ManagedFileTransferState');
|
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
|
* Determines whether the given file transfer state indicates an
|
||||||
* in-progress transfer.
|
* 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
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#menu {
|
.menu {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -127,38 +127,6 @@
|
|||||||
flex: 0 0 auto;
|
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 {
|
.menu-section h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -169,122 +137,13 @@
|
|||||||
padding-top: 1em;
|
padding-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu .content > * {
|
.menu,
|
||||||
|
.menu.closed {
|
||||||
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 {
|
|
||||||
left: -480px;
|
left: -480px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu.open {
|
.menu.open {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
opacity: 1;
|
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
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -20,22 +20,27 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.transfer-manager .action-buttons {
|
.transfer-manager {
|
||||||
text-align: center;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transfer-manager .no-transfers {
|
.transfer-manager .header h2 {
|
||||||
|
font-size: 1em;
|
||||||
color: rgba(255, 255, 255, 0.5);
|
padding-top: 0;
|
||||||
text-shadow: -1px -1px rgba(0, 0, 0, 0.5);
|
padding-bottom: 0;
|
||||||
opacity: 0.5;
|
|
||||||
|
|
||||||
font-size: 2em;
|
|
||||||
font-weight: bolder;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.transfer-manager .transfer {
|
.transfer-manager .header {
|
||||||
margin: 1em;
|
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 {
|
.transfer {
|
||||||
|
display: table-row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer .transfer-status {
|
||||||
|
display: table-cell;
|
||||||
|
padding: 0.25em;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0.5em;
|
}
|
||||||
font-size: 0.75em;
|
|
||||||
|
.transfer .text {
|
||||||
|
display: table-cell;
|
||||||
|
text-align: right;
|
||||||
|
padding: 0.25em
|
||||||
}
|
}
|
||||||
|
|
||||||
.transfer .filename {
|
.transfer .filename {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
position: relative;
|
||||||
margin-bottom: 0.5em;
|
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
padding: 0.125em;
|
||||||
|
|
||||||
.transfer .text {
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes transfer-progress {
|
@keyframes transfer-progress {
|
||||||
@@ -55,11 +59,8 @@
|
|||||||
.transfer .progress {
|
.transfer .progress {
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #C2C2C2;
|
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
|
|
||||||
border: 1px solid gray;
|
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -70,6 +71,7 @@
|
|||||||
|
|
||||||
.transfer.in-progress .progress {
|
.transfer.in-progress .progress {
|
||||||
|
|
||||||
|
background-color: #EEE;
|
||||||
background-image: url('images/progress.png');
|
background-image: url('images/progress.png');
|
||||||
|
|
||||||
background-size: 16px 16px;
|
background-size: 16px 16px;
|
||||||
@@ -90,6 +92,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.transfer .progress .bar {
|
.transfer .progress .bar {
|
||||||
|
display: none;
|
||||||
background: #A3D655;
|
background: #A3D655;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -98,32 +101,35 @@
|
|||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.savable.transfer {
|
.transfer.in-progress .progress .bar {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer.savable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.savable.transfer:hover .progress {
|
.transfer.savable .filename {
|
||||||
border-color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.savable.transfer .filename {
|
|
||||||
color: blue;
|
color: blue;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error.transfer {
|
.transfer.error {
|
||||||
background: #FDD;
|
background: #FDD;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error.transfer .progress {
|
.transfer.error .text,
|
||||||
border-color: rgba(0, 0, 0, 0.125);
|
.transfer.error .progress .bar {
|
||||||
}
|
|
||||||
|
|
||||||
.error.transfer .text,
|
|
||||||
.error.transfer .progress .bar {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-text {
|
.transfer .error-text {
|
||||||
margin-bottom: 0;
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer.error .error-text {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@@ -52,8 +52,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- File transfers -->
|
||||||
|
<div id="file-transfer-dialog" ng-show="hasTransfers()">
|
||||||
|
<guac-file-transfer-manager client="client"></guac-file-transfer-manager>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Menu -->
|
<!-- Menu -->
|
||||||
<div ng-class="{open: menu.shown}" id="menu">
|
<div class="menu" ng-class="{open: menu.shown}" id="guac-menu">
|
||||||
<div class="menu-content">
|
<div class="menu-content">
|
||||||
|
|
||||||
<!-- Stationary header -->
|
<!-- Stationary header -->
|
||||||
@@ -74,11 +79,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- File transfers -->
|
<!-- Devices -->
|
||||||
<div class="menu-section" id="file-transfers">
|
<div class="menu-section" id="devices" ng-show="client.filesystems.length">
|
||||||
<h3 guac-marker="menu.fileTransferMarker">{{'CLIENT.SECTION_HEADER_FILE_TRANSFERS' | translate}}</h3>
|
<h3>{{'CLIENT.SECTION_HEADER_DEVICES' | translate}}</h3>
|
||||||
<div class="content">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -154,4 +161,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Filesystem menu -->
|
||||||
|
<div id="filesystem-menu" class="menu" ng-class="{open: isFilesystemMenuShown()}">
|
||||||
|
<div class="menu-content">
|
||||||
|
|
||||||
|
<!-- Stationary header -->
|
||||||
|
<div class="header">
|
||||||
|
<h2>{{filesystemMenuContents.name}}</h2>
|
||||||
|
<button class="upload button" guac-upload="uploadFiles">{{'CLIENT.ACTION_UPLOAD_FILES' | translate}}</button>
|
||||||
|
<button class="back" ng-click="hideFilesystemMenu()">{{'CLIENT.ACTION_NAVIGATE_BACK' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Breadcrumbs -->
|
||||||
|
<div class="header breadcrumbs"><div
|
||||||
|
class="breadcrumb root"
|
||||||
|
ng-click="changeDirectory(filesystemMenuContents, filesystemMenuContents.root)"></div><div
|
||||||
|
class="breadcrumb"
|
||||||
|
ng-repeat="file in getPath(filesystemMenuContents.currentDirectory)"
|
||||||
|
ng-click="changeDirectory(filesystemMenuContents, file)">{{file.name}}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scrollable body -->
|
||||||
|
<div class="menu-body">
|
||||||
|
<guac-file-browser client="client" filesystem="filesystemMenuContents"></guac-file-browser>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</guac-viewport>
|
</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.
|
THE SOFTWARE.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- Filename -->
|
<!-- Overall status of transfer -->
|
||||||
<div class="filename">{{transfer.filename}}</div>
|
<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 -->
|
<!-- Progress/status text -->
|
||||||
<div class="text"
|
<div class="text"
|
||||||
translate="CLIENT.TEXT_FILE_TRANSFER_PROGRESS"
|
translate="CLIENT.TEXT_FILE_TRANSFER_PROGRESS"
|
||||||
translate-values="{PROGRESS: getProgressValue(), UNIT: getProgressUnit()}"></div>
|
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>
|
</div>
|
||||||
|
@@ -21,23 +21,21 @@
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- No transfers currently present -->
|
<!-- File transfer manager header -->
|
||||||
<p class="no-transfers" ng-hide="hasTransfers()">{{'CLIENT.INFO_NO_FILE_TRANSFERS' | translate}}</p>
|
<div class="header">
|
||||||
|
<h2>{{'CLIENT.SECTION_HEADER_FILE_TRANSFERS' | translate}}</h2>
|
||||||
<!-- Sent files -->
|
<button ng-click="clearCompletedTransfers()">{{'CLIENT.ACTION_CLEAR_COMPLETED_TRANSFERS' | translate}}</button>
|
||||||
<div ng-repeat="upload in client.uploads">
|
|
||||||
<guac-file-transfer transfer="upload"></guac-file-transfer>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Received files -->
|
<!-- Sent/received files files -->
|
||||||
<div ng-repeat="download in client.downloads">
|
<div class="transfers">
|
||||||
<guac-file-transfer transfer="download"></guac-file-transfer>
|
<guac-file-transfer
|
||||||
</div>
|
transfer="upload"
|
||||||
|
ng-repeat="upload in client.uploads">
|
||||||
<!-- Form buttons -->
|
</guac-file-transfer><guac-file-transfer
|
||||||
<div class="action-buttons">
|
transfer="download"
|
||||||
<a class="upload button" guac-upload="uploadFiles">{{'CLIENT.ACTION_UPLOAD_FILES' | translate}}</a>
|
ng-repeat="download in client.downloads">
|
||||||
<a class="button" ng-click="clearCompletedTransfers()">{{'CLIENT.ACTION_CLEAR_COMPLETED_TRANSFERS' | translate}}</a>
|
</guac-file-transfer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -31,6 +31,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
|||||||
var ManagedClientState = $injector.get('ManagedClientState');
|
var ManagedClientState = $injector.get('ManagedClientState');
|
||||||
var ManagedDisplay = $injector.get('ManagedDisplay');
|
var ManagedDisplay = $injector.get('ManagedDisplay');
|
||||||
var ManagedFileDownload = $injector.get('ManagedFileDownload');
|
var ManagedFileDownload = $injector.get('ManagedFileDownload');
|
||||||
|
var ManagedFilesystem = $injector.get('ManagedFilesystem');
|
||||||
var ManagedFileUpload = $injector.get('ManagedFileUpload');
|
var ManagedFileUpload = $injector.get('ManagedFileUpload');
|
||||||
|
|
||||||
// Required services
|
// Required services
|
||||||
@@ -119,6 +120,15 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
|||||||
*/
|
*/
|
||||||
this.uploads = template.uploads || [];
|
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,
|
* The current state of the Guacamole client (idle, connecting,
|
||||||
* connected, terminated with error, etc.).
|
* 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
|
// Manage the client display
|
||||||
managedClient.managedDisplay = ManagedDisplay.getInstance(client.getDisplay());
|
managedClient.managedDisplay = ManagedDisplay.getInstance(client.getDisplay());
|
||||||
|
|
||||||
@@ -421,9 +438,31 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
|||||||
*
|
*
|
||||||
* @param {File} file
|
* @param {File} file
|
||||||
* The file to upload.
|
* 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.uploadFile = function uploadFile(managedClient, file, filesystem, directory) {
|
||||||
managedClient.uploads.push(ManagedFileUpload.getInstance(managedClient.client, file));
|
|
||||||
|
// 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;
|
return ManagedClient;
|
||||||
|
@@ -124,11 +124,19 @@ angular.module('client').factory('ManagedFileUpload', ['$rootScope', '$injector'
|
|||||||
* @param {File} file
|
* @param {File} file
|
||||||
* The file to upload.
|
* 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}
|
* @return {ManagedFileUpload}
|
||||||
* A new ManagedFileUpload object which can be used to track the
|
* A new ManagedFileUpload object which can be used to track the
|
||||||
* progress of the upload.
|
* progress of the upload.
|
||||||
*/
|
*/
|
||||||
ManagedFileUpload.getInstance = function getInstance(client, file) {
|
ManagedFileUpload.getInstance = function getInstance(client, file, object, streamName) {
|
||||||
|
|
||||||
var managedFileUpload = new ManagedFileUpload();
|
var managedFileUpload = new ManagedFileUpload();
|
||||||
|
|
||||||
@@ -137,7 +145,14 @@ angular.module('client').factory('ManagedFileUpload', ['$rootScope', '$injector'
|
|||||||
reader.onloadend = function fileContentsLoaded() {
|
reader.onloadend = function fileContentsLoaded() {
|
||||||
|
|
||||||
// Open file for writing
|
// 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 valid = true;
|
||||||
var bytes = new Uint8Array(reader.result);
|
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) {
|
this.$get = ['$injector', function formServiceFactory($injector) {
|
||||||
|
|
||||||
// Required services
|
// Required services
|
||||||
var $compile = $injector.get('$compile');
|
var $compile = $injector.get('$compile');
|
||||||
var $http = $injector.get('$http');
|
var $q = $injector.get('$q');
|
||||||
var $q = $injector.get('$q');
|
var $templateRequest = $injector.get('$templateRequest');
|
||||||
var $templateCache = $injector.get('$templateCache');
|
|
||||||
|
|
||||||
var service = {};
|
var service = {};
|
||||||
|
|
||||||
service.fieldTypes = provider.fieldTypes;
|
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
|
* Compiles and links the field associated with the given name to the given
|
||||||
* scope, producing a distinct and independent DOM Element which functions
|
* scope, producing a distinct and independent DOM Element which functions
|
||||||
@@ -249,7 +203,7 @@ angular.module('form').provider('formService', function formServiceProvider() {
|
|||||||
else {
|
else {
|
||||||
|
|
||||||
// Attempt to retrieve template HTML
|
// Attempt to retrieve template HTML
|
||||||
templateRequest(fieldType.templateUrl)
|
$templateRequest(fieldType.templateUrl)
|
||||||
|
|
||||||
// Resolve with compiled HTML upon success
|
// Resolve with compiled HTML upon success
|
||||||
.then(function templateRetrieved(html) {
|
.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" : {
|
"CLIENT" : {
|
||||||
|
|
||||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||||
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "Clear Completed Transfers",
|
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "Clear",
|
||||||
"ACTION_DISCONNECT" : "Disconnect",
|
"ACTION_DISCONNECT" : "Disconnect",
|
||||||
|
"ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK",
|
||||||
"ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME",
|
"ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME",
|
||||||
"ACTION_RECONNECT" : "Reconnect",
|
"ACTION_RECONNECT" : "Reconnect",
|
||||||
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
|
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
|
||||||
@@ -106,6 +107,7 @@
|
|||||||
"NAME_MOUSE_MODE_RELATIVE" : "Touchpad",
|
"NAME_MOUSE_MODE_RELATIVE" : "Touchpad",
|
||||||
|
|
||||||
"SECTION_HEADER_CLIPBOARD" : "Clipboard",
|
"SECTION_HEADER_CLIPBOARD" : "Clipboard",
|
||||||
|
"SECTION_HEADER_DEVICES" : "Devices",
|
||||||
"SECTION_HEADER_DISPLAY" : "Display",
|
"SECTION_HEADER_DISPLAY" : "Display",
|
||||||
"SECTION_HEADER_FILE_TRANSFERS" : "File Transfers",
|
"SECTION_HEADER_FILE_TRANSFERS" : "File Transfers",
|
||||||
"SECTION_HEADER_INPUT_METHOD" : "Input method",
|
"SECTION_HEADER_INPUT_METHOD" : "Input method",
|
||||||
|
@@ -39,8 +39,9 @@
|
|||||||
"CLIENT" : {
|
"CLIENT" : {
|
||||||
|
|
||||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||||
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "Vider transferts terminés",
|
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "Vider",
|
||||||
"ACTION_DISCONNECT" : "Déconnecter",
|
"ACTION_DISCONNECT" : "Déconnecter",
|
||||||
|
"ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK",
|
||||||
"ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME",
|
"ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME",
|
||||||
"ACTION_RECONNECT" : "Reconnecter",
|
"ACTION_RECONNECT" : "Reconnecter",
|
||||||
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
|
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
|
||||||
|
@@ -41,8 +41,9 @@
|
|||||||
"CLIENT" : {
|
"CLIENT" : {
|
||||||
|
|
||||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||||
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "Wis lijst Voltooide Overdrachten",
|
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "Wis lijst",
|
||||||
"ACTION_DISCONNECT" : "Verbreek Verbinding",
|
"ACTION_DISCONNECT" : "Verbreek Verbinding",
|
||||||
|
"ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK",
|
||||||
"ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME",
|
"ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME",
|
||||||
"ACTION_RECONNECT" : "Verbind Opnieuw",
|
"ACTION_RECONNECT" : "Verbind Opnieuw",
|
||||||
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
|
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
|
||||||
|
@@ -40,8 +40,9 @@
|
|||||||
"CLIENT" : {
|
"CLIENT" : {
|
||||||
|
|
||||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||||
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "Очистить завершенные загрузки",
|
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "Очистить",
|
||||||
"ACTION_DISCONNECT" : "Отключиться",
|
"ACTION_DISCONNECT" : "Отключиться",
|
||||||
|
"ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK",
|
||||||
"ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME",
|
"ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME",
|
||||||
"ACTION_RECONNECT" : "Переподключиться",
|
"ACTION_RECONNECT" : "Переподключиться",
|
||||||
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
|
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
|
||||||
|
Reference in New Issue
Block a user