Procházet zdrojové kódy

feat: support hiding UI and disabling interactivity

dwelle před 2 roky
rodič
revize
77b8f5afb6

+ 63 - 19
src/components/App.tsx

@@ -1211,6 +1211,7 @@ class App extends React.Component<AppProps, AppState> {
                           }
                           }
                           app={this}
                           app={this}
                           isCollaborating={this.props.isCollaborating}
                           isCollaborating={this.props.isCollaborating}
+                          uiDisabled={this.props.ui === false}
                         >
                         >
                           {this.props.children}
                           {this.props.children}
                         </LayerUI>
                         </LayerUI>
@@ -1237,14 +1238,16 @@ class App extends React.Component<AppProps, AppState> {
                             closable={this.state.toast.closable}
                             closable={this.state.toast.closable}
                           />
                           />
                         )}
                         )}
-                        {this.state.contextMenu && (
-                          <ContextMenu
-                            items={this.state.contextMenu.items}
-                            top={this.state.contextMenu.top}
-                            left={this.state.contextMenu.left}
-                            actionManager={this.actionManager}
-                          />
-                        )}
+                        {this.state.contextMenu &&
+                          this.props.interactive !== false &&
+                          this.props.ui !== false && (
+                            <ContextMenu
+                              items={this.state.contextMenu.items}
+                              top={this.state.contextMenu.top}
+                              left={this.state.contextMenu.left}
+                              actionManager={this.actionManager}
+                            />
+                          )}
                         <StaticCanvas
                         <StaticCanvas
                           canvas={this.canvas}
                           canvas={this.canvas}
                           rc={this.rc}
                           rc={this.rc}
@@ -2108,6 +2111,10 @@ class App extends React.Component<AppProps, AppState> {
       event.preventDefault();
       event.preventDefault();
     }
     }
 
 
+    if (this.props.interactive === false) {
+      return;
+    }
+
     if (!didTapTwice) {
     if (!didTapTwice) {
       didTapTwice = true;
       didTapTwice = true;
       clearTimeout(tappedTwiceTimer);
       clearTimeout(tappedTwiceTimer);
@@ -2142,6 +2149,10 @@ class App extends React.Component<AppProps, AppState> {
   };
   };
 
 
   private onTouchEnd = (event: TouchEvent) => {
   private onTouchEnd = (event: TouchEvent) => {
+    if (this.props.interactive === false) {
+      return;
+    }
+
     this.resetContextMenuTimer();
     this.resetContextMenuTimer();
     if (event.touches.length > 0) {
     if (event.touches.length > 0) {
       this.setState({
       this.setState({
@@ -2158,6 +2169,10 @@ class App extends React.Component<AppProps, AppState> {
 
 
   public pasteFromClipboard = withBatchedUpdates(
   public pasteFromClipboard = withBatchedUpdates(
     async (event: ClipboardEvent | null) => {
     async (event: ClipboardEvent | null) => {
+      if (this.props.interactive === false) {
+        return;
+      }
+
       const isPlainPaste = !!(IS_PLAIN_PASTE && event);
       const isPlainPaste = !!(IS_PLAIN_PASTE && event);
 
 
       // #686
       // #686
@@ -3200,6 +3215,10 @@ class App extends React.Component<AppProps, AppState> {
   private onGestureStart = withBatchedUpdates((event: GestureEvent) => {
   private onGestureStart = withBatchedUpdates((event: GestureEvent) => {
     event.preventDefault();
     event.preventDefault();
 
 
+    if (this.props.interactive === false) {
+      return false;
+    }
+
     // we only want to deselect on touch screens because user may have selected
     // we only want to deselect on touch screens because user may have selected
     // elements by mistake while zooming
     // elements by mistake while zooming
     if (this.isTouchScreenMultiTouchGesture()) {
     if (this.isTouchScreenMultiTouchGesture()) {
@@ -3215,6 +3234,10 @@ class App extends React.Component<AppProps, AppState> {
   private onGestureChange = withBatchedUpdates((event: GestureEvent) => {
   private onGestureChange = withBatchedUpdates((event: GestureEvent) => {
     event.preventDefault();
     event.preventDefault();
 
 
+    if (this.props.interactive === false) {
+      return false;
+    }
+
     // onGestureChange only has zoom factor but not the center.
     // onGestureChange only has zoom factor but not the center.
     // If we're on iPad or iPhone, then we recognize multi-touch and will
     // If we're on iPad or iPhone, then we recognize multi-touch and will
     // zoom in at the right location in the touchmove handler
     // zoom in at the right location in the touchmove handler
@@ -3246,6 +3269,11 @@ class App extends React.Component<AppProps, AppState> {
   // fires only on Safari
   // fires only on Safari
   private onGestureEnd = withBatchedUpdates((event: GestureEvent) => {
   private onGestureEnd = withBatchedUpdates((event: GestureEvent) => {
     event.preventDefault();
     event.preventDefault();
+
+    if (this.props.interactive === false) {
+      return false;
+    }
+
     // reselect elements only on touch screens (see onGestureStart)
     // reselect elements only on touch screens (see onGestureStart)
     if (this.isTouchScreenMultiTouchGesture()) {
     if (this.isTouchScreenMultiTouchGesture()) {
       this.setState({
       this.setState({
@@ -3827,6 +3855,10 @@ class App extends React.Component<AppProps, AppState> {
   private handleCanvasPointerMove = (
   private handleCanvasPointerMove = (
     event: React.PointerEvent<HTMLCanvasElement>,
     event: React.PointerEvent<HTMLCanvasElement>,
   ) => {
   ) => {
+    if (this.props.interactive === false) {
+      return false;
+    }
+
     this.savePointer(event.clientX, event.clientY, this.state.cursorButton);
     this.savePointer(event.clientX, event.clientY, this.state.cursorButton);
 
 
     if (gesture.pointers.has(event.pointerId)) {
     if (gesture.pointers.has(event.pointerId)) {
@@ -4479,8 +4511,10 @@ class App extends React.Component<AppProps, AppState> {
       return;
       return;
     }
     }
 
 
-    if (this.handleCanvasPanUsingWheelOrSpaceDrag(event)) {
-      return;
+    if (this.props.interactive !== false) {
+      if (this.handleCanvasPanUsingWheelOrSpaceDrag(event)) {
+        return;
+      }
     }
     }
 
 
     this.lastPointerDownEvent = event;
     this.lastPointerDownEvent = event;
@@ -4512,14 +4546,20 @@ class App extends React.Component<AppProps, AppState> {
       selectedElementsAreBeingDragged: false,
       selectedElementsAreBeingDragged: false,
     });
     });
 
 
-    if (this.handleDraggingScrollBar(event, pointerDownState)) {
+    if (
+      this.props.interactive !== false &&
+      this.handleDraggingScrollBar(event, pointerDownState)
+    ) {
       return;
       return;
     }
     }
 
 
     this.clearSelectionIfNotUsingSelection();
     this.clearSelectionIfNotUsingSelection();
     this.updateBindingEnabledOnPointerMove(event);
     this.updateBindingEnabledOnPointerMove(event);
 
 
-    if (this.handleSelectionOnPointerDown(event, pointerDownState)) {
+    if (
+      this.props.interactive !== false &&
+      this.handleSelectionOnPointerDown(event, pointerDownState)
+    ) {
       return;
       return;
     }
     }
 
 
@@ -4601,15 +4641,15 @@ class App extends React.Component<AppProps, AppState> {
     const onPointerMove =
     const onPointerMove =
       this.onPointerMoveFromPointerDownHandler(pointerDownState);
       this.onPointerMoveFromPointerDownHandler(pointerDownState);
 
 
-    const onPointerUp =
-      this.onPointerUpFromPointerDownHandler(pointerDownState);
+    if (!this.state.viewModeEnabled || this.state.activeTool.type === "laser") {
+      const onPointerUp =
+        this.onPointerUpFromPointerDownHandler(pointerDownState);
 
 
-    const onKeyDown = this.onKeyDownFromPointerDownHandler(pointerDownState);
-    const onKeyUp = this.onKeyUpFromPointerDownHandler(pointerDownState);
+      const onKeyDown = this.onKeyDownFromPointerDownHandler(pointerDownState);
+      const onKeyUp = this.onKeyUpFromPointerDownHandler(pointerDownState);
 
 
-    lastPointerUp = onPointerUp;
+      lastPointerUp = onPointerUp;
 
 
-    if (!this.state.viewModeEnabled || this.state.activeTool.type === "laser") {
       window.addEventListener(EVENT.POINTER_MOVE, onPointerMove);
       window.addEventListener(EVENT.POINTER_MOVE, onPointerMove);
       window.addEventListener(EVENT.POINTER_UP, onPointerUp);
       window.addEventListener(EVENT.POINTER_UP, onPointerUp);
       window.addEventListener(EVENT.KEYDOWN, onKeyDown);
       window.addEventListener(EVENT.KEYDOWN, onKeyDown);
@@ -7804,6 +7844,10 @@ class App extends React.Component<AppProps, AppState> {
   ) => {
   ) => {
     event.preventDefault();
     event.preventDefault();
 
 
+    if (this.props.interactive === false) {
+      return;
+    }
+
     if (
     if (
       (("pointerType" in event.nativeEvent &&
       (("pointerType" in event.nativeEvent &&
         event.nativeEvent.pointerType === "touch") ||
         event.nativeEvent.pointerType === "touch") ||
@@ -8215,7 +8259,7 @@ class App extends React.Component<AppProps, AppState> {
       event: WheelEvent | React.WheelEvent<HTMLDivElement | HTMLCanvasElement>,
       event: WheelEvent | React.WheelEvent<HTMLDivElement | HTMLCanvasElement>,
     ) => {
     ) => {
       event.preventDefault();
       event.preventDefault();
-      if (isPanning) {
+      if (isPanning || this.props.interactive === false) {
         return;
         return;
       }
       }
 
 

+ 6 - 0
src/components/LayerUI.tsx

@@ -79,6 +79,7 @@ interface LayerUIProps {
   children?: React.ReactNode;
   children?: React.ReactNode;
   app: AppClassProperties;
   app: AppClassProperties;
   isCollaborating: boolean;
   isCollaborating: boolean;
+  uiDisabled: boolean;
 }
 }
 
 
 const DefaultMainMenu: React.FC<{
 const DefaultMainMenu: React.FC<{
@@ -137,6 +138,7 @@ const LayerUI = ({
   children,
   children,
   app,
   app,
   isCollaborating,
   isCollaborating,
+  uiDisabled,
 }: LayerUIProps) => {
 }: LayerUIProps) => {
   const device = useDevice();
   const device = useDevice();
   const tunnels = useInitializeTunnels();
   const tunnels = useInitializeTunnels();
@@ -354,6 +356,10 @@ const LayerUI = ({
 
 
   const isSidebarDocked = useAtomValue(isSidebarDockedAtom, jotaiScope);
   const isSidebarDocked = useAtomValue(isSidebarDockedAtom, jotaiScope);
 
 
+  if (uiDisabled) {
+    return null;
+  }
+
   const layerUIJSX = (
   const layerUIJSX = (
     <>
     <>
       {/* ------------------------- tunneled UI ---------------------------- */}
       {/* ------------------------- tunneled UI ---------------------------- */}

+ 5 - 1
src/packages/excalidraw/index.tsx

@@ -44,6 +44,8 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
     children,
     children,
     validateEmbeddable,
     validateEmbeddable,
     renderEmbeddable,
     renderEmbeddable,
+    ui,
+    interactive,
   } = props;
   } = props;
 
 
   const canvasActions = props.UIOptions?.canvasActions;
   const canvasActions = props.UIOptions?.canvasActions;
@@ -100,7 +102,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
           onPointerUpdate={onPointerUpdate}
           onPointerUpdate={onPointerUpdate}
           renderTopRightUI={renderTopRightUI}
           renderTopRightUI={renderTopRightUI}
           langCode={langCode}
           langCode={langCode}
-          viewModeEnabled={viewModeEnabled}
+          viewModeEnabled={interactive === false ? true : viewModeEnabled}
           zenModeEnabled={zenModeEnabled}
           zenModeEnabled={zenModeEnabled}
           gridModeEnabled={gridModeEnabled}
           gridModeEnabled={gridModeEnabled}
           libraryReturnUrl={libraryReturnUrl}
           libraryReturnUrl={libraryReturnUrl}
@@ -119,6 +121,8 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
           onScrollChange={onScrollChange}
           onScrollChange={onScrollChange}
           validateEmbeddable={validateEmbeddable}
           validateEmbeddable={validateEmbeddable}
           renderEmbeddable={renderEmbeddable}
           renderEmbeddable={renderEmbeddable}
+          ui={ui}
+          interactive={interactive}
         >
         >
           {children}
           {children}
         </App>
         </App>

+ 2 - 0
src/types.ts

@@ -445,6 +445,8 @@ export interface ExcalidrawProps {
     element: NonDeleted<ExcalidrawEmbeddableElement>,
     element: NonDeleted<ExcalidrawEmbeddableElement>,
     appState: AppState,
     appState: AppState,
   ) => JSX.Element | null;
   ) => JSX.Element | null;
+  interactive?: boolean;
+  ui?: boolean;
 }
 }
 
 
 export type SceneData = {
 export type SceneData = {