Forráskód Böngészése

docs: migrate example to typescript (#5243)

* docs: migrate example to typescript

* fix

* fix sidebar

* fix
Aakansha Doshi 3 éve
szülő
commit
6196fba286

+ 169 - 102
src/packages/excalidraw/example/App.js → src/packages/excalidraw/example/App.tsx

@@ -1,12 +1,13 @@
 import { useEffect, useState, useRef, useCallback } from "react";
 
-import InitialData from "./initialData";
 import Sidebar from "./sidebar/Sidebar";
 
 import "./App.scss";
 import initialData from "./initialData";
 import { nanoid } from "nanoid";
 import {
+  resolvablePromise,
+  ResolvablePromise,
   withBatchedUpdates,
   withBatchedUpdatesThrottled,
 } from "../../../utils";
@@ -14,7 +15,42 @@ import { EVENT } from "../../../constants";
 import { distance2d } from "../../../math";
 import { fileOpen } from "../../../data/filesystem";
 import { loadSceneOrLibraryFromBlob } from "../../utils";
+import {
+  AppState,
+  BinaryFileData,
+  ExcalidrawImperativeAPI,
+  ExcalidrawInitialDataState,
+  Gesture,
+  LibraryItems,
+  PointerDownState as ExcalidrawPointerDownState,
+} from "../../../types";
+import { ExcalidrawElement } from "../../../element/types";
+import { ImportedLibraryData } from "../../../data/types";
+
+declare global {
+  interface Window {
+    ExcalidrawLib: any;
+  }
+}
+
+type Comment = {
+  x: number;
+  y: number;
+  value: string;
+  id?: string;
+};
 
+type PointerDownState = {
+  x: number;
+  y: number;
+  hitElement: Comment;
+  onMove: any;
+  onUp: any;
+  hitElementOffsets: {
+    x: number;
+    y: number;
+  };
+};
 // This is so that we use the bundled excalidraw.development.js file instead
 // of the actual source code
 
@@ -28,6 +64,7 @@ const {
   MIME_TYPES,
   sceneCoordsToViewportCoords,
   viewportCoordsToSceneCoords,
+  restoreElements,
 } = window.ExcalidrawLib;
 
 const COMMENT_SVG = (
@@ -41,7 +78,7 @@ const COMMENT_SVG = (
     stroke-width="2"
     stroke-linecap="round"
     stroke-linejoin="round"
-    class="feather feather-message-circle"
+    className="feather feather-message-circle"
   >
     <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
   </svg>
@@ -50,18 +87,6 @@ const COMMENT_ICON_DIMENSION = 32;
 const COMMENT_INPUT_HEIGHT = 50;
 const COMMENT_INPUT_WIDTH = 150;
 
-const resolvablePromise = () => {
-  let resolve;
-  let reject;
-  const promise = new Promise((_resolve, _reject) => {
-    resolve = _resolve;
-    reject = _reject;
-  });
-  promise.resolve = resolve;
-  promise.reject = reject;
-  return promise;
-};
-
 const renderTopRightUI = () => {
   return (
     <button
@@ -75,25 +100,31 @@ const renderTopRightUI = () => {
 };
 
 export default function App() {
-  const appRef = useRef(null);
+  const appRef = useRef<any>(null);
   const [viewModeEnabled, setViewModeEnabled] = useState(false);
   const [zenModeEnabled, setZenModeEnabled] = useState(false);
   const [gridModeEnabled, setGridModeEnabled] = useState(false);
-  const [blobUrl, setBlobUrl] = useState(null);
-  const [canvasUrl, setCanvasUrl] = useState(null);
+  const [blobUrl, setBlobUrl] = useState<string>("");
+  const [canvasUrl, setCanvasUrl] = useState<string>("");
   const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
   const [exportEmbedScene, setExportEmbedScene] = useState(false);
   const [theme, setTheme] = useState("light");
   const [isCollaborating, setIsCollaborating] = useState(false);
-  const [commentIcons, setCommentIcons] = useState({});
-  const [comment, setComment] = useState(null);
+  const [commentIcons, setCommentIcons] = useState<{ [id: string]: Comment }>(
+    {},
+  );
+  const [comment, setComment] = useState<Comment | null>(null);
 
-  const initialStatePromiseRef = useRef({ promise: null });
+  const initialStatePromiseRef = useRef<{
+    promise: ResolvablePromise<ExcalidrawInitialDataState | null>;
+  }>({ promise: null! });
   if (!initialStatePromiseRef.current.promise) {
-    initialStatePromiseRef.current.promise = resolvablePromise();
+    initialStatePromiseRef.current.promise =
+      resolvablePromise<ExcalidrawInitialDataState | null>();
   }
 
-  const [excalidrawAPI, setExcalidrawAPI] = useState(null);
+  const [excalidrawAPI, setExcalidrawAPI] =
+    useState<ExcalidrawImperativeAPI | null>(null);
 
   useHandleLibrary({ excalidrawAPI });
 
@@ -108,16 +139,17 @@ export default function App() {
       reader.readAsDataURL(imageData);
 
       reader.onload = function () {
-        const imagesArray = [
+        const imagesArray: BinaryFileData[] = [
           {
-            id: "rocket",
-            dataURL: reader.result,
+            id: "rocket" as BinaryFileData["id"],
+            dataURL: reader.result as BinaryFileData["dataURL"],
             mimeType: MIME_TYPES.jpg,
             created: 1644915140367,
           },
         ];
 
-        initialStatePromiseRef.current.promise.resolve(InitialData);
+        //@ts-ignore
+        initialStatePromiseRef.current.promise.resolve(initialData);
         excalidrawAPI.addFiles(imagesArray);
       };
     };
@@ -131,7 +163,7 @@ export default function App() {
         <button
           className="custom-element"
           onClick={() => {
-            excalidrawAPI.setActiveTool({
+            excalidrawAPI?.setActiveTool({
               type: "custom",
               customType: "comment",
             });
@@ -151,7 +183,7 @@ export default function App() {
               <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
             </svg>`,
             )}`;
-            excalidrawAPI.setCursor(`url(${url}), auto`);
+            excalidrawAPI?.setCursor(`url(${url}), auto`);
           }}
         >
           {COMMENT_SVG}
@@ -168,10 +200,10 @@ export default function App() {
     const file = await fileOpen({ description: "Excalidraw or library file" });
     const contents = await loadSceneOrLibraryFromBlob(file, null, null);
     if (contents.type === MIME_TYPES.excalidraw) {
-      excalidrawAPI.updateScene(contents.data);
+      excalidrawAPI?.updateScene(contents.data as any);
     } else if (contents.type === MIME_TYPES.excalidrawlib) {
-      excalidrawAPI.updateLibrary({
-        libraryItems: contents.data.libraryItems,
+      excalidrawAPI?.updateLibrary({
+        libraryItems: (contents.data as ImportedLibraryData).libraryItems!,
         openLibraryMenu: true,
       });
     }
@@ -179,34 +211,42 @@ export default function App() {
 
   const updateScene = () => {
     const sceneData = {
-      elements: [
-        {
-          type: "rectangle",
-          version: 141,
-          versionNonce: 361174001,
-          isDeleted: false,
-          id: "oDVXy8D6rom3H1-LLH2-f",
-          fillStyle: "hachure",
-          strokeWidth: 1,
-          strokeStyle: "solid",
-          roughness: 1,
-          opacity: 100,
-          angle: 0,
-          x: 100.50390625,
-          y: 93.67578125,
-          strokeColor: "#c92a2a",
-          backgroundColor: "transparent",
-          width: 186.47265625,
-          height: 141.9765625,
-          seed: 1968410350,
-          groupIds: [],
-        },
-      ],
+      elements: restoreElements(
+        [
+          {
+            type: "rectangle",
+            version: 141,
+            versionNonce: 361174001,
+            isDeleted: false,
+            id: "oDVXy8D6rom3H1-LLH2-f",
+            fillStyle: "hachure",
+            strokeWidth: 1,
+            strokeStyle: "solid",
+            roughness: 1,
+            opacity: 100,
+            angle: 0,
+            x: 100.50390625,
+            y: 93.67578125,
+            strokeColor: "#c92a2a",
+            backgroundColor: "transparent",
+            width: 186.47265625,
+            height: 141.9765625,
+            seed: 1968410350,
+            groupIds: [],
+            boundElements: null,
+            locked: false,
+            link: null,
+            updated: 1,
+            strokeSharpness: "round",
+          },
+        ],
+        null,
+      ),
       appState: {
         viewBackgroundColor: "#edf2ff",
       },
     };
-    excalidrawAPI.updateScene(sceneData);
+    excalidrawAPI?.updateScene(sceneData);
   };
 
   const onLinkOpen = useCallback((element, event) => {
@@ -224,19 +264,26 @@ export default function App() {
     }
   }, []);
 
-  const onCopy = async (type) => {
+  const onCopy = async (type: string) => {
     await exportToClipboard({
-      elements: excalidrawAPI.getSceneElements(),
-      appState: excalidrawAPI.getAppState(),
-      files: excalidrawAPI.getFiles(),
+      elements: excalidrawAPI?.getSceneElements(),
+      appState: excalidrawAPI?.getAppState(),
+      files: excalidrawAPI?.getFiles(),
       type,
     });
     window.alert(`Copied to clipboard as ${type} sucessfully`);
   };
 
-  const [pointerData, setPointerData] = useState(null);
+  const [pointerData, setPointerData] = useState<{
+    pointer: { x: number; y: number };
+    button: "down" | "up";
+    pointersMap: Gesture["pointers"];
+  } | null>(null);
 
-  const onPointerDown = (activeTool, pointerDownState) => {
+  const onPointerDown = (
+    activeTool: AppState["activeTool"],
+    pointerDownState: ExcalidrawPointerDownState,
+  ) => {
     if (activeTool.type === "custom" && activeTool.customType === "comment") {
       const { x, y } = pointerDownState.origin;
       setComment({ x, y, value: "" });
@@ -244,48 +291,53 @@ export default function App() {
   };
 
   const rerenderCommentIcons = () => {
-    const commentIconsElements =
-      appRef.current.querySelectorAll(".comment-icon");
+    const commentIconsElements = appRef.current.querySelectorAll(
+      ".comment-icon",
+    ) as HTMLElement[];
     commentIconsElements.forEach((ele) => {
       const id = ele.id;
-      const appstate = excalidrawAPI.getAppState();
+      const appstate = excalidrawAPI?.getAppState();
       const { x, y } = sceneCoordsToViewportCoords(
         { sceneX: commentIcons[id].x, sceneY: commentIcons[id].y },
         appstate,
       );
       ele.style.left = `${
-        x - COMMENT_ICON_DIMENSION / 2 - appstate.offsetLeft
+        x - COMMENT_ICON_DIMENSION / 2 - appstate!.offsetLeft
       }px`;
       ele.style.top = `${
-        y - COMMENT_ICON_DIMENSION / 2 - appstate.offsetTop
+        y - COMMENT_ICON_DIMENSION / 2 - appstate!.offsetTop
       }px`;
     });
   };
 
-  const onPointerMoveFromPointerDownHandler = (pointerDownState) => {
+  const onPointerMoveFromPointerDownHandler = (
+    pointerDownState: PointerDownState,
+  ) => {
     return withBatchedUpdatesThrottled((event) => {
       const { x, y } = viewportCoordsToSceneCoords(
         {
           clientX: event.clientX - pointerDownState.hitElementOffsets.x,
           clientY: event.clientY - pointerDownState.hitElementOffsets.y,
         },
-        excalidrawAPI.getAppState(),
+        excalidrawAPI?.getAppState(),
       );
       setCommentIcons({
         ...commentIcons,
-        [pointerDownState.hitElement.id]: {
-          ...commentIcons[pointerDownState.hitElement.id],
+        [pointerDownState.hitElement.id!]: {
+          ...commentIcons[pointerDownState.hitElement.id!],
           x,
           y,
         },
       });
     });
   };
-  const onPointerUpFromPointerDownHandler = (pointerDownState) => {
+  const onPointerUpFromPointerDownHandler = (
+    pointerDownState: PointerDownState,
+  ) => {
     return withBatchedUpdates((event) => {
       window.removeEventListener(EVENT.POINTER_MOVE, pointerDownState.onMove);
       window.removeEventListener(EVENT.POINTER_UP, pointerDownState.onUp);
-      excalidrawAPI.setActiveTool({ type: "selection" });
+      excalidrawAPI?.setActiveTool({ type: "selection" });
       const distance = distance2d(
         pointerDownState.x,
         pointerDownState.y,
@@ -308,18 +360,18 @@ export default function App() {
   };
   const renderCommentIcons = () => {
     return Object.values(commentIcons).map((commentIcon) => {
-      const appState = excalidrawAPI.getAppState();
+      const appState = excalidrawAPI?.getAppState();
       const { x, y } = sceneCoordsToViewportCoords(
         { sceneX: commentIcon.x, sceneY: commentIcon.y },
-        excalidrawAPI.getAppState(),
+        excalidrawAPI?.getAppState(),
       );
       return (
         <div
           id={commentIcon.id}
           key={commentIcon.id}
           style={{
-            top: `${y - COMMENT_ICON_DIMENSION / 2 - appState.offsetTop}px`,
-            left: `${x - COMMENT_ICON_DIMENSION / 2 - appState.offsetLeft}px`,
+            top: `${y - COMMENT_ICON_DIMENSION / 2 - appState!.offsetTop}px`,
+            left: `${x - COMMENT_ICON_DIMENSION / 2 - appState!.offsetLeft}px`,
             position: "absolute",
             zIndex: 1,
             width: `${COMMENT_ICON_DIMENSION}px`,
@@ -334,7 +386,7 @@ export default function App() {
               commentIcon.value = comment.value;
               saveComment();
             }
-            const pointerDownState = {
+            const pointerDownState: any = {
               x: event.clientX,
               y: event.clientY,
               hitElement: commentIcon,
@@ -350,7 +402,7 @@ export default function App() {
             pointerDownState.onMove = onPointerMove;
             pointerDownState.onUp = onPointerUp;
 
-            excalidrawAPI.setActiveTool({
+            excalidrawAPI?.setActiveTool({
               type: "custom",
               customType: "comment",
             });
@@ -365,6 +417,9 @@ export default function App() {
   };
 
   const saveComment = () => {
+    if (!comment) {
+      return;
+    }
     if (!comment.id && !comment.value) {
       setComment(null);
       return;
@@ -383,7 +438,10 @@ export default function App() {
   };
 
   const renderComment = () => {
-    const appState = excalidrawAPI.getAppState();
+    if (!comment) {
+      return null;
+    }
+    const appState = excalidrawAPI?.getAppState()!;
     const { x, y } = sceneCoordsToViewportCoords(
       { sceneX: comment.x, sceneY: comment.y },
       appState,
@@ -450,24 +508,29 @@ export default function App() {
           <button
             className="reset-scene"
             onClick={() => {
-              excalidrawAPI.resetScene();
+              excalidrawAPI?.resetScene();
             }}
           >
             Reset Scene
           </button>
           <button
             onClick={() => {
-              excalidrawAPI.updateLibrary({
-                libraryItems: [
-                  {
-                    status: "published",
-                    elements: initialData.libraryItems[0],
-                  },
-                  {
-                    status: "unpublished",
-                    elements: initialData.libraryItems[1],
-                  },
-                ],
+              const libraryItems: LibraryItems = [
+                {
+                  status: "published",
+                  id: "1",
+                  created: 1,
+                  elements: initialData.libraryItems[1] as any,
+                },
+                {
+                  status: "unpublished",
+                  id: "2",
+                  created: 2,
+                  elements: initialData.libraryItems[1] as any,
+                },
+              ];
+              excalidrawAPI?.updateLibrary({
+                libraryItems,
               });
             }}
           >
@@ -535,9 +598,9 @@ export default function App() {
                     username: "fallback",
                     avatarUrl: "https://example.com",
                   });
-                  excalidrawAPI.updateScene({ collaborators });
+                  excalidrawAPI?.updateScene({ collaborators });
                 } else {
-                  excalidrawAPI.updateScene({
+                  excalidrawAPI?.updateScene({
                     collaborators: new Map(),
                   });
                 }
@@ -571,12 +634,16 @@ export default function App() {
         </div>
         <div className="excalidraw-wrapper">
           <Excalidraw
-            ref={(api) => setExcalidrawAPI(api)}
+            ref={(api: ExcalidrawImperativeAPI) => setExcalidrawAPI(api)}
             initialData={initialStatePromiseRef.current.promise}
-            onChange={(elements, state) => {
+            onChange={(elements: ExcalidrawElement[], state: AppState) => {
               console.info("Elements :", elements, "State : ", state);
             }}
-            onPointerUpdate={(payload) => setPointerData(payload)}
+            onPointerUpdate={(payload: {
+              pointer: { x: number; y: number };
+              button: "down" | "up";
+              pointersMap: Gesture["pointers"];
+            }) => setPointerData(payload)}
             onCollabButtonClick={() =>
               window.alert("You clicked on collab button")
             }
@@ -616,7 +683,7 @@ export default function App() {
           <button
             onClick={async () => {
               const svg = await exportToSvg({
-                elements: excalidrawAPI.getSceneElements(),
+                elements: excalidrawAPI?.getSceneElements(),
                 appState: {
                   ...initialData.appState,
                   exportWithDarkMode,
@@ -625,7 +692,7 @@ export default function App() {
                   height: 100,
                 },
                 embedScene: true,
-                files: excalidrawAPI.getFiles(),
+                files: excalidrawAPI?.getFiles(),
               });
               appRef.current.querySelector(".export-svg").innerHTML =
                 svg.outerHTML;
@@ -638,14 +705,14 @@ export default function App() {
           <button
             onClick={async () => {
               const blob = await exportToBlob({
-                elements: excalidrawAPI.getSceneElements(),
+                elements: excalidrawAPI?.getSceneElements(),
                 mimeType: "image/png",
                 appState: {
                   ...initialData.appState,
                   exportEmbedScene,
                   exportWithDarkMode,
                 },
-                files: excalidrawAPI.getFiles(),
+                files: excalidrawAPI?.getFiles(),
               });
               setBlobUrl(window.URL.createObjectURL(blob));
             }}
@@ -659,12 +726,12 @@ export default function App() {
           <button
             onClick={async () => {
               const canvas = await exportToCanvas({
-                elements: excalidrawAPI.getSceneElements(),
+                elements: excalidrawAPI?.getSceneElements(),
                 appState: {
                   ...initialData.appState,
                   exportWithDarkMode,
                 },
-                files: excalidrawAPI.getFiles(),
+                files: excalidrawAPI?.getFiles(),
               });
               const ctx = canvas.getContext("2d");
               ctx.font = "30px Virgil";

+ 0 - 0
src/packages/excalidraw/example/index.js → src/packages/excalidraw/example/index.tsx


+ 3 - 3
src/packages/excalidraw/example/sidebar/Sidebar.js → src/packages/excalidraw/example/sidebar/Sidebar.tsx

@@ -1,6 +1,6 @@
-import { useState } from "react";
+import React, { useState } from "react";
 import "./Sidebar.scss";
-export default function Sidebar(props) {
+export default function Sidebar({ children }: { children: React.ReactNode }) {
   const [open, setOpen] = useState(false);
 
   return (
@@ -23,7 +23,7 @@ export default function Sidebar(props) {
         >
           Open Sidebar
         </button>
-        {props.children}
+        {children}
       </div>
     </>
   );

+ 1 - 1
src/packages/excalidraw/webpack.dev-server.config.js

@@ -5,7 +5,7 @@ const devConfig = require("./webpack.dev.config");
 
 const devServerConfig = {
   entry: {
-    bundle: "./example/index.js",
+    bundle: "./example/index.tsx",
   },
   // Server Configuration options
   devServer: {