mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-08 03:42:27 +02:00
Implemented chat interface script and tablet web page for worklist #21280
This commit is contained in:
parent
7cb2bff4c5
commit
a9c1e2781e
2 changed files with 1501 additions and 0 deletions
990
unpublishedScripts/marketplace/chat/Chat.js
Normal file
990
unpublishedScripts/marketplace/chat/Chat.js
Normal file
|
@ -0,0 +1,990 @@
|
|||
"use strict";
|
||||
|
||||
// Chat.js
|
||||
// By Don Hopkins (dhopkins@donhopkins.com)
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
|
||||
var webPageURL = "ChatPage.html"; // URL of tablet web page.
|
||||
var randomizeWebPageURL = true; // Set to true for debugging.
|
||||
var lastWebPageURL = ""; // Last random URL of tablet web page.
|
||||
var onChatPage = false; // True when chat web page is opened.
|
||||
var webHandlerConnected = false; // True when the web handler has been connected.
|
||||
var channelName = "Chat"; // Unique name for channel that we listen to.
|
||||
var tabletButtonName = "CHAT"; // Tablet button label.
|
||||
var tabletButtonIcon = "icons/tablet-icons/menu-i.svg"; // Icon for chat button.
|
||||
var tabletButtonActiveIcon = "icons/tablet-icons/menu-a.svg"; // Active icon for chat button.
|
||||
var tabletButton = null; // The button we create in the tablet.
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); // The awesome tablet.
|
||||
var chatLog = []; // Array of chat messages in the form of [avatarID, displayName, message, data].
|
||||
var avatarIdentifiers = {}; // Map of avatar ids to dict of identifierParams.
|
||||
var speechBubbleShowing = false; // Is the speech bubble visible?
|
||||
var speechBubbleMessage = null; // The message shown in the speech bubble.
|
||||
var speechBubbleData = null; // The data of the speech bubble message.
|
||||
var speechBubbleTextID = null; // The id of the speech bubble local text entity.
|
||||
var speechBubbleTimer = null; // The timer to pop down the speech bubble.
|
||||
var speechBubbleParams = null; // The params used to create or edit the speech bubble.
|
||||
|
||||
// Persistent variables saved in the Settings.
|
||||
var chatName = ''; // The user's name shown in chat.
|
||||
var chatLogMaxSize = 100; // The maximum number of chat messages we remember.
|
||||
var sendTyping = true; // Send typing begin and end notification.
|
||||
var identifyAvatarDuration = 10; // How long to leave the avatar identity line up, in seconds.
|
||||
var identifyAvatarLineColor = { red: 0, green: 255, blue: 0 }; // The color of the avatar identity line.
|
||||
var identifyAvatarMyJointName = 'Head'; // My bone from which to draw the avatar identity line.
|
||||
var identifyAvatarYourJointName = 'Head'; // Your bone to which to draw the avatar identity line.
|
||||
var speechBubbleDuration = 10; // How long to leave the speech bubble up, in seconds.
|
||||
var speechBubbleTextColor = {red: 255, green: 255, blue: 255}; // The text color of the speech bubble.
|
||||
var speechBubbleBackgroundColor = {red: 0, green: 0, blue: 0}; // The background color of the speech bubble.
|
||||
var speechBubbleOffset = {x: 0, y: 0.3, z: 0.0}; // The offset from the joint to whic the speech bubble is attached.
|
||||
var speechBubbleJointName = 'Head'; // The name of the joint to which the speech bubble is attached.
|
||||
var speechBubbleLineHeight = 0.05; // The height of a line of text in the speech bubble.
|
||||
|
||||
// Load the persistent variables from the Settings, with defaults.
|
||||
function loadSettings() {
|
||||
chatName = Settings.getValue('Chat_chatName', MyAvatar.displayName);
|
||||
print("loadSettings: chatName", chatName);
|
||||
if (!chatName) {
|
||||
chatName = randomAvatarName();
|
||||
}
|
||||
print("loadSettings: now chatName", chatName);
|
||||
chatLogMaxSize = Settings.getValue('Chat_chatLogMaxSize', 100);
|
||||
sendTyping = Settings.getValue('Chat_sendTyping', true);
|
||||
identifyAvatarDuration = Settings.getValue('Chat_identifyAvatarDuration', 10);
|
||||
identifyAvatarLineColor = Settings.getValue('Chat_identifyAvatarLineColor', { red: 0, green: 255, blue: 0 });
|
||||
identifyAvatarMyJointName = Settings.getValue('Chat_identifyAvatarMyJointName', 'Head');
|
||||
identifyAvatarYourJointName = Settings.getValue('Chat_identifyAvatarYourJointName', 'Head');
|
||||
speechBubbleDuration = Settings.getValue('Chat_speechBubbleDuration', 10);
|
||||
speechBubbleTextColor = Settings.getValue('Chat_speechBubbleTextColor', {red: 255, green: 255, blue: 255});
|
||||
speechBubbleBackgroundColor = Settings.getValue('Chat_speechBubbleBackgroundColor', {red: 0, green: 0, blue: 0});
|
||||
speechBubbleOffset = Settings.getValue('Chat_speechBubbleOffset', {x: 0.0, y: 0.3, z:0.0});
|
||||
speechBubbleJointName = Settings.getValue('Chat_speechBubbleJointName', 'Head');
|
||||
speechBubbleLineHeight = Settings.getValue('Chat_speechBubbleLineHeight', 0.05);
|
||||
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
// Save the persistent variables to the Settings.
|
||||
function saveSettings() {
|
||||
Settings.setValue('Chat_chatName', chatName);
|
||||
print("saveSettings: chatName", chatName, "or", Settings.getValue('Chat_chatName', 'xxx'));
|
||||
Settings.setValue('Chat_chatLogMaxSize', chatLogMaxSize);
|
||||
Settings.setValue('Chat_sendTyping', sendTyping);
|
||||
Settings.setValue('Chat_identifyAvatarDuration', identifyAvatarDuration);
|
||||
Settings.setValue('Chat_identifyAvatarLineColor', identifyAvatarLineColor);
|
||||
Settings.setValue('Chat_identifyAvatarMyJointName', identifyAvatarMyJointName);
|
||||
Settings.setValue('Chat_identifyAvatarYourJointName', identifyAvatarYourJointName);
|
||||
Settings.setValue('Chat_speechBubbleDuration', speechBubbleDuration);
|
||||
Settings.setValue('Chat_speechBubbleTextColor', speechBubbleTextColor);
|
||||
Settings.setValue('Chat_speechBubbleBackgroundColor', speechBubbleBackgroundColor);
|
||||
Settings.setValue('Chat_speechBubbleOffset', speechBubbleOffset);
|
||||
Settings.setValue('Chat_speechBubbleJointName', speechBubbleJointName);
|
||||
Settings.setValue('Chat_speechBubbleLineHeight', speechBubbleLineHeight);
|
||||
}
|
||||
|
||||
// Reset the Settings and persistent variables to the defaults.
|
||||
function resetSettings() {
|
||||
Settings.setValue('Chat_chatName', null);
|
||||
Settings.setValue('Chat_chatLogMaxSize', null);
|
||||
Settings.setValue('Chat_sendTyping', null);
|
||||
Settings.setValue('Chat_identifyAvatarDuration', null);
|
||||
Settings.setValue('Chat_identifyAvatarLineColor', null);
|
||||
Settings.setValue('Chat_identifyAvatarMyJointName', null);
|
||||
Settings.setValue('Chat_identifyAvatarYourJointName', null);
|
||||
Settings.setValue('Chat_speechBubbleDuration', null);
|
||||
Settings.setValue('Chat_speechBubbleTextColor', null);
|
||||
Settings.setValue('Chat_speechBubbleBackgroundColor', null);
|
||||
Settings.setValue('Chat_speechBubbleOffset', null);
|
||||
Settings.setValue('Chat_speechBubbleJointName', null);
|
||||
Settings.setValue('Chat_speechBubbleLineHeight', null);
|
||||
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
// Update anything that might depend on the settings.
|
||||
function updateSettings() {
|
||||
updateSpeechBubble();
|
||||
trimChatLog();
|
||||
updateChatPage();
|
||||
}
|
||||
|
||||
// Trim the chat log so it is no longer than chatLogMaxSize lines.
|
||||
function trimChatLog() {
|
||||
if (chatLog.length > chatLogMaxSize) {
|
||||
chatLog.splice(0, chatLogMaxSize - chatLog.length);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the local chat log.
|
||||
function clearChatLog() {
|
||||
//print("clearChatLog");
|
||||
chatLog = [];
|
||||
updateChatPage();
|
||||
}
|
||||
|
||||
// We got a chat message from the channel.
|
||||
// Trim the chat log, save the latest message in the chat log,
|
||||
// and show the message on the tablet, if the chat page is showing.
|
||||
function handleTransmitChatMessage(avatarID, displayName, message, data) {
|
||||
//print("receiveChat", "avatarID", avatarID, "displayName", displayName, "message", message, "data", data);
|
||||
|
||||
trimChatLog();
|
||||
chatLog.push([avatarID, displayName, message, data]);
|
||||
|
||||
if (onChatPage) {
|
||||
tablet.emitScriptEvent(
|
||||
JSON.stringify({
|
||||
type: "ReceiveChatMessage",
|
||||
avatarID: avatarID,
|
||||
displayName: displayName,
|
||||
message: message,
|
||||
data: data
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Trim the chat log, save the latest log message in the chat log,
|
||||
// and show the message on the tablet, if the chat page is showing.
|
||||
function logMessage(message, data) {
|
||||
//print("logMessage", message, data);
|
||||
|
||||
trimChatLog();
|
||||
chatLog.push([null, null, message, data]);
|
||||
|
||||
if (onChatPage) {
|
||||
tablet.emitScriptEvent(
|
||||
JSON.stringify({
|
||||
type: "LogMessage",
|
||||
message: message,
|
||||
data: data
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// An empty chat message was entered.
|
||||
// Hide our speech bubble.
|
||||
function emptyChatMessage(data) {
|
||||
popDownSpeechBubble();
|
||||
}
|
||||
|
||||
// Notification that we typed a keystroke.
|
||||
function type() {
|
||||
//print("type");
|
||||
}
|
||||
|
||||
// Notification that we began typing.
|
||||
// Notify everyone that we started typing.
|
||||
function beginTyping() {
|
||||
//print("beginTyping");
|
||||
if (!sendTyping) {
|
||||
return;
|
||||
}
|
||||
|
||||
Messages.sendMessage(
|
||||
channelName,
|
||||
JSON.stringify({
|
||||
type: 'AvatarBeginTyping',
|
||||
avatarID: MyAvatar.sessionUUID,
|
||||
displayName: chatName
|
||||
}));
|
||||
}
|
||||
|
||||
// Notification that somebody started typing.
|
||||
function handleAvatarBeginTyping(avatarID, displayName) {
|
||||
print("handleAvatarBeginTyping:", "avatarID", avatarID, displayName);
|
||||
}
|
||||
|
||||
// Notification that we stopped typing.
|
||||
// Notify everyone that we stopped typing.
|
||||
function endTyping() {
|
||||
//print("endTyping");
|
||||
if (!sendTyping) {
|
||||
return;
|
||||
}
|
||||
|
||||
Messages.sendMessage(
|
||||
channelName,
|
||||
JSON.stringify({
|
||||
type: 'AvatarEndTyping',
|
||||
avatarID: MyAvatar.sessionUUID,
|
||||
displayName: chatName
|
||||
}));
|
||||
}
|
||||
|
||||
// Notification that somebody stopped typing.
|
||||
function handleAvatarEndTyping(avatarID, displayName) {
|
||||
print("handleAvatarEndTyping:", "avatarID", avatarID, displayName);
|
||||
}
|
||||
|
||||
// Identify an avatar by drawing a line from our head to their head.
|
||||
// If the avatar is our own, then just draw a line up into the sky.
|
||||
function identifyAvatar(yourAvatarID) {
|
||||
//print("identifyAvatar", yourAvatarID);
|
||||
|
||||
unidentifyAvatars();
|
||||
|
||||
var myAvatarID = MyAvatar.sessionUUID;
|
||||
var myJointIndex = MyAvatar.getJointIndex(identifyAvatarMyJointName);
|
||||
var myJointRotation =
|
||||
Quat.multiply(
|
||||
MyAvatar.orientation,
|
||||
MyAvatar.getAbsoluteJointRotationInObjectFrame(myJointIndex));
|
||||
var myJointPosition =
|
||||
Vec3.sum(
|
||||
MyAvatar.position,
|
||||
Vec3.multiplyQbyV(
|
||||
MyAvatar.orientation,
|
||||
MyAvatar.getAbsoluteJointTranslationInObjectFrame(myJointIndex)));
|
||||
|
||||
var yourJointIndex = -1;
|
||||
var yourJointPosition;
|
||||
|
||||
if (yourAvatarID == myAvatarID) {
|
||||
|
||||
// You pointed at your own name, so draw a line up from your head.
|
||||
|
||||
yourJointPosition = {
|
||||
x: myJointPosition.x,
|
||||
y: myJointPosition.y + 1000.0,
|
||||
z: myJointPosition.z
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
// You pointed at somebody else's name, so draw a line from your head to their head.
|
||||
|
||||
var yourAvatar = AvatarList.getAvatar(yourAvatarID);
|
||||
if (!yourAvatar) {
|
||||
return;
|
||||
}
|
||||
|
||||
yourJointIndex = yourAvatar.getJointIndex(identifyAvatarMyJointName)
|
||||
|
||||
var yourJointRotation =
|
||||
Quat.multiply(
|
||||
yourAvatar.orientation,
|
||||
yourAvatar.getAbsoluteJointRotationInObjectFrame(yourJointIndex));
|
||||
yourJointPosition =
|
||||
Vec3.sum(
|
||||
yourAvatar.position,
|
||||
Vec3.multiplyQbyV(
|
||||
yourAvatar.orientation,
|
||||
yourAvatar.getAbsoluteJointTranslationInObjectFrame(yourJointIndex)));
|
||||
|
||||
}
|
||||
|
||||
var identifierParams = {
|
||||
parentID: myAvatarID,
|
||||
parentJointIndex: myJointIndex,
|
||||
lifetime: identityAvatarDuration,
|
||||
start: myJointPosition,
|
||||
endParentID: yourAvatarID,
|
||||
endParentJointIndex: yourJointIndex,
|
||||
end: yourJointPosition,
|
||||
color: identifyAvatarLineColor,
|
||||
alpha: 1,
|
||||
lineWidth: 1
|
||||
};
|
||||
|
||||
avatarIdentifiers[yourAvatarID] = identifierParams;
|
||||
|
||||
identifierParams.lineID = Overlays.addOverlay("line3d", identifierParams);
|
||||
|
||||
//print("ADDOVERLAY lineID", lineID, "myJointPosition", JSON.stringify(myJointPosition), "yourJointPosition", JSON.stringify(yourJointPosition), "lineData", JSON.stringify(lineData));
|
||||
|
||||
identifierParams.timer =
|
||||
Script.setTimeout(function() {
|
||||
//print("DELETEOVERLAY lineID");
|
||||
unidentifyAvatar(yourAvatarID);
|
||||
}, identifyAvatarDuration * 1000);
|
||||
|
||||
}
|
||||
|
||||
// Stop identifying an avatar.
|
||||
function unidentifyAvatar(yourAvatarID) {
|
||||
//print("unidentifyAvatar", yourAvatarID);
|
||||
|
||||
var identifierParams = avatarIdentifiers[yourAvatarID];
|
||||
if (!identifierParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (identifierParams.timer) {
|
||||
Script.clearTimeout(identifierParams.timer);
|
||||
}
|
||||
|
||||
if (identifierParams.lineID) {
|
||||
Overlays.deleteOverlay(identifierParams.lineID);
|
||||
}
|
||||
|
||||
delete avatarIdentifiers[yourAvatarID];
|
||||
}
|
||||
|
||||
// Stop identifying all avatars.
|
||||
function unidentifyAvatars() {
|
||||
var ids = [];
|
||||
|
||||
for (var avatarID in avatarIdentifiers) {
|
||||
ids.push(avatarID);
|
||||
}
|
||||
|
||||
for (var i = 0, n = ids.length; i < n; i++) {
|
||||
var avatarID = ids[i];
|
||||
unidentifyAvatar(avatarID);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Turn to face another avatar.
|
||||
function faceAvatar(yourAvatarID, displayName) {
|
||||
//print("faceAvatar:", yourAvatarID, displayName);
|
||||
|
||||
var myAvatarID = MyAvatar.sessionUUID;
|
||||
if (yourAvatarID == myAvatarID) {
|
||||
// You clicked on yourself.
|
||||
return;
|
||||
}
|
||||
|
||||
var yourAvatar = AvatarList.getAvatar(yourAvatarID);
|
||||
if (!yourAvatar) {
|
||||
logMessage(displayName + ' is not here!', null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Project avatar positions to the floor and get the direction between those points,
|
||||
// then face my avatar towards your avatar.
|
||||
var yourPosition = yourAvatar.position;
|
||||
yourPosition.y = 0;
|
||||
var myPosition = MyAvatar.position;
|
||||
myPosition.y = 0;
|
||||
var myOrientation = Quat.lookAtSimple(myPosition, yourPosition);
|
||||
MyAvatar.orientation = myOrientation;
|
||||
}
|
||||
|
||||
// Make a hopefully unique random anonymous avatar name.
|
||||
function randomAvatarName() {
|
||||
return 'Anon_' + Math.floor(Math.random() * 1000000);
|
||||
}
|
||||
|
||||
// Change the avatar size to bigger.
|
||||
function biggerSize() {
|
||||
//print("biggerSize");
|
||||
logMessage("Increasing avatar size bigger!", null);
|
||||
MyAvatar.increaseSize();
|
||||
}
|
||||
|
||||
// Change the avatar size to smaller.
|
||||
function smallerSize() {
|
||||
//print("smallerSize");
|
||||
logMessage("Decreasing avatar size smaler!", null);
|
||||
MyAvatar.decreaseSize();
|
||||
}
|
||||
|
||||
// Set the avatar size to normal.
|
||||
function normalSize() {
|
||||
//print("normalSize");
|
||||
logMessage("Resetting avatar size to normal!", null);
|
||||
MyAvatar.resetSize();
|
||||
}
|
||||
|
||||
// Send out a "Who" message, including our avatarID as myAvatarID,
|
||||
// which will be sent in the response, so we can tell the reply
|
||||
// is to our request.
|
||||
function transmitWho() {
|
||||
//print("transmitWho");
|
||||
logMessage("Who is here?", null);
|
||||
Messages.sendMessage(
|
||||
channelName,
|
||||
JSON.stringify({
|
||||
type: 'Who',
|
||||
myAvatarID: MyAvatar.sessionUUID
|
||||
}));
|
||||
}
|
||||
|
||||
// Send a reply to a "Who" message, with a friendly message,
|
||||
// our avatarID and our displayName. myAvatarID is the id
|
||||
// of the avatar who send the Who message, to whom we're
|
||||
// responding.
|
||||
function handleWho(myAvatarID) {
|
||||
var avatarID = MyAvatar.sessionUUID;
|
||||
if (myAvatarID == avatarID) {
|
||||
// Don't reply to myself.
|
||||
return;
|
||||
}
|
||||
|
||||
var message = "I'm here!";
|
||||
var data = {};
|
||||
|
||||
Messages.sendMessage(
|
||||
channelName,
|
||||
JSON.stringify({
|
||||
type: 'ReplyWho',
|
||||
myAvatarID: myAvatarID,
|
||||
avatarID: avatarID,
|
||||
displayName: chatName,
|
||||
message: message,
|
||||
data: data
|
||||
}));
|
||||
}
|
||||
|
||||
// Receive the reply to a "Who" message. Ignore it unless we were the one
|
||||
// who sent it out (if myAvatarIS is our avatar's id).
|
||||
function handleReplyWho(myAvatarID, avatarID, displayName, message, data) {
|
||||
if (myAvatarID != MyAvatar.sessionUUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
receiveChatMessageTablet(avatarID, displayName, message, data);
|
||||
}
|
||||
|
||||
// Handle input form the user, possibly multiple lines separated by newlines.
|
||||
// Each line may be a chat command starting with "/", or a chat message.
|
||||
function handleChatMessage(message, data) {
|
||||
|
||||
var messageLines = message.trim().split('\n');
|
||||
|
||||
for (var i = 0, n = messageLines.length; i < n; i++) {
|
||||
var messageLine = messageLines[i];
|
||||
|
||||
if (messageLine.substr(0, 1) == '/') {
|
||||
handleChatCommand(messageLine, data);
|
||||
} else {
|
||||
transmitChatMessage(messageLine, data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle a chat command prefixed by "/".
|
||||
function handleChatCommand(message, data) {
|
||||
|
||||
var commandLine = message.substr(1);
|
||||
var tokens = commandLine.trim().split(' ');
|
||||
var command = tokens[0];
|
||||
var rest = commandLine.substr(command.length + 1).trim();
|
||||
|
||||
//print("commandLine", commandLine, "command", command, "tokens", tokens, "rest", rest);
|
||||
|
||||
switch (command) {
|
||||
|
||||
case '?':
|
||||
case 'help':
|
||||
logMessage('Type "/?" or "/help" for help, which is this!', null);
|
||||
logMessage('Type "/name <name>" to set your chat name, or "/name" to use your display name, or a random name if that is not defined.', null);
|
||||
logMessage('Type "/shutup" to shut up your overhead chat message.', null);
|
||||
logMessage('Type "/say <something>" to say something.', null);
|
||||
logMessage('Type "/clear" to clear your cha, nullt log.', null);
|
||||
logMessage('Type "/who" to ask who is h, nullere to chat.', null);
|
||||
logMessage('Type "/bigger", "/smaller" or "/normal" to change, null your avatar size.', null);
|
||||
logMessage('(Sorry, that\'s all there is so far!)', null);
|
||||
break;
|
||||
|
||||
case 'name':
|
||||
if (rest == '') {
|
||||
if (MyAvatar.displayName) {
|
||||
chatName = MyAvatar.displayName;
|
||||
saveSettings();
|
||||
logMessage('Your chat name has been set to your display name "' + chatName + '".', null);
|
||||
} else {
|
||||
chatName = randomAvatarName();
|
||||
saveSettings();
|
||||
logMessage('Your avatar\'s display name is not defined, so your chat name has been set to "' + chatName + '".', null);
|
||||
}
|
||||
} else {
|
||||
chatName = rest;
|
||||
saveSettings();
|
||||
logMessage('Your chat name has been set to "' + chatName + '".', null);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'shutup':
|
||||
popDownSpeechBubble();
|
||||
logMessage('Overhead chat message shut up.', null);
|
||||
break;
|
||||
|
||||
case 'say':
|
||||
if (rest == '') {
|
||||
emptyChatMessage(data);
|
||||
} else {
|
||||
transmitChatMessage(rest, data);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'who':
|
||||
transmitWho();
|
||||
break;
|
||||
|
||||
case 'clear':
|
||||
clearChatLog();
|
||||
break;
|
||||
|
||||
case 'bigger':
|
||||
biggerSize();
|
||||
break;
|
||||
|
||||
case 'smaller':
|
||||
smallerSize();
|
||||
break;
|
||||
|
||||
case 'normal':
|
||||
normalSize();
|
||||
break;
|
||||
|
||||
case 'resetsettings':
|
||||
resetSettings();
|
||||
updateSettings();
|
||||
break;
|
||||
|
||||
case 'speechbubbleheight':
|
||||
var y = parseInt(rest);
|
||||
if (!isNaN(y)) {
|
||||
speechBubbleOffset.y = y;
|
||||
}
|
||||
saveSettings();
|
||||
updateSettings();
|
||||
break;
|
||||
|
||||
case 'speechbubbleduration':
|
||||
var duration = parseFloat(rest);
|
||||
if (!isNaN(duration)) {
|
||||
speechBubbleDuration = duration;
|
||||
}
|
||||
saveSettings();
|
||||
updateSettings();
|
||||
break;
|
||||
|
||||
default:
|
||||
logMessage('Unknown chat command. Type "/help" or "/?" for help.', null);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Send out a chat message to everyone.
|
||||
function transmitChatMessage(message, data) {
|
||||
//print("transmitChatMessage", 'avatarID', avatarID, 'displayName', displayName, 'message', message, 'data', data);
|
||||
|
||||
popUpSpeechBubble(message, data);
|
||||
|
||||
Messages.sendMessage(
|
||||
channelName,
|
||||
JSON.stringify({
|
||||
type: 'TransmitChatMessage',
|
||||
avatarID: MyAvatar.sessionUUID,
|
||||
displayName: chatName,
|
||||
message: message,
|
||||
data: data
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
// Show the speech bubble.
|
||||
function popUpSpeechBubble(message, data) {
|
||||
//print("popUpSpeechBubble", message, data);
|
||||
|
||||
popDownSpeechBubble();
|
||||
|
||||
speechBubbleShowing = true;
|
||||
speechBubbleMessage = message;
|
||||
speechBubbleData = data;
|
||||
|
||||
updateSpeechBubble();
|
||||
|
||||
if (speechBubbleDuration > 0) {
|
||||
speechBubbleTimer = Script.setTimeout(
|
||||
function () {
|
||||
popDownSpeechBubble();
|
||||
},
|
||||
speechBubbleDuration * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the speech bubble.
|
||||
// This is factored out so we can update an existing speech bubble if any settings change.
|
||||
function updateSpeechBubble() {
|
||||
if (!speechBubbleShowing) {
|
||||
return;
|
||||
}
|
||||
|
||||
var jointIndex = MyAvatar.getJointIndex(speechBubbleJointName);
|
||||
var dimensions = {
|
||||
x: 100.0,
|
||||
y: 100.0,
|
||||
z: 0.1
|
||||
};
|
||||
|
||||
speechBubbleParams = {
|
||||
type: "Text",
|
||||
lifetime: speechBubbleDuration,
|
||||
parentID: MyAvatar.sessionUUID,
|
||||
jointIndex: jointIndex,
|
||||
dimensions: dimensions,
|
||||
lineHeight: speechBubbleLineHeight,
|
||||
leftMargin: 0,
|
||||
topMargin: 0,
|
||||
rightMargin: 0,
|
||||
bottomMargin: 0,
|
||||
faceCamera: true,
|
||||
drawInFront: true,
|
||||
ignoreRayIntersection: true,
|
||||
text: speechBubbleMessage,
|
||||
textColor: speechBubbleTextColor,
|
||||
color: speechBubbleTextColor,
|
||||
backgroundColor: speechBubbleBackgroundColor
|
||||
};
|
||||
|
||||
// Only overlay text3d has a way to measure the text, not entities.
|
||||
// So we make a temporary one just for measuring text, then delete it.
|
||||
var speechBubbleTextOverlayID = Overlays.addOverlay("text3d", speechBubbleParams);
|
||||
var textSize = Overlays.textSize(speechBubbleTextOverlayID, speechBubbleMessage);
|
||||
try {
|
||||
Overlays.deleteOverlay(speechBubbleTextOverlayID);
|
||||
} catch (e) {}
|
||||
|
||||
print("updateSpeechBubble:", "speechBubbleMessage", speechBubbleMessage, "textSize", textSize.width, textSize.height);
|
||||
|
||||
var fudge = 0.02;
|
||||
var width = textSize.width + fudge;
|
||||
var height = textSize.height + fudge;
|
||||
dimensions = {
|
||||
x: width,
|
||||
y: height,
|
||||
z: 0.1
|
||||
};
|
||||
speechBubbleParams.dimensions = dimensions;
|
||||
|
||||
var headRotation =
|
||||
Quat.multiply(
|
||||
MyAvatar.orientation,
|
||||
MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex));
|
||||
var headPosition =
|
||||
Vec3.sum(
|
||||
MyAvatar.position,
|
||||
Vec3.multiplyQbyV(
|
||||
MyAvatar.orientation,
|
||||
MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex)));
|
||||
var rotatedOffset =
|
||||
Vec3.multiplyQbyV(
|
||||
headRotation,
|
||||
speechBubbleOffset);
|
||||
var position =
|
||||
Vec3.sum(
|
||||
headPosition,
|
||||
rotatedOffset);
|
||||
speechBubbleParams.position = position;
|
||||
|
||||
if (!speechBubbleTextID) {
|
||||
speechBubbleTextID =
|
||||
Entities.addEntity(speechBubbleParams, true);
|
||||
} else {
|
||||
Entities.editEntity(speechBubbleTextID, speechBubbleParams);
|
||||
}
|
||||
|
||||
print("speechBubbleTextID:", speechBubbleTextID, "speechBubbleParams", JSON.stringify(speechBubbleParams));
|
||||
}
|
||||
|
||||
// Hide the speech bubble.
|
||||
function popDownSpeechBubble() {
|
||||
cancelSpeechBubbleTimer();
|
||||
|
||||
speechBubbleShowing = false;
|
||||
|
||||
print("popDownSpeechBubble speechBubbleTextID", speechBubbleTextID);
|
||||
|
||||
if (speechBubbleTextID) {
|
||||
try {
|
||||
Entities.deleteEntity(speechBubbleTextID);
|
||||
} catch (e) {}
|
||||
speechBubbleTextID = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel the speech bubble popup timer.
|
||||
function cancelSpeechBubbleTimer() {
|
||||
if (speechBubbleTimer) {
|
||||
Script.clearTimeout(speechBubbleTimer);
|
||||
speechBubbleTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Show the tablet web page and connect the web handler.
|
||||
function showTabletWebPage() {
|
||||
var url = Script.resolvePath(webPageURL);
|
||||
if (randomizeWebPageURL) {
|
||||
url += '?rand=' + Math.random();
|
||||
}
|
||||
lastWebPageURL = url;
|
||||
onChatPage = true;
|
||||
tablet.gotoWebScreen(lastWebPageURL);
|
||||
// Connect immediately so we don't miss anything.
|
||||
connectWebHandler();
|
||||
}
|
||||
|
||||
// Update the tablet web page with the chat log.
|
||||
function updateChatPage() {
|
||||
if (!onChatPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
tablet.emitScriptEvent(
|
||||
JSON.stringify({
|
||||
type: "Update",
|
||||
chatLog: chatLog
|
||||
}));
|
||||
}
|
||||
|
||||
function onChatMessageReceived(channel, message, senderID) {
|
||||
|
||||
// Ignore messages to any other channel than mine.
|
||||
if (channel != channelName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the message and pull out the message parameters.
|
||||
var messageData = JSON.parse(message);
|
||||
var messageType = messageData.type;
|
||||
|
||||
//print("MESSAGE", message);
|
||||
//print("MESSAGEDATA", messageData, JSON.stringify(messageData));
|
||||
|
||||
switch (messageType) {
|
||||
|
||||
case 'TransmitChatMessage':
|
||||
handleTransmitChatMessage(messageData.avatarID, messageData.displayName, messageData.message, messageData.data);
|
||||
break;
|
||||
|
||||
case 'AvatarBeginTyping':
|
||||
handleAvatarBeginTyping(messageData.avatarID, messageData.displayName);
|
||||
break;
|
||||
|
||||
case 'AvatarEndTyping':
|
||||
handleAvatarEndTyping(messageData.avatarID, messageData.displayName);
|
||||
break;
|
||||
|
||||
case 'Who':
|
||||
handleWho(messageData.myAvatarID);
|
||||
break;
|
||||
|
||||
case 'ReplyWho':
|
||||
handleReplyWho(messageData.myAvatarID, messageData.avatarID, messageData.displayName, messageData.message, messageData.data);
|
||||
break;
|
||||
|
||||
default:
|
||||
print("onChatMessageReceived: unknown messageType", messageType, "message", message);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle events from the tablet web page.
|
||||
function onWebEventReceived(event) {
|
||||
if (!onChatPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
//print("onWebEventReceived: event", event);
|
||||
|
||||
var eventData = JSON.parse(event);
|
||||
var eventType = eventData.type;
|
||||
|
||||
switch (eventType) {
|
||||
|
||||
case 'Ready':
|
||||
updateChatPage();
|
||||
break;
|
||||
|
||||
case 'Update':
|
||||
updateChatPage();
|
||||
break;
|
||||
|
||||
case 'HandleChatMessage':
|
||||
var message = eventData.message;
|
||||
var data = eventData.data;
|
||||
//print("onWebEventReceived: HandleChatMessage:", 'message', message, 'data', data);
|
||||
handleChatMessage(message, data);
|
||||
break;
|
||||
|
||||
case 'PopDownSpeechBubble':
|
||||
popDownSpeechBubble();
|
||||
break;
|
||||
|
||||
case 'EmptyChatMessage':
|
||||
emptyChatMessage();
|
||||
break;
|
||||
|
||||
case 'Type':
|
||||
type();
|
||||
break;
|
||||
|
||||
case 'BeginTyping':
|
||||
beginTyping();
|
||||
break;
|
||||
|
||||
case 'EndTyping':
|
||||
endTyping();
|
||||
break;
|
||||
|
||||
case 'IdentifyAvatar':
|
||||
identifyAvatar(eventData.avatarID);
|
||||
break;
|
||||
|
||||
case 'UnidentifyAvatar':
|
||||
unidentifyAvatar(eventData.avatarID);
|
||||
break;
|
||||
|
||||
case 'FaceAvatar':
|
||||
faceAvatar(eventData.avatarID, eventData.displayName);
|
||||
break;
|
||||
|
||||
case 'ClearChatLog':
|
||||
clearChatLog();
|
||||
break;
|
||||
|
||||
case 'Who':
|
||||
transmitWho();
|
||||
break;
|
||||
|
||||
case 'Bigger':
|
||||
biggerSize();
|
||||
break;
|
||||
|
||||
case 'Smaller':
|
||||
smallerSize();
|
||||
break;
|
||||
|
||||
case 'Normal':
|
||||
normalSize();
|
||||
break;
|
||||
|
||||
default:
|
||||
print("onWebEventReceived: unexpected eventType", eventType);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function onScreenChanged(type, url) {
|
||||
//print("onScreenChanged", "type", type, "url", url, "lastWebPageURL", lastWebPageURL);
|
||||
|
||||
if ((type === "Web") &&
|
||||
(url === lastWebPageURL)) {
|
||||
if (!onChatPage) {
|
||||
onChatPage = true;
|
||||
connectWebHandler();
|
||||
}
|
||||
} else {
|
||||
if (onChatPage) {
|
||||
onChatPage = false;
|
||||
disconnectWebHandler();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function connectWebHandler() {
|
||||
if (webHandlerConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
tablet.webEventReceived.connect(onWebEventReceived);
|
||||
} catch (e) {
|
||||
print("connectWebHandler: error connecting: " + e);
|
||||
return;
|
||||
}
|
||||
|
||||
webHandlerConnected = true;
|
||||
//print("connectWebHandler connected");
|
||||
|
||||
updateChatPage();
|
||||
}
|
||||
|
||||
function disconnectWebHandler() {
|
||||
if (!webHandlerConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
tablet.webEventReceived.disconnect(onWebEventReceived);
|
||||
} catch (e) {
|
||||
print("disconnectWebHandler: error disconnecting web handler: " + e);
|
||||
return;
|
||||
}
|
||||
webHandlerConnected = false;
|
||||
|
||||
//print("disconnectWebHandler: disconnected");
|
||||
}
|
||||
|
||||
// Show the tablet web page when the chat button on the tablet is clicked.
|
||||
function onTabletButtonClicked() {
|
||||
showTabletWebPage();
|
||||
}
|
||||
|
||||
// Shut down the chat application when the tablet button is destroyed.
|
||||
function onTabletButtonDestroyed() {
|
||||
shutDown();
|
||||
}
|
||||
|
||||
// Start up the chat application.
|
||||
function startUp() {
|
||||
//print("startUp");
|
||||
|
||||
loadSettings();
|
||||
|
||||
tabletButton = tablet.addButton({
|
||||
icon: tabletButtonIcon,
|
||||
activeIcon: tabletButtonActiveIcon,
|
||||
text: tabletButtonName,
|
||||
sortOrder: 0
|
||||
});
|
||||
|
||||
Messages.subscribe(channelName);
|
||||
|
||||
tablet.screenChanged.connect(onScreenChanged);
|
||||
|
||||
Messages.messageReceived.connect(onChatMessageReceived);
|
||||
|
||||
tabletButton.clicked.connect(onTabletButtonClicked);
|
||||
|
||||
Script.scriptEnding.connect(onTabletButtonDestroyed);
|
||||
|
||||
logMessage('Type "/?" or "/help" for help with chat.', null);
|
||||
|
||||
//print("Added chat button to tablet.");
|
||||
}
|
||||
|
||||
// Shut down the chat application.
|
||||
function shutDown() {
|
||||
//print("shutDown");
|
||||
|
||||
popDownSpeechBubble();
|
||||
unidentifyAvatars();
|
||||
disconnectWebHandler();
|
||||
|
||||
if (onChatPage) {
|
||||
tablet.gotoHomeScreen();
|
||||
onChatPage = false;
|
||||
}
|
||||
|
||||
tablet.screenChanged.disconnect(onScreenChanged);
|
||||
|
||||
Messages.messageReceived.disconnect(onChatMessageReceived);
|
||||
|
||||
// Clean up the tablet button we made.
|
||||
tabletButton.clicked.disconnect(onTabletButtonClicked);
|
||||
tablet.removeButton(tabletButton);
|
||||
tabletButton = null;
|
||||
|
||||
//print("Removed chat button from tablet.");
|
||||
}
|
||||
|
||||
// Kick off the chat application!
|
||||
startUp();
|
||||
|
||||
}());
|
511
unpublishedScripts/marketplace/chat/ChatPage.html
Normal file
511
unpublishedScripts/marketplace/chat/ChatPage.html
Normal file
|
@ -0,0 +1,511 @@
|
|||
<!--
|
||||
// ChatPage.html
|
||||
//
|
||||
// Created by Faye Li on 3 Feb 2017
|
||||
// Modified by Don Hopkins (dhopkins@donhopkins.com).
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Chat</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600,700"" rel="stylesheet">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<style>
|
||||
input[type=button],
|
||||
button {
|
||||
font-family: 'Raleway';
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
min-width: 120px;
|
||||
padding: 0px 18px;
|
||||
margin-top: 5px;
|
||||
margin-right: 6px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
background: linear-gradient(#343434 20%, #000 100%);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.commandButton {
|
||||
width: 100px !important;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
font-family: 'Raleway';
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
vertical-align: top;
|
||||
height: 40px;
|
||||
color: #000;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
background: linear-gradient(#343434 20%, #fff 100%);
|
||||
}
|
||||
|
||||
input[type=button].red {
|
||||
color: #fff;
|
||||
background-color: #94132e;
|
||||
background: linear-gradient(#d42043 20%, #94132e 100%);
|
||||
}
|
||||
input[type=button].blue {
|
||||
color: #fff;
|
||||
background-color: #1080b8;
|
||||
background: linear-gradient(#00b4ef 20%, #1080b8 100%);
|
||||
}
|
||||
input[type=button].white {
|
||||
color: #121212;
|
||||
background-color: #afafaf;
|
||||
background: linear-gradient(#fff 20%, #afafaf 100%);
|
||||
}
|
||||
|
||||
input[type=button]:enabled:hover {
|
||||
background: linear-gradient(#000, #000);
|
||||
border: none;
|
||||
}
|
||||
input[type=button].red:enabled:hover {
|
||||
background: linear-gradient(#d42043, #d42043);
|
||||
border: none;
|
||||
}
|
||||
input[type=button].blue:enabled:hover {
|
||||
background: linear-gradient(#00b4ef, #00b4ef);
|
||||
border: none;
|
||||
}
|
||||
input[type=button].white:enabled:hover {
|
||||
background: linear-gradient(#fff, #fff);
|
||||
border: none;
|
||||
}
|
||||
|
||||
input[type=button]:active {
|
||||
background: linear-gradient(#343434, #343434);
|
||||
}
|
||||
input[type=button].red:active {
|
||||
background: linear-gradient(#94132e, #94132e);
|
||||
}
|
||||
input[type=button].blue:active {
|
||||
background: linear-gradient(#1080b8, #1080b8);
|
||||
}
|
||||
input[type=button].white:active {
|
||||
background: linear-gradient(#afafaf, #afafaf);
|
||||
}
|
||||
|
||||
input[type=button]:disabled {
|
||||
color: #252525;
|
||||
background: linear-gradient(#575757 20%, #252525 100%);
|
||||
}
|
||||
|
||||
input[type=button][pressed=pressed] {
|
||||
color: #00b4ef;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
margin: 0;
|
||||
font-family: 'Raleway', sans-serif;
|
||||
color: white;
|
||||
background: linear-gradient(#2b2b2b, #0f212e);
|
||||
}
|
||||
|
||||
.Content {
|
||||
font-size: 20px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.TopBar {
|
||||
height: 40px;
|
||||
background: linear-gradient(#2b2b2b, #1e1e1e);
|
||||
font-weight: bold;
|
||||
padding: 10px 10px 10px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
font-size: 28px;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.ChatLog {
|
||||
padding: 20px;
|
||||
font-size: 20px;
|
||||
flex-grow: 1;
|
||||
color: white;
|
||||
background-color: black;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.ChatLogLine {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.ChatLogLineDisplayName {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ChatLogLineMessage {
|
||||
}
|
||||
|
||||
.LogLogLine {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.LogLogLineMessage {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.ChatInput {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.ChatInputText {
|
||||
padding: 20px 20px 20px 20px;
|
||||
height: 60px !important;
|
||||
font-size: 20px !important;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="Content">
|
||||
|
||||
<div class="TopBar">
|
||||
<b>Chat</b>
|
||||
</div>
|
||||
|
||||
<div class="ChatLog" id="ChatLog"></div>
|
||||
|
||||
<div class="ChatInput">
|
||||
<input type="text" class="ChatInputText" id="ChatInputText" size="256"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
<script>
|
||||
|
||||
//console.log("ChatPage: loading script...");
|
||||
|
||||
var messageData = {}; // The data that is sent along with the message.
|
||||
var typing = false; // True while the user is typing.
|
||||
var typingTimerDuration = 1; // How long to wait before ending typing, in seconds.
|
||||
var typingTimer = null; // The timer to end typing.
|
||||
var $ChatLog; // The scrolling chat log.
|
||||
var $ChatInputText; // The text field for entering text.
|
||||
|
||||
// Recreate the lines in chatLog as the DOM in $ChatLog.
|
||||
function updateChatLog() {
|
||||
$ChatLog.html('');
|
||||
for (var i = 0, n = chatLog.length; i < n; i++) {
|
||||
var a = chatLog[i];
|
||||
var avatarID = a[0];
|
||||
var displayName = a[1];
|
||||
var message = a[2];
|
||||
var data = a[3];
|
||||
//console.log("updateChatLog", i, a, displayName, message);
|
||||
if (avatarID) {
|
||||
receiveChatMessage(avatarID, displayName, message, data);
|
||||
} else {
|
||||
logMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this no every keystroke.
|
||||
function type() {
|
||||
beginTyping();
|
||||
handleType();
|
||||
}
|
||||
|
||||
// Reset the typing timer, and notify if we're starting.
|
||||
function beginTyping() {
|
||||
if (typingTimer) {
|
||||
clearTimeout(typingTimer);
|
||||
}
|
||||
|
||||
typingTimer = setTimeout(function() {
|
||||
typing = false;
|
||||
handleEndTyping();
|
||||
}, typingTimerDuration * 1000);
|
||||
|
||||
if (typing) {
|
||||
return;
|
||||
}
|
||||
|
||||
typing = true;
|
||||
handleBeginTyping();
|
||||
}
|
||||
|
||||
// Clear the typing timer and notify if we're finished.
|
||||
function endTyping() {
|
||||
if (typingTimer) {
|
||||
clearTimeout(typingTimer);
|
||||
typingTimer = null;
|
||||
}
|
||||
|
||||
if (!typing) {
|
||||
return;
|
||||
}
|
||||
|
||||
typing = false;
|
||||
handleEndTyping();
|
||||
}
|
||||
|
||||
// Notify the interface script on every keystroke.
|
||||
function handleType() {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "Type"
|
||||
}));
|
||||
}
|
||||
|
||||
// Notify the interface script when we begin typing.
|
||||
function handleBeginTyping() {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "BeginTyping"
|
||||
}));
|
||||
}
|
||||
|
||||
// Notify the interface script when we end typing.
|
||||
function handleEndTyping() {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "EndTyping"
|
||||
}));
|
||||
}
|
||||
|
||||
// Append a chat message to $ChatLog.
|
||||
function receiveChatMessage(avatarID, displayName, message, data) {
|
||||
var $logLine =
|
||||
$('<div/>')
|
||||
.addClass('ChatLogLine')
|
||||
.data('chat-avatarID', avatarID)
|
||||
.data('chat-displayName', displayName)
|
||||
.data('chat-message', message)
|
||||
.data('chat-data', data)
|
||||
.appendTo($ChatLog);
|
||||
var $logLineDisplayName =
|
||||
$('<span/>')
|
||||
.addClass('ChatLogLineDisplayName')
|
||||
.text(displayName + ': ')
|
||||
.on('mouseenter', function(event) {
|
||||
identifyAvatar(avatarID);
|
||||
//event.cancelBubble();
|
||||
$logLineDisplayName.css({
|
||||
color: '#00ff00',
|
||||
'text-decoration': 'underline'
|
||||
});
|
||||
})
|
||||
.on('mouseleave', function(event) {
|
||||
unidentifyAvatar(avatarID);
|
||||
//event.cancelBubble();
|
||||
$logLineDisplayName.css({
|
||||
color: 'white',
|
||||
'text-decoration': 'none'
|
||||
});
|
||||
})
|
||||
.click(function(event) {
|
||||
faceAvatar(avatarID, displayName);
|
||||
//event.cancelBubble();
|
||||
})
|
||||
.appendTo($logLine);
|
||||
var $logLineMessage =
|
||||
$('<span/>')
|
||||
.addClass('ChatLogLineMessage')
|
||||
.text(message)
|
||||
.appendTo($logLine);
|
||||
}
|
||||
|
||||
// Append a log message to $ChatLog.
|
||||
function logMessage(message) {
|
||||
var $logLine =
|
||||
$('<div/>')
|
||||
.addClass('LogLogLine')
|
||||
.data('chat-message', message)
|
||||
.appendTo($ChatLog);
|
||||
var $logLineMessage =
|
||||
$('<span/>')
|
||||
.addClass('LogLogLineMessage')
|
||||
.text(message)
|
||||
.appendTo($logLine);
|
||||
}
|
||||
|
||||
// Scroll $ChatLog so the last line is visible.
|
||||
function scrollChatLog() {
|
||||
var $logLine = $ChatLog.children().last();
|
||||
if (!$logLine || !$logLine.length) {
|
||||
return;
|
||||
}
|
||||
var chatLogHeight = $ChatLog.outerHeight(true);
|
||||
var logLineTop = ($logLine.offset().top - $ChatLog.offset().top);
|
||||
var logLineBottom = logLineTop + $logLine.outerHeight(true);
|
||||
var scrollUp = logLineBottom - chatLogHeight;
|
||||
if (scrollUp > 0) {
|
||||
$ChatLog.scrollTop($ChatLog.scrollTop() + scrollUp);
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the interface script we have initialized.
|
||||
function emitReadyEvent() {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "Ready"
|
||||
}));
|
||||
}
|
||||
|
||||
// The user entered an empty chat message.
|
||||
function emptyChatMessage() {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "EmptyChatMessage",
|
||||
data: null
|
||||
}));
|
||||
}
|
||||
|
||||
// The user entered a non-empty chat message.
|
||||
function handleChatMessage(message, data) {
|
||||
//console.log("handleChatMessage", message);
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "HandleChatMessage",
|
||||
message: message,
|
||||
data: data
|
||||
}));
|
||||
}
|
||||
|
||||
// Clear the chat log, of course.
|
||||
function clearChatLog() {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "ClearChatLog"
|
||||
}));
|
||||
}
|
||||
|
||||
// Identify an avatar.
|
||||
function identifyAvatar(avatarID) {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "IdentifyAvatar",
|
||||
avatarID: avatarID
|
||||
}));
|
||||
}
|
||||
|
||||
// Stop identifying an avatar.
|
||||
function unidentifyAvatar(avatarID) {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "UnidentifyAvatar",
|
||||
avatarID: avatarID
|
||||
}));
|
||||
}
|
||||
|
||||
// Face an avatar.
|
||||
function faceAvatar(avatarID, displayName) {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "FaceAvatar",
|
||||
avatarID: avatarID,
|
||||
displayName: displayName
|
||||
}));
|
||||
}
|
||||
|
||||
// Let's get this show on the road!
|
||||
function main() {
|
||||
|
||||
//console.log("ChatPage: main");
|
||||
|
||||
$ChatLog = $('#ChatLog');
|
||||
$ChatInputText = $('#ChatInputText');
|
||||
|
||||
// Whenever the chat log resizes, or the input text gets or loses focus,
|
||||
// scroll the chat log to the last line.
|
||||
$ChatLog.on('resize', function(event) {
|
||||
//console.log("ChatLog resize", $ChatLog, event);
|
||||
scrollChatLog();
|
||||
});
|
||||
$ChatInputText.on('focus blur', function(event) {
|
||||
//console.log("ChatInputText focus blur", $ChatInputText, event);
|
||||
scrollChatLog();
|
||||
});
|
||||
|
||||
// Track when the user is typing, and handle the message when the user hits return.
|
||||
$ChatInputText.on('keydown', function(event) {
|
||||
type();
|
||||
if (event.keyCode == 13) {
|
||||
var message = $ChatInputText.val().substr(0, 256);
|
||||
$ChatInputText.val('');
|
||||
if (message == '') {
|
||||
emptyChatMessage();
|
||||
} else {
|
||||
handleChatMessage(message, messageData);
|
||||
}
|
||||
endTyping();
|
||||
}
|
||||
});
|
||||
|
||||
// Start out with the input text in focus.
|
||||
$ChatInputText.focus();
|
||||
|
||||
// Hook up a handler for events that come from hifi.
|
||||
EventBridge.scriptEventReceived.connect(function (message) {
|
||||
|
||||
//console.log("ChatPage: main: scriptEventReceived", message);
|
||||
|
||||
var messageData = JSON.parse(message);
|
||||
var messageType = messageData['type'];
|
||||
|
||||
switch (messageType) {
|
||||
|
||||
case "Update":
|
||||
chatLog = messageData['chatLog'];
|
||||
updateChatLog();
|
||||
scrollChatLog();
|
||||
break;
|
||||
|
||||
case "ReceiveChatMessage":
|
||||
receiveChatMessage(messageData['avatarID'], messageData['displayName'], messageData['message'], message['data']);
|
||||
scrollChatLog();
|
||||
break;
|
||||
|
||||
case "LogMessage":
|
||||
logMessage(messageData['message']);
|
||||
scrollChatLog();
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("WEB: received unexpected script event message: " + message);
|
||||
break;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
emitReadyEvent();
|
||||
}
|
||||
|
||||
// Start up once the document is loaded.
|
||||
$(document).ready(main);
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
Loading…
Reference in a new issue