mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
Merge 1.5.0 changes back to master.
This commit is contained in:
@@ -712,7 +712,10 @@ Guacamole.Client = function(tunnel) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired when an arbitrary message is received from the tunnel that should
|
* Fired when an arbitrary message is received from the tunnel that should
|
||||||
* be processed by the client.
|
* be processed by the client. By default, additional message-specific
|
||||||
|
* events such as "onjoin" and "onleave" will fire for the received message
|
||||||
|
* after this event has been processed. An event handler for "onmsg" need
|
||||||
|
* not be supplied if "onjoin" and/or "onleave" will be used.
|
||||||
*
|
*
|
||||||
* @event
|
* @event
|
||||||
* @param {!number} msgcode
|
* @param {!number} msgcode
|
||||||
@@ -722,9 +725,49 @@ Guacamole.Client = function(tunnel) {
|
|||||||
* @param {string[]} args
|
* @param {string[]} args
|
||||||
* An array of arguments to be processed with the message sent to the
|
* An array of arguments to be processed with the message sent to the
|
||||||
* client.
|
* client.
|
||||||
|
*
|
||||||
|
* @return {boolean}
|
||||||
|
* true if message-specific events such as "onjoin" and
|
||||||
|
* "onleave" should be fired for this message, false otherwise. If
|
||||||
|
* no value is returned, message-specific events will be allowed to
|
||||||
|
* fire.
|
||||||
*/
|
*/
|
||||||
this.onmsg = null;
|
this.onmsg = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when a user joins a shared connection.
|
||||||
|
*
|
||||||
|
* @event
|
||||||
|
* @param {!string} userID
|
||||||
|
* A unique value representing this specific user's connection to the
|
||||||
|
* shared connection. This value is generated by the server and is
|
||||||
|
* guaranteed to be unique relative to other users of the connection.
|
||||||
|
*
|
||||||
|
* @param {!string} name
|
||||||
|
* A human-readable name representing the user that joined, such as
|
||||||
|
* their username. This value is provided by the web application during
|
||||||
|
* the connection handshake and is not necessarily unique relative to
|
||||||
|
* other users of the connection.
|
||||||
|
*/
|
||||||
|
this.onjoin = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when a user leaves a shared connection.
|
||||||
|
*
|
||||||
|
* @event
|
||||||
|
* @param {!string} userID
|
||||||
|
* A unique value representing this specific user's connection to the
|
||||||
|
* shared connection. This value is generated by the server and is
|
||||||
|
* guaranteed to be unique relative to other users of the connection.
|
||||||
|
*
|
||||||
|
* @param {!string} name
|
||||||
|
* A human-readable name representing the user that left, such as their
|
||||||
|
* username. This value is provided by the web application during the
|
||||||
|
* connection handshake and is not necessarily unique relative to other
|
||||||
|
* users of the connection.
|
||||||
|
*/
|
||||||
|
this.onleave = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired when a audio stream is created. The stream provided to this event
|
* Fired when a audio stream is created. The stream provided to this event
|
||||||
* handler will contain its own event handlers for received data.
|
* handler will contain its own event handlers for received data.
|
||||||
@@ -1445,7 +1488,39 @@ Guacamole.Client = function(tunnel) {
|
|||||||
|
|
||||||
"msg" : function(parameters) {
|
"msg" : function(parameters) {
|
||||||
|
|
||||||
if (guac_client.onmsg) guac_client.onmsg(parseInt(parameters[0]), parameters.slice(1));
|
var userID;
|
||||||
|
var username;
|
||||||
|
|
||||||
|
// Fire general message handling event first
|
||||||
|
var allowDefault = true;
|
||||||
|
var msgid = parseInt(parameters[0]);
|
||||||
|
if (guac_client.onmsg) {
|
||||||
|
allowDefault = guac_client.onmsg(msgid, parameters.slice(1));
|
||||||
|
if (allowDefault === undefined)
|
||||||
|
allowDefault = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire message-specific convenience events if not prevented by the
|
||||||
|
// "onmsg" handler
|
||||||
|
if (allowDefault) {
|
||||||
|
switch (msgid) {
|
||||||
|
|
||||||
|
case Guacamole.Client.Message.USER_JOINED:
|
||||||
|
userID = parameters[1];
|
||||||
|
username = parameters[2];
|
||||||
|
if (guac_client.onjoin)
|
||||||
|
guac_client.onjoin(userID, username);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Guacamole.Client.Message.USER_LEFT:
|
||||||
|
userID = parameters[1];
|
||||||
|
username = parameters[2];
|
||||||
|
if (guac_client.onleave)
|
||||||
|
guac_client.onleave(userID, username);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@@ -792,24 +792,6 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the attached client group has any associated client
|
|
||||||
* messages to display.
|
|
||||||
*
|
|
||||||
* @returns {Boolean}
|
|
||||||
* true if there are messages to display; otherwise false.
|
|
||||||
*/
|
|
||||||
$scope.hasMessages = function hasMessages() {
|
|
||||||
|
|
||||||
// No client group means no messages
|
|
||||||
if (!$scope.clientGroup)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Otherwise, find messages within the clients in the group.
|
|
||||||
return _.findIndex($scope.clientGroup.clients, ManagedClient.hasMessages) !== -1;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the attached client group has any associated file
|
* Determines whether the attached client group has any associated file
|
||||||
* transfers, regardless of those file transfers' state.
|
* transfers, regardless of those file transfers' state.
|
||||||
|
@@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Directive which displays a message for the client.
|
|
||||||
*/
|
|
||||||
angular.module('client').directive('guacClientMessage', [function guacClientMessage() {
|
|
||||||
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
replace: true,
|
|
||||||
scope: {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message to display to the client.
|
|
||||||
*
|
|
||||||
* @type {!ManagedClientMessage}
|
|
||||||
*/
|
|
||||||
message : '='
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
templateUrl: 'app/client/templates/guacClientMessage.html',
|
|
||||||
|
|
||||||
controller: ['$scope', '$injector', '$element',
|
|
||||||
function guacClientMessageController($scope, $injector, $element) {
|
|
||||||
|
|
||||||
// Required types
|
|
||||||
const ManagedClientMessage = $injector.get('ManagedClientMessage');
|
|
||||||
|
|
||||||
// Required services
|
|
||||||
var translationStringService = $injector.get('translationStringService');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the msgcode to retrieve the correct translation key for
|
|
||||||
* the client message.
|
|
||||||
*
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
$scope.getMessageKey = function getMessageKey() {
|
|
||||||
|
|
||||||
let msgString = "DEFAULT";
|
|
||||||
if (Object.values(Guacamole.Client.Message).includes($scope.message.msgcode))
|
|
||||||
msgString = Object.keys(Guacamole.Client.Message).find(key => Guacamole.Client.Message[key] === $scope.message.msgcode);
|
|
||||||
|
|
||||||
return "CLIENT.MESSAGE_" + translationStringService.canonicalize(msgString);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a set of key/value object pairs that represent the
|
|
||||||
* arguments provided as part of the message in the form
|
|
||||||
* "ARGS_0 = value". Guacamole's translation system relies on
|
|
||||||
* the arguments being available in this format in order to be able
|
|
||||||
* to handle substituting values for an arbitrary list of arguments.
|
|
||||||
*
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
$scope.getMessageArgs = function getMessageArgs() {
|
|
||||||
return $scope.message.args.reduce(
|
|
||||||
function(acc, value, index) {
|
|
||||||
acc[`ARGS_${index}`] = value;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
}]
|
|
||||||
|
|
||||||
};
|
|
||||||
}]);
|
|
@@ -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;
|
||||||
|
|
||||||
|
}]);
|
@@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Directive which displays all client messages.
|
|
||||||
*/
|
|
||||||
angular.module('client').directive('guacMessageDialog', [function guacMessageDialog() {
|
|
||||||
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
replace: true,
|
|
||||||
scope: {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The client group whose messages should be managed by this
|
|
||||||
* directive.
|
|
||||||
*
|
|
||||||
* @type ManagedClientGroup
|
|
||||||
*/
|
|
||||||
clientGroup : '='
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
templateUrl: 'app/client/templates/guacMessageDialog.html',
|
|
||||||
controller: ['$scope', '$injector', function guacMessageDialogController($scope, $injector) {
|
|
||||||
|
|
||||||
// Required types
|
|
||||||
const ManagedClient = $injector.get('ManagedClient');
|
|
||||||
const ManagedClientGroup = $injector.get('ManagedClientGroup');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all messages.
|
|
||||||
*/
|
|
||||||
$scope.clearAllMessages = function clearAllMessages() {
|
|
||||||
|
|
||||||
// Nothing to clear if no client group attached
|
|
||||||
if (!$scope.clientGroup)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Remove each client's messages
|
|
||||||
$scope.clientGroup.clients.forEach(client => {
|
|
||||||
client.messages = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @borrows ManagedClientGroup.hasMultipleClients
|
|
||||||
*/
|
|
||||||
$scope.hasMultipleClients = ManagedClientGroup.hasMultipleClients;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @borrows ManagedClient.hasMessages
|
|
||||||
*/
|
|
||||||
$scope.hasMessages = ManagedClient.hasMessages;
|
|
||||||
|
|
||||||
}]
|
|
||||||
|
|
||||||
};
|
|
||||||
}]);
|
|
@@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
p.client-message-text {
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
|
|
@@ -1,122 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#message-dialog {
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 20;
|
|
||||||
|
|
||||||
font-size: 0.8em;
|
|
||||||
|
|
||||||
width: 3in;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 3in;
|
|
||||||
|
|
||||||
background: white;
|
|
||||||
opacity: 0.75;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#message-dialog .message-dialog-box {
|
|
||||||
|
|
||||||
/* IE10 */
|
|
||||||
display: -ms-flexbox;
|
|
||||||
-ms-flex-align: stretch;
|
|
||||||
-ms-flex-direction: column;
|
|
||||||
|
|
||||||
/* Ancient Mozilla */
|
|
||||||
display: -moz-box;
|
|
||||||
-moz-box-align: stretch;
|
|
||||||
-moz-box-orient: vertical;
|
|
||||||
|
|
||||||
/* Ancient WebKit */
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-align: stretch;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
|
|
||||||
/* Old WebKit */
|
|
||||||
display: -webkit-flex;
|
|
||||||
-webkit-align-items: stretch;
|
|
||||||
-webkit-flex-direction: column;
|
|
||||||
|
|
||||||
/* W3C */
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
max-width: inherit;
|
|
||||||
max-height: inherit;
|
|
||||||
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
|
||||||
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#message-dialog .message-dialog-box .header {
|
|
||||||
-ms-flex: 0 0 auto;
|
|
||||||
-moz-box-flex: 0;
|
|
||||||
-webkit-box-flex: 0;
|
|
||||||
-webkit-flex: 0 0 auto;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#message-dialog .message-dialog-box .client-message-body {
|
|
||||||
|
|
||||||
-ms-flex: 1 1 auto;
|
|
||||||
-moz-box-flex: 1;
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-webkit-flex: 1 1 auto;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Shrink maximum height if viewport is too small for default 3in dialog.
|
|
||||||
*/
|
|
||||||
@media all and (max-height: 3in) {
|
|
||||||
|
|
||||||
#message-dialog {
|
|
||||||
max-height: 1.5in;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If viewport is too small for even the 1.5in dialog, fit all available space.
|
|
||||||
*/
|
|
||||||
@media all and (max-height: 1.5in) {
|
|
||||||
|
|
||||||
#message-dialog {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#message-dialog .message-dialog-box {
|
|
||||||
position: absolute;
|
|
||||||
left: 0.5em;
|
|
||||||
top: 0.5em;
|
|
||||||
right: 0.5em;
|
|
||||||
bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -110,10 +110,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tiled-client-grid .client-tile .client-tile-header .client-tile-name {
|
.tiled-client-grid .client-tile .client-tile-header .client-tile-name {
|
||||||
|
|
||||||
-webkit-box-flex: 1;
|
-webkit-box-flex: 1;
|
||||||
-webkit-flex: 1;
|
-webkit-flex: 1;
|
||||||
-ms-flex: 1;
|
-ms-flex: 1;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
|
padding: 0 0.5em;
|
||||||
|
margin-bottom: -0.125em;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiled-client-grid .client-tile .main {
|
.tiled-client-grid .client-tile .main {
|
||||||
@@ -136,3 +141,132 @@
|
|||||||
.tiled-client-grid .shared .client-tile-shared-indicator {
|
.tiled-client-grid .shared .client-tile-shared-indicator {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-user-count {
|
||||||
|
|
||||||
|
visibility: hidden;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
border-radius: 0.25em;
|
||||||
|
padding: 0.125em 0.75em;
|
||||||
|
margin: 0.5em;
|
||||||
|
|
||||||
|
background: #055;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.8em;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-user-count::before {
|
||||||
|
|
||||||
|
content: ' ';
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
margin-bottom: -0.2em;
|
||||||
|
padding-right: 0.25em;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
|
||||||
|
background: center / contain no-repeat url('images/user-icons/guac-user-white.svg');
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
|
background: black;
|
||||||
|
padding-left: 0.5em;
|
||||||
|
padding-right: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .client-tile-header .client-user-count::before {
|
||||||
|
padding-right: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiled-client-grid .joined .client-user-count {
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
@@ -45,11 +45,6 @@
|
|||||||
{{'CLIENT.TEXT_CLIENT_STATUS_UNSTABLE' | translate}}
|
{{'CLIENT.TEXT_CLIENT_STATUS_UNSTABLE' | translate}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Message dialog -->
|
|
||||||
<div id="message-dialog" ng-show="hasMessages()">
|
|
||||||
<guac-message-dialog client-group="clientGroup"></guac-message-dialog>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Menu -->
|
<!-- Menu -->
|
||||||
<div class="menu" ng-class="{open: menu.shown}" id="guac-menu">
|
<div class="menu" ng-class="{open: menu.shown}" id="guac-menu">
|
||||||
<div class="menu-content" ng-if="menu.shown" guac-touch-drag="menuDrag">
|
<div class="menu-content" ng-if="menu.shown" guac-touch-drag="menuDrag">
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
<div class="client-message" ng-click="clear()">
|
|
||||||
|
|
||||||
<!-- Message text -->
|
|
||||||
<p class="client-message-text"
|
|
||||||
translate="{{ getMessageKey() }}"
|
|
||||||
translate-values="{{ getMessageArgs() }}"></p>
|
|
||||||
|
|
||||||
</div>
|
|
@@ -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>
|
@@ -1,21 +0,0 @@
|
|||||||
<div class="message-dialog-box">
|
|
||||||
|
|
||||||
<!-- Message dialog header -->
|
|
||||||
<div class="header">
|
|
||||||
<h2>{{'CLIENT.SECTION_HEADER_CLIENT_MESSAGES' | translate}}</h2>
|
|
||||||
<button ng-click="clearAllMessages()">{{'CLIENT.ACTION_CLEAR_CLIENT_MESSAGES' | translate}}</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Received messages -->
|
|
||||||
<div class="client-messages-body">
|
|
||||||
<div class="client-messages-body-section" ng-repeat="client in clientGroup.clients" ng-show="hasMessages(client)">
|
|
||||||
<h3 ng-show="hasMultipleClients(clientGroup)">{{ client.name }}</h3>
|
|
||||||
<div class="messages">
|
|
||||||
<guac-client-message
|
|
||||||
message="message"
|
|
||||||
ng-repeat="message in client.messages">
|
|
||||||
</guac-client-message>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@@ -5,12 +5,14 @@
|
|||||||
<div class="client-tile" ng-if="client"
|
<div class="client-tile" ng-if="client"
|
||||||
ng-class="{
|
ng-class="{
|
||||||
'focused' : client.clientProperties.focused,
|
'focused' : client.clientProperties.focused,
|
||||||
'shared' : isShared(client)
|
'shared' : isShared(client),
|
||||||
|
'joined' : client.userCount
|
||||||
}"
|
}"
|
||||||
guac-click="getFocusAssignmentCallback(client)">
|
guac-click="getFocusAssignmentCallback(client)">
|
||||||
<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>
|
||||||
|
<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 }}"
|
||||||
@@ -21,6 +23,9 @@
|
|||||||
|
|
||||||
<!-- 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>
|
||||||
|
|
||||||
|
<guac-client-user-count client="client" ng-if="!hasMultipleClients(clientGroup)"></guac-client-user-count>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -17,6 +17,8 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* global Guacamole, _ */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the ManagedClient class used by the guacClientManager service.
|
* Provides the ManagedClient class used by the guacClientManager service.
|
||||||
*/
|
*/
|
||||||
@@ -28,7 +30,6 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
|||||||
const ClientIdentifier = $injector.get('ClientIdentifier');
|
const ClientIdentifier = $injector.get('ClientIdentifier');
|
||||||
const ClipboardData = $injector.get('ClipboardData');
|
const ClipboardData = $injector.get('ClipboardData');
|
||||||
const ManagedArgument = $injector.get('ManagedArgument');
|
const ManagedArgument = $injector.get('ManagedArgument');
|
||||||
const ManagedClientMessage = $injector.get('ManagedClientMessage');
|
|
||||||
const ManagedClientState = $injector.get('ManagedClientState');
|
const ManagedClientState = $injector.get('ManagedClientState');
|
||||||
const ManagedClientThumbnail = $injector.get('ManagedClientThumbnail');
|
const ManagedClientThumbnail = $injector.get('ManagedClientThumbnail');
|
||||||
const ManagedDisplay = $injector.get('ManagedDisplay');
|
const ManagedDisplay = $injector.get('ManagedDisplay');
|
||||||
@@ -176,12 +177,23 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
|||||||
this.filesystems = template.filesystems || [];
|
this.filesystems = template.filesystems || [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All messages that have been sent to the client that should be
|
* The current number of users sharing this connection, excluding the
|
||||||
* displayed.
|
* user that originally started the connection. Duplicate connections
|
||||||
*
|
* from the same user are included in this total.
|
||||||
* @type ManagedClientMessage[]
|
|
||||||
*/
|
*/
|
||||||
this.messages = template.messages || [];
|
this.userCount = template.userCount || 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All users currently sharing this connection, excluding the user that
|
||||||
|
* originally started the connection. If the connection is not shared,
|
||||||
|
* this object will be empty. This map consists of key/value pairs
|
||||||
|
* where each key is the user's username and each value is an object
|
||||||
|
* tracking the unique connections currently used by that user (a map
|
||||||
|
* of Guacamole protocol user IDs to boolean values).
|
||||||
|
*
|
||||||
|
* @type Object.<string, Object.<string, boolean>>
|
||||||
|
*/
|
||||||
|
this.users = template.users || {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All available share links generated for the this ManagedClient via
|
* All available share links generated for the this ManagedClient via
|
||||||
@@ -496,17 +508,34 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle messages received from guacd to display to the client.
|
// Update user count when a new user joins
|
||||||
client.onmsg = function clientMessage(msgcode, args) {
|
client.onjoin = function userJoined(id, username) {
|
||||||
|
$rootScope.$apply(function usersChanged() {
|
||||||
|
|
||||||
msg = new ManagedClientMessage();
|
var connections = managedClient.users[username] || {};
|
||||||
msg.msgcode = msgcode;
|
managedClient.users[username] = connections;
|
||||||
msg.args = args;
|
|
||||||
|
managedClient.userCount++;
|
||||||
|
connections[id] = true;
|
||||||
|
|
||||||
$rootScope.$apply(function updateMessages() {
|
|
||||||
managedClient.messages.push(msg);
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update user count when a user leaves
|
||||||
|
client.onleave = function userLeft(id, username) {
|
||||||
|
$rootScope.$apply(function usersChanged() {
|
||||||
|
|
||||||
|
var connections = managedClient.users[username] || {};
|
||||||
|
managedClient.users[username] = connections;
|
||||||
|
|
||||||
|
managedClient.userCount--;
|
||||||
|
delete connections[id];
|
||||||
|
|
||||||
|
// Delete user entry after no connections remain
|
||||||
|
if (_.isEmpty(connections))
|
||||||
|
delete managedClient.users[username];
|
||||||
|
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Automatically update the client thumbnail
|
// Automatically update the client thumbnail
|
||||||
@@ -915,19 +944,6 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the given client has any associated messages to display.
|
|
||||||
*
|
|
||||||
* @param {GuacamoleClient} client
|
|
||||||
* The client for which messages should be checked.
|
|
||||||
*
|
|
||||||
* @returns {Boolean}
|
|
||||||
* true if the given client has any messages, otherwise false.
|
|
||||||
*/
|
|
||||||
ManagedClient.hasMessages = function hasMessages(client) {
|
|
||||||
return !!(client && client.messages && client.messages.length);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the given client has any associated file transfers,
|
* Returns whether the given client has any associated file transfers,
|
||||||
* regardless of those file transfers' state.
|
* regardless of those file transfers' state.
|
||||||
|
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides the ManagedClientMessage class used for messages displayed in
|
|
||||||
* a ManagedClient.
|
|
||||||
*/
|
|
||||||
angular.module('client').factory('ManagedClientMessage', [function defineManagedClientMessage() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object which represents a message to be displayed to a Guacamole client.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {ManagedClientMessage|Object} [template={}]
|
|
||||||
* The object whose properties should be copied within the new
|
|
||||||
* ManagedClientMessage.
|
|
||||||
*/
|
|
||||||
var ManagedClientMessage = function ManagedClientMessage(template) {
|
|
||||||
|
|
||||||
// Use empty object by default
|
|
||||||
template = template || {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message code sent by the server that will be used to locate the
|
|
||||||
* message within the Guacamole translation framework.
|
|
||||||
*
|
|
||||||
* @type Number
|
|
||||||
*/
|
|
||||||
this.msgcode = template.msgcode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Any arguments that should be passed through the translation system
|
|
||||||
* and displayed as part of the message.
|
|
||||||
*
|
|
||||||
* @type String[]
|
|
||||||
*/
|
|
||||||
this.args = template.args;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
return ManagedClientMessage;
|
|
||||||
|
|
||||||
}]);
|
|
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M474.341 504.195c.005 31.957-.398 24.504-.398 58.628H208.914c0-52.862-.398-8.867-.398-58.628 0-47.522 67.134-73.133 131.412-73.133 64.277 0 134.405 18.656 134.413 73.133z" style="fill:#fff;fill-opacity:1;stroke:#000;stroke-width:.09023736;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0" transform="translate(-33.342 -44.364) scale(.19138)"/><path d="M48.002 28.63a16.506 16.506 0 1 1-33.012 0 16.506 16.506 0 1 1 33.012 0z" style="fill:#fff;fill-rule:evenodd;stroke:none" transform="translate(.504 -8)"/></svg>
|
After Width: | Height: | Size: 635 B |
@@ -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