diff --git a/guacamole/src/main/frontend/src/app/client/controllers/clientController.js b/guacamole/src/main/frontend/src/app/client/controllers/clientController.js
index 2194ce270..d14cda8f6 100644
--- a/guacamole/src/main/frontend/src/app/client/controllers/clientController.js
+++ b/guacamole/src/main/frontend/src/app/client/controllers/clientController.js
@@ -211,28 +211,43 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
* view.
*/
updateAttachedClients : function updateAttachedClients(id) {
-
- // Deconstruct current path into corresponding client IDs
- var ids = ManagedClientGroup.getClientIdentifiers($routeParams.id);
-
- // Add/remove ID as requested
- if ($scope.connectionListContext.attachedClients[id])
- ids.push(id);
- else
- _.pull(ids, id);
-
- // Reconstruct path, updating attached clients via change in route
- $location.path('/client/' + encodeURIComponent(ManagedClientGroup.getIdentifier(ids)));
-
+ $scope.addRemoveClient(id, !$scope.connectionListContext.attachedClients[id]);
}
};
+ /**
+ * Adds or removes the client with the given ID from the set of clients
+ * within the current view, updating the current URL accordingly.
+ *
+ * @param {string} id
+ * The ID of the client to add or remove from the current view.
+ *
+ * @param {boolean} [remove=false]
+ * Whether the specified client should be added (false) or removed
+ * (true).
+ */
+ $scope.addRemoveClient = function addRemoveClient(id, remove) {
+
+ // Deconstruct current path into corresponding client IDs
+ var ids = ManagedClientGroup.getClientIdentifiers($routeParams.id);
+
+ // Add/remove ID as requested
+ if (remove)
+ _.pull(ids, id);
+ else
+ ids.push(id);
+
+ // Reconstruct path, updating attached clients via change in route
+ $location.path('/client/' + encodeURIComponent(ManagedClientGroup.getIdentifier(ids)));
+
+ };
+
/**
* Reloads the contents of $scope.clientGroup to reflect the client IDs
* currently listed in the URL.
*/
- var updateAttachedClients = function updateAttachedClients() {
+ var reparseRoute = function reparseRoute() {
var previousClients = $scope.clientGroup ? $scope.clientGroup.clients.slice() : [];
detachCurrentGroup();
@@ -290,11 +305,11 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
};
// Init sets of clients based on current URL ...
- updateAttachedClients();
+ reparseRoute();
// ... and re-initialize those sets if the URL has changed without
// reloading the route
- $scope.$on('$routeUpdate', updateAttachedClients);
+ $scope.$on('$routeUpdate', reparseRoute);
/**
* The root connection groups of the connection hierarchy that should be
@@ -621,6 +636,18 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
};
+ /**
+ * Disconnects the given ManagedClient, removing it from the current
+ * view.
+ *
+ * @param {ManagedClient} client
+ * The client to disconnect.
+ */
+ $scope.closeClientTile = function closeClientTile(client) {
+ $scope.addRemoveClient(client.id, true);
+ guacClientManager.removeManagedClient(client.id);
+ };
+
/**
* Action which immediately disconnects the currently-connected client, if
* any.
diff --git a/guacamole/src/main/frontend/src/app/client/directives/guacTiledClients.js b/guacamole/src/main/frontend/src/app/client/directives/guacTiledClients.js
index efb0d936a..99063769b 100644
--- a/guacamole/src/main/frontend/src/app/client/directives/guacTiledClients.js
+++ b/guacamole/src/main/frontend/src/app/client/directives/guacTiledClients.js
@@ -31,6 +31,16 @@ angular.module('client').directive('guacTiledClients', [function guacTiledClient
directive.scope = {
+ /**
+ * The function to invoke when the "close" button in the header of a
+ * client tile is clicked. The ManagedClient that is closed will be
+ * made available to the Angular expression defining the callback as
+ * "$client".
+ *
+ * @type function
+ */
+ onClose : '&',
+
/**
* The group of Guacamole clients that should be displayed in an
* evenly-tiled grid arrangement.
diff --git a/guacamole/src/main/frontend/src/app/client/styles/tiled-client-grid.css b/guacamole/src/main/frontend/src/app/client/styles/tiled-client-grid.css
index 11d3a0280..49e0be0f7 100644
--- a/guacamole/src/main/frontend/src/app/client/styles/tiled-client-grid.css
+++ b/guacamole/src/main/frontend/src/app/client/styles/tiled-client-grid.css
@@ -74,19 +74,47 @@
line-height: 1.5;
}
-.tiled-client-grid .client-tile .client-tile-name {
+.tiled-client-grid .client-tile .client-tile-header {
+
+ display: -webkit-box;
+
+ display: -webkit-flex;
+
+ display: -ms-flexbox;
+
+ display: flex;
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
+
margin: 0;
background: #444;
padding: 0 0.25em;
font-size: 0.8em;
color: white;
z-index: 30;
+
}
-.tiled-client-grid .client-tile.focused .client-tile-name {
+.tiled-client-grid .client-tile.focused .client-tile-header {
background-color: #3161a9;
}
+.tiled-client-grid .client-tile .client-tile-header > * {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0;
+ -ms-flex: 0;
+ flex: 0;
+}
+
+.tiled-client-grid .client-tile .client-tile-header .client-tile-name {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+}
+
.tiled-client-grid .client-tile .main {
-webkit-box-flex: 1;
-webkit-flex: 1;
@@ -94,12 +122,16 @@
flex: 1;
}
+.tiled-client-grid .client-tile-disconnect,
.tiled-client-grid .client-tile-shared-indicator {
- display: none;
max-height: 1em;
height: 100%;
}
+.tiled-client-grid .client-tile-shared-indicator {
+ display: none;
+}
+
.tiled-client-grid .shared .client-tile-shared-indicator {
display: inline;
}
diff --git a/guacamole/src/main/frontend/src/app/client/templates/client.html b/guacamole/src/main/frontend/src/app/client/templates/client.html
index 5c92f6652..040743953 100644
--- a/guacamole/src/main/frontend/src/app/client/templates/client.html
+++ b/guacamole/src/main/frontend/src/app/client/templates/client.html
@@ -10,6 +10,7 @@