docs: migrate example to typescript (#5243)

* docs: migrate example to typescript

* fix

* fix sidebar

* fix
This commit is contained in:
Aakansha Doshi 2022-06-14 17:56:05 +05:30 committed by GitHub
parent 5daff2d3cd
commit 6196fba286
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 173 additions and 106 deletions

View File

@ -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,7 +211,8 @@ export default function App() {
const updateScene = () => {
const sceneData = {
elements: [
elements: restoreElements(
[
{
type: "rectangle",
version: 141,
@ -200,13 +233,20 @@ export default function App() {
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: [
const libraryItems: LibraryItems = [
{
status: "published",
elements: initialData.libraryItems[0],
id: "1",
created: 1,
elements: initialData.libraryItems[1] as any,
},
{
status: "unpublished",
elements: initialData.libraryItems[1],
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";

View File

@ -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>
</>
);

View File

@ -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: {