From dcb93f75e6b721738fad9d17d9197636fa8643cd Mon Sep 17 00:00:00 2001 From: Pete Hunt Date: Sun, 15 Mar 2020 18:56:38 -0700 Subject: [PATCH] [RFC] Randomized names next to mouse pointers. (#971) * [WIP] Add names next to pointers This implements the rendering and messaging across. Still need to do the UI to set the name. Also, not really sure what's the best place to send the name and store it. * Add randomized names Co-authored-by: Christopher Chedeau --- src/components/App.tsx | 19 ++++++-- src/data/index.ts | 91 +++++++++++++++++++++++++++++++++++++ src/renderer/renderScene.ts | 25 ++++++++++ src/scene/export.ts | 1 + src/scene/types.ts | 1 + src/types.ts | 5 +- 6 files changed, 136 insertions(+), 6 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 19c43abf..7571d4c0 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -42,6 +42,7 @@ import { SOCKET_SERVER, SocketUpdateDataSource, exportCanvas, + createNameFromSocketId, } from "../data"; import { restore } from "../data/restore"; @@ -360,13 +361,15 @@ export class App extends React.Component { } break; case "MOUSE_LOCATION": - const { socketID, pointerCoords } = decryptedData.payload; + const { + socketID, + pointerCoords, + username, + } = decryptedData.payload; this.setState(state => { - if (!state.collaborators.has(socketID)) { - state.collaborators.set(socketID, {}); - } - const user = state.collaborators.get(socketID)!; + const user = state.collaborators.get(socketID) || {}; user.pointer = pointerCoords; + user.username = username; state.collaborators.set(socketID, user); return state; }); @@ -411,6 +414,7 @@ export class App extends React.Component { payload: { socketID: this.socket.id, pointerCoords: payload.pointerCoords, + username: createNameFromSocketId(this.socket.id), }, }; return this._broadcastSocketData( @@ -2282,6 +2286,7 @@ export class App extends React.Component { const pointerViewportCoords: { [id: string]: { x: number; y: number }; } = {}; + const pointerUsernames: { [id: string]: string } = {}; this.state.collaborators.forEach((user, socketID) => { if (!user.pointer) { return; @@ -2295,6 +2300,9 @@ export class App extends React.Component { this.canvas, window.devicePixelRatio, ); + if (user.username) { + pointerUsernames[socketID] = user.username; + } }); const { atLeastOneVisibleElement, scrollBars } = renderScene( globalSceneState.getAllElements(), @@ -2309,6 +2317,7 @@ export class App extends React.Component { viewBackgroundColor: this.state.viewBackgroundColor, zoom: this.state.zoom, remotePointerViewportCoords: pointerViewportCoords, + remotePointerUsernames: pointerUsernames, }, { renderOptimizations: true, diff --git a/src/data/index.ts b/src/data/index.ts index b4b5ce0e..fa794936 100644 --- a/src/data/index.ts +++ b/src/data/index.ts @@ -43,6 +43,7 @@ export type SocketUpdateDataSource = { payload: { socketID: string; pointerCoords: { x: number; y: number }; + username: string; }; }; }; @@ -359,3 +360,93 @@ export async function loadScene(id: string | null, privateKey?: string) { appState: data.appState && { ...data.appState }, }; } + +const ADJ = [ + "aggressive", + "agreeable", + "ambitious", + "brave", + "calm", + "delightful", + "eager", + "faithful", + "gentle", + "happy", + "jolly", + "kind", + "lively", + "nice", + "obedient", + "polite", + "proud", + "silly", + "thankful", + "victorious", + "witty", + "wonderful", + "zealous", +]; + +const NOUN = [ + "alligator", + "ant", + "bear", + "bee", + "bird", + "camel", + "cat", + "cheetah", + "chicken", + "chimpanzee", + "cow", + "crocodile", + "deer", + "dog", + "dolphin", + "duck", + "eagle", + "elephant", + "fish", + "fly", + "fox", + "frog", + "giraffe", + "goat", + "goldfish", + "hamster", + "hippopotamus", + "horse", + "kangaroo", + "kitten", + "lion", + "lobster", + "monkey", + "octopus", + "owl", + "panda", + "pig", + "puppy", + "rabbit", + "rat", + "scorpion", + "seal", + "shark", + "sheep", + "snail", + "snake", + "spider", + "squirrel", + "tiger", + "turtle", + "wolf", + "zebra", +]; + +export function createNameFromSocketId(socketId: string) { + const buf = new Buffer(socketId, "utf8"); + + return [ + ADJ[buf.readUInt32LE(0) % ADJ.length], + NOUN[buf.readUInt32LE(4) % NOUN.length], + ].join(" "); +} diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 8426edfd..d67992be 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -167,6 +167,7 @@ export function renderScene( // Paint remote pointers for (const clientId in sceneState.remotePointerViewportCoords) { let { x, y } = sceneState.remotePointerViewportCoords[clientId]; + const username = sceneState.remotePointerUsernames[clientId]; const width = 9; const height = 14; @@ -200,6 +201,30 @@ export function renderScene( context.lineTo(x, y); context.fill(); context.stroke(); + + if (!isOutOfBounds && username) { + const offsetX = x + width; + const offsetY = y + height; + const paddingHorizontal = 4; + const paddingVertical = 4; + const measure = context.measureText(username); + const measureHeight = + measure.actualBoundingBoxDescent + measure.actualBoundingBoxAscent; + + context.fillRect( + offsetX, + offsetY, + measure.width + 2 * paddingHorizontal, + measureHeight + 2 * paddingVertical, + ); + context.fillStyle = "white"; + context.fillText( + username, + offsetX + paddingHorizontal, + offsetY + paddingVertical + measure.actualBoundingBoxAscent, + ); + } + context.strokeStyle = strokeStyle; context.fillStyle = fillStyle; context.globalAlpha = globalAlpha; diff --git a/src/scene/export.ts b/src/scene/export.ts index bdad08db..9ccff82b 100644 --- a/src/scene/export.ts +++ b/src/scene/export.ts @@ -50,6 +50,7 @@ export function exportToCanvas( scrollY: normalizeScroll(-minY + exportPadding), zoom: 1, remotePointerViewportCoords: {}, + remotePointerUsernames: {}, }, { renderScrollbars: false, diff --git a/src/scene/types.ts b/src/scene/types.ts index cd48c4ab..0a4c506c 100644 --- a/src/scene/types.ts +++ b/src/scene/types.ts @@ -8,6 +8,7 @@ export type SceneState = { viewBackgroundColor: string | null; zoom: number; remotePointerViewportCoords: { [id: string]: { x: number; y: number } }; + remotePointerUsernames: { [id: string]: string }; }; export type SceneScroll = { diff --git a/src/types.ts b/src/types.ts index 1cdc1e1b..c437583b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,7 +36,10 @@ export type AppState = { openMenu: "canvas" | "shape" | null; lastPointerDownWith: PointerType; selectedElementIds: { [id: string]: boolean }; - collaborators: Map; + collaborators: Map< + string, + { pointer?: { x: number; y: number }; username?: string } + >; }; export type PointerCoords = Readonly<{