refactor: single source of truths with editor interface (#10178)

* refactor device to editor interface and derive styles panel

* allow host app to control form factor and ui mode

* add editor interface event listener

* put new props inside UIOptions

* refactor: move related apis into one file

* expose getFormFactor

* privatize the setting of desktop mode and fix snapshots

* refactor and fix test

* remove unimplemented code

* export getFormFactor()

* replace `getFormFactor` with `getEditorInterface`

* remove dead & useless

* comment

* fix ts

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Ryan Di
2025-11-04 09:34:17 +11:00
committed by Mark Tolmacs
parent 51a7cb1db6
commit f4f234653e
53 changed files with 913 additions and 775 deletions

View File

@@ -5,17 +5,20 @@ import {
type Radians,
} from "@excalidraw/math";
import { SIDE_RESIZING_THRESHOLD } from "@excalidraw/common";
import {
SIDE_RESIZING_THRESHOLD,
type EditorInterface,
} from "@excalidraw/common";
import type { GlobalPoint, LineSegment, LocalPoint } from "@excalidraw/math";
import type { AppState, Device, Zoom } from "@excalidraw/excalidraw/types";
import type { AppState, Zoom } from "@excalidraw/excalidraw/types";
import { getElementAbsoluteCoords } from "./bounds";
import {
getTransformHandlesFromCoords,
getTransformHandles,
getOmitSidesForDevice,
getOmitSidesForEditorInterface,
canResizeFromSides,
} from "./transformHandles";
import { isImageElement, isLinearElement } from "./typeChecks";
@@ -51,7 +54,7 @@ export const resizeTest = <Point extends GlobalPoint | LocalPoint>(
y: number,
zoom: Zoom,
pointerType: PointerType,
device: Device,
editorInterface: EditorInterface,
): MaybeTransformHandleType => {
if (!appState.selectedElementIds[element.id]) {
return false;
@@ -63,7 +66,7 @@ export const resizeTest = <Point extends GlobalPoint | LocalPoint>(
zoom,
elementsMap,
pointerType,
getOmitSidesForDevice(device),
getOmitSidesForEditorInterface(editorInterface),
);
if (
@@ -86,7 +89,7 @@ export const resizeTest = <Point extends GlobalPoint | LocalPoint>(
return filter[0] as TransformHandleType;
}
if (canResizeFromSides(device)) {
if (canResizeFromSides(editorInterface)) {
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
element,
elementsMap,
@@ -132,7 +135,7 @@ export const getElementWithTransformHandleType = (
zoom: Zoom,
pointerType: PointerType,
elementsMap: ElementsMap,
device: Device,
editorInterface: EditorInterface,
) => {
return elements.reduce((result, element) => {
if (result) {
@@ -146,7 +149,7 @@ export const getElementWithTransformHandleType = (
scenePointerY,
zoom,
pointerType,
device,
editorInterface,
);
return transformHandleType ? { element, transformHandleType } : null;
}, null as { element: NonDeletedExcalidrawElement; transformHandleType: MaybeTransformHandleType } | null);
@@ -160,14 +163,14 @@ export const getTransformHandleTypeFromCoords = <
scenePointerY: number,
zoom: Zoom,
pointerType: PointerType,
device: Device,
editorInterface: EditorInterface,
): MaybeTransformHandleType => {
const transformHandles = getTransformHandlesFromCoords(
[x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2],
0 as Radians,
zoom,
pointerType,
getOmitSidesForDevice(device),
getOmitSidesForEditorInterface(editorInterface),
);
const found = Object.keys(transformHandles).find((key) => {
@@ -183,7 +186,7 @@ export const getTransformHandleTypeFromCoords = <
return found as MaybeTransformHandleType;
}
if (canResizeFromSides(device)) {
if (canResizeFromSides(editorInterface)) {
const cx = (x1 + x2) / 2;
const cy = (y1 + y2) / 2;

View File

@@ -1,8 +1,6 @@
import {
DEFAULT_TRANSFORM_HANDLE_SPACING,
isAndroid,
isIOS,
isMobileOrTablet,
type EditorInterface,
} from "@excalidraw/common";
import { pointFrom, pointRotateRads } from "@excalidraw/math";
@@ -10,7 +8,6 @@ import { pointFrom, pointRotateRads } from "@excalidraw/math";
import type { Radians } from "@excalidraw/math";
import type {
Device,
InteractiveCanvasAppState,
Zoom,
} from "@excalidraw/excalidraw/types";
@@ -112,20 +109,21 @@ const generateTransformHandle = (
return [xx - width / 2, yy - height / 2, width, height];
};
export const canResizeFromSides = (device: Device) => {
if (device.viewport.isMobile) {
return false;
}
if (device.isTouchScreen && (isAndroid || isIOS)) {
export const canResizeFromSides = (editorInterface: EditorInterface) => {
if (
editorInterface.formFactor === "phone" &&
editorInterface.userAgent.isMobileDevice
) {
return false;
}
return true;
};
export const getOmitSidesForDevice = (device: Device) => {
if (canResizeFromSides(device)) {
export const getOmitSidesForEditorInterface = (
editorInterface: EditorInterface,
) => {
if (canResizeFromSides(editorInterface)) {
return DEFAULT_OMIT_SIDES;
}
@@ -330,6 +328,7 @@ export const getTransformHandles = (
export const hasBoundingBox = (
elements: readonly NonDeletedExcalidrawElement[],
appState: InteractiveCanvasAppState,
editorInterface: EditorInterface,
) => {
if (appState.selectedLinearElement?.isEditing) {
return false;
@@ -348,5 +347,5 @@ export const hasBoundingBox = (
// on mobile/tablet we currently don't show bbox because of resize issues
// (also prob best for simplicity's sake)
return element.points.length > 2 && !isMobileOrTablet();
return element.points.length > 2 && !editorInterface.userAgent.isMobileDevice;
};