Merge pull request #41 from glyptodon/managed-client

GUAC-963: Restore file transfer support (again)
This commit is contained in:
James Muehlner
2015-01-02 20:43:36 -08:00
14 changed files with 872 additions and 95 deletions

View File

@@ -112,24 +112,6 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
0x031D: true
};
/**
* All upload error codes handled and passed off for translation. Any error
* code not present in this list will be represented by the "DEFAULT"
* translation.
*/
var UPLOAD_ERRORS = {
0x0100: true,
0x0201: true,
0x0202: true,
0x0203: true,
0x0204: true,
0x0205: true,
0x0301: true,
0x0303: true,
0x0308: true,
0x031D: true
};
/**
* All error codes for which automatic reconnection is appropriate when a
* tunnel error occurs.
@@ -333,7 +315,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
});
$scope.$watch('menuShown', function setKeyboardEnabled(menuShown, menuShownPreviousState) {
$scope.$watch('menuShown', function menuVisibilityChanged(menuShown, menuShownPreviousState) {
// Send clipboard data if menu is hidden
if (!menuShown && menuShownPreviousState)
@@ -385,6 +367,17 @@ 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.menuShown = true;
$scope.fileTransferMarker.scrollIntoView();
}
});
// Show status dialog when connection status changes
$scope.$watch('client.clientState.connectionState', function clientStateChanged(connectionState) {
@@ -505,60 +498,6 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
};
/**
* Returns a progress object, as required by $scope.addNotification(), which
* contains the given number of bytes as an appropriate combination of
* progress value and associated unit.
*
* @param {String} text
* The translation string to associate with the progress object
* returned.
*
* @param {Number} bytes The number of bytes.
* @param {Number} [length] The file length, in bytes, if known.
*
* @returns {Object}
* A progress object, as required by $scope.addNotification().
*/
var getFileProgress = function getFileProgress(text, bytes, length) {
// Gigabytes
if (bytes > 1000000000)
return {
text : text,
value : (bytes / 1000000000).toFixed(1),
ratio : bytes / length,
unit : "gb"
};
// Megabytes
if (bytes > 1000000)
return {
text : text,
value : (bytes / 1000000).toFixed(1),
ratio : bytes / length,
unit : "mb"
};
// Kilobytes
if (bytes > 1000)
return {
text : text,
value : (bytes / 1000).toFixed(1),
ratio : bytes / length,
unit : "kb"
};
// Bytes
return {
text : text,
value : bytes,
ratio : bytes / length,
unit : "b"
};
};
// Clean up when view destroyed
$scope.$on('$destroy', function clientViewDestroyed() {

View File

@@ -0,0 +1,237 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Directive which displays an active file transfer, providing links for
* downloads, if applicable.
*/
angular.module('client').directive('guacFileTransfer', [function guacFileTransfer() {
return {
restrict: 'E',
replace: true,
scope: {
/**
* The file transfer to display.
*
* @type ManagedFileUpload|ManagedFileDownload
*/
transfer : '='
},
templateUrl: 'app/client/templates/guacFileTransfer.html',
controller: ['$scope', '$injector', function guacFileTransferController($scope, $injector) {
// Required types
var ManagedFileTransferState = $injector.get('ManagedFileTransferState');
/**
* All upload error codes handled and passed off for translation.
* Any error code not present in this list will be represented by
* the "DEFAULT" translation.
*/
var UPLOAD_ERRORS = {
0x0100: true,
0x0201: true,
0x0202: true,
0x0203: true,
0x0204: true,
0x0205: true,
0x0301: true,
0x0303: true,
0x0308: true,
0x031D: true
};
/**
* Returns the unit string that is most appropriate for the
* number of bytes transferred thus far - either 'gb', 'mb', 'kb',
* or 'b'.
*
* @returns {String}
* The unit string that is most appropriate for the number of
* bytes transferred thus far.
*/
$scope.getProgressUnit = function getProgressUnit() {
var bytes = $scope.transfer.progress;
// Gigabytes
if (bytes > 1000000000)
return 'gb';
// Megabytes
if (bytes > 1000000)
return 'mb';
// Kilobytes
if (bytes > 1000)
return 'kb';
// Bytes
return 'b';
};
/**
* Returns the amount of data transferred thus far, in the units
* returned by getProgressUnit().
*
* @returns {Number}
* The amount of data transferred thus far, in the units
* returned by getProgressUnit().
*/
$scope.getProgressValue = function getProgressValue() {
var bytes = $scope.transfer.progress;
if (!bytes)
return bytes;
// Convert bytes to necessary units
switch ($scope.getProgressUnit()) {
// Gigabytes
case 'gb':
return (bytes / 1000000000).toFixed(1);
// Megabytes
case 'mb':
return (bytes / 1000000).toFixed(1);
// Kilobytes
case 'kb':
return (bytes / 1000).toFixed(1);
// Bytes
case 'b':
default:
return bytes;
}
};
/**
* Returns the percentage of bytes transferred thus far, if the
* overall length of the file is known.
*
* @returns {Number}
* The percentage of bytes transferred thus far, if the
* overall length of the file is known.
*/
$scope.getPercentDone = function getPercentDone() {
return $scope.transfer.progress / $scope.transfer.length * 100;
};
/**
* Determines whether the associated file transfer is in progress.
*
* @returns {Boolean}
* true if the file transfer is in progress, false othherwise.
*/
$scope.isInProgress = function isInProgress() {
// Not in progress if there is no transfer
if (!$scope.transfer)
return false;
// Determine in-progress status based on stream state
switch ($scope.transfer.transferState.streamState) {
// IDLE or OPEN file transfers are active
case ManagedFileTransferState.StreamState.IDLE:
case ManagedFileTransferState.StreamState.OPEN:
return true;
// All others are not active
default:
return false;
}
};
/**
* Returns whether the file associated with this file transfer can
* be saved locally via a call to save().
*
* @returns {Boolean}
* true if a call to save() will result in the file being
* saved, false otherwise.
*/
$scope.isSavable = function isSavable() {
return !!$scope.transfer.blob;
};
/**
* Saves the downloaded file, if any. If this transfer is an upload
* or the download is not yet complete, this function has no
* effect.
*/
$scope.save = function save() {
// Ignore if no blob exists
if (!$scope.transfer.blob)
return;
// Save file
saveAs($scope.transfer.blob, $scope.transfer.filename);
};
/**
* Returns whether an error has occurred. If an error has occurred,
* the transfer is no longer active, and the text of the error can
* be read from getErrorText().
*
* @returns {Boolean}
* true if an error has occurred during transfer, false
* otherwise.
*/
$scope.hasError = function hasError() {
return $scope.transfer.transferState.streamState === ManagedFileTransferState.StreamState.ERROR;
};
/**
* Returns the text of the current error as a translation string.
*
* @returns {String}
* The name of the translation string containing the text
* associated with the current error.
*/
$scope.getErrorText = function getErrorText() {
// Determine translation name of error
var status = $scope.transfer.transferState.statusCode;
var errorName = (status in UPLOAD_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT";
// Return translation string
return 'CLIENT.ERROR_UPLOAD_' + errorName;
};
}] // end file transfer controller
};
}]);

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Directive which displays all active file transfers.
*/
angular.module('client').directive('guacFileTransferManager', [function guacFileTransferManager() {
return {
restrict: 'E',
replace: true,
scope: {
/**
* The client whose file transfers should be managed by this
* directive.
*
* @type ManagerClient
*/
client : '='
},
templateUrl: 'app/client/templates/guacFileTransferManager.html',
controller: ['$scope', '$injector', function guacFileTransferManagerController($scope, $injector) {
// Required types
var ManagedClient = $injector.get('ManagedClient');
var ManagedFileTransferState = $injector.get('ManagedFileTransferState');
/**
* Determines whether the attached client has associated file
* transfers, regardless of those file transfers' state.
*
* @returns {Boolean}
* true if there are any file transfers associated with the
* attached client, false otherise.
*/
$scope.hasTransfers = function hasTransfers() {
// There are no file transfers if there is no client
if (!$scope.client)
return false;
return !!($scope.client.uploads.length || $scope.client.downloads.length);
};
/**
* Determines whether the given file transfer state indicates an
* in-progress transfer.
*
* @param {ManagedFileTransferState} transferState
* The file transfer state to check.
*
* @returns {Boolean}
* true if the given file transfer state indicates an in-
* progress transfer, false otherwise.
*/
var isInProgress = function isInProgress(transferState) {
switch (transferState.streamState) {
// IDLE or OPEN file transfers are active
case ManagedFileTransferState.StreamState.IDLE:
case ManagedFileTransferState.StreamState.OPEN:
return true;
// All others are not active
default:
return false;
}
};
/**
* Removes all file transfers which are not currently in-progress.
*/
$scope.clearCompletedTransfers = function clearCompletedTransfers() {
// Nothing to clear if no client attached
if (!$scope.client)
return;
// Remove completed uploads
$scope.client.uploads = $scope.client.uploads.filter(function isUploadInProgress(upload) {
return isInProgress(upload.transferState);
});
// Remove completed downloads
$scope.client.downloads = $scope.client.downloads.filter(function isDownloadInProgress(download) {
return isInProgress(download.transferState);
});
};
/**
* Begins a file upload through the attached Guacamole client for
* each file in the given FileList.
*
* @param {FileList} files
* The files to upload.
*/
$scope.uploadFiles = function uploadFiles(files) {
// Ignore file uploads if no attached client
if (!$scope.client)
return;
// Upload each file
for (var i = 0; i < files.length; i++)
ManagedClient.uploadFile($scope.client, files[i]);
};
}]
};
}]);

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
.transfer-manager .action-buttons {
text-align: center;
}
.transfer-manager .no-transfers {
color: rgba(255, 255, 255, 0.5);
text-shadow: -1px -1px rgba(0, 0, 0, 0.5);
opacity: 0.5;
font-size: 2em;
font-weight: bolder;
text-align: center;
}
.transfer-manager .transfer {
margin: 1em;
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
.transfer {
position: relative;
padding: 0.5em;
font-size: 0.75em;
}
.transfer .filename {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
margin-bottom: 0.5em;
font-family: monospace;
font-weight: bold;
}
.transfer .text {
position: relative;
text-align: center;
margin-top: 0.5em;
}
@keyframes transfer-progress {
from {background-position: 0px 0px;}
to {background-position: 64px 0px;}
}
@-webkit-keyframes transfer-progress {
from {background-position: 0px 0px;}
to {background-position: 64px 0px;}
}
.transfer .progress {
width: 100%;
background: #C2C2C2;
padding: 0.25em;
border: 1px solid gray;
position: absolute;
top: 0;
left: 0;
bottom: 0;
opacity: 0.25;
}
.transfer.in-progress .progress {
background-image: url('images/progress.png');
background-size: 16px 16px;
-moz-background-size: 16px 16px;
-webkit-background-size: 16px 16px;
-khtml-background-size: 16px 16px;
animation-name: transfer-progress;
animation-duration: 2s;
animation-timing-function: linear;
animation-iteration-count: infinite;
-webkit-animation-name: transfer-progress;
-webkit-animation-duration: 2s;
-webkit-animation-timing-function: linear;
-webkit-animation-iteration-count: infinite;
}
.transfer .progress .bar {
background: #A3D655;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 0;
}
.savable.transfer {
cursor: pointer;
}
.savable.transfer:hover .progress {
border-color: black;
}
.savable.transfer .filename {
color: blue;
text-decoration: underline;
}
.error.transfer {
background: #FDD;
}
.error.transfer .progress {
border-color: rgba(0, 0, 0, 0.125);
}
.error.transfer .text,
.error.transfer .progress .bar {
display: none;
}
.error-text {
margin-bottom: 0;
}

View File

@@ -60,13 +60,21 @@
<a class="disconnect danger button" ng-click="disconnect()">{{'CLIENT.ACTION_DISCONNECT' | translate}}</a>
</div>
<h2>{{client.name}}</h2>
<!-- Clipboard -->
<h3>{{'CLIENT.SECTION_HEADER_CLIPBOARD' | translate}}</h3>
<div class="content" id="clipboard-settings">
<p class="description">{{'CLIENT.HELP_CLIPBOARD' | translate}}</p>
<textarea ng-model="client.clipboardData" rows="10" cols="40" id="clipboard"></textarea>
</div>
<!-- File transfers -->
<h3 guac-marker="fileTransferMarker">{{'CLIENT.SECTION_HEADER_FILE_TRANSFERS' | translate}}</h3>
<div class="content" id="file-transfers">
<guac-file-transfer-manager client="client"></guac-file-transfer-manager>
</div>
<!-- Input method -->
<h3>{{'CLIENT.SECTION_HEADER_INPUT_METHOD' | translate}}</h3>
<div class="content" id="keyboard-settings">
@@ -91,6 +99,7 @@
</div>
<!-- Mouse mode -->
<h3>{{'CLIENT.SECTION_HEADER_MOUSE_MODE' | translate}}</h3>
<div class="content" id="mouse-settings">
<p class="description">{{'CLIENT.HELP_MOUSE_MODE' | translate}}</p>
@@ -115,6 +124,7 @@
</div>
<!-- Display options -->
<h3>{{'CLIENT.SECTION_HEADER_DISPLAY' | translate}}</h3>
<div class="content">
<div id="zoom-settings">
@@ -124,11 +134,5 @@
</div>
<div><label><input ng-model="autoFit" ng-change="changeAutoFit()" ng-disabled="autoFitDisabled()" type="checkbox" id="auto-fit"/> {{'CLIENT.TEXT_ZOOM_AUTO_FIT' | translate}}</label></div>
</div>
</div>
<!-- Images which should be preloaded -->
<div id="preload">
<img src="images/action-icons/guac-close.png" alt=""/>
<img src="images/progress.png" alt=""/>
</div>

View File

@@ -0,0 +1,36 @@
<div class="transfer" ng-class="{'in-progress': isInProgress(), 'savable': isSavable(), 'error': hasError()}" ng-click="save()">
<!--
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.
-->
<!-- Filename -->
<div class="filename">{{transfer.filename}}</div>
<!-- Progress/status text -->
<div class="text">{{'CLIENT.TEXT_FILE_TRANSFER_PROGRESS' | translate:'{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>

View File

@@ -0,0 +1,43 @@
<div class="transfer-manager">
<!--
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.
-->
<!-- 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>
</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>
</div>
</div>

View File

@@ -99,7 +99,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
*
* @type String
*/
this.clipboardData = template.clipboardData;
this.clipboardData = template.clipboardData || '';
/**
* All downloaded files. As files are downloaded, their progress can be

View File

@@ -39,7 +39,7 @@ angular.module('element').directive('guacFocus', ['$parse', function guacFocus($
var guacFocus = $parse($attrs.guacFocus);
/**
* The element which will register the drag gesture.
* The element which will be focused / blurred.
*
* @type Element
*/

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* A directive which stores a marker which refers to a specific element,
* allowing that element to be scrolled into view when desired.
*/
angular.module('element').directive('guacMarker', ['$injector', function guacMarker($injector) {
// Required types
var Marker = $injector.get('Marker');
// Required services
var $parse = $injector.get('$parse');
return {
restrict: 'A',
link: function linkGuacMarker($scope, $element, $attrs) {
/**
* The property in which a new Marker should be stored. The new
* Marker will refer to the element associated with this directive.
*
* @type Marker
*/
var guacMarker = $parse($attrs.guacMarker);
/**
* The element to associate with the new Marker.
*
* @type Element
*/
var element = $element[0];
// Assign new marker
guacMarker.assign($scope, new Marker(element));
}
};
}]);

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* A directive which allows multiple files to be uploaded. Clicking on the
* associated element will result in a file selector dialog, which then calls
* the provided callback function with any chosen files.
*/
angular.module('element').directive('guacUpload', ['$document', function guacUpload($document) {
return {
restrict: 'A',
link: function linkGuacUpload($scope, $element, $attrs) {
/**
* The function to call whenever files are chosen. The callback is
* provided a single parameter: the FileList containing all chosen
* files.
*
* @type Function
*/
var guacUpload = $scope.$eval($attrs.guacUpload);
/**
* The element which will register the drag gesture.
*
* @type Element
*/
var element = $element[0];
/**
* Internal form, containing a single file input element.
*
* @type HTMLFormElement
*/
var form = $document[0].createElement('form');
/**
* Internal file input element.
*
* @type HTMLInputElement
*/
var input = $document[0].createElement('input');
// Init input element
input.type = 'file';
input.multiple = true;
// Add input element to internal form
form.appendChild(input);
// Notify of any chosen files
input.addEventListener('change', function filesSelected() {
$scope.$apply(function setSelectedFiles() {
// Only set chosen files selection is not canceled
if (guacUpload && input.files.length > 0)
guacUpload(input.files);
// Reset selection
form.reset();
});
});
// Open file chooser when element is clicked
element.addEventListener('click', function elementClicked() {
input.click();
});
} // end guacUpload link function
};
}]);

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Provides the Marker class definition.
*/
angular.module('element').factory('Marker', [function defineMarker() {
/**
* Creates a new Marker which allows its associated element to be scolled
* into view as desired.
*
* @constructor
* @param {Element} element
* The element to associate with this marker.
*/
var Marker = function Marker(element) {
/**
* Scrolls scrollable elements, or the window, as needed to bring the
* element associated with this marker into view.
*/
this.scrollIntoView = function scrollIntoView() {
element.scrollIntoView();
};
};
return Marker;
}]);

View File

@@ -17,16 +17,17 @@
"CLIENT" : {
"ACTION_RECONNECT" : "Reconnect",
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
"ACTION_DISCONNECT" : "Disconnect",
"ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK",
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "Clear Completed Transfers",
"ACTION_DISCONNECT" : "Disconnect",
"ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK",
"ACTION_RECONNECT" : "Reconnect",
"ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE",
"ACTION_UPLOAD_FILES" : "Upload Files",
"DIALOG_HEADER_CONNECTING" : "Connecting",
"DIALOG_HEADER_CONNECTION_ERROR" : "Connection Error",
"DIALOG_HEADER_DISCONNECTED" : "Disconnected",
"DIALOG_HEADER_FILE_TRANSFER" : "File Transfer",
"ERROR_CLIENT_201" : "This connection has been closed because the server is busy. Please wait a few minutes and try again.",
"ERROR_CLIENT_202" : "The Guacamole server has closed the connection because the remote desktop is taking too long to respond. Please try again or contact your system administrator.",
@@ -69,6 +70,8 @@
"HELP_MOUSE_MODE_ABSOLUTE" : "Tap to click. The click occurs at the location of the touch.",
"HELP_MOUSE_MODE_RELATIVE" : "Drag to move the mouse pointer and tap to click. The click occurs at the location of the pointer.",
"INFO_NO_FILE_TRANSFERS" : "No file transfers.",
"NAME_INPUT_METHOD_NONE" : "None",
"NAME_INPUT_METHOD_OSK" : "On-screen keyboard",
"NAME_INPUT_METHOD_TEXT" : "Text input",
@@ -79,10 +82,11 @@
"NAME_MOUSE_MODE_ABSOLUTE" : "Touchscreen",
"NAME_MOUSE_MODE_RELATIVE" : "Touchpad",
"SECTION_HEADER_CLIPBOARD" : "Clipboard",
"SECTION_HEADER_INPUT_METHOD" : "Input method",
"SECTION_HEADER_DISPLAY" : "Display",
"SECTION_HEADER_MOUSE_MODE" : "Mouse emulation mode",
"SECTION_HEADER_CLIPBOARD" : "Clipboard",
"SECTION_HEADER_DISPLAY" : "Display",
"SECTION_HEADER_FILE_TRANSFERS" : "File Transfers",
"SECTION_HEADER_INPUT_METHOD" : "Input method",
"SECTION_HEADER_MOUSE_MODE" : "Mouse emulation mode",
"TEXT_ZOOM_AUTO_FIT" : "Automatically fit to browser window",
"TEXT_CLIENT_STATUS_IDLE" : "Idle.",
@@ -101,10 +105,10 @@
"ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT",
"ACTION_MANAGE" : "@:APP.ACTION_MANAGE",
"INFO_NO_RECENT_CONNECTIONS" : "No recent Connections.",
"INFO_NO_RECENT_CONNECTIONS" : "No recent connections.",
"SECTION_HEADER_RECENT_CONNECTIONS" : "Recent Connections",
"SECTION_HEADER_ALL_CONNECTIONS" : "All Connections"
"SECTION_HEADER_ALL_CONNECTIONS" : "All Connections",
"SECTION_HEADER_RECENT_CONNECTIONS" : "Recent Connections"
},