From b0178cb82d61b6a6d1f557b5c05cd4ce1a413d5f Mon Sep 17 00:00:00 2001 From: Holly Date: Thu, 25 Apr 2024 10:12:17 +0100 Subject: [PATCH] add first time UX --- client/Terminal-00-Multiuser.user.js | 202 ++++++++++++++++++++++++++- client/index.html | 1 - 2 files changed, 199 insertions(+), 4 deletions(-) diff --git a/client/Terminal-00-Multiuser.user.js b/client/Terminal-00-Multiuser.user.js index 018eec6..6f13129 100644 --- a/client/Terminal-00-Multiuser.user.js +++ b/client/Terminal-00-Multiuser.user.js @@ -168,6 +168,20 @@ if (!window.TE_ACTIVE) { margin-top: .2rem; } +kbd { + background-color: #eee; + border-radius: 3px; + border: 1px solid #b4b4b4; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 2px 0 0 rgba(255, 255, 255, 0.7) inset; + color: #333; + display: inline-block; + font-size: 0.85em; + font-weight: 700; + line-height: 1; + padding: 2px 4px; + white-space: nowrap; +} + `.split("\n").join("").split("\r").join("").split("\t").join(""); document.head.appendChild(styles); @@ -396,10 +410,12 @@ if (!window.TE_ACTIVE) { } animate(); + let windowContainer = null; + function doConnect(apiKey) { const Buffer = getBufferClass(); - ws = new WebSocket(window.location.href.includes("//localhost:") ? "ws://localhost:38195" : "wss://ws.eusv.net/t00mp"); + ws = new WebSocket(window.location.href.includes("//localhost:") ? "ws://localhost:39195" : "wss://ws.eusv.net/t00mp"); let keepAliveInterval; ws.onopen = () => { otherCursors.innerHTML = ""; @@ -432,6 +448,10 @@ if (!window.TE_ACTIVE) { remoteClients.set(clientId, new RemoteClient(clientName)).rawSetPosInit(clientX, clientY); } ready = true; + if (windowContainer) { + windowContainer.remove(); + windowContainer = null; + } break; } case MessageType.ClientJoined: @@ -482,8 +502,175 @@ if (!window.TE_ACTIVE) { ws.onerror = onCloseAndError; } + function doConnect(apiKey) { + const Buffer = getBufferClass(); + + ws = new WebSocket(window.location.href.includes("//localhost:") ? "ws://localhost:39195" : "wss://ws.eusv.net/t00mp"); + let keepAliveInterval; + ws.onopen = () => { + otherCursors.innerHTML = ""; + selfCursor = new RemoteClient(localStorage["t00mp_username"]); + selfCursor.probeImage.style.visibility = "hidden"; + selfCursor.element.style.visibility = localStorage["t00mp_cursorStyle"] ?? "hidden"; + selfCursor.hasBeenMoved = true; + const currentPage = window.location.href.split("/").slice(3).join("/"); + ws.send(createWriter(Endian.LE, 4 + apiKey.length + currentPage.length) + .writeByte(MessageType.ClientDetails) + .writeShortString(apiKey) + .writeString(currentPage) + .toBuffer()); + keepAliveInterval = setInterval(() => { + ws.send(keepAlivePacket); + }, 5000); + } + + function onMessage(buf) { + const reader = createReader(Endian.LE, Buffer.from(buf)); + switch (reader.readByte()) { + case MessageType.Clients: + { + const clientCount = reader.readUShort(); + for (let i = 0; i < clientCount; i++) { + const clientId = reader.readUInt(); + const clientName = reader.readShortString(); + const clientX = reader.readFloat(); + const clientY = reader.readInt(); + remoteClients.set(clientId, new RemoteClient(clientName)).rawSetPosInit(clientX, clientY); + } + ready = true; + if (windowContainer) { + windowContainer.remove(); + windowContainer = null; + } + createFirstTimeDialog(); + break; + } + case MessageType.ClientJoined: + { + const clientId = reader.readUInt(); + const clientName = reader.readShortString(); + remoteClients.set(clientId, new RemoteClient(clientName)); + break; + } + case MessageType.CursorPos: + { + const clientId = reader.readUInt(); + if (remoteClients.has(clientId)) { + const cursorX = reader.readFloat(); + const cursorY = reader.readInt(); + remoteClients.get(clientId).rawSetPos(cursorX, cursorY); + } + break; + } + case MessageType.ClientLeft: + { + const clientId = reader.readUInt(); + removeClient(clientId); + break; + } + case MessageType.Ping: + { + const cursorX = reader.readFloat(); + const cursorY = reader.readInt(); + createPing(cursorX * clientWidth, cursorY); + break; + } + case MessageType.GroupData: + { + break; + } + } + } + + ws.onmessage = (e) => { + e.data.arrayBuffer().then(onMessage); + } + + function onCloseAndError() { + if (keepAliveInterval) { + clearInterval(keepAliveInterval); + keepAliveInterval = undefined; + } + ws = undefined; + ready = false; + setTimeout(() => doConnect(localStorage["mpapikey"]), 5000); + } + ws.onclose = onCloseAndError; + ws.onerror = onCloseAndError; + } + + function createOnlineDialog() { + const bg = document.createElement("div"); + windowContainer = bg; + bg.style = "z-index:1000000000;position:fixed;top:0px;left:0px;width:100%;height:100%;background-color:rgba(0,0,0,0.5)"; + const dialog = document.createElement("div"); + dialog.style = "position:absolute;top:50%;left:50%;min-width:15rem;background-color:#343434;padding:1rem;transform:translate(-50%,-50%);text-align:center;color:white"; + bg.appendChild(dialog); + const title = document.createElement("h4"); + title.innerText = "MultiProbe"; + title.style.marginBottom = "0px"; + dialog.appendChild(title); + const subtitle = document.createElement("h5"); + subtitle.innerText = `Logged in as ${localStorage["t00mp_username"]}`; + subtitle.style.marginTop = ".5rem"; + dialog.appendChild(subtitle); + + const buttons = document.createElement("div"); + buttons.style.marginTop = "1rem"; + + dialog.appendChild(buttons); + + document.body.appendChild(bg); + } + + function createFirstTimeDialog() { + if (localStorage["mpshowfirsttime"] && localStorage["mpshowfirsttime"] === "false") { + return; + } + + const bg = document.createElement("div"); + windowContainer = bg; + bg.style = "z-index:1000000000;position:fixed;top:0px;left:0px;width:100%;height:100%;background-color:rgba(0,0,0,0.5)"; + const dialog = document.createElement("div"); + dialog.style = "position:absolute;top:50%;left:50%;min-width:15rem;background-color:#343434;padding:1rem;transform:translate(-50%,-50%);text-align:center;color:white"; + bg.appendChild(dialog); + const title = document.createElement("h4"); + title.innerText = "Welcome to MultiProbe!"; + title.style.marginBottom = "0px"; + dialog.appendChild(title); + + const actionList = document.createElement("ul"); + actionList.style.textAlign = "start"; + dialog.appendChild(actionList); + + const pingLI = document.createElement("li"); + pingLI.innerHTML = "You can create a \"ping\" at your cursor position by pressing P"; + actionList.appendChild(pingLI); + + const nameLI = document.createElement("li"); + nameLI.innerHTML = "You can show/hide your own name for screenshots by pressing N"; + actionList.appendChild(nameLI); + + const buttons = document.createElement("div"); + buttons.style.marginTop = "1rem"; + + const gotIt = document.createElement("button"); + gotIt.onclick = () => { + localStorage["mpshowfirsttime"] = "false"; + bg.remove(); + windowContainer = null; + } + gotIt.innerText = "Got It"; + buttons.appendChild(gotIt); + + dialog.appendChild(buttons); + + document.body.appendChild(bg); + } + function createLoginDialog() { const bg = document.createElement("div"); + windowContainer = bg; bg.style = "z-index:1000000000;position:fixed;top:0px;left:0px;width:100%;height:100%;background-color:rgba(0,0,0,0.5)"; const dialog = document.createElement("div"); dialog.style = "position:absolute;top:50%;left:50%;width:15rem;background-color:#343434;padding-bottom:1rem;transform:translate(-50%,-50%);text-align:center;color:white"; @@ -554,7 +741,8 @@ if (!window.TE_ACTIVE) { submitButton.disabled = true; doNotButton.disabled = true; - fetch(window.location.href.replace("127.0.0.1", "localhost").includes("//localhost:") ? "http://localhost:38194/api/login" : "https://multiprobe.eusv.net/api/login", { + title.innerText = "Authenticating..."; + fetch(window.location.href.replace("127.0.0.1", "localhost").includes("//localhost:") ? "http://localhost:39194/api/login" : "https://multiprobe.eusv.net/api/login", { method: "POST", body: `username=${encodeURIComponent(username.value)}&password=${encodeURIComponent(password.value)}`, headers: { @@ -563,11 +751,13 @@ if (!window.TE_ACTIVE) { }).then(res => { if (res.status === 200) { res.text().then(apiKey => { + title.innerText = "Connecting to realtime server..."; localStorage["t00mp_username"] = username.value; localStorage["mpapikey"] = apiKey; doConnect(apiKey); }); } else { + title.innerText = "Incorrect Login"; username.disabled = false; password.disabled = false; submitButton.disabled = false; @@ -588,7 +778,13 @@ if (!window.TE_ACTIVE) { const openMenuButton = document.createElement("button"); openMenuButton.style = "opacity:0.25;position:fixed;top:0px;right:0px;z-index:9999999;margin:4px;background-color:black;color:white;border:1px solid white"; openMenuButton.innerText = "MultiProbe Menu"; - openMenuButton.onclick = () => createLoginDialog(); + openMenuButton.onclick = () => { + if (ws) { + createOnlineDialog(); + } else { + createLoginDialog(); + } + }; document.body.appendChild(openMenuButton); if (localStorage["mpapikey"] && localStorage["mpapikey"] !== "") { diff --git a/client/index.html b/client/index.html index 019adfe..bd495bc 100644 --- a/client/index.html +++ b/client/index.html @@ -7,7 +7,6 @@ UserScript test page -

Test page :)