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 19c43abfc6..7571d4c001 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 b4b5ce0e52..fa79493639 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 8426edfd3f..d67992bed3 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 bdad08db7b..9ccff82b78 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 cd48c4abef..0a4c506c3b 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 1cdc1e1bd0..c437583b77 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<{