merge from upstream

This commit is contained in:
Seth Alves 2017-01-20 10:14:11 -08:00
commit c492319be2
11 changed files with 345 additions and 102 deletions

View file

@ -782,41 +782,48 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
void DomainServerSettingsManager::processUsernameFromIDRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
// From the packet, pull the UUID we're identifying
QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
if (!nodeUUID.isNull()) {
// Before we do any processing on this packet, make sure it comes from a node that is allowed to kick (is an admin)
// OR from a node whose UUID matches the one in the packet
if (sendingNode->getCanKick() || nodeUUID == sendingNode->getUUID()) {
// First, make sure we actually have a node with this UUID
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
auto matchingNode = limitedNodeList->nodeWithUUID(nodeUUID);
// First, make sure we actually have a node with this UUID
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
auto matchingNode = limitedNodeList->nodeWithUUID(nodeUUID);
// If we do have a matching node...
if (matchingNode) {
// If we do have a matching node...
if (matchingNode) {
// Setup the packet
auto usernameFromIDReplyPacket = NLPacket::create(PacketType::UsernameFromIDReply);
QString verifiedUsername;
QUuid machineFingerprint;
// Write the UUID to the packet
usernameFromIDReplyPacket->write(nodeUUID.toRfc4122());
// Check if the sending node has permission to kick (is an admin)
// OR if the message is from a node whose UUID matches the one in the packet
if (sendingNode->getCanKick() || nodeUUID == sendingNode->getUUID()) {
// It's time to figure out the username
QString verifiedUsername = matchingNode->getPermissions().getVerifiedUserName();
// Setup the packet
auto usernameFromIDReplyPacket = NLPacket::create(PacketType::UsernameFromIDReply);
usernameFromIDReplyPacket->write(nodeUUID.toRfc4122());
verifiedUsername = matchingNode->getPermissions().getVerifiedUserName();
usernameFromIDReplyPacket->writeString(verifiedUsername);
// now put in the machine fingerprint
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
QUuid machineFingerprint = nodeData ? nodeData->getMachineFingerprint() : QUuid();
machineFingerprint = nodeData ? nodeData->getMachineFingerprint() : QUuid();
usernameFromIDReplyPacket->write(machineFingerprint.toRfc4122());
qDebug() << "Sending username" << verifiedUsername << "and machine fingerprint" << machineFingerprint << "associated with node" << nodeUUID;
// Ship it!
limitedNodeList->sendPacket(std::move(usernameFromIDReplyPacket), *sendingNode);
} else {
qWarning() << "Node username request received for unknown node. Refusing to process.";
usernameFromIDReplyPacket->writeString(verifiedUsername);
usernameFromIDReplyPacket->write(machineFingerprint.toRfc4122());
}
} else {
qWarning() << "Refusing to process a username request packet from node" << uuidStringWithoutCurlyBraces(sendingNode->getUUID())
<< ". Either node doesn't have kick permissions or is requesting a username not from their UUID.";
}
// Write whether or not the user is an admin
bool isAdmin = matchingNode->getCanKick();
usernameFromIDReplyPacket->writePrimitive(isAdmin);
qDebug() << "Sending username" << verifiedUsername << "and machine fingerprint" << machineFingerprint << "associated with node" << nodeUUID << ". Node admin status: " << isAdmin;
// Ship it!
limitedNodeList->sendPacket(std::move(usernameFromIDReplyPacket), *sendingNode);
} else {
qWarning() << "Node username request received for unknown node. Refusing to process.";
}
} else {
qWarning() << "Node username request received for invalid node ID. Refusing to process.";
}

View file

@ -15,7 +15,13 @@ import "../styles-uit"
Item {
property alias text: popupText.text
property alias headerGlyph: headerGlyph.text
property alias headerText: headerText.text
property real popupRadius: hifi.dimensions.borderRadius
property real headerTextPixelSize: 22
property real popupTextPixelSize: 16
FontLoader { id: ralewayRegular; source: "../../fonts/Raleway-Regular.ttf"; }
FontLoader { id: ralewaySemiBold; source: "../../fonts/Raleway-SemiBold.ttf"; }
visible: false
id: letterbox
anchors.fill: parent
@ -27,19 +33,79 @@ Item {
}
Rectangle {
width: Math.max(parent.width * 0.75, 400)
height: popupText.contentHeight*1.5
height: contentContainer.height + 50
anchors.centerIn: parent
radius: popupRadius
color: "white"
FiraSansSemiBold {
id: popupText
size: hifi.fontSizes.textFieldInput
color: hifi.colors.darkGray
horizontalAlignment: Text.AlignHCenter
anchors.fill: parent
anchors.leftMargin: 15
anchors.rightMargin: 15
wrapMode: Text.WordWrap
Item {
id: contentContainer
width: parent.width - 60
height: childrenRect.height
anchors.centerIn: parent
Item {
id: popupHeaderContainer
visible: headerText.text !== "" || headerGlyph.text !== ""
height: 30
// Anchors
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
// Header Glyph
HiFiGlyphs {
id: headerGlyph
visible: headerGlyph.text !== ""
// Size
height: parent.height
// Anchors
anchors.left: parent.left
anchors.leftMargin: -15
// Text Size
size: headerTextPixelSize*2.5
// Style
horizontalAlignment: Text.AlignHLeft
verticalAlignment: Text.AlignVCenter
color: hifi.colors.darkGray
}
// Header Text
Text {
id: headerText
visible: headerText.text !== ""
// Size
height: parent.height
// Anchors
anchors.left: headerGlyph.right
anchors.leftMargin: -5
// Text Size
font.pixelSize: headerTextPixelSize
// Style
font.family: ralewaySemiBold.name
color: hifi.colors.darkGray
horizontalAlignment: Text.AlignHLeft
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
textFormat: Text.StyledText
}
}
// Popup Text
Text {
id: popupText
// Size
width: parent.width
// Anchors
anchors.top: popupHeaderContainer.visible ? popupHeaderContainer.bottom : parent.top
anchors.topMargin: popupHeaderContainer.visible ? 15 : 0
anchors.left: parent.left
anchors.right: parent.right
// Text alignment
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHLeft
// Style
font.pixelSize: popupTextPixelSize
font.family: ralewayRegular.name
color: hifi.colors.darkGray
wrapMode: Text.WordWrap
textFormat: Text.StyledText
}
}
}
MouseArea {

View file

@ -33,6 +33,7 @@ Item {
property real audioLevel: 0.0
property bool isMyCard: false
property bool selected: false
property bool isAdmin: false
/* User image commented out for now - will probably be re-introduced later.
Column {
@ -139,32 +140,94 @@ Item {
}
}
// Spacer for DisplayName for my card
Rectangle {
Item {
id: myDisplayNameSpacer
width: myDisplayName.width
width: 1
height: 4
// Anchors
anchors.top: myDisplayName.bottom
height: 5
visible: isMyCard
opacity: 0
}
// DisplayName Text for others' cards
FiraSansSemiBold {
id: displayNameText
// Properties
text: thisNameCard.displayName
elide: Text.ElideRight
// DisplayName container for others' cards
Item {
id: displayNameContainer
visible: !isMyCard
// Size
width: parent.width
height: displayNameTextPixelSize + 4
// Anchors
anchors.top: parent.top
// Text Size
size: displayNameTextPixelSize
// Text Positioning
verticalAlignment: Text.AlignVCenter
// Style
color: hifi.colors.darkGray
anchors.left: parent.left
// DisplayName Text for others' cards
FiraSansSemiBold {
id: displayNameText
// Properties
text: thisNameCard.displayName
elide: Text.ElideRight
// Size
width: isAdmin ? Math.min(displayNameTextMetrics.tightBoundingRect.width + 8, parent.width - adminLabelText.width - adminLabelQuestionMark.width + 8) : parent.width
// Anchors
anchors.top: parent.top
anchors.left: parent.left
// Text Size
size: displayNameTextPixelSize
// Text Positioning
verticalAlignment: Text.AlignVCenter
// Style
color: hifi.colors.darkGray
}
TextMetrics {
id: displayNameTextMetrics
font: displayNameText.font
text: displayNameText.text
}
// "ADMIN" label for other users' cards
RalewaySemiBold {
id: adminLabelText
visible: isAdmin
text: "ADMIN"
// Text size
size: displayNameText.size - 4
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: displayNameText.right
// Style
font.capitalization: Font.AllUppercase
color: hifi.colors.redHighlight
// Alignment
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
}
// This Rectangle refers to the [?] popup button next to "ADMIN"
Item {
id: adminLabelQuestionMark
visible: isAdmin
// Size
width: 20
height: displayNameText.height
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: adminLabelText.right
RalewayRegular {
id: adminLabelQuestionMarkText
text: "[?]"
size: adminLabelText.size
font.capitalization: Font.AllUppercase
color: hifi.colors.redHighlight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: letterbox(hifi.glyphs.question,
"Domain Admin",
"This user is an admin on this domain. Admins can <b>Silence</b> and <b>Ban</b> other users at their discretion - so be extra nice!")
onEntered: adminLabelQuestionMarkText.color = "#94132e"
onExited: adminLabelQuestionMarkText.color = hifi.colors.redHighlight
}
}
}
// UserName Text
@ -177,7 +240,7 @@ Item {
// Size
width: parent.width
// Anchors
anchors.top: isMyCard ? myDisplayNameSpacer.bottom : displayNameText.bottom
anchors.top: isMyCard ? myDisplayNameSpacer.bottom : displayNameContainer.bottom
// Text Size
size: thisNameCard.usernameTextHeight
// Text Positioning
@ -188,7 +251,7 @@ Item {
// Spacer
Item {
id: spacer
id: userNameSpacer
height: 4
width: parent.width
// Anchors
@ -199,10 +262,10 @@ Item {
Rectangle {
id: nameCardVUMeter
// Size
width: isMyCard ? myDisplayName.width - 20 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width
width: isMyCard ? myDisplayName.width - 70 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width
height: 8
// Anchors
anchors.top: spacer.bottom
anchors.top: userNameSpacer.bottom
// Style
radius: 4
color: "#c5c5c5"
@ -283,7 +346,12 @@ Item {
maximumValue: 20.0
stepSize: 5
updateValueWhileDragging: true
onValueChanged: updateGainFromQML(uuid, value)
onValueChanged: updateGainFromQML(uuid, value, false)
onPressedChanged: {
if (!pressed) {
updateGainFromQML(uuid, value, true)
}
}
MouseArea {
anchors.fill: parent
onWheel: {
@ -297,7 +365,8 @@ Item {
mouse.accepted = false
}
onReleased: {
// Pass through to Slider
// the above mouse.accepted seems to make this
// never get called, nonetheless...
mouse.accepted = false
}
}
@ -319,12 +388,13 @@ Item {
}
}
function updateGainFromQML(avatarUuid, sliderValue) {
if (pal.gainSliderValueDB[avatarUuid] !== sliderValue) {
function updateGainFromQML(avatarUuid, sliderValue, isReleased) {
if (isReleased || pal.gainSliderValueDB[avatarUuid] !== sliderValue) {
pal.gainSliderValueDB[avatarUuid] = sliderValue;
var data = {
sessionId: avatarUuid,
gain: sliderValue
gain: sliderValue,
isReleased: isReleased
};
pal.sendToScript({method: 'updateGain', params: data});
}

View file

@ -28,7 +28,7 @@ Rectangle {
property int rowHeight: 70
property int actionButtonWidth: 75
property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 4 : 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth
property var myData: ({displayName: "", userName: "", audioLevel: 0.0}) // valid dummy until set
property var myData: ({displayName: "", userName: "", audioLevel: 0.0, admin: true}) // valid dummy until set
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities.
property bool iAmAdmin: false
@ -41,7 +41,9 @@ Rectangle {
id: letterboxMessage
z: 999 // Force the popup on top of everything else
}
function letterbox(message) {
function letterbox(headerGlyph, headerText, message) {
letterboxMessage.headerGlyph = headerGlyph
letterboxMessage.headerText = headerText
letterboxMessage.text = message
letterboxMessage.visible = true
letterboxMessage.popupRadius = 0
@ -221,6 +223,7 @@ Rectangle {
visible: !isCheckBox && !isButton
uuid: model && model.sessionId
selected: styleData.selected
isAdmin: model && model.admin
// Size
width: nameCardWidth
height: parent.height
@ -247,6 +250,7 @@ Rectangle {
userModel.setProperty(model.userIndex, styleData.role, newValue)
userModelData[model.userIndex][styleData.role] = newValue // Defensive programming
Users[styleData.role](model.sessionId, newValue)
UserActivityLogger["palAction"](newValue ? styleData.role : "un-" + styleData.role, model.sessionId)
if (styleData.role === "ignore") {
userModel.setProperty(model.userIndex, "personalMute", newValue)
userModelData[model.userIndex]["personalMute"] = newValue // Defensive programming
@ -273,6 +277,7 @@ Rectangle {
height: 24
onClicked: {
Users[styleData.role](model.sessionId)
UserActivityLogger["palAction"](styleData.role, model.sessionId)
if (styleData.role === "kick") {
// Just for now, while we cannot undo "Ban":
userModel.remove(model.userIndex)
@ -369,9 +374,11 @@ Rectangle {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: letterbox("Bold names in the list are Avatar Display Names.\n" +
"If a Display Name isn't set, a unique Session Display Name is assigned." +
"\n\nAdministrators of this domain can also see the Username or Machine ID associated with each avatar present.")
onClicked: letterbox(hifi.glyphs.question,
"Display Names",
"Bold names in the list are <b>avatar display names</b>.<br>" +
"If a display name isn't set, a unique <b>session display name</b> is assigned." +
"<br><br>Administrators of this domain can also see the <b>username</b> or <b>machine ID</b> associated with each avatar present.")
onEntered: helpText.color = hifi.colors.baseGrayHighlight
onExited: helpText.color = hifi.colors.darkGray
}
@ -400,8 +407,10 @@ Rectangle {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: letterbox('Silencing a user mutes their microphone. Silenced users can unmute themselves by clicking the "UNMUTE" button on their HUD.\n\n' +
"Banning a user will remove them from this domain and prevent them from returning. You can un-ban users from your domain's settings page.)")
onClicked: letterbox(hifi.glyphs.question,
"Admin Actions",
"<b>Silence</b> mutes a user's microphone. Silenced users can unmute themselves by clicking &quot;UNMUTE&quot; on their toolbar.<br><br>" +
"<b>Ban</b> removes a user from this domain and prevents them from returning. Admins can un-ban users from the Sandbox Domain Settings page.")
onEntered: adminHelpText.color = "#94132e"
onExited: adminHelpText.color = hifi.colors.redHighlight
}
@ -446,9 +455,9 @@ Rectangle {
var selected = message.params[1];
var userIndex = findSessionIndex(sessionIds[0]);
if (sessionIds.length > 1) {
letterbox('Only one user can be selected at a time.');
letterbox("", "", 'Only one user can be selected at a time.');
} else if (userIndex < 0) {
letterbox('The last editor is not among this list of users.');
letterbox("", "", 'The last editor is not among this list of users.');
} else {
if (selected) {
table.selection.clear(); // for now, no multi-select
@ -465,6 +474,7 @@ Rectangle {
var userId = message.params[0];
// The text that goes in the userName field is the second parameter in the message.
var userName = message.params[1];
var admin = message.params[2];
// If the userId is empty, we're updating "myData".
if (!userId) {
myData.userName = userName;
@ -476,6 +486,9 @@ Rectangle {
// Set the userName appropriately
userModel.setProperty(userIndex, "userName", userName);
userModelData[userIndex].userName = userName; // Defensive programming
// Set the admin status appropriately
userModel.setProperty(userIndex, "admin", admin);
userModelData[userIndex].admin = admin; // Defensive programming
}
}
break;

View file

@ -1941,6 +1941,8 @@ void Application::initializeUi() {
rootContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
rootContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
rootContext->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
rootContext->setContextProperty("Camera", &_myCamera);
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)

View file

@ -1040,25 +1040,20 @@ void NodeList::muteNodeBySessionID(const QUuid& nodeID) {
}
void NodeList::requestUsernameFromSessionID(const QUuid& nodeID) {
// send a request to domain-server to get the username associated with the given session ID
if (getThisNodeCanKick() || nodeID.isNull()) {
// setup the packet
auto usernameFromIDRequestPacket = NLPacket::create(PacketType::UsernameFromIDRequest, NUM_BYTES_RFC4122_UUID, true);
// send a request to domain-server to get the username/machine fingerprint/admin status associated with the given session ID
// If the requesting user isn't an admin, the username and machine fingerprint will return "".
auto usernameFromIDRequestPacket = NLPacket::create(PacketType::UsernameFromIDRequest, NUM_BYTES_RFC4122_UUID, true);
// write the node ID to the packet
if (nodeID.isNull()) {
usernameFromIDRequestPacket->write(getSessionUUID().toRfc4122());
} else {
usernameFromIDRequestPacket->write(nodeID.toRfc4122());
}
qCDebug(networking) << "Sending packet to get username of node" << uuidStringWithoutCurlyBraces(nodeID);
sendPacket(std::move(usernameFromIDRequestPacket), _domainHandler.getSockAddr());
// write the node ID to the packet
if (nodeID.isNull()) {
usernameFromIDRequestPacket->write(getSessionUUID().toRfc4122());
} else {
qWarning() << "You do not have permissions to kick in this domain."
<< "Request to get the username of node" << uuidStringWithoutCurlyBraces(nodeID) << "will not be sent";
usernameFromIDRequestPacket->write(nodeID.toRfc4122());
}
qCDebug(networking) << "Sending packet to get username/fingerprint/admin status of node" << uuidStringWithoutCurlyBraces(nodeID);
sendPacket(std::move(usernameFromIDRequestPacket), _domainHandler.getSockAddr());
}
void NodeList::processUsernameFromIDReply(QSharedPointer<ReceivedMessage> message) {
@ -1068,10 +1063,13 @@ void NodeList::processUsernameFromIDReply(QSharedPointer<ReceivedMessage> messag
QString username = message->readString();
// read the machine fingerprint from the packet
QString machineFingerprintString = (QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID))).toString();
bool isAdmin;
message->readPrimitive(&isAdmin);
qCDebug(networking) << "Got username" << username << "and machine fingerprint" << machineFingerprintString << "for node" << nodeUUIDString;
qCDebug(networking) << "Got username" << username << "and machine fingerprint"
<< machineFingerprintString << "for node" << nodeUUIDString << ". isAdmin:" << isAdmin;
emit usernameFromIDReply(nodeUUIDString, username, machineFingerprintString);
emit usernameFromIDReply(nodeUUIDString, username, machineFingerprintString, isAdmin);
}
void NodeList::setRequestsDomainListData(bool isRequesting) {

View file

@ -120,7 +120,7 @@ signals:
void receivedDomainServerList();
void ignoredNode(const QUuid& nodeID, bool enabled);
void ignoreRadiusEnabledChanged(bool isIgnored);
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint);
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint, bool isAdmin);
private slots:
void stopKeepalivePingTimer();

View file

@ -38,6 +38,21 @@ void UserActivityLoggerScriptingInterface::tutorialProgress( QString stepName, i
}
void UserActivityLoggerScriptingInterface::palAction(QString action, QString target) {
QJsonObject payload;
payload["action"] = action;
if (target.length() > 0) {
payload["target"] = target;
}
logAction("pal_activity", payload);
}
void UserActivityLoggerScriptingInterface::palOpened(float secondsOpened) {
logAction("pal_opened", {
{ "seconds_opened", secondsOpened }
});
}
void UserActivityLoggerScriptingInterface::logAction(QString action, QJsonObject details) {
QMetaObject::invokeMethod(&UserActivityLogger::getInstance(), "logAction",
Q_ARG(QString, action),

View file

@ -25,7 +25,8 @@ public:
Q_INVOKABLE void toggledAway(bool isAway);
Q_INVOKABLE void tutorialProgress(QString stepName, int stepNumber, float secondsToComplete,
float tutorialElapsedTime, QString tutorialRunID = "", int tutorialVersion = 0, QString controllerType = "");
Q_INVOKABLE void palAction(QString action, QString target);
Q_INVOKABLE void palOpened(float secondsOpen);
private:
void logAction(QString action, QJsonObject details = {});
};

View file

@ -135,9 +135,10 @@ signals:
/**jsdoc
* Notifies scripts of the username and machine fingerprint associated with a UUID.
* Username and machineFingerprint will be their default constructor output if the requesting user isn't an admin.
* @function Users.usernameFromIDReply
*/
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint);
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint, bool isAdmin);
/**jsdoc
* Notifies scripts that a user has disconnected from the domain

View file

@ -103,6 +103,8 @@ ExtendedOverlay.prototype.select = function (selected) {
return;
}
UserActivityLogger.palAction(selected ? "avatar_selected" : "avatar_deselected", this.key);
this.editOverlay({color: color(selected, this.hovering, this.audioLevel)});
if (this.model) {
this.model.editOverlay({textures: textures(selected)});
@ -232,14 +234,24 @@ pal.fromQml.connect(function (message) { // messages are {method, params}, like
case 'refresh':
removeOverlays();
populateUserList();
UserActivityLogger.palAction("refresh", "");
break;
case 'updateGain':
data = message.params;
Users.setAvatarGain(data['sessionId'], data['gain']);
if (data['isReleased']) {
// isReleased=true happens once at the end of a cycle of dragging
// the slider about, but with same gain as last isReleased=false so
// we don't set the gain in that case, and only here do we want to
// send an analytic event.
UserActivityLogger.palAction("avatar_gain_changed", data['sessionId']);
} else {
Users.setAvatarGain(data['sessionId'], data['gain']);
}
break;
case 'displayNameUpdate':
if (MyAvatar.displayName != message.params) {
MyAvatar.displayName = message.params;
UserActivityLogger.palAction("display_name_change", "");
}
break;
default:
@ -267,14 +279,12 @@ function populateUserList() {
displayName: avatar.sessionDisplayName,
userName: '',
sessionId: id || '',
audioLevel: 0.0
audioLevel: 0.0,
admin: false
};
// If the current user is an admin OR
// they're requesting their own username ("id" is blank)...
if (Users.canKick || !id) {
// Request the username from the given UUID
Users.requestUsernameFromID(id);
}
// Request the username, fingerprint, and admin status from the given UUID
// Username and fingerprint returns default constructor output if the requesting user isn't an admin
Users.requestUsernameFromID(id);
// Request personal mute status and ignore status
// from NodeList (as long as we're not requesting it for our own ID)
if (id) {
@ -289,16 +299,19 @@ function populateUserList() {
}
// The function that handles the reply from the server
function usernameFromIDReply(id, username, machineFingerprint) {
function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
var data;
// If the ID we've received is our ID...
if (MyAvatar.sessionUUID === id) {
// Set the data to contain specific strings.
data = ['', username];
} else {
data = ['', username, isAdmin];
} else if (Users.canKick) {
// Set the data to contain the ID and the username (if we have one)
// or fingerprint (if we don't have a username) string.
data = [id, username || machineFingerprint];
data = [id, username || machineFingerprint, isAdmin];
} else {
// Set the data to contain specific strings.
data = [id, '', isAdmin];
}
print('Username Data:', JSON.stringify(data));
// Ship the data off to QML
@ -576,6 +589,63 @@ function createAudioInterval() {
}, AUDIO_LEVEL_UPDATE_INTERVAL_MS);
}
//
// Manage the connection between the button and the window.
//
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
var buttonName = "pal";
var button = toolBar.addButton({
objectName: buttonName,
imageURL: Script.resolvePath("assets/images/tools/people.svg"),
visible: true,
hoverState: 2,
defaultState: 1,
buttonState: 1,
alpha: 0.9
});
var isWired = false;
var palOpenedAt;
function off() {
if (isWired) { // It is not ok to disconnect these twice, hence guard.
Script.update.disconnect(updateOverlays);
Controller.mousePressEvent.disconnect(handleMouseEvent);
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
isWired = false;
}
triggerMapping.disable(); // It's ok if we disable twice.
triggerPressMapping.disable(); // see above
removeOverlays();
Users.requestsDomainListData = false;
if (palOpenedAt) {
var duration = new Date().getTime() - palOpenedAt;
UserActivityLogger.palOpened(duration / 1000.0);
palOpenedAt = 0; // just a falsy number is good enough.
}
if (audioInterval) {
Script.clearInterval(audioInterval);
}
}
function onClicked() {
if (!pal.visible) {
Users.requestsDomainListData = true;
populateUserList();
pal.raise();
isWired = true;
Script.update.connect(updateOverlays);
Controller.mousePressEvent.connect(handleMouseEvent);
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
triggerMapping.enable();
triggerPressMapping.enable();
createAudioInterval();
palOpenedAt = new Date().getTime();
} else {
off();
}
pal.setVisible(!pal.visible);
}
function avatarDisconnected(nodeID) {
// remove from the pal list
pal.sendToQml({method: 'avatarDisconnected', params: [nodeID]});