Преглед изворни кода

feat: support src collaborators (#5114)

* feat: support avatarURLfor collaborators

* fix

* better avatars :)

* use position fixed for tooltips so it renders correctly when offsets updated

* update docs

* Update src/excalidraw-app/collab/CollabWrapper.tsx

* rename avatarUrl to src
Aakansha Doshi пре 3 година
родитељ
комит
9e6d5fdbcb

+ 4 - 5
src/actions/actionNavigate.tsx

@@ -1,4 +1,4 @@
-import { getClientColors, getClientInitials } from "../clients";
+import { getClientColors } from "../clients";
 import { Avatar } from "../components/Avatar";
 import { centerScrollOn } from "../scene/scroll";
 import { Collaborator } from "../types";
@@ -43,16 +43,15 @@ export const actionGoToCollaborator = register({
     }
 
     const { background, stroke } = getClientColors(clientId, appState);
-    const shortName = getClientInitials(collaborator.username);
 
     return (
       <Avatar
         color={background}
         border={stroke}
         onClick={() => updateData(collaborator.pointer)}
-      >
-        {shortName}
-      </Avatar>
+        name={collaborator.username || ""}
+        src={collaborator.src}
+      />
     );
   },
 });

+ 6 - 0
src/components/Avatar.scss

@@ -12,5 +12,11 @@
     cursor: pointer;
     font-size: 0.8rem;
     font-weight: 500;
+
+    &-img {
+      width: 100%;
+      height: 100%;
+      border-radius: 100%;
+    }
   }
 }

+ 18 - 10
src/components/Avatar.tsx

@@ -1,20 +1,28 @@
 import "./Avatar.scss";
 
 import React from "react";
+import { getClientInitials } from "../clients";
 
 type AvatarProps = {
-  children: string;
   onClick: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
   color: string;
   border: string;
+  name: string;
+  src?: string;
 };
 
-export const Avatar = ({ children, color, border, onClick }: AvatarProps) => (
-  <div
-    className="Avatar"
-    style={{ background: color, border: `1px solid ${border}` }}
-    onClick={onClick}
-  >
-    {children}
-  </div>
-);
+export const Avatar = ({ color, border, onClick, name, src }: AvatarProps) => {
+  const shortName = getClientInitials(name);
+  const style = src
+    ? undefined
+    : { background: color, border: `1px solid ${border}` };
+  return (
+    <div className="Avatar" style={style} onClick={onClick}>
+      {src ? (
+        <img className="Avatar-img" src={src} alt={shortName} />
+      ) : (
+        shortName
+      )}
+    </div>
+  );
+};

+ 1 - 1
src/components/Tooltip.scss

@@ -2,7 +2,7 @@
 
 // container in body where the actual tooltip is appended to
 .excalidraw-tooltip {
-  position: absolute;
+  position: fixed;
   z-index: 1000;
 
   padding: 8px;

+ 1 - 0
src/packages/excalidraw/CHANGELOG.md

@@ -17,6 +17,7 @@ Please add the latest change on the top under the correct section.
 
 #### Features
 
+- Support [`src`](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L50) for collaborators. Now onwards host can pass `src` to render the customized avatar for collaborators [#5114](https://github.com/excalidraw/excalidraw/pull/5114).
 - Support `libraryItems` argument in `initialData.libraryItems` and `updateScene({ libraryItems })` to be a Promise resolving to `LibraryItems`, and support functional update of `libraryItems` in [`updateScene({ libraryItems })`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#updateScene). [#5101](https://github.com/excalidraw/excalidraw/pull/5101).
 - Expose util [`mergeLibraryItems`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#mergeLibraryItems) [#5101](https://github.com/excalidraw/excalidraw/pull/5101).
 - Expose util [`exportToClipboard`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exportToClipboard) which allows to copy the scene contents to clipboard as `svg`, `png` or `json` [#5103](https://github.com/excalidraw/excalidraw/pull/5103).

+ 1 - 1
src/packages/excalidraw/README_NEXT.md

@@ -512,7 +512,7 @@ You can use this function to update the scene with the sceneData. It accepts the
 | --- | --- | --- |
 | `elements` | [`ImportedDataState["elements"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L17) | The `elements` to be updated in the scene |
 | `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L18) | The `appState` to be updated in the scene. |
-| `collaborators` | <pre>Map<string, <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L29">Collaborator></a></pre> | The list of collaborators to be updated in the scene. |
+| `collaborators` | <pre>Map<string, <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L35">Collaborator></a></pre> | The list of collaborators to be updated in the scene. |
 | `commitToHistory` | `boolean` | Implies if the `history (undo/redo)` should be recorded. Defaults to `false`. |
 | `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200) &#124; Promise<[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)> &#124; ((currentItems: [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)>) => [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200) &#124; Promise<[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)>) | The `libraryItems` to be update in the scene. |
 

+ 35 - 1
src/packages/excalidraw/example/App.js

@@ -31,7 +31,10 @@ const resolvablePromise = () => {
 
 const renderTopRightUI = () => {
   return (
-    <button onClick={() => alert("This is dummy top right UI")}>
+    <button
+      onClick={() => alert("This is dummy top right UI")}
+      style={{ height: "2.5rem" }}
+    >
       {" "}
       Click me{" "}
     </button>
@@ -58,6 +61,7 @@ export default function App() {
   const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
   const [exportEmbedScene, setExportEmbedScene] = useState(false);
   const [theme, setTheme] = useState("light");
+  const [isCollaborating, setIsCollaborating] = useState(false);
 
   const initialStatePromiseRef = useRef({ promise: null });
   if (!initialStatePromiseRef.current.promise) {
@@ -228,6 +232,36 @@ export default function App() {
             />
             Switch to Dark Theme
           </label>
+          <label>
+            <input
+              type="checkbox"
+              checked={isCollaborating}
+              onChange={() => {
+                if (!isCollaborating) {
+                  const collaborators = new Map();
+                  collaborators.set("id1", {
+                    username: "Doremon",
+                    src: "doremon.png",
+                  });
+                  collaborators.set("id2", {
+                    username: "Excalibot",
+                    src: "https://avatars.githubusercontent.com/excalibot",
+                  });
+                  collaborators.set("id3", {
+                    username: "Pika",
+                    src: "pika.jpeg",
+                  });
+                  excalidrawRef.current.updateScene({ collaborators });
+                } else {
+                  excalidrawRef.current.updateScene({
+                    collaborators: new Map(),
+                  });
+                }
+                setIsCollaborating(!isCollaborating);
+              }}
+            />
+            Show collaborators
+          </label>
           <div>
             <button onClick={onCopy.bind(null, "png")}>
               Copy to Clipboard as PNG

BIN
src/packages/excalidraw/example/public/doremon.png


BIN
src/packages/excalidraw/example/public/pika.jpeg


+ 3 - 0
src/types.ts

@@ -45,6 +45,9 @@ export type Collaborator = {
     background: string;
     stroke: string;
   };
+  // The url of the collaborator's avatar, defaults to username intials
+  // if not present
+  src?: string;
 };
 
 export type DataURL = string & { _brand: "DataURL" };