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