Browse Source

[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 <vjeux@fb.com>
Pete Hunt 5 years ago
parent
commit
dcb93f75e6
6 changed files with 136 additions and 6 deletions
  1. 14 5
      src/components/App.tsx
  2. 91 0
      src/data/index.ts
  3. 25 0
      src/renderer/renderScene.ts
  4. 1 0
      src/scene/export.ts
  5. 1 0
      src/scene/types.ts
  6. 4 1
      src/types.ts

+ 14 - 5
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<any, AppState> {
               }
               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<any, AppState> {
         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<any, AppState> {
     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<any, AppState> {
         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<any, AppState> {
         viewBackgroundColor: this.state.viewBackgroundColor,
         zoom: this.state.zoom,
         remotePointerViewportCoords: pointerViewportCoords,
+        remotePointerUsernames: pointerUsernames,
       },
       {
         renderOptimizations: true,

+ 91 - 0
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(" ");
+}

+ 25 - 0
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;

+ 1 - 0
src/scene/export.ts

@@ -50,6 +50,7 @@ export function exportToCanvas(
       scrollY: normalizeScroll(-minY + exportPadding),
       zoom: 1,
       remotePointerViewportCoords: {},
+      remotePointerUsernames: {},
     },
     {
       renderScrollbars: false,

+ 1 - 0
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 = {

+ 4 - 1
src/types.ts

@@ -36,7 +36,10 @@ export type AppState = {
   openMenu: "canvas" | "shape" | null;
   lastPointerDownWith: PointerType;
   selectedElementIds: { [id: string]: boolean };
-  collaborators: Map<string, { pointer?: { x: number; y: number } }>;
+  collaborators: Map<
+    string,
+    { pointer?: { x: number; y: number }; username?: string }
+  >;
 };
 
 export type PointerCoords = Readonly<{