<!-- // 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" maxlength="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>