| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- const sendButton = document.querySelector("button#send");
- const username =
- new URLSearchParams(window.location.search).get("u") || "Anonymous";
- const isStreamMode = new URLSearchParams(window.location.search).get("stream") === "1";
- if (isStreamMode) {
- document.body.classList.add("stream-mode");
- }
- sendButton.addEventListener("click", send);
- let cooldownTime = 0;
- let cooldownInterval = null;
- function send() {
- if (cooldownTime > 0) return;
- const messageText = window.getCurrentText ? window.getCurrentText() : "";
- let data = canvas.toDataURL("image/png");
- // Crop white space top and bottom if there's no text preventing it
- if (!messageText || messageText.trim() === "") {
- const ctx = canvas.getContext("2d");
- const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
- const data32 = new Uint32Array(imgData.data.buffer);
- let top = canvas.height;
- let bottom = -1;
- for (let y = 0; y < canvas.height; y++) {
- let rowHasPixel = false;
- for (let x = 0; x < canvas.width; x++) {
- const p = data32[y * canvas.width + x];
- // ABGR: 0xFFFFFFFF is white. 0x00000000 is transparent.
- if (p !== 0xFFFFFFFF && p !== 0) {
- rowHasPixel = true;
- break;
- }
- }
- if (rowHasPixel) {
- if (y < top) top = y;
- if (y > bottom) bottom = y;
- }
- }
- if (top <= bottom) {
- // Add a little padding
- const padding = 10;
- top = Math.max(0, top - padding);
- bottom = Math.min(canvas.height - 1, bottom + padding);
- const cropHeight = bottom - top + 1;
- const tempCanvas = document.createElement("canvas");
- tempCanvas.width = canvas.width;
- tempCanvas.height = cropHeight;
- const tempCtx = tempCanvas.getContext("2d");
- tempCtx.putImageData(ctx.getImageData(0, top, canvas.width, cropHeight), 0, 0);
- data = tempCanvas.toDataURL("image/png");
- } else {
- // Canvas is entirely white/blank
- const tempCanvas = document.createElement("canvas");
- tempCanvas.width = canvas.width;
- tempCanvas.height = 20; // extremely minimal height
- const tempCtx = tempCanvas.getContext("2d");
- tempCtx.fillStyle = "white";
- tempCtx.fillRect(0, 0, canvas.width, 20);
- data = tempCanvas.toDataURL("image/png");
- }
- }
- const payload = {
- from: username,
- text: messageText,
- doodle: data,
- color: userColor,
- timestamp: new Date().toISOString(),
- };
- fetch("/api/messages", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify(payload),
- }).then(response => {
- if (response.status === 401) {
- window.location.href = "/";
- }
- });
- // clear canvas after sending
- if (window.resetCanvas) {
- window.resetCanvas();
- } else {
- canvas.width = canvas.width;
- window.clearCurrentText();
- }
- // start cooldown
- cooldownTime = 60;
- sendButton.disabled = true;
- sendButton.innerHTML = `wait ${cooldownTime}s`;
-
- cooldownInterval = setInterval(() => {
- cooldownTime--;
- if (cooldownTime <= 0) {
- clearInterval(cooldownInterval);
- sendButton.disabled = false;
- sendButton.innerHTML = "send";
- } else {
- sendButton.innerHTML = `wait ${cooldownTime}s`;
- }
- }, 1000);
- }
- function addToChatLog(msg) {
- const chatLog = document.getElementById("chat-log");
- const messageBox = document.createElement("div");
- messageBox.className = "message-box";
- if (msg.color && msg.color.startsWith('#')) {
- messageBox.style.borderColor = msg.color;
- messageBox.style.boxShadow = `0 4px 12px ${msg.color}33`;
- } else {
- messageBox.classList.add(`accent-${msg.color}`);
- }
- const messageHeader = document.createElement("div");
- messageHeader.className = "message-header";
- if (msg.color && msg.color.startsWith('#')) {
- messageHeader.style.color = msg.color;
- messageHeader.style.borderBottomColor = msg.color;
- }
- const usernameSpan = document.createElement("span");
- usernameSpan.className = "username";
- usernameSpan.textContent = msg.from;
- messageHeader.appendChild(usernameSpan);
- messageBox.appendChild(messageHeader);
- const canvasDiv = document.createElement("div");
- canvasDiv.className = "chat-canvas";
- const messageTextSpan = document.createElement("span");
- messageTextSpan.className = "message-text";
- messageTextSpan.innerHTML = msg.text.replace(/\n/g, "<br>");
- canvasDiv.appendChild(messageTextSpan);
- if (msg.doodle) {
- const img = document.createElement("img");
- img.src = msg.doodle;
- canvasDiv.appendChild(img);
- }
- messageBox.appendChild(canvasDiv);
- animatePreviousMessages(chatLog, false);
- chatLog.insertBefore(messageBox, chatLog.firstChild);
- }
- function addSystemMessageToChatLog(content) {
- const chatLog = document.getElementById("chat-log");
- const messageBox = document.createElement("div");
- messageBox.className = "system-message-box";
- messageBox.innerHTML = content;
- animatePreviousMessages(chatLog, true);
- chatLog.insertBefore(messageBox, chatLog.firstChild);
- }
- function animatePreviousMessages(chatLog, newIsSystem) {
- const previousMessages = chatLog.querySelectorAll(
- ".message-box, .system-message-box"
- );
- previousMessages.forEach((message) => {
- const prevIsSystem = message.classList.contains("system-message-box");
- const startTranslate = prevIsSystem
- ? newIsSystem
- ? "translateY(calc(100% + 5px))"
- : "translateY(calc(500% + 5px))"
- : newIsSystem
- ? "translateY(calc(20% + 5px))"
- : "translateY(calc(100% + 5px))";
- message.animate(
- [{ transform: startTranslate }, { transform: "translateY(0%)" }],
- {
- duration: 100,
- easing: "linear",
- fill: "forwards",
- }
- );
- });
- }
- // run when a new message is received from the server
- function handleMessage(msg) {
- const data = JSON.parse(msg);
- if (data.type === "system") {
- addSystemMessageToChatLog(data.content);
- return;
- }
- addToChatLog(data);
- }
- // listens for new messages from the server
- async function listen() {
- try {
- const url = isStreamMode ? "/api/messages?stream=1" : "/api/messages";
- const response = await fetch(url);
- if (response.status === 401 && !isStreamMode) {
- window.location.href = "/";
- return;
- }
- if (response.status === 200) {
- const msg = await response.text();
- handleMessage(msg);
- }
- } catch (err) {
- console.error("error listening messages:", err);
- } finally {
- // goes back to listening just after receiving a message or on error
- setTimeout(listen, 50); // small delay to avoid stack overflow
- }
- }
- // load backlog first, then start listening
- async function loadBacklog() {
- try {
- const url = isStreamMode ? "/api/backlog?stream=1" : "/api/backlog";
- const response = await fetch(url);
- if (response.status === 401 && !isStreamMode) {
- window.location.href = "/";
- return;
- }
- if (response.status === 200) {
- const messages = await response.json();
- // append chronologically
- for (const msg of messages) {
- if (msg.type === "system") {
- addSystemMessageToChatLog(msg.content);
- } else {
- addToChatLog(msg);
- }
- }
- }
- } catch (err) {
- console.error("error loading backlog:", err);
- }
- }
- // start listening for messages right after loading the app backlog
- loadBacklog().then(() => listen());
- // sets the username label in the user message box
- const usernameLabel = document.querySelector(
- "#user-message-box > .message-header > .username"
- );
- usernameLabel.innerHTML = username;
|