shift/main.js

333 lines
9.1 KiB
JavaScript

const startTime = Date.now();
var conversation = null;
class Conversation {
constructor(name) {
this.messages = [];
this.name = name;
}
setInteractive(isInteractive) {
const children = document.getElementById("textbox").children;
for (let i = 0; i < children.length; i++) {
children[i].disabled = !isInteractive;
}
}
initialize(initialMessages) {
document.title = this.name;
document.getElementById("header-title").innerHTML = this.name;
this.messages = initialMessages;
}
// for the user to send their own messages
sendUserMessage(text) {
const message = new UserMessage(text);
message.updateStatus("sent");
setTimeout(() => {
message.updateStatus("delivered");
this.render();
}, 1000);
this.messages.push(message);
}
// update the current HTML based on messages
render() {
// clear stale HTML
getMessageList().innerHTML = "";
// render message elements
for (let i = 0; i < this.messages.length; i++)
{
const messageRoot = document.getElementById("messages");
const newMessage = this.messages[i];
messageRoot.appendChild(newMessage.getElement());
}
}
}
function getMessageList() {
return document.getElementById("messages");
}
class AgentMessage {
constructor(text, senderName) {
this.text = text;
this.senderName = senderName;
}
getIsOurs() {
return false;
}
getElement() {
const liElem = document.createElement("li");
liElem.className = "message";
const contentElem = document.createElement("span");
contentElem.className = "message-content rounded-rectangle theirs";
liElem.appendChild(contentElem);
if (this.senderName) {
const nameElem = document.createElement("h3");
nameElem.innerHTML = this.senderName;
contentElem.appendChild(nameElem);
}
const textElem = document.createElement("span");
textElem.className = "message-text";
textElem.innerHTML = this.text;
contentElem.appendChild(textElem);
return liElem;
}
}
class UserMessage {
constructor(text) {
this.createdTime = Date.now();
this.text = text;
this.status = "";
}
getIsOurs() {
return true;
}
getElement() {
const liElem = document.createElement("li");
liElem.className = "message";
const contentElem = document.createElement("span");
contentElem.className = "message-content rounded-rectangle ours";
liElem.appendChild(contentElem);
const textElem = document.createElement("span");
textElem.className = "message-text";
textElem.innerHTML = this.text;
contentElem.appendChild(textElem);
const statusElem = document.createElement("p");
statusElem.className = "message-status";
statusElem.innerHTML = this.status;
liElem.appendChild(statusElem);
return liElem;
}
updateStatus(newStatus) {
this.status = newStatus;
}
}
class SystemMessage {
constructor(text) {
this.text = text;
}
getElement() {
const liElem = document.createElement("li");
liElem.className = "system-message";
liElem.innerHTML = this.text;
return liElem;
}
}
function setTypingIndicator(isTyping) {
document.getElementById("typing-indicator").innerHTML = isTyping
? `${conversation.contactName} is typing...`
: "";
}
// add the message at the index to the displayed messages
function addMessage(message) {
getMessageList().innerHTML += message.getHtml();
// scroll as far as we can so that messages aren't hidden
window.scrollTo(0, document.body.scrollHeight);
}
function updatePings() {
const title = conversation.name;
let newTitle = conversation.pings > 0
? `(${conversation.pings}) ${title}`
: title;
document.title = newTitle;
}
function clearPings() {
conversation.pings = 0;
updatePings();
}
// returns a decimal value between min and max
function getRandomInRange(min, max) {
const range = max - min;
return min + Math.random() * range;
}
function updateChat(message) {
addMessage(message);
const previewText = conversation.getTypedMessageText();
document.getElementById("textbox-input").value = previewText;
updatePings();
}
function pressSendButton() {
if (event.type == "keydown" && event.key != "Enter")
return;
// we have interacted with the page so remove all pings
clearPings();
// get the content of the text box
const textBox = document.getElementById("textbox-input");
const text = textBox.value;
if (!text)
return;
textBox.value = "";
conversation.sendUserMessage(text);
conversation.render();
// TODO: start process of receiving next message from server (or fake it for now)
}
function onMessageReceived(message) {
updateChat(message);
setTypingIndicator(false);
}
function onMessageSent(message) {
updateChat(message);
}
// probably a bit hacky! but this saves having to do like, state or something in CSS?
// which probably is possible and probably would be the better way to do it, but that
// sounds like a bunch of learning i'm not SUPER in the mood for
function setVisibleOnMobile(element, isVisible) {
let classes = element.className.split().filter(c => c != "");
const invisibleClass = "invisible-on-mobile";
const visibleClass = "visible";
if (isVisible && !classes.includes(visibleClass)) {
const idx = classes.indexOf(invisibleClass);
if (idx != -1) {
classes.splice(idx, 1);
}
classes.push(visibleClass);
} else if (!isVisible && !classes.includes(invisibleClass)) {
const idx = classes.indexOf(visibleClass);
if (idx != -1) {
classes.splice(idx, 1);
}
classes.push(invisibleClass);
}
element.className = classes.join(" ");
}
function showSidePanel() {
// this function can only be called on mobile. the main conversation should be
// hidden and the side conversations panel should take up the whole screen.
const mainPanel = document.getElementById("main-panel");
const conversationListElem = document.getElementById("side-panel");
setVisibleOnMobile(mainPanel, false);
setVisibleOnMobile(conversationListElem, true);
}
function readConversationJson(path, callback) {
fetch(path)
.then(response => response.json())
.then(json => callback(json));
}
function showConversation(path) {
const mainPanel = document.getElementById("main-panel");
const conversationListElem = document.getElementById("side-panel");
setVisibleOnMobile(mainPanel, true);
setVisibleOnMobile(conversationListElem, false);
readConversationJson(path, json => {
conversation = new Conversation(json.title);
const jsonMessages = json.messages;
const participants = json.characters;
let initialMessages = [];
for (let i = 0; i < jsonMessages.length; i++) {
const data = jsonMessages[i];
const text = data.text;
if (data.character == -1) {
const message = new SystemMessage(text);
initialMessages.push(message);
} else if (data.character == 0) {
const message = new UserMessage(text);
message.updateStatus("delivered");
initialMessages.push(message);
} else {
const message = participants.length > 2
? new AgentMessage(text, participants[data.character])
: new AgentMessage(text);
initialMessages.push(message);
}
}
conversation.initialize(initialMessages);
conversation.setInteractive(json.interactive);
conversation.render();
});
}
function addConversationPreview(path) {
const listRoot = document.getElementById("side-panel");
readConversationJson(path, json => {
const messages = json.messages;
const elem = document.createElement("div");
elem.onclick = () => showConversation(path);
elem.className = "conversation";
const headerElem = document.createElement("h2");
headerElem.innerHTML = json.title;
elem.appendChild(headerElem);
const previewElem = document.createElement("span");
previewElem.innerHTML = messages[messages.length - 1].text;
elem.appendChild(previewElem);
listRoot.appendChild(elem);
});
}
function populateConversationList() {
const conversationFiles = [
"caesar.json",
"lucius.json",
"ides-of-march.json",
"lepidus.json"
];
for (let i = 0; i < conversationFiles.length; i++) {
const path = conversationFiles[i];
addConversationPreview(path);
}
}
setTypingIndicator(false);
populateConversationList();
showConversation("ides-of-march.json");