diff --git a/src/components/App.tsx b/src/components/App.tsx index 737d2bed7f..18b680e73a 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1211,6 +1211,7 @@ class App extends React.Component { } app={this} isCollaborating={this.props.isCollaborating} + uiDisabled={this.props.ui === false} > {this.props.children} @@ -1237,14 +1238,16 @@ class App extends React.Component { closable={this.state.toast.closable} /> )} - {this.state.contextMenu && ( - - )} + {this.state.contextMenu && + this.props.interactive !== false && + this.props.ui !== false && ( + + )} { event.preventDefault(); } + if (this.props.interactive === false) { + return; + } + if (!didTapTwice) { didTapTwice = true; clearTimeout(tappedTwiceTimer); @@ -2142,6 +2149,10 @@ class App extends React.Component { }; private onTouchEnd = (event: TouchEvent) => { + if (this.props.interactive === false) { + return; + } + this.resetContextMenuTimer(); if (event.touches.length > 0) { this.setState({ @@ -2158,6 +2169,10 @@ class App extends React.Component { public pasteFromClipboard = withBatchedUpdates( async (event: ClipboardEvent | null) => { + if (this.props.interactive === false) { + return; + } + const isPlainPaste = !!(IS_PLAIN_PASTE && event); // #686 @@ -3200,6 +3215,10 @@ class App extends React.Component { private onGestureStart = withBatchedUpdates((event: GestureEvent) => { event.preventDefault(); + if (this.props.interactive === false) { + return false; + } + // we only want to deselect on touch screens because user may have selected // elements by mistake while zooming if (this.isTouchScreenMultiTouchGesture()) { @@ -3215,6 +3234,10 @@ class App extends React.Component { private onGestureChange = withBatchedUpdates((event: GestureEvent) => { event.preventDefault(); + if (this.props.interactive === false) { + return false; + } + // onGestureChange only has zoom factor but not the center. // If we're on iPad or iPhone, then we recognize multi-touch and will // zoom in at the right location in the touchmove handler @@ -3246,6 +3269,11 @@ class App extends React.Component { // fires only on Safari private onGestureEnd = withBatchedUpdates((event: GestureEvent) => { event.preventDefault(); + + if (this.props.interactive === false) { + return false; + } + // reselect elements only on touch screens (see onGestureStart) if (this.isTouchScreenMultiTouchGesture()) { this.setState({ @@ -3827,6 +3855,10 @@ class App extends React.Component { private handleCanvasPointerMove = ( event: React.PointerEvent, ) => { + if (this.props.interactive === false) { + return false; + } + this.savePointer(event.clientX, event.clientY, this.state.cursorButton); if (gesture.pointers.has(event.pointerId)) { @@ -4479,8 +4511,10 @@ class App extends React.Component { return; } - if (this.handleCanvasPanUsingWheelOrSpaceDrag(event)) { - return; + if (this.props.interactive !== false) { + if (this.handleCanvasPanUsingWheelOrSpaceDrag(event)) { + return; + } } this.lastPointerDownEvent = event; @@ -4512,14 +4546,20 @@ class App extends React.Component { selectedElementsAreBeingDragged: false, }); - if (this.handleDraggingScrollBar(event, pointerDownState)) { + if ( + this.props.interactive !== false && + this.handleDraggingScrollBar(event, pointerDownState) + ) { return; } this.clearSelectionIfNotUsingSelection(); this.updateBindingEnabledOnPointerMove(event); - if (this.handleSelectionOnPointerDown(event, pointerDownState)) { + if ( + this.props.interactive !== false && + this.handleSelectionOnPointerDown(event, pointerDownState) + ) { return; } @@ -4601,15 +4641,15 @@ class App extends React.Component { const onPointerMove = 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_UP, onPointerUp); window.addEventListener(EVENT.KEYDOWN, onKeyDown); @@ -7804,6 +7844,10 @@ class App extends React.Component { ) => { event.preventDefault(); + if (this.props.interactive === false) { + return; + } + if ( (("pointerType" in event.nativeEvent && event.nativeEvent.pointerType === "touch") || @@ -8215,7 +8259,7 @@ class App extends React.Component { event: WheelEvent | React.WheelEvent, ) => { event.preventDefault(); - if (isPanning) { + if (isPanning || this.props.interactive === false) { return; } diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index 59ac60a763..78df1f205e 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -79,6 +79,7 @@ interface LayerUIProps { children?: React.ReactNode; app: AppClassProperties; isCollaborating: boolean; + uiDisabled: boolean; } const DefaultMainMenu: React.FC<{ @@ -137,6 +138,7 @@ const LayerUI = ({ children, app, isCollaborating, + uiDisabled, }: LayerUIProps) => { const device = useDevice(); const tunnels = useInitializeTunnels(); @@ -354,6 +356,10 @@ const LayerUI = ({ const isSidebarDocked = useAtomValue(isSidebarDockedAtom, jotaiScope); + if (uiDisabled) { + return null; + } + const layerUIJSX = ( <> {/* ------------------------- tunneled UI ---------------------------- */} diff --git a/src/packages/excalidraw/index.tsx b/src/packages/excalidraw/index.tsx index 901785f1cb..01d34d07af 100644 --- a/src/packages/excalidraw/index.tsx +++ b/src/packages/excalidraw/index.tsx @@ -44,6 +44,8 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { children, validateEmbeddable, renderEmbeddable, + ui, + interactive, } = props; const canvasActions = props.UIOptions?.canvasActions; @@ -100,7 +102,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { onPointerUpdate={onPointerUpdate} renderTopRightUI={renderTopRightUI} langCode={langCode} - viewModeEnabled={viewModeEnabled} + viewModeEnabled={interactive === false ? true : viewModeEnabled} zenModeEnabled={zenModeEnabled} gridModeEnabled={gridModeEnabled} libraryReturnUrl={libraryReturnUrl} @@ -119,6 +121,8 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { onScrollChange={onScrollChange} validateEmbeddable={validateEmbeddable} renderEmbeddable={renderEmbeddable} + ui={ui} + interactive={interactive} > {children} diff --git a/src/types.ts b/src/types.ts index 8b05ba40a7..fd1d8d4213 100644 --- a/src/types.ts +++ b/src/types.ts @@ -445,6 +445,8 @@ export interface ExcalidrawProps { element: NonDeleted, appState: AppState, ) => JSX.Element | null; + interactive?: boolean; + ui?: boolean; } export type SceneData = {