mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-10-20 14:39:57 +02:00
expose getFormFactor
This commit is contained in:
@@ -323,26 +323,6 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
|
||||
},
|
||||
};
|
||||
|
||||
// breakpoints
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// mobile: up to 699px
|
||||
export const MQ_MAX_MOBILE = 599;
|
||||
|
||||
export const MQ_MAX_WIDTH_LANDSCAPE = 1000;
|
||||
export const MQ_MAX_HEIGHT_LANDSCAPE = 500;
|
||||
|
||||
// tablets
|
||||
export const MQ_MIN_TABLET = MQ_MAX_MOBILE + 1; // lower bound (excludes phones)
|
||||
export const MQ_MAX_TABLET = 1400; // upper bound (excludes laptops/desktops)
|
||||
|
||||
// desktop/laptop
|
||||
export const MQ_MIN_WIDTH_DESKTOP = 1440;
|
||||
|
||||
// sidebar
|
||||
export const MQ_RIGHT_SIDEBAR_MIN_WIDTH = 1229;
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
export const MAX_DECIMALS_FOR_SVG_EXPORT = 2;
|
||||
|
||||
export const EXPORT_SCALES = [1, 2, 3];
|
||||
|
@@ -13,8 +13,29 @@ export type EditorInterface = Readonly<{
|
||||
isLandscape: boolean;
|
||||
}>;
|
||||
|
||||
// storage key
|
||||
export const DESKTOP_UI_MODE_STORAGE_KEY = "excalidraw.desktopUIMode";
|
||||
|
||||
// breakpoints
|
||||
// mobile: up to 699px
|
||||
export const MQ_MAX_MOBILE = 599;
|
||||
|
||||
export const MQ_MAX_WIDTH_LANDSCAPE = 1000;
|
||||
export const MQ_MAX_HEIGHT_LANDSCAPE = 500;
|
||||
|
||||
// tablets
|
||||
export const MQ_MIN_TABLET = MQ_MAX_MOBILE + 1; // lower bound (excludes phones)
|
||||
export const MQ_MAX_TABLET = 1400; // upper bound (excludes laptops/desktops)
|
||||
|
||||
// desktop/laptop
|
||||
export const MQ_MIN_WIDTH_DESKTOP = 1440;
|
||||
|
||||
// sidebar
|
||||
export const MQ_RIGHT_SIDEBAR_MIN_WIDTH = 1229;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// user agent detections
|
||||
export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
||||
export const isWindows = /^Win/.test(navigator.platform);
|
||||
export const isAndroid = /\b(android)\b/i.test(navigator.userAgent);
|
||||
@@ -41,6 +62,24 @@ export const isMobile =
|
||||
) ||
|
||||
/android|ios|ipod|blackberry|windows phone/i.test(navigator.platform);
|
||||
|
||||
// utilities
|
||||
export const isMobileBreakpoint = (width: number, height: number) => {
|
||||
return (
|
||||
width <= MQ_MAX_MOBILE ||
|
||||
(height < MQ_MAX_HEIGHT_LANDSCAPE && width < MQ_MAX_WIDTH_LANDSCAPE)
|
||||
);
|
||||
};
|
||||
|
||||
export const isTabletBreakpoint = (
|
||||
editorWidth: number,
|
||||
editorHeight: number,
|
||||
) => {
|
||||
const minSide = Math.min(editorWidth, editorHeight);
|
||||
const maxSide = Math.max(editorWidth, editorHeight);
|
||||
|
||||
return minSide >= MQ_MIN_TABLET && maxSide <= MQ_MAX_TABLET;
|
||||
};
|
||||
|
||||
export const isMobileOrTablet = (): boolean => {
|
||||
const ua = navigator.userAgent || "";
|
||||
const platform = navigator.platform || "";
|
||||
@@ -97,19 +136,15 @@ export const isMobileOrTablet = (): boolean => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export const deriveFormFactor = (
|
||||
export const getFormFactor = (
|
||||
editorWidth: number,
|
||||
editorHeight: number,
|
||||
breakpoints: {
|
||||
isMobile: (width: number, height: number) => boolean;
|
||||
isTablet: (width: number, height: number) => boolean;
|
||||
},
|
||||
): EditorInterface["formFactor"] => {
|
||||
if (breakpoints.isMobile(editorWidth, editorHeight)) {
|
||||
if (isMobileBreakpoint(editorWidth, editorHeight)) {
|
||||
return "phone";
|
||||
}
|
||||
|
||||
if (breakpoints.isTablet(editorWidth, editorHeight)) {
|
||||
if (isTabletBreakpoint(editorWidth, editorHeight)) {
|
||||
return "tablet";
|
||||
}
|
||||
|
||||
|
@@ -96,14 +96,9 @@ import {
|
||||
Emitter,
|
||||
MINIMUM_ARROW_SIZE,
|
||||
DOUBLE_TAP_POSITION_THRESHOLD,
|
||||
MQ_MAX_MOBILE,
|
||||
MQ_MIN_TABLET,
|
||||
MQ_MAX_TABLET,
|
||||
MQ_MAX_HEIGHT_LANDSCAPE,
|
||||
MQ_MAX_WIDTH_LANDSCAPE,
|
||||
DESKTOP_UI_MODE_STORAGE_KEY,
|
||||
createUserAgentDescriptor,
|
||||
deriveFormFactor,
|
||||
getFormFactor,
|
||||
deriveStylesPanelMode,
|
||||
isIOS,
|
||||
isBrave,
|
||||
@@ -755,7 +750,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||
props.UIOptions.desktopUIMode ??
|
||||
storedDesktopUIMode ??
|
||||
this.editorInterface.desktopUIMode,
|
||||
formFactor: props.UIOptions.formFactor ?? this.editorInterface.formFactor,
|
||||
formFactor:
|
||||
props.UIOptions.formFactor ??
|
||||
getFormFactor(this.state.width, this.state.height),
|
||||
userAgent: userAgentDescriptor,
|
||||
});
|
||||
this.stylesPanelMode = deriveStylesPanelMode(this.editorInterface);
|
||||
@@ -806,6 +803,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
setActiveTool: this.setActiveTool,
|
||||
setCursor: this.setCursor,
|
||||
resetCursor: this.resetCursor,
|
||||
getFormFactor: () => getFormFactor(this.state.width, this.state.height),
|
||||
updateFrameRendering: this.updateFrameRendering,
|
||||
toggleSidebar: this.toggleSidebar,
|
||||
onChange: (cb) => this.onChangeEmitter.on(cb),
|
||||
@@ -2498,20 +2496,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
};
|
||||
|
||||
private isMobileBreakpoint = (width: number, height: number) => {
|
||||
return (
|
||||
width <= MQ_MAX_MOBILE ||
|
||||
(height < MQ_MAX_HEIGHT_LANDSCAPE && width < MQ_MAX_WIDTH_LANDSCAPE)
|
||||
);
|
||||
};
|
||||
|
||||
private isTabletBreakpoint = (editorWidth: number, editorHeight: number) => {
|
||||
const minSide = Math.min(editorWidth, editorHeight);
|
||||
const maxSide = Math.max(editorWidth, editorHeight);
|
||||
|
||||
return minSide >= MQ_MIN_TABLET && maxSide <= MQ_MAX_TABLET;
|
||||
};
|
||||
|
||||
private refreshEditorInterface = () => {
|
||||
const container = this.excalidrawContainerRef.current;
|
||||
if (!container) {
|
||||
@@ -2526,27 +2510,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
? this.props.UIOptions.dockedSidebarBreakpoint
|
||||
: MQ_RIGHT_SIDEBAR_MIN_WIDTH;
|
||||
|
||||
// if host doesn't control formFactor, we'll update it ourselves
|
||||
if (!this.props.UIOptions.formFactor) {
|
||||
const nextEditorInterface = updateObject(this.editorInterface, {
|
||||
formFactor: deriveFormFactor(editorWidth, editorHeight, {
|
||||
isMobile: (width, height) => this.isMobileBreakpoint(width, height),
|
||||
isTablet: (width, height) => this.isTabletBreakpoint(width, height),
|
||||
}),
|
||||
canFitSidebar: editorWidth > sidebarBreakpoint,
|
||||
isLandscape: editorWidth > editorHeight,
|
||||
});
|
||||
const didChange = nextEditorInterface !== this.editorInterface;
|
||||
if (didChange) {
|
||||
this.editorInterface = nextEditorInterface;
|
||||
this.reconcileStylesPanelMode(nextEditorInterface);
|
||||
this.props.UIOptions.onEditorInterfaceChange?.(nextEditorInterface);
|
||||
}
|
||||
return didChange;
|
||||
}
|
||||
|
||||
// host controls formFactor, just update sidebar/landscape for context
|
||||
const nextEditorInterface = updateObject(this.editorInterface, {
|
||||
formFactor:
|
||||
this.props.UIOptions.formFactor ??
|
||||
getFormFactor(editorWidth, editorHeight),
|
||||
canFitSidebar: editorWidth > sidebarBreakpoint,
|
||||
isLandscape: editorWidth > editorHeight,
|
||||
});
|
||||
|
@@ -857,6 +857,10 @@ export interface ExcalidrawImperativeAPI {
|
||||
setCursor: InstanceType<typeof App>["setCursor"];
|
||||
resetCursor: InstanceType<typeof App>["resetCursor"];
|
||||
toggleSidebar: InstanceType<typeof App>["toggleSidebar"];
|
||||
getFormFactor: (
|
||||
width: number,
|
||||
height: number,
|
||||
) => EditorInterface["formFactor"];
|
||||
/**
|
||||
* Disables rendering of frames (including element clipping), but currently
|
||||
* the frames are still interactive in edit mode. As such, this API should be
|
||||
|
Reference in New Issue
Block a user