GUACAMOLE-724: Implement base support for displaying multiple connections in a tiled grid.

This commit is contained in:
Michael Jumper
2021-01-17 16:15:50 -08:00
parent e550b244f8
commit 85d01ba730
10 changed files with 272 additions and 15 deletions

View File

@@ -279,20 +279,37 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
/**
* The client which should be attached to the client UI.
*
* @type ManagedClient
* @type ManagedClient[]
*/
$scope.client = guacClientManager.getManagedClient($routeParams.id, $routeParams.params);
$scope.clients = (function getClients() {
var clients = [];
var ids = $routeParams.id.split(/[ +]/);
ids.forEach(function addClient(id) {
clients.push(guacClientManager.getManagedClient(id, $routeParams.params));
});
return clients;
})();
/**
* All active clients which are not the current client ($scope.client).
* All active clients which are not any current client ($scope.clients).
* Each key is the ID of the connection used by that client.
*
* @type Object.<String, ManagedClient>
*/
$scope.otherClients = (function getOtherClients(clients) {
var otherClients = angular.extend({}, clients);
delete otherClients[$scope.client.id];
$scope.clients.forEach(function removeActiveCLient(client) {
delete otherClients[client.id];
});
return otherClients;
})(guacClientManager.getManagedClients());
/**
@@ -526,7 +543,9 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
$scope.menu.connectionParameters = ManagedClient.getArgumentModel($scope.client);
// Disable client keyboard if the menu is shown
$scope.client.clientProperties.keyboardEnabled = !menuShown;
angular.forEach($scope.clients, function updateKeyboardEnabled(client) {
client.clientProperties.keyboardEnabled = !menuShown;
});
});
@@ -731,7 +750,15 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
* otherwise.
*/
$scope.isConnectionUnstable = function isConnectionUnstable() {
return $scope.client && $scope.client.clientState.tunnelUnstable;
var unstable = false;
angular.forEach($scope.clients, function checkStability(client) {
unstable |= client.clientState.tunnelUnstable;
});
return unstable;
};
/**

View File

@@ -421,7 +421,7 @@ angular.module('client').directive('guacClient', [function guacClient() {
// Translate local keydown events to remote keydown events if keyboard is enabled
$scope.$on('guacKeydown', function keydownListener(event, keysym, keyboard) {
if ($scope.client.clientProperties.keyboardEnabled && !event.defaultPrevented) {
if ($scope.client.clientProperties.keyboardEnabled && $scope.client.clientProperties.focused) {
client.sendKeyEvent(1, keysym);
event.preventDefault();
}
@@ -429,7 +429,7 @@ angular.module('client').directive('guacClient', [function guacClient() {
// Translate local keyup events to remote keyup events if keyboard is enabled
$scope.$on('guacKeyup', function keyupListener(event, keysym, keyboard) {
if ($scope.client.clientProperties.keyboardEnabled && !event.defaultPrevented) {
if ($scope.client.clientProperties.keyboardEnabled && $scope.client.clientProperties.focused) {
client.sendKeyEvent(0, keysym);
event.preventDefault();
}
@@ -437,11 +437,13 @@ angular.module('client').directive('guacClient', [function guacClient() {
// Universally handle all synthetic keydown events
$scope.$on('guacSyntheticKeydown', function syntheticKeydownListener(event, keysym) {
if ($scope.client.clientProperties.focused)
client.sendKeyEvent(1, keysym);
});
// Universally handle all synthetic keyup events
$scope.$on('guacSyntheticKeyup', function syntheticKeyupListener(event, keysym) {
if ($scope.client.clientProperties.focused)
client.sendKeyEvent(0, keysym);
});

View File

@@ -0,0 +1,145 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* A directive which displays one or more Guacamole clients in an evenly-tiled
* view. The number of rows and columns used for the arrangement of tiles is
* automatically determined by the number of clients present.
*/
angular.module('client').directive('guacTiledClients', [function guacTiledClients() {
var directive = {
restrict: 'E',
templateUrl: 'app/client/templates/guacTiledClients.html',
};
directive.scope = {
/**
* The Guacamole clients that should be displayed in an evenly-tiled
* grid arrangement.
*
* @type ManagedClient[]
*/
clients : '='
};
directive.controller = ['$scope', '$injector', '$element',
function guacTiledListController($scope, $injector, $element) {
/**
* Returns the number of columns that should be used to evenly arrange
* all provided clients in a tiled grid.
*
* @returns {Number}
* The number of columns that should be used for the grid of
* clients.
*/
var getColumns = function getColumns() {
if (!$scope.clients || !$scope.clients.length)
return 0;
return Math.ceil(Math.sqrt($scope.clients.length));
};
/**
* Returns the number of rows that should be used to evenly arrange all
* provided clients in a tiled grid.
*
* @returns {Number}
* The number of rows that should be used for the grid of clients.
*/
var getRows = function getRows() {
if (!$scope.clients || !$scope.clients.length)
return 0;
return Math.ceil($scope.clients.length / getColumns());
};
/**
* Assigns keyboard focus to the given client, allowing that client to
* receive and handle keyboard events. Multiple clients may have
* keyboard focus simultaneously.
*
* @param {ManagedClient} client
* The client that should receive keyboard focus.
*/
$scope.assignFocus = function assignFocus(client) {
client.clientProperties.focused = true;
};
/**
* Returns whether multiple clients are currently shown within the
* tiled grid.
*
* @returns {Boolean}
* true if two or more clients are currently present, false
* otherwise.
*/
$scope.hasMultipleClients = function hasMultipleClients() {
return $scope.clients && $scope.clients.length > 1;
};
/**
* Returns the CSS width that should be applied to each tile to
* achieve an even arrangement.
*
* @returns {String}
* The CSS width that should be applied to each tile.
*/
$scope.getTileWidth = function getTileWidth() {
return Math.floor(100 / getColumns()) + '%';
};
/**
* Returns the CSS height that should be applied to each tile to
* achieve an even arrangement.
*
* @returns {String}
* The CSS height that should be applied to each tile.
*/
$scope.getTileHeight = function getTileHeight() {
return Math.floor(100 / getRows()) + '%';
};
/**
* Returns the display title of the given Guacamole client. If the
* title is not yet known, a placeholder title will be returned.
*
* @param {ManagedClient} client
* The client whose title should be retrieved.
*
* @returns {String}
* The title of the given client, or a placeholder title if the
* client's title is not yet known.
*/
$scope.getClientTitle = function getClientTitle(client) {
return client.title || '...';
};
}];
return directive;
}]);

View File

@@ -103,7 +103,7 @@ body.client {
flex: 0 0 auto;
}
.client-view .client-body .main {
.client-view .client-body .tiled-client-list {
position: absolute;
left: 0;

View File

@@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.tiled-client-list {
padding: 0;
margin: 0;
line-height: 0;
}
.tiled-client-list li.client-tile {
position: relative;
display: inline-flex;
flex-direction: column;
line-height: 1.5;
}
.tiled-client-list li.client-tile h3 {
margin: 0;
background: #444;
padding: 0 0.25em;
font-size: 0.8em;
color: white;
display: none;
}
.tiled-client-list.multiple-clients li.client-tile h3 {
display: block;
}
.tiled-client-list.multiple-clients li.client-tile {
border: 1px solid #444;
}
.tiled-client-list li.client-tile.focused {
border-color: #3161a9;
}
.tiled-client-list li.client-tile.focused h3 {
background-color: #3161a9;
}
.tiled-client-list li.client-tile .main {
flex: 1;
}

View File

@@ -8,8 +8,8 @@
<!-- Central portion of view -->
<div class="client-body" guac-touch-drag="clientDrag" guac-touch-pinch="clientPinch">
<!-- Client for current connection -->
<guac-client client="client"></guac-client>
<!-- All connections in current display -->
<guac-tiled-clients clients="clients"></guac-tiled-clients>
<!-- All other active connections -->
<div id="other-connections">

View File

@@ -1,4 +1,5 @@
<a class="connection" ng-href="{{ item.getClientURL() }}">
<div class="icon type" ng-class="item.protocol"></div>
<input type="checkbox">
<span class="name">{{item.name}}</span>
</a>

View File

@@ -1,4 +1,5 @@
<a class="connection-group" ng-href="{{ item.getClientURL() }}">
<div ng-show="item.balancing" class="icon type balancer"></div>
<input type="checkbox">
<span class="name">{{item.name}}</span>
</a>

View File

@@ -0,0 +1,13 @@
<ul class="tiled-client-list" ng-class="{ 'multiple-clients' : hasMultipleClients() }">
<li class="client-tile"
ng-repeat="client in clients"
ng-style="{ 'width' : getTileWidth(), 'height' : getTileHeight() }"
ng-class="{ 'focused' : client.clientProperties.focused }"
ng-click="assignFocus(client)">
<h3>{{ getClientTitle(client) }}</h3>
<guac-client client="client"></guac-client>
</li>
</ul>

View File

@@ -69,12 +69,20 @@ angular.module('client').factory('ClientProperties', ['$injector', function defi
this.maxScale = template.maxScale || 3;
/**
* Whether or not the client should listen to keyboard events.
* Whether this client should listen to keyboard events that it
* receives.
*
* @type Boolean
*/
this.keyboardEnabled = template.keyboardEnabled || true;
/**
* Whether this client should receive keyboard events.
*
* @type Boolean
*/
this.focused = template.focused || false;
/**
* Whether translation of touch to mouse events should emulate an
* absolute pointer device, or a relative pointer device.