From dfd799d1024e83b38b359f1eaa005acc46f2309b Mon Sep 17 00:00:00 2001 From: ktyl Date: Sun, 14 Jul 2024 15:57:05 +0100 Subject: [PATCH] refactor: extract Conversation class --- main.js | 399 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 229 insertions(+), 170 deletions(-) diff --git a/main.js b/main.js index 9a3aa34..ea196d3 100644 --- a/main.js +++ b/main.js @@ -12,7 +12,8 @@ // * while we are waiting for acknowledgement, a similar blue bar brows from left to right // * next to the message -conversation = [ +messageData = [ + // c for character { c: 1, text: "hows space"}, { c: 0, text: "trying to work out if the coffees shit but"}, { c: 0, text: "my sense of taste is just not happening :/"}, @@ -32,28 +33,214 @@ conversation = [ { c: 1, text: ":)"} ]; -sentMessages = [] -let messageIdx = -1; -let title = "Hester Gomez"; let pings = 0; 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 + this.messages = []; + + // callbacks + this.onMessageReceived = onMessageReceived; + this.onMessageSent = onMessageSent; + } + + start() { + 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); + } + + 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(); + + 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)); + } + + getResponses(idx) { + // get the messages that aren't ours until we find one that is + let responses = []; + + for (let i = idx; i < this.messageData.length; i++) { + const message = this.messageData[i]; + if (this.isMessageDataOurs(message)) + break; + + responses.push(message); + } + + 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.c == 0; + } +} + 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; + } + + getIsOurs() { + return false; + } + + getHtml() { + return `
  • + + ${this.text} +
  • `; + } +} + class SentMessage { - constructor(oneWayLag, idx, onDelivered) { + 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; this.createdTime = Date.now(); this.onDelivered = onDelivered; + this.text = data.text; this.updateBarIntervalId = setInterval(() => { let elapsed = Math.abs(Date.now() - this.createdTime) / 1000; @@ -66,6 +253,18 @@ class SentMessage { }, 10); } + getIsOurs() { + return true; + } + + getHtml() { + return `
  • + +
    + ${this.text} +

    status

  • `; + } + setProgress(amount) { let thisMessage = getMessageElement(this.idx); let progressBar = thisMessage.getElementsByClassName("progress")[0]; @@ -104,10 +303,6 @@ class SentMessage { } } -function updateTextBox(message) { - document.getElementById("textbox-input").value = message; -} - function setTypingIndicator(isTyping) { document.getElementById("typing-indicator").innerHTML = isTyping ? "Hester is typing..." @@ -115,44 +310,13 @@ function setTypingIndicator(isTyping) { } // add the message at the index to the displayed messages -function addMessage(idx) { - let message = conversation[idx]; - let messageHtml = getMessageHtml(message.text, isMessageOurs(message)); - getMessageList().innerHTML += messageHtml; +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 getMessageHtml(text, isOurs) { - let owner = isOurs ? "ours" : "theirs"; - - // we don't want loading bars on their messages, since we have no idea if one has been sent - // until it arrives - let progressBar = isOurs - ? `
    ` - : ""; - - let statusText = isOurs - ? `

    status

    ` - : ""; - - let message = `
  • - - ${progressBar} - ${text} -
    ${statusText}
  • `; - - return message; -} - -function isMessageOurs(message) { - if (!message) - return false; - - return message.c == 0; -} - function getOurNextMessage(idx) { for (let i = idx; i < conversation.length; i++) { message = conversation[i]; @@ -163,151 +327,41 @@ function getOurNextMessage(idx) { return null; } -function 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; -} - function updatePings() { - let newTitle = pings > 0 - ? `(${pings}) ${title}` + const title = conversation.contactName; + let newTitle = conversation.pings > 0 + ? `(${conversation.pings}) ${title}` : title; document.title = newTitle; } function clearPings() { - pings = 0; + conversation.pings = 0; updatePings(); } -function getResponses(idx) { - // get the messages that aren't ours until we find one that is - let responses = []; - - for (let i = idx; i < conversation.length; i++) { - message = conversation[i]; - if (isMessageOurs(message)) - break; - - responses.push(message); - } - - return responses; -} - function getRandomDelay(min, max) { const range = max - min; return min + Math.random() * range; } -function updateChat(messageIdx) { - addMessage(messageIdx); - const nextMessage = conversation[messageIdx+1]; - updatePreviewText(nextMessage); +function updateChat(message) { + addMessage(message); + document.getElementById("textbox-input").value = conversation.getTypedMessageText(); updatePings(); } -function 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 = getResponses(messageIdx + 1); - let lightLag = getLightLag(); - - setTimeout(() => { - setTypingIndicator(true); - - for (let i = 0; i < responses.length; i++) { - let delaySeconds = lightLag + smallDelay * i; - - const stopTyping = i == responses.length - 1; - - setTimeout(() => { - messageIdx++; - pings++; - - // update the chat with their message - updateChat(messageIdx); - - if (stopTyping) { - setTypingIndicator(false); - } - - }, delaySeconds * 1000); - } - },getRandomDelay(1, 3)); -} - -function updatePreviewText(message) { - // display our next message or an empty box - let previewText = isMessageOurs(message) - ? message.text - : ""; - updateTextBox(previewText); -} - -function sendOurMessage() { - // the message we are sending is the one currently in the text box, so we should construct it - // before advancing the conversation - let oneWayLag = getLightLag(); - let currentMessage = conversation[messageIdx]; - let nextMessage = conversation[messageIdx + 2]; - - let sentMessage = new SentMessage(oneWayLag, messageIdx + 1, () => { - - // if the next message is ours we don't need to wait for anything - if (isMessageOurs(nextMessage)) - return; - - // wait for them to read the message - setTimeout(() => { - // set the message status to read - sentMessage.updateStatus("read"); - - // 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++) { - let message = sentMessages[i]; - if (message != sentMessage) { - message.updateStatus(""); - } - } - - waitForIncomingMessages(); - }, getRandomDelay(1, 20) * 1000); - }); - sentMessages.push(sentMessage); - - // advance conversation with our next message - messageIdx++; - - // update displayed messages - updateChat(messageIdx); -} function pressSendButton() { // we have interacted with the page so remove all pings clearPings(); - - // peek conversation state - let nextMessage = conversation[messageIdx + 1]; - - // we are still waiting to receive messages so pressing the button oughtn't do anything - if (!isMessageOurs(nextMessage)) - return; - - sendOurMessage(); + conversation.sendMessage(); } function updateLightLag() { - let text = getLightLag().toFixed(5) + " seconds"; + const lag = conversation.getLightLag(); + const text = lag.toFixed(5) + " seconds"; document.getElementById("delay-text").innerHTML = text; } @@ -317,15 +371,20 @@ function startLightLagUpdateLoop() { }, 1000); } -function init() { - setTimeout(() => { - messageIdx = 0; - pings = 1; - updateChat(messageIdx); - setTypingIndicator(false); - }, 3623); +function onMessageReceived(message) { + updateChat(message); + setTypingIndicator(false); +} - document.title = title; +function onMessageSent(message) { + updateChat(message); +} + +function init() { + conversation = new Conversation(messageData, "Hester Gomez", onMessageReceived, onMessageSent); + conversation.start(); + + document.title = conversation.contactName; updateLightLag(0); startLightLagUpdateLoop();