diff --git a/index.html b/index.html index 8530fa3..12047e5 100644 --- a/index.html +++ b/index.html @@ -15,7 +15,7 @@

NAME is typing...

- +
diff --git a/main.js b/main.js index 9a38832..e511b93 100644 --- a/main.js +++ b/main.js @@ -3,177 +3,41 @@ const startTime = Date.now(); var conversation = null; class Conversation { - constructor(messageData, contactName, onMessageReceived, onMessageSent) { - this.messageData = messageData; - this.contactName = contactName; - - this.messageIdx = -1; - // contains both sent and received messages + constructor(name) { this.messages = []; - - // callbacks - this.onMessageReceived = onMessageReceived; - this.onMessageSent = onMessageSent; + this.name = name; } - start() { - document.title = this.contactName; - document.getElementById("header-title").innerHTML = this.contactName; - - // if the first message comes from the other party, - const firstMessage = this.messageData[0]; - const firstMessageIsOurs = firstMessage.character == 0; - if (firstMessageIsOurs) { - } else { - setTimeout(() => { - this.messageIdx = 0; - this.pings = 1; - - const data = this.messageData[this.messageIdx]; - const message = new ReceivedMessage(data); - this.messages.push(message); - this.onMessageReceived(message); - }, 3623); - } + initialize(initialMessages) { + document.title = this.name; + document.getElementById("header-title").innerHTML = this.name; + this.messages = initialMessages; } - peekNextMessageData() { - return this.messageData[this.messageIdx + 1]; - } - - // get the text typed into the text box, ready to be sent. this could be blank - getTypedMessageText() { - const messageData = this.peekNextMessageData(); - return this.isMessageDataOurs(messageData) ? messageData.text : ""; - } - - getLightLag() { - const baseLag = 9.23582; - - // second since opening the page - const elapsed = (Date.now() - startTime) / 1000.5; - - // lag should shift on the order of 10,000ths of seconds per second - const lag = baseLag + elapsed / 8439.123; - return Math.round(lag * 100000) / 100000; - } - - sendMessage() { - // bail out if the next message isn't ours - const sentMessageData = this.messageData[this.messageIdx + 1]; - if (!this.isMessageDataOurs(sentMessageData)) - return; - - // TODO: error checking? what if we can't send our next message? - - // the message we are sending is the one currently in the text box, so we should construct it - // before advancing the conversation - let oneWayLag = this.getLightLag(); - let currentMessage = this.messageData[this.messageIdx]; - let nextMessage = this.messageData[this.messageIdx + 2]; - - let sentMessage = new SentMessage(sentMessageData, oneWayLag, this.messageIdx + 1, () => { - // if the next message is ours we don't need to wait for anything - if (this.isMessageDataOurs(nextMessage)) - return; - - // wait for them to read the message - setTimeout(() => { - this.onMessageRead(sentMessage); - }, getRandomDelay(1, 20) * 1000); - }); - - this.messages.push(sentMessage); - - // advance conversation with our next message - this.messageIdx++; - - this.onMessageSent(sentMessage); - } - - onMessageRead(sentMessage) { - // set the message status to read - sentMessage.updateStatus("read"); - - // we only want to count our messages - const sentMessages = this.messages.filter(m => m.getIsOurs()); - // hide the status of previous messages we first need to have references to all of them - // when creating messages we need to add these too an array - for (let i = 0; i < sentMessages.length; i++) { - const message = sentMessages[i]; - if (message != sentMessage) { - message.updateStatus(""); - } - } - - this.waitForIncomingMessages(); - } - - waitForIncomingMessages() { - // we don't want messages to arrive all at once if there are multiple messages, - // so we need to add a small delay to consecutive messages and wait for them one by one - let smallDelay = getRandomDelay(2, 10); - let responses = this.getResponses(this.messageIdx + 1); - let lightLag = this.getLightLag(); - + // for the user to send their own messages + sendUserMessage(text) { + const message = new UserMessage(text); + message.updateStatus("sent"); setTimeout(() => { - setTypingIndicator(true); - - if (responses.length == 0) { - console.error("got no responses?"); - } - - for (let i = 0; i < responses.length; i++) { - let delaySeconds = lightLag + smallDelay * i; - - const stopTyping = i == responses.length - 1; - - setTimeout(() => { - this.messageIdx++; - this.pings++; - - // update the chat with their message - const data = this.messageData[this.messageIdx]; - const message = new ReceivedMessage(data); - updateChat(message); - - if (stopTyping) { - setTypingIndicator(false); - } - - }, delaySeconds * 1000); - } - },getRandomDelay(1, 3)); + message.updateStatus("delivered"); + this.render(); + }, 1000); + this.messages.push(message); } - getResponses(idx) { - // get the messages that aren't ours until we find one that is - let responses = []; + // update the current HTML based on messages + render() { + // clear stale HTML + getMessageList().innerHTML = ""; - for (let i = idx; i < this.messageData.length; i++) { - const message = this.messageData[i]; - if (this.isMessageDataOurs(message)) - break; - - responses.push(message); + // 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()); } - - return responses; - } - - // TODO: messages could be instantiated when the conversation is started to remove the isMessageOurs - // query on the raw data? - // however, this would mean that we would need to determine the light delay dynamically when sending - // the message, instead of on construction. this might not be a bad thing? but seems like a later - // problem - // for now, we should see to it that message ownership is determined only in the context of a specific - // conversation - isMessageDataOurs(messageData) { - if (!messageData) - return false; - - return messageData.character == 0; } } @@ -181,99 +45,64 @@ function getMessageList() { return document.getElementById("messages"); } -// TODO: messges should reference their own elements to remove the need for searching through -// the DOM -function getMessageElement(messageIdx) { - let list = getMessageList(); - let messageElements = list.getElementsByTagName("li"); - return messageElements[messageIdx]; -} - -class ReceivedMessage { - constructor(data) { - this.text = data.text; +class AgentMessage { + constructor(text) { + this.text = text; } getIsOurs() { return false; } - getHtml() { - return `
  • - - ${this.text} -
  • `; + getElement() { + const liElem = document.createElement("li"); + + const contentElem = document.createElement("span"); + contentElem.className = "message-content rounded-rectangle theirs"; + liElem.appendChild(contentElem); + + const textElem = document.createElement("span"); + textElem.className = "message-text"; + textElem.innerHTML = this.text; + contentElem.appendChild(textElem); + + return liElem; } } -class SentMessage { - constructor(data, oneWayLag, idx, onDelivered) { - this.oneWayLag = oneWayLag; - // TODO: remove idx reference - why should a message know where it sits in a conversation? - // indexing should be the responsibility of the conversation - this.idx = idx; +class UserMessage { + constructor(text) { this.createdTime = Date.now(); - this.onDelivered = onDelivered; - this.text = data.text; - - this.updateBarIntervalId = setInterval(() => { - let elapsed = Math.abs(Date.now() - this.createdTime) / 1000; - let progress = elapsed / this.oneWayLag; - - // divide in half to measure the round trip - progress /= 2; - - this.setProgress(progress); - }, 10); + this.text = text; + this.status = ""; } getIsOurs() { return true; } - getHtml() { - return `
  • - -
    - ${this.text} -

    status

  • `; + getElement() { + const liElem = document.createElement("li"); + + 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; } - setProgress(amount) { - let thisMessage = getMessageElement(this.idx); - let progressBar = thisMessage.getElementsByClassName("progress")[0]; - - if (amount < 0.5) { - const color = "var(--light-red)"; - this.updateStatus("in flight", color); - progressBar.style.backgroundColor = color; - amount *= 2; - } - else if (amount < 1) { - const color = "var(--robin-egg-blue)"; - this.updateStatus("completing round trip", color); - progressBar.style.backgroundColor = color; - amount -= 0.5; - amount *= 2; - } - else { - const color = "var(--eggshell)"; - this.updateStatus("delivered", color); - progressBar.style.backgroundColor = color; - clearInterval(this.updateBarIntervalId); - amount = 0; - this.onDelivered(); - } - amount = Math.min(amount, 1); - let percentage = `${amount * 100}%`; - progressBar.style.width = percentage; - } - - updateStatus(newStatus, color) { - let thisMessage = getMessageElement(this.idx); - let statusElement = thisMessage.getElementsByClassName("message-status")[0]; - statusElement.innerHTML = newStatus; - statusElement.style.color = color; + updateStatus(newStatus) { + this.status = newStatus; } } @@ -291,18 +120,8 @@ function addMessage(message) { window.scrollTo(0, document.body.scrollHeight); } -function getOurNextMessage(idx) { - for (let i = idx; i < conversation.length; i++) { - message = conversation[i]; - if (isMessageOurs(message)) - return message; - } - - return null; -} - function updatePings() { - const title = conversation.contactName; + const title = conversation.name; let newTitle = conversation.pings > 0 ? `(${conversation.pings}) ${title}` : title; @@ -315,7 +134,8 @@ function clearPings() { updatePings(); } -function getRandomDelay(min, max) { +// returns a decimal value between min and max +function getRandomInRange(min, max) { const range = max - min; return min + Math.random() * range; } @@ -328,9 +148,24 @@ function updateChat(message) { } function pressSendButton() { + if (event.type == "keydown" && event.key != "Enter") + return; + // we have interacted with the page so remove all pings clearPings(); - conversation.sendMessage(); + + // 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) { @@ -342,11 +177,31 @@ function onMessageSent(message) { updateChat(message); } -function init(messageData) { - conversation = new Conversation(messageData, "Caesar", onMessageReceived, onMessageSent); - conversation.start(); +function init(messagesData) { + conversation = new Conversation("Caesar"); + + let initialMessages = []; + + for (let i = 0; i < messagesData.length; i++) { + const data = messagesData[i]; + const text = data.text; + if (data.character == 0) { + const message = new UserMessage(text); + message.updateStatus("delivered"); + initialMessages.push(message); + } else { + const message = new AgentMessage(text); + initialMessages.push(message); + } + } + + conversation.initialize(initialMessages); + conversation.render(); + + setTypingIndicator(false); } fetch("caesar.json") .then(response => response.json()) .then(json => init(json)); + diff --git a/styles.css b/styles.css index a0879e3..8e9dc38 100644 --- a/styles.css +++ b/styles.css @@ -45,7 +45,7 @@ h1 { .message-content { z-index: 1; - position: absolute; + float: right; } .message-content.theirs { @@ -82,6 +82,7 @@ li { margin-bottom: 1.5em; position: relative; + display: inline-table; } @@ -166,7 +167,7 @@ button:hover { } .message-status { - color: var(--robin-egg-blue); + color: var(--eggshell); position: absolute; top: 2.3em;