|
@@ -195,6 +195,7 @@ import {
|
|
LibraryItems,
|
|
LibraryItems,
|
|
PointerDownState,
|
|
PointerDownState,
|
|
SceneData,
|
|
SceneData,
|
|
|
|
+ DeviceType,
|
|
} from "../types";
|
|
} from "../types";
|
|
import {
|
|
import {
|
|
debounce,
|
|
debounce,
|
|
@@ -214,6 +215,7 @@ import {
|
|
withBatchedUpdates,
|
|
withBatchedUpdates,
|
|
wrapEvent,
|
|
wrapEvent,
|
|
withBatchedUpdatesThrottled,
|
|
withBatchedUpdatesThrottled,
|
|
|
|
+ updateObject,
|
|
setEraserCursor,
|
|
setEraserCursor,
|
|
} from "../utils";
|
|
} from "../utils";
|
|
import ContextMenu, { ContextMenuOption } from "./ContextMenu";
|
|
import ContextMenu, { ContextMenuOption } from "./ContextMenu";
|
|
@@ -253,8 +255,12 @@ import {
|
|
isLocalLink,
|
|
isLocalLink,
|
|
} from "../element/Hyperlink";
|
|
} from "../element/Hyperlink";
|
|
|
|
|
|
-const IsMobileContext = React.createContext(false);
|
|
|
|
-export const useIsMobile = () => useContext(IsMobileContext);
|
|
|
|
|
|
+const defaultDeviceTypeContext: DeviceType = {
|
|
|
|
+ isMobile: false,
|
|
|
|
+ isTouchScreen: false,
|
|
|
|
+};
|
|
|
|
+const DeviceTypeContext = React.createContext(defaultDeviceTypeContext);
|
|
|
|
+export const useDeviceType = () => useContext(DeviceTypeContext);
|
|
const ExcalidrawContainerContext = React.createContext<{
|
|
const ExcalidrawContainerContext = React.createContext<{
|
|
container: HTMLDivElement | null;
|
|
container: HTMLDivElement | null;
|
|
id: string | null;
|
|
id: string | null;
|
|
@@ -286,7 +292,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
rc: RoughCanvas | null = null;
|
|
rc: RoughCanvas | null = null;
|
|
unmounted: boolean = false;
|
|
unmounted: boolean = false;
|
|
actionManager: ActionManager;
|
|
actionManager: ActionManager;
|
|
- isMobile = false;
|
|
|
|
|
|
+ deviceType: DeviceType = {
|
|
|
|
+ isMobile: false,
|
|
|
|
+ isTouchScreen: false,
|
|
|
|
+ };
|
|
detachIsMobileMqHandler?: () => void;
|
|
detachIsMobileMqHandler?: () => void;
|
|
|
|
|
|
private excalidrawContainerRef = React.createRef<HTMLDivElement>();
|
|
private excalidrawContainerRef = React.createRef<HTMLDivElement>();
|
|
@@ -468,7 +477,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
<div
|
|
<div
|
|
className={clsx("excalidraw excalidraw-container", {
|
|
className={clsx("excalidraw excalidraw-container", {
|
|
"excalidraw--view-mode": viewModeEnabled,
|
|
"excalidraw--view-mode": viewModeEnabled,
|
|
- "excalidraw--mobile": this.isMobile,
|
|
|
|
|
|
+ "excalidraw--mobile": this.deviceType.isMobile,
|
|
})}
|
|
})}
|
|
ref={this.excalidrawContainerRef}
|
|
ref={this.excalidrawContainerRef}
|
|
onDrop={this.handleAppOnDrop}
|
|
onDrop={this.handleAppOnDrop}
|
|
@@ -480,7 +489,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
<ExcalidrawContainerContext.Provider
|
|
<ExcalidrawContainerContext.Provider
|
|
value={this.excalidrawContainerValue}
|
|
value={this.excalidrawContainerValue}
|
|
>
|
|
>
|
|
- <IsMobileContext.Provider value={this.isMobile}>
|
|
|
|
|
|
+ <DeviceTypeContext.Provider value={this.deviceType}>
|
|
<LayerUI
|
|
<LayerUI
|
|
canvas={this.canvas}
|
|
canvas={this.canvas}
|
|
appState={this.state}
|
|
appState={this.state}
|
|
@@ -547,7 +556,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
/>
|
|
/>
|
|
)}
|
|
)}
|
|
<main>{this.renderCanvas()}</main>
|
|
<main>{this.renderCanvas()}</main>
|
|
- </IsMobileContext.Provider>
|
|
|
|
|
|
+ </DeviceTypeContext.Provider>
|
|
</ExcalidrawContainerContext.Provider>
|
|
</ExcalidrawContainerContext.Provider>
|
|
</div>
|
|
</div>
|
|
);
|
|
);
|
|
@@ -891,9 +900,12 @@ class App extends React.Component<AppProps, AppState> {
|
|
// ---------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------
|
|
const { width, height } =
|
|
const { width, height } =
|
|
this.excalidrawContainerRef.current!.getBoundingClientRect();
|
|
this.excalidrawContainerRef.current!.getBoundingClientRect();
|
|
- this.isMobile =
|
|
|
|
- width < MQ_MAX_WIDTH_PORTRAIT ||
|
|
|
|
- (height < MQ_MAX_HEIGHT_LANDSCAPE && width < MQ_MAX_WIDTH_LANDSCAPE);
|
|
|
|
|
|
+ this.deviceType = updateObject(this.deviceType, {
|
|
|
|
+ isMobile:
|
|
|
|
+ width < MQ_MAX_WIDTH_PORTRAIT ||
|
|
|
|
+ (height < MQ_MAX_HEIGHT_LANDSCAPE &&
|
|
|
|
+ width < MQ_MAX_WIDTH_LANDSCAPE),
|
|
|
|
+ });
|
|
// refresh offsets
|
|
// refresh offsets
|
|
// ---------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------
|
|
this.updateDOMRect();
|
|
this.updateDOMRect();
|
|
@@ -903,7 +915,11 @@ class App extends React.Component<AppProps, AppState> {
|
|
const mediaQuery = window.matchMedia(
|
|
const mediaQuery = window.matchMedia(
|
|
`(max-width: ${MQ_MAX_WIDTH_PORTRAIT}px), (max-height: ${MQ_MAX_HEIGHT_LANDSCAPE}px) and (max-width: ${MQ_MAX_WIDTH_LANDSCAPE}px)`,
|
|
`(max-width: ${MQ_MAX_WIDTH_PORTRAIT}px), (max-height: ${MQ_MAX_HEIGHT_LANDSCAPE}px) and (max-width: ${MQ_MAX_WIDTH_LANDSCAPE}px)`,
|
|
);
|
|
);
|
|
- const handler = () => (this.isMobile = mediaQuery.matches);
|
|
|
|
|
|
+ const handler = () => {
|
|
|
|
+ this.deviceType = updateObject(this.deviceType, {
|
|
|
|
+ isMobile: mediaQuery.matches,
|
|
|
|
+ });
|
|
|
|
+ };
|
|
mediaQuery.addListener(handler);
|
|
mediaQuery.addListener(handler);
|
|
this.detachIsMobileMqHandler = () => mediaQuery.removeListener(handler);
|
|
this.detachIsMobileMqHandler = () => mediaQuery.removeListener(handler);
|
|
}
|
|
}
|
|
@@ -1205,7 +1221,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
theme: this.state.theme,
|
|
theme: this.state.theme,
|
|
imageCache: this.imageCache,
|
|
imageCache: this.imageCache,
|
|
isExporting: false,
|
|
isExporting: false,
|
|
- renderScrollbars: !this.isMobile,
|
|
|
|
|
|
+ renderScrollbars: !this.deviceType.isMobile,
|
|
},
|
|
},
|
|
);
|
|
);
|
|
|
|
|
|
@@ -2391,7 +2407,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
element,
|
|
element,
|
|
this.state,
|
|
this.state,
|
|
[scenePointer.x, scenePointer.y],
|
|
[scenePointer.x, scenePointer.y],
|
|
- this.isMobile,
|
|
|
|
|
|
+ this.deviceType.isMobile,
|
|
) &&
|
|
) &&
|
|
index <= hitElementIndex
|
|
index <= hitElementIndex
|
|
);
|
|
);
|
|
@@ -2424,7 +2440,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
this.hitLinkElement!,
|
|
this.hitLinkElement!,
|
|
this.state,
|
|
this.state,
|
|
[lastPointerDownCoords.x, lastPointerDownCoords.y],
|
|
[lastPointerDownCoords.x, lastPointerDownCoords.y],
|
|
- this.isMobile,
|
|
|
|
|
|
+ this.deviceType.isMobile,
|
|
);
|
|
);
|
|
const lastPointerUpCoords = viewportCoordsToSceneCoords(
|
|
const lastPointerUpCoords = viewportCoordsToSceneCoords(
|
|
this.lastPointerUp!,
|
|
this.lastPointerUp!,
|
|
@@ -2434,7 +2450,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
this.hitLinkElement!,
|
|
this.hitLinkElement!,
|
|
this.state,
|
|
this.state,
|
|
[lastPointerUpCoords.x, lastPointerUpCoords.y],
|
|
[lastPointerUpCoords.x, lastPointerUpCoords.y],
|
|
- this.isMobile,
|
|
|
|
|
|
+ this.deviceType.isMobile,
|
|
);
|
|
);
|
|
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
|
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
|
const url = this.hitLinkElement.link;
|
|
const url = this.hitLinkElement.link;
|
|
@@ -2856,6 +2872,13 @@ class App extends React.Component<AppProps, AppState> {
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (
|
|
|
|
+ !this.deviceType.isTouchScreen &&
|
|
|
|
+ ["pen", "touch"].includes(event.pointerType)
|
|
|
|
+ ) {
|
|
|
|
+ this.deviceType = updateObject(this.deviceType, { isTouchScreen: true });
|
|
|
|
+ }
|
|
|
|
+
|
|
if (isPanning) {
|
|
if (isPanning) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -2986,9 +3009,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
event: React.PointerEvent<HTMLCanvasElement>,
|
|
event: React.PointerEvent<HTMLCanvasElement>,
|
|
) => {
|
|
) => {
|
|
this.lastPointerUp = event;
|
|
this.lastPointerUp = event;
|
|
- const isTouchScreen = ["pen", "touch"].includes(event.pointerType);
|
|
|
|
-
|
|
|
|
- if (isTouchScreen) {
|
|
|
|
|
|
+ if (this.deviceType.isTouchScreen) {
|
|
const scenePointer = viewportCoordsToSceneCoords(
|
|
const scenePointer = viewportCoordsToSceneCoords(
|
|
{ clientX: event.clientX, clientY: event.clientY },
|
|
{ clientX: event.clientX, clientY: event.clientY },
|
|
this.state,
|
|
this.state,
|
|
@@ -3006,7 +3027,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
this.hitLinkElement &&
|
|
this.hitLinkElement &&
|
|
!this.state.selectedElementIds[this.hitLinkElement.id]
|
|
!this.state.selectedElementIds[this.hitLinkElement.id]
|
|
) {
|
|
) {
|
|
- this.redirectToLink(event, isTouchScreen);
|
|
|
|
|
|
+ this.redirectToLink(event, this.deviceType.isTouchScreen);
|
|
}
|
|
}
|
|
|
|
|
|
this.removePointer(event);
|
|
this.removePointer(event);
|
|
@@ -3376,7 +3397,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
pointerDownState.hit.element,
|
|
pointerDownState.hit.element,
|
|
this.state,
|
|
this.state,
|
|
[pointerDownState.origin.x, pointerDownState.origin.y],
|
|
[pointerDownState.origin.x, pointerDownState.origin.y],
|
|
- this.isMobile,
|
|
|
|
|
|
+ this.deviceType.isMobile,
|
|
)
|
|
)
|
|
) {
|
|
) {
|
|
return false;
|
|
return false;
|
|
@@ -5407,7 +5428,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
} else {
|
|
} else {
|
|
ContextMenu.push({
|
|
ContextMenu.push({
|
|
options: [
|
|
options: [
|
|
- this.isMobile &&
|
|
|
|
|
|
+ this.deviceType.isMobile &&
|
|
navigator.clipboard && {
|
|
navigator.clipboard && {
|
|
name: "paste",
|
|
name: "paste",
|
|
perform: (elements, appStates) => {
|
|
perform: (elements, appStates) => {
|
|
@@ -5418,7 +5439,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
},
|
|
},
|
|
contextItemLabel: "labels.paste",
|
|
contextItemLabel: "labels.paste",
|
|
},
|
|
},
|
|
- this.isMobile && navigator.clipboard && separator,
|
|
|
|
|
|
+ this.deviceType.isMobile && navigator.clipboard && separator,
|
|
probablySupportsClipboardBlob &&
|
|
probablySupportsClipboardBlob &&
|
|
elements.length > 0 &&
|
|
elements.length > 0 &&
|
|
actionCopyAsPng,
|
|
actionCopyAsPng,
|
|
@@ -5464,9 +5485,9 @@ class App extends React.Component<AppProps, AppState> {
|
|
} else {
|
|
} else {
|
|
ContextMenu.push({
|
|
ContextMenu.push({
|
|
options: [
|
|
options: [
|
|
- this.isMobile && actionCut,
|
|
|
|
- this.isMobile && navigator.clipboard && actionCopy,
|
|
|
|
- this.isMobile &&
|
|
|
|
|
|
+ this.deviceType.isMobile && actionCut,
|
|
|
|
+ this.deviceType.isMobile && navigator.clipboard && actionCopy,
|
|
|
|
+ this.deviceType.isMobile &&
|
|
navigator.clipboard && {
|
|
navigator.clipboard && {
|
|
name: "paste",
|
|
name: "paste",
|
|
perform: (elements, appStates) => {
|
|
perform: (elements, appStates) => {
|
|
@@ -5477,7 +5498,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
},
|
|
},
|
|
contextItemLabel: "labels.paste",
|
|
contextItemLabel: "labels.paste",
|
|
},
|
|
},
|
|
- this.isMobile && separator,
|
|
|
|
|
|
+ this.deviceType.isMobile && separator,
|
|
...options,
|
|
...options,
|
|
separator,
|
|
separator,
|
|
actionCopyStyles,
|
|
actionCopyStyles,
|