GUAC-963: List active connections within recent connections.

This commit is contained in:
Michael Jumper
2014-12-29 01:42:03 -08:00
parent c71ef76bf5
commit b197c7c63c
10 changed files with 416 additions and 13 deletions

View File

@@ -229,6 +229,12 @@ angular.module('client').directive('guacClient', [function guacClient() {
displayElement = display.getElement();
displayContainer.appendChild(displayElement);
// Do nothing when the display element is clicked on
display.getElement().onclick = function(e) {
e.preventDefault();
return false;
};
});
// Update actual view scrollLeft when scroll properties change

View File

@@ -0,0 +1,199 @@
/*
* 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 for displaying a Guacamole client as a non-interactive
* thumbnail.
*/
angular.module('client').directive('guacThumbnail', [function guacThumbnail() {
return {
// Element only
restrict: 'E',
replace: true,
scope: {
/**
* The client to display within this guacThumbnail directive.
*
* @type ManagedClient
*/
client : '='
},
templateUrl: 'app/client/templates/guacThumbnail.html',
controller: ['$scope', '$injector', '$element', function guacThumbnailController($scope, $injector, $element) {
// Required services
var $window = $injector.get('$window');
/**
* The optimal thumbnail width, in pixels.
*
* @type Number
*/
var THUMBNAIL_WIDTH = 320;
/**
* The optimal thumbnail height, in pixels.
*
* @type Number
*/
var THUMBNAIL_HEIGHT = 240;
/**
* The current Guacamole client instance.
*
* @type Guacamole.Client
*/
var client = null;
/**
* The display of the current Guacamole client instance.
*
* @type Guacamole.Display
*/
var display = null;
/**
* The element associated with the display of the current
* Guacamole client instance.
*
* @type Element
*/
var displayElement = null;
/**
* The element which must contain the Guacamole display element.
*
* @type Element
*/
var displayContainer = $element.find('.display')[0];
/**
* The main containing element for the entire directive.
*
* @type Element
*/
var main = $element[0];
/**
* The element which functions as a detector for size changes.
*
* @type Element
*/
var resizeSensor = $element.find('.resize-sensor')[0];
/**
* Updates the scale of the attached Guacamole.Client based on current window
* size and "auto-fit" setting.
*/
var updateDisplayScale = function updateDisplayScale() {
if (!display) return;
// Fit within available area
display.scale(Math.min(
main.offsetWidth / Math.max(display.getWidth(), 1),
main.offsetHeight / Math.max(display.getHeight(), 1)
));
};
// Attach any given managed client
$scope.$watch('client', function attachManagedClient(managedClient) {
// Remove any existing display
displayContainer.innerHTML = "";
// Only proceed if a client is given
if (!managedClient)
return;
// Get Guacamole client instance
client = managedClient.client;
// Attach possibly new display
display = client.getDisplay();
// Add display element
displayElement = display.getElement();
displayContainer.appendChild(displayElement);
});
// Update scale when display is resized
$scope.$watch('client.managedDisplay.size', function setDisplaySize(size) {
var width;
var height;
// If no display size yet, assume optimal thumbnail size
if (!size || size.width === 0 || size.height === 0) {
width = THUMBNAIL_WIDTH;
height = THUMBNAIL_HEIGHT;
}
// Otherwise, generate size that fits within thumbnail bounds
else {
var scale = Math.min(THUMBNAIL_WIDTH / size.width, THUMBNAIL_HEIGHT / size.height, 1);
width = size.width * scale;
height = size.height * scale;
}
// Generate dummy background image
var thumbnail = document.createElement("canvas");
thumbnail.width = width;
thumbnail.height = height;
$scope.thumbnail = thumbnail.toDataURL("image/png");
$scope.$evalAsync(updateDisplayScale);
});
// If the element is resized, attempt to resize client
resizeSensor.contentWindow.addEventListener('resize', function mainElementResized() {
// Send new display size, if changed
if (client && display) {
var pixelDensity = $window.devicePixelRatio || 1;
var width = main.offsetWidth * pixelDensity;
var height = main.offsetHeight * pixelDensity;
if (display.getWidth() !== width || display.getHeight() !== height)
client.sendSize(width, height);
}
$scope.$apply(updateDisplayScale);
});
// Do not allow nested elements to prevent handling of click events
main.addEventListener('click', function preventClickPropagation(e) {
e.stopPropagation();
}, true);
}]
};
}]);

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2013 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.
*/
div.thumbnail-main {
overflow: hidden;
width: 100%;
height: 100%;
position: relative;
font-size: 0px;
}
.thumbnail-main img {
max-width: 100%;
}
.thumbnail-main .display {
position: absolute;
}

View File

@@ -0,0 +1,34 @@
<div class="thumbnail-main">
<!--
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.
-->
<!-- Resize sensor -->
<iframe class="resize-sensor" src="app/client/templates/blank.html"></iframe>
<!-- Display -->
<div class="display">
</div>
<!-- Dummy background thumbnail -->
<img alt="" ng-src="{{thumbnail}}"/>
</div>

View File

@@ -168,12 +168,6 @@ angular.module('client').factory('ManagedDisplay', ['$rootScope',
});
};
// Do nothing when the display element is clicked on
display.getElement().onclick = function(e) {
e.preventDefault();
return false;
};
return managedDisplay;
};

View File

@@ -42,9 +42,50 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo
},
templateUrl: 'app/home/templates/guacRecentConnections.html',
controller: ['$scope', '$injector', 'guacHistory', 'RecentConnection',
function guacRecentConnectionsController($scope, $injector, guacHistory, RecentConnection) {
controller: ['$scope', '$injector', function guacRecentConnectionsController($scope, $injector) {
// Required types
var ActiveConnection = $injector.get('ActiveConnection');
var RecentConnection = $injector.get('RecentConnection');
// Required services
var guacClientManager = $injector.get('guacClientManager');
var guacHistory = $injector.get('guacHistory');
/**
* Array of all known and visible active connections.
*
* @type ActiveConnection[]
*/
$scope.activeConnections = [];
/**
* Array of all known and visible recently-used connections.
*
* @type RecentConnection[]
*/
$scope.recentConnections = [];
/**
* Returns whether recent connections are available for display.
* Note that, for the sake of this directive, recent connections
* include any currently-active connections, even if they are not
* yet in the history.
*
* @returns {Boolean}
* true if recent (or active) connections are present, false
* otherwise.
*/
$scope.hasRecentConnections = function hasRecentConnections() {
return !!($scope.activeConnections.length || $scope.recentConnections.length);
};
/**
* Map of all visible objects, connections or connection groups, by
* object identifier.
*
* @type Object.<String, Connection|ConnectionGroup>
*/
var visibleObjects = {};
/**
@@ -87,6 +128,8 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo
// Update visible objects when root group is set
$scope.$watch("rootGroup", function setRootGroup(rootGroup) {
// Clear connection arrays
$scope.activeConnections = [];
$scope.recentConnections = [];
// Produce collection of visible objects
@@ -94,11 +137,27 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo
if (rootGroup)
addVisibleConnectionGroup(rootGroup);
// Add all active connections
for (var id in guacClientManager.managedClients) {
// Get corresponding managed client
var client = guacClientManager.managedClients[id];
// Add active connections for clients with associated visible objects
if (id in visibleObjects) {
var object = visibleObjects[id];
$scope.activeConnections.push(new ActiveConnection(object.name, client));
}
}
// Add any recent connections that are visible
guacHistory.recentConnections.forEach(function addRecentConnection(historyEntry) {
// Add recent connections for history entries with associated visible objects
if (historyEntry.id in visibleObjects) {
if (historyEntry.id in visibleObjects && !(historyEntry.id in guacClientManager.managedClients)) {
var object = visibleObjects[historyEntry.id];
$scope.recentConnections.push(new RecentConnection(object.name, historyEntry));

View File

@@ -20,4 +20,4 @@
* THE SOFTWARE.
*/
angular.module('home', ['history', 'groupList', 'rest']);
angular.module('home', ['client', 'history', 'groupList', 'rest']);

View File

@@ -22,11 +22,28 @@
-->
<!-- Text displayed if no recent connections exist -->
<p class="no-recent" ng-hide="recentConnections.length">{{'HOME.INFO_NO_RECENT_CONNECTIONS' | translate}}</p>
<p class="no-recent" ng-hide="hasRecentConnections()">{{'HOME.INFO_NO_RECENT_CONNECTIONS' | translate}}</p>
<!-- All active connections -->
<div ng-repeat="activeConnection in activeConnections" class="connection">
<a href="#/client/{{activeConnection.client.id}}">
<!-- Connection thumbnail -->
<div class="thumbnail">
<guac-thumbnail client="activeConnection.client"/></guac-thumbnail>
</div>
<!-- Connection name -->
<div class="caption">
<span class="name">{{activeConnection.name}}</span>
</div>
</a>
</div>
<!-- All recent connections -->
<div ng-repeat="recentConnection in recentConnections" class="connection">
<a href="#/client/{{recentConnection.entry.id}}/{{recentConnection.name}}">
<a href="#/client/{{recentConnection.entry.id}}">
<!-- Connection thumbnail -->
<div class="thumbnail">

View File

@@ -0,0 +1,55 @@
/*
* 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 ActiveConnection class used by the guacRecentConnections
* directive.
*/
angular.module('home').factory('ActiveConnection', [function defineActiveConnection() {
/**
* A recently-user connection, visible to the current user, with an
* associated history entry.
*
* @constructor
*/
var ActiveConnection = function ActiveConnection(name, client) {
/**
* The human-readable name of this connection.
*
* @type String
*/
this.name = name;
/**
* The client associated with this active connection.
*
* @type ManagedClient
*/
this.client = client;
};
return ActiveConnection;
}]);

View File

@@ -55,10 +55,12 @@
margin: 0.5em;
}
.connection .thumbnail img {
.connection .thumbnail > * {
border: 1px solid black;
background: black;
box-shadow: 1px 1px 5px black;
max-width: 75%;
display: inline-block;
}
div.recent-connections .connection .thumbnail {