From 5f4a5b1789451c06832476317a0d7776ac07d6ce Mon Sep 17 00:00:00 2001 From: ad1992 Date: Thu, 24 Mar 2022 17:24:54 +0530 Subject: [PATCH] Add onElementClick and export sceneCoordsToViewportCoords --- src/components/App.tsx | 27 ++++++++++++---- src/packages/excalidraw/example/App.js | 45 ++++++++++++++++++++++++-- src/packages/excalidraw/index.tsx | 4 +++ src/types.ts | 4 +++ 4 files changed, 71 insertions(+), 9 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 1247ec53b..334e14107 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -3091,15 +3091,13 @@ class App extends React.Component { event: React.PointerEvent, ) => { this.lastPointerUp = event; - if (this.deviceType.isTouchScreen) { + let hitElement; + if (this.deviceType.isTouchScreen || this.props.onElementClick) { const scenePointer = viewportCoordsToSceneCoords( { clientX: event.clientX, clientY: event.clientY }, this.state, ); - const hitElement = this.getElementAtPosition( - scenePointer.x, - scenePointer.y, - ); + hitElement = this.getElementAtPosition(scenePointer.x, scenePointer.y); this.hitLinkElement = this.getElementLinkAtPosition( scenePointer, hitElement, @@ -3112,6 +3110,23 @@ class App extends React.Component { this.redirectToLink(event, this.deviceType.isTouchScreen); } + if ( + this.state.elementType === "selection" && + this.props.onElementClick && + hitElement + ) { + const threshold = 5; + const isSinglePointClick = + distance2d( + this.lastPointerDown!.clientX, + this.lastPointerDown!.clientY, + this.lastPointerUp!.clientX, + this.lastPointerUp!.clientY, + ) <= threshold; + if (isSinglePointClick) { + this.props.onElementClick(hitElement, event); + } + } this.removePointer(event); }; @@ -4517,6 +4532,7 @@ class App extends React.Component { // Code below handles selection when element(s) weren't // drag or added to selection on pointer down phase. const hitElement = pointerDownState.hit.element; + if (isEraserActive(this.state)) { const draggedDistance = distance2d( this.lastPointerDown!.clientX, @@ -4550,7 +4566,6 @@ class App extends React.Component { } else if (Object.keys(pointerDownState.elementIdsToErase).length) { this.restoreReadyToEraseElements(pointerDownState); } - if ( hitElement && !pointerDownState.drag.hasOccurred && diff --git a/src/packages/excalidraw/example/App.js b/src/packages/excalidraw/example/App.js index 92096e28d..f9e21f5dd 100644 --- a/src/packages/excalidraw/example/App.js +++ b/src/packages/excalidraw/example/App.js @@ -9,7 +9,12 @@ import { MIME_TYPES } from "../../../constants"; // This is so that we use the bundled excalidraw.development.js file instead // of the actual source code -const { exportToCanvas, exportToSvg, exportToBlob } = window.Excalidraw; +const { + exportToCanvas, + exportToSvg, + exportToBlob, + sceneCoordsToViewportCoords, +} = window.Excalidraw; const Excalidraw = window.Excalidraw.default; const STAR_SVG = ( @@ -56,7 +61,7 @@ const renderFooter = () => { export default function App() { const excalidrawRef = useRef(null); - + const excalidrawWrapperRef = useRef(null); const [viewModeEnabled, setViewModeEnabled] = useState(false); const [zenModeEnabled, setZenModeEnabled] = useState(false); const [gridModeEnabled, setGridModeEnabled] = useState(false); @@ -200,6 +205,39 @@ export default function App() { }, ]; }; + + const onElementClick = (element) => { + if (element.type === "custom" && element.customType === "comment") { + const { x: viewPortX, y: viewPortY } = sceneCoordsToViewportCoords( + { + sceneX: element.x, + sceneY: element.y, + }, + excalidrawRef.current.getAppState(), + ); + const textarea = document.createElement("textarea"); + Object.assign(textarea.style, { + position: "absolute", + display: "inline-block", + left: `${viewPortX + element.width / 2}px`, + top: `${viewPortY + element.height / 2}px`, + height: `${100}px`, + width: `${100}px`, + zIndex: 10, + className: "comment-textarea", + whiteSpace: "pre-wrap", + fontSize: "13px", + }); + textarea.placeholder = "Start typing your comments"; + textarea.onblur = () => { + textarea.remove(); + }; + excalidrawWrapperRef.current + .querySelector(".excalidraw") + .append(textarea); + textarea.focus(); + } + }; return (

Excalidraw Example

@@ -273,7 +311,7 @@ export default function App() { Switch to Dark Theme
-
+
diff --git a/src/packages/excalidraw/index.tsx b/src/packages/excalidraw/index.tsx index 37572be46..0c87182cf 100644 --- a/src/packages/excalidraw/index.tsx +++ b/src/packages/excalidraw/index.tsx @@ -40,6 +40,7 @@ const Excalidraw = (props: ExcalidrawProps) => { generateIdForFile, onLinkOpen, renderCustomElementWidget, + onElementClick, } = props; const canvasActions = props.UIOptions?.canvasActions; @@ -109,6 +110,7 @@ const Excalidraw = (props: ExcalidrawProps) => { onLinkOpen={onLinkOpen} renderCustomElementWidget={renderCustomElementWidget} customElementsConfig={customElementsConfig} + onElementClick={onElementClick} /> ); @@ -207,3 +209,5 @@ export { newElementWith, bumpVersion, } from "../../element/mutateElement"; + +export { sceneCoordsToViewportCoords } from "../../utils"; diff --git a/src/types.ts b/src/types.ts index 1a81c1c06..68ec90577 100644 --- a/src/types.ts +++ b/src/types.ts @@ -263,6 +263,10 @@ export interface ExcalidrawProps { ) => void; renderCustomElementWidget?: (appState: AppState) => void; customElementsConfig?: CustomElementConfig[]; + onElementClick: ( + element: NonDeleted, + event: React.PointerEvent, + ) => void; } export type SceneData = {