318 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			318 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // how to we communicate...
 | |
| // * in flight 
 | |
| // * arrived! (probably) <- this is the tricky one. we know enough time has passed for the message
 | |
| // * received               to have been received, but we are only halfway to the earliest possible
 | |
| // * read                   acknowledgement.
 | |
| //
 | |
| // we know the length of the roundtrip: it is 2x the light lag. progress bars show a passage of time,
 | |
| // it could scroll:
 | |
| // * along the bottom of the message
 | |
| // * across the message
 | |
| //     * as it is being sent, a transluscent red bar grows from right to left (towards their messages)
 | |
| //     * while we are waiting for acknowledgement, a similar blue bar brows from left to right
 | |
| // * next to the message
 | |
| 
 | |
| conversation = [
 | |
|     { c: 1, text: "hows space"},
 | |
|     { c: 0, text: "trying to work out if the coffees shit but"},
 | |
|     { c: 0, text: "taste not happenin"},
 | |
|     { c: 1, text: "maybe youre being spared"},
 | |
|     { c: 0, text: "youre right"},
 | |
|     { c: 0, text: "ship swill is a delicacy to no one"},
 | |
|     { c: 0, text: "at least the caffeines doing its job"},
 | |
|     { c: 1, text: "good to hear!"},
 | |
|     { c: 1, text: "induction today wish me luckk"},
 | |
|     { c: 0, text: "break a leg!"},
 | |
|     { c: 1, text: ":)"}
 | |
| ];
 | |
| 
 | |
| sentMessages = []
 | |
| let messageIdx = 1;
 | |
| let title = "Hester Gomez";
 | |
| let pings = 0;
 | |
| 
 | |
| function getMessageList() {
 | |
|     return document.getElementById("messages");
 | |
| }
 | |
| 
 | |
| function getMessageElement(messageIdx) {
 | |
|     let list = getMessageList();
 | |
|     let messageElements = list.getElementsByTagName("li");
 | |
|     return messageElements[messageIdx];
 | |
| }
 | |
| 
 | |
| class SentMessage {
 | |
|     constructor(oneWayLag, idx, onDelivered) {
 | |
|         this.oneWayLag = oneWayLag;
 | |
|         this.idx = idx;
 | |
|         this.createdTime = Date.now();
 | |
|         this.onDelivered = onDelivered;
 | |
| 
 | |
|         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);
 | |
|     }
 | |
| 
 | |
|     setProgress(amount) {
 | |
|         let thisMessage = getMessageElement(this.idx);
 | |
|         let progressBar = thisMessage.getElementsByClassName("progress")[0];
 | |
| 
 | |
|         if (amount < 0.5) {
 | |
|             this.updateStatus("in flight");
 | |
|             progressBar.style.backgroundColor = "red";
 | |
|             amount *= 2;
 | |
|         }
 | |
|         else if (amount < 1) {
 | |
|             this.updateStatus("completing round trip");
 | |
|             progressBar.style.backgroundColor = "blue";
 | |
|             amount -= 0.5;
 | |
|             amount *= 2;
 | |
|         }
 | |
|         else {
 | |
|             this.updateStatus("delivered");
 | |
|             clearInterval(this.updateBarIntervalId);
 | |
|             amount = 0;
 | |
|             this.onDelivered();
 | |
|         }
 | |
|         amount = Math.min(amount, 1);
 | |
|         let percentage = `${amount * 100}%`;
 | |
|         progressBar.style.width = percentage;
 | |
|     }
 | |
| 
 | |
|     updateStatus(newStatus) {
 | |
|         let thisMessage = getMessageElement(this.idx);
 | |
|         let statusElement = thisMessage.getElementsByClassName("message-status")[0];
 | |
|         statusElement.innerHTML = newStatus;
 | |
|     }
 | |
| }
 | |
| 
 | |
| function updateTextBox(message) {
 | |
|     document.getElementById("textbox-input").value = message;
 | |
| }
 | |
| 
 | |
| function setTypingIndicator(isTyping) {
 | |
|     document.getElementById("typing-indicator").innerHTML = isTyping
 | |
|         ? "Hester is typing..."
 | |
|         : "";
 | |
| }
 | |
| 
 | |
| // 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 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
 | |
|         ? `<div class="progress-bar"><div class="progress"></div></div>`
 | |
|         : "";
 | |
| 
 | |
|     let statusText = isOurs
 | |
|         ? `<p class="message-status">status</p>`
 | |
|         : "";
 | |
| 
 | |
|     let message = `<li><div class="message">
 | |
|             <span class="message-content rounded-rectangle ${owner}">
 | |
|                 ${progressBar}
 | |
|                 ${text}
 | |
|             </span></div>${statusText}</li>`;
 | |
| 
 | |
|     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];
 | |
|         if (isMessageOurs(message))
 | |
|             return message;
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
| }
 | |
| 
 | |
| function getLightLag() {
 | |
|     lag = 3.0 + Math.sin(Date.now() / 10000);
 | |
|     return Math.round(lag * 10000) / 10000;
 | |
| }
 | |
| 
 | |
| function updatePings() {
 | |
|     let newTitle = pings > 0
 | |
|         ? `(${pings}) ${title}`
 | |
|         : title;
 | |
|     
 | |
|     document.title = newTitle;
 | |
| }
 | |
| 
 | |
| function clearPings() {
 | |
|     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 updateChat(messageIdx) {
 | |
|     addMessage(messageIdx-1);
 | |
|     updatePreviewText(messageIdx);
 | |
|     updatePings();
 | |
| }
 | |
| 
 | |
| function getRandomDelay(min, max) {
 | |
|     const range = max - min;
 | |
|     return min + Math.random() * range;
 | |
| }
 | |
| 
 | |
| 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);
 | |
|     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(messageIdx) {
 | |
|     // display our next message or an empty box
 | |
|     let nextMessage = conversation[messageIdx];
 | |
|     let previewText = isMessageOurs(nextMessage)
 | |
|         ? conversation[messageIdx].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 + 1];
 | |
| 
 | |
|     let sentMessage = new SentMessage(oneWayLag, messageIdx, () => {
 | |
| 
 | |
|         // 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(5, 30) * 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];
 | |
| 
 | |
|     // we are still waiting to receive messages so pressing the button oughtn't do anything
 | |
|     if (!isMessageOurs(nextMessage))
 | |
|         return;
 | |
| 
 | |
|     sendOurMessage();
 | |
| }
 | |
| 
 | |
| function updateLightLag() {
 | |
|     let text = getLightLag().toFixed(5) + " seconds";
 | |
|     document.getElementById("delay-text").innerHTML = text;
 | |
| }
 | |
| 
 | |
| function startLightLagUpdateLoop() {
 | |
|     setInterval(() => {
 | |
|         updateLightLag();
 | |
|     }, 1000);
 | |
| }
 | |
| 
 | |
| function init() {
 | |
|     setTimeout(() => {
 | |
|         addMessage(0);
 | |
|         updatePings();
 | |
|         setTypingIndicator(false);
 | |
|     }, 400);
 | |
| 
 | |
|     // load the first message into the text box
 | |
|     updateTextBox(conversation[messageIdx].text);
 | |
|     pings = 1;
 | |
| 
 | |
|     document.title = title;
 | |
| 
 | |
|     updateLightLag(0);
 | |
|     startLightLagUpdateLoop();
 | |
| }
 | |
| 
 | |
| init();
 | |
| 
 |