mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-1293: Display user count and join/leave notifications in out-of-the-way status indicator.
This commit is contained in:
@@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* 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 that displays a status indicator showing the number of users
|
||||||
|
* joined to a connection. The specific usernames of those users are visible in
|
||||||
|
* a tooltip on mouseover, and small notifications are displayed as users
|
||||||
|
* join/leave the connection.
|
||||||
|
*/
|
||||||
|
angular.module('client').directive('guacClientUserCount', [function guacClientUserCount() {
|
||||||
|
|
||||||
|
const directive = {
|
||||||
|
restrict: 'E',
|
||||||
|
replace: true,
|
||||||
|
templateUrl: 'app/client/templates/guacClientUserCount.html'
|
||||||
|
};
|
||||||
|
|
||||||
|
directive.scope = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client whose current users should be displayed.
|
||||||
|
*
|
||||||
|
* @type ManagedClient
|
||||||
|
*/
|
||||||
|
client : '='
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
directive.controller = ['$scope', '$injector', '$element',
|
||||||
|
function guacClientUserCountController($scope, $injector, $element) {
|
||||||
|
|
||||||
|
// Required services
|
||||||
|
var $translate = $injector.get('$translate');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of messages displayed by this directive at any
|
||||||
|
* given time. Old messages will be discarded as necessary to ensure
|
||||||
|
* the number of messages displayed never exceeds this value.
|
||||||
|
*
|
||||||
|
* @constant
|
||||||
|
* @type number
|
||||||
|
*/
|
||||||
|
var MAX_MESSAGES = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list that should contain any notifications regarding users
|
||||||
|
* joining or leaving the connection.
|
||||||
|
*
|
||||||
|
* @type HTMLUListElement
|
||||||
|
*/
|
||||||
|
var messages = $element.find('.client-user-count-messages')[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of the usernames of all users of the current connection to the
|
||||||
|
* number of concurrent connections those users have to the current
|
||||||
|
* connection.
|
||||||
|
*
|
||||||
|
* @type Object.<string, number>
|
||||||
|
*/
|
||||||
|
$scope.userCounts = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a message noting that a change related to a particular user
|
||||||
|
* of this connection has occurred.
|
||||||
|
*
|
||||||
|
* @param {!string} str
|
||||||
|
* The key of the translation string containing the message to
|
||||||
|
* display. This translation key must accept "USERNAME" as the
|
||||||
|
* name of the translation parameter containing the username of
|
||||||
|
* the user in question.
|
||||||
|
*
|
||||||
|
* @param {!string} username
|
||||||
|
* The username of the user in question.
|
||||||
|
*/
|
||||||
|
var notify = function notify(str, username) {
|
||||||
|
$translate(str, { 'USERNAME' : username }).then(function translationReady(text) {
|
||||||
|
|
||||||
|
if (messages.childNodes.length === 3)
|
||||||
|
messages.removeChild(messages.lastChild);
|
||||||
|
|
||||||
|
var message = document.createElement('li');
|
||||||
|
message.className = 'client-user-count-message';
|
||||||
|
message.textContent = text;
|
||||||
|
messages.insertBefore(message, messages.firstChild);
|
||||||
|
|
||||||
|
// Automatically remove the notification after its "fadeout"
|
||||||
|
// animation ends. NOTE: This will not fire if the element is
|
||||||
|
// not visible at all.
|
||||||
|
message.addEventListener('animationend', function animationEnded() {
|
||||||
|
messages.removeChild(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a message noting that a particular user has joined the
|
||||||
|
* current connection.
|
||||||
|
*
|
||||||
|
* @param {!string} username
|
||||||
|
* The username of the user that joined.
|
||||||
|
*/
|
||||||
|
var notifyUserJoined = function notifyUserJoined(username) {
|
||||||
|
notify('CLIENT.TEXT_USER_JOINED', username);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a message noting that a particular user has left the
|
||||||
|
* current connection.
|
||||||
|
*
|
||||||
|
* @param {!string} username
|
||||||
|
* The username of the user that left.
|
||||||
|
*/
|
||||||
|
var notifyUserLeft = function notifyUserLeft(username) {
|
||||||
|
notify('CLIENT.TEXT_USER_LEFT', username);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ManagedClient attached to this directive at the time the
|
||||||
|
* notification update scope watch was last invoked. This is necessary
|
||||||
|
* as $scope.$watchGroup() does not allow for the callback to know
|
||||||
|
* whether the scope was previously uninitialized (it's "oldValues"
|
||||||
|
* parameter receives a copy of the new values if there are no old
|
||||||
|
* values).
|
||||||
|
*
|
||||||
|
* @type ManagedClient
|
||||||
|
*/
|
||||||
|
var oldClient = null;
|
||||||
|
|
||||||
|
// Update visible notifications as users join/leave
|
||||||
|
$scope.$watchGroup([ 'client', 'client.userCount' ], function usersChanged() {
|
||||||
|
|
||||||
|
// Resynchronize directive with state of any attached client when
|
||||||
|
// the client changes, to ensure notifications are only shown for
|
||||||
|
// future changes in users present
|
||||||
|
if (oldClient !== $scope.client) {
|
||||||
|
|
||||||
|
$scope.userCounts = {};
|
||||||
|
oldClient = $scope.client;
|
||||||
|
|
||||||
|
angular.forEach($scope.client.users, function initUsers(connections, username) {
|
||||||
|
var count = Object.keys(connections).length;
|
||||||
|
$scope.userCounts[username] = count;
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display join/leave notifications for users who are currently
|
||||||
|
// connected but whose connection counts have changed
|
||||||
|
angular.forEach($scope.client.users, function addNewUsers(connections, username) {
|
||||||
|
|
||||||
|
var count = Object.keys(connections).length;
|
||||||
|
var known = $scope.userCounts[username] || 0;
|
||||||
|
|
||||||
|
if (count > known)
|
||||||
|
notifyUserJoined(username);
|
||||||
|
else if (count < known)
|
||||||
|
notifyUserLeft(username);
|
||||||
|
|
||||||
|
$scope.userCounts[username] = count;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Display leave notifications for users who are no longer connected
|
||||||
|
angular.forEach($scope.userCounts, function removeOldUsers(count, username) {
|
||||||
|
if (!$scope.client.users[username]) {
|
||||||
|
notifyUserLeft(username);
|
||||||
|
delete $scope.userCounts[username];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}];
|
||||||
|
|
||||||
|
return directive;
|
||||||
|
|
||||||
|
}]);
|
@@ -180,6 +180,32 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-user-count .client-user-count-users,
|
||||||
|
.tiled-client-grid .client-user-count .client-user-count-messages {
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-user-count .client-user-count-users,
|
||||||
|
.tiled-client-grid .client-user-count .client-user-count-message {
|
||||||
|
border-radius: 0.25em;
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-user-count .client-user-count-message {
|
||||||
|
white-space: nowrap;
|
||||||
|
animation: 1s linear 3s fadeout;
|
||||||
|
}
|
||||||
|
|
||||||
.tiled-client-grid .client-tile-header .client-user-count {
|
.tiled-client-grid .client-tile-header .client-user-count {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -196,3 +222,51 @@
|
|||||||
.tiled-client-grid .joined .client-user-count {
|
.tiled-client-grid .joined .client-user-count {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-user-count .client-user-count-users {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-user-count:hover .client-user-count-users {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-user-count .client-user-count-user::after {
|
||||||
|
content: ', ';
|
||||||
|
margin-right: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-user-count .client-user-count-user:last-child::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-user-count .client-user-count-user {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-user-count .client-user-count-users {
|
||||||
|
width: 256px;
|
||||||
|
max-width: 75vw;
|
||||||
|
white-space: normal;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-user-count .client-user-count-users::before {
|
||||||
|
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
right: 0.5em;
|
||||||
|
top: -0.5em;
|
||||||
|
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
|
||||||
|
background: black;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: none;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
|
||||||
|
}
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
<div class="client-user-count" title="{{ instance }}">
|
||||||
|
<span class="client-user-count-value">{{ client.userCount }}</span>
|
||||||
|
<ul class="client-user-count-messages"></ul>
|
||||||
|
<ul class="client-user-count-users">
|
||||||
|
<li class="client-user-count-user" ng-repeat="user in userCounts | toArray | orderBy: key"
|
||||||
|
translate="CLIENT.INFO_USER_COUNT"
|
||||||
|
translate-values="{ USERNAME : user.key, COUNT : user.value }"></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
@@ -12,7 +12,7 @@
|
|||||||
<h3 class="client-tile-header" ng-if="hasMultipleClients(clientGroup)">
|
<h3 class="client-tile-header" ng-if="hasMultipleClients(clientGroup)">
|
||||||
<img class="client-tile-shared-indicator" src="images/share-white.svg">
|
<img class="client-tile-shared-indicator" src="images/share-white.svg">
|
||||||
<span class="client-tile-name">{{ client.title }}</span>
|
<span class="client-tile-name">{{ client.title }}</span>
|
||||||
<span class="client-user-count">{{ client.userCount }}</span>
|
<guac-client-user-count client="client"></guac-client-user-count>
|
||||||
<img ng-click="onClose({ '$client' : client })"
|
<img ng-click="onClose({ '$client' : client })"
|
||||||
class="client-tile-disconnect"
|
class="client-tile-disconnect"
|
||||||
ng-attr-alt="{{ 'CLIENT.ACTION_DISCONNECT' | translate }}"
|
ng-attr-alt="{{ 'CLIENT.ACTION_DISCONNECT' | translate }}"
|
||||||
@@ -24,9 +24,7 @@
|
|||||||
<!-- Client-specific status/error dialog -->
|
<!-- Client-specific status/error dialog -->
|
||||||
<guac-client-notification client="client"></guac-client-notification>
|
<guac-client-notification client="client"></guac-client-notification>
|
||||||
|
|
||||||
<div class="client-user-count" ng-if="!hasMultipleClients(clientGroup)">
|
<guac-client-user-count client="client" ng-if="!hasMultipleClients(clientGroup)"></guac-client-user-count>
|
||||||
{{ client.userCount }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -128,10 +128,7 @@
|
|||||||
|
|
||||||
"INFO_CONNECTION_SHARED" : "This connection is now shared.",
|
"INFO_CONNECTION_SHARED" : "This connection is now shared.",
|
||||||
"INFO_NO_FILE_TRANSFERS" : "No file transfers.",
|
"INFO_NO_FILE_TRANSFERS" : "No file transfers.",
|
||||||
|
"INFO_USER_COUNT" : "{USERNAME}{COUNT, plural, one{} other{ (#)}}",
|
||||||
"MESSAGE_DEFAULT" : "",
|
|
||||||
"MESSAGE_USER_JOINED" : "User {ARGS_1} has joined the connection.",
|
|
||||||
"MESSAGE_USER_LEFT" : "User {ARGS_1} has left the connection.",
|
|
||||||
|
|
||||||
"NAME_INPUT_METHOD_NONE" : "None",
|
"NAME_INPUT_METHOD_NONE" : "None",
|
||||||
"NAME_INPUT_METHOD_OSK" : "On-screen keyboard",
|
"NAME_INPUT_METHOD_OSK" : "On-screen keyboard",
|
||||||
@@ -158,6 +155,8 @@
|
|||||||
"TEXT_CLIENT_STATUS_DISCONNECTED" : "You have been disconnected.",
|
"TEXT_CLIENT_STATUS_DISCONNECTED" : "You have been disconnected.",
|
||||||
"TEXT_CLIENT_STATUS_UNSTABLE" : "The network connection to the Guacamole server appears unstable.",
|
"TEXT_CLIENT_STATUS_UNSTABLE" : "The network connection to the Guacamole server appears unstable.",
|
||||||
"TEXT_CLIENT_STATUS_WAITING" : "Connected to Guacamole. Waiting for response...",
|
"TEXT_CLIENT_STATUS_WAITING" : "Connected to Guacamole. Waiting for response...",
|
||||||
|
"TEXT_USER_JOINED" : "{USERNAME} has joined the connection.",
|
||||||
|
"TEXT_USER_LEFT" : "{USERNAME} has left the connection.",
|
||||||
"TEXT_RECONNECT_COUNTDOWN" : "Reconnecting in {REMAINING} {REMAINING, plural, one{second} other{seconds}}...",
|
"TEXT_RECONNECT_COUNTDOWN" : "Reconnecting in {REMAINING} {REMAINING, plural, one{second} other{seconds}}...",
|
||||||
"TEXT_FILE_TRANSFER_PROGRESS" : "{PROGRESS} {UNIT, select, b{B} kb{KB} mb{MB} gb{GB} other{}}",
|
"TEXT_FILE_TRANSFER_PROGRESS" : "{PROGRESS} {UNIT, select, b{B} kb{KB} mb{MB} gb{GB} other{}}",
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user