fix: emitted visible scene bounds not accounting for offsets (#7450)
This commit is contained in:
parent
561e919a2e
commit
6dfa89e846
@ -20,9 +20,12 @@ export const WS_EVENTS = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export enum WS_SUBTYPES {
|
export enum WS_SUBTYPES {
|
||||||
|
INVALID_RESPONSE = "INVALID_RESPONSE",
|
||||||
INIT = "SCENE_INIT",
|
INIT = "SCENE_INIT",
|
||||||
UPDATE = "SCENE_UPDATE",
|
UPDATE = "SCENE_UPDATE",
|
||||||
USER_VIEWPORT_BOUNDS = "USER_VIEWPORT_BOUNDS",
|
MOUSE_LOCATION = "MOUSE_LOCATION",
|
||||||
|
IDLE_STATUS = "IDLE_STATUS",
|
||||||
|
USER_VISIBLE_SCENE_BOUNDS = "USER_VISIBLE_SCENE_BOUNDS",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIREBASE_STORAGE_PREFIXES = {
|
export const FIREBASE_STORAGE_PREFIXES = {
|
||||||
|
@ -18,10 +18,10 @@ import {
|
|||||||
} from "../../packages/excalidraw/index";
|
} from "../../packages/excalidraw/index";
|
||||||
import { Collaborator, Gesture } from "../../packages/excalidraw/types";
|
import { Collaborator, Gesture } from "../../packages/excalidraw/types";
|
||||||
import {
|
import {
|
||||||
|
assertNever,
|
||||||
preventUnload,
|
preventUnload,
|
||||||
resolvablePromise,
|
resolvablePromise,
|
||||||
throttleRAF,
|
throttleRAF,
|
||||||
viewportCoordsToSceneCoords,
|
|
||||||
withBatchedUpdates,
|
withBatchedUpdates,
|
||||||
} from "../../packages/excalidraw/utils";
|
} from "../../packages/excalidraw/utils";
|
||||||
import {
|
import {
|
||||||
@ -81,7 +81,8 @@ import { resetBrowserStateVersions } from "../data/tabSync";
|
|||||||
import { LocalData } from "../data/LocalData";
|
import { LocalData } from "../data/LocalData";
|
||||||
import { atom, useAtom } from "jotai";
|
import { atom, useAtom } from "jotai";
|
||||||
import { appJotaiStore } from "../app-jotai";
|
import { appJotaiStore } from "../app-jotai";
|
||||||
import { Mutable } from "../../packages/excalidraw/utility-types";
|
import { Mutable, ValueOf } from "../../packages/excalidraw/utility-types";
|
||||||
|
import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds";
|
||||||
|
|
||||||
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
||||||
export const collabDialogShownAtom = atom(false);
|
export const collabDialogShownAtom = atom(false);
|
||||||
@ -174,7 +175,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
this.portal.socket && this.portal.broadcastUserFollowed(payload);
|
this.portal.socket && this.portal.broadcastUserFollowed(payload);
|
||||||
});
|
});
|
||||||
const throttledRelayUserViewportBounds = throttleRAF(
|
const throttledRelayUserViewportBounds = throttleRAF(
|
||||||
this.relayUserViewportBounds,
|
this.relayVisibleSceneBounds,
|
||||||
);
|
);
|
||||||
const unsubOnScrollChange = this.excalidrawAPI.onScrollChange(() =>
|
const unsubOnScrollChange = this.excalidrawAPI.onScrollChange(() =>
|
||||||
throttledRelayUserViewportBounds(),
|
throttledRelayUserViewportBounds(),
|
||||||
@ -384,7 +385,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
iv: Uint8Array,
|
iv: Uint8Array,
|
||||||
encryptedData: ArrayBuffer,
|
encryptedData: ArrayBuffer,
|
||||||
decryptionKey: string,
|
decryptionKey: string,
|
||||||
) => {
|
): Promise<ValueOf<SocketUpdateDataSource>> => {
|
||||||
try {
|
try {
|
||||||
const decrypted = await decryptData(iv, encryptedData, decryptionKey);
|
const decrypted = await decryptData(iv, encryptedData, decryptionKey);
|
||||||
|
|
||||||
@ -396,7 +397,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
window.alert(t("alerts.decryptFailed"));
|
window.alert(t("alerts.decryptFailed"));
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return {
|
return {
|
||||||
type: "INVALID_RESPONSE",
|
type: WS_SUBTYPES.INVALID_RESPONSE,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -512,7 +513,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
switch (decryptedData.type) {
|
switch (decryptedData.type) {
|
||||||
case "INVALID_RESPONSE":
|
case WS_SUBTYPES.INVALID_RESPONSE:
|
||||||
return;
|
return;
|
||||||
case WS_SUBTYPES.INIT: {
|
case WS_SUBTYPES.INIT: {
|
||||||
if (!this.portal.socketInitialized) {
|
if (!this.portal.socketInitialized) {
|
||||||
@ -535,7 +536,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
this.reconcileElements(decryptedData.payload.elements),
|
this.reconcileElements(decryptedData.payload.elements),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "MOUSE_LOCATION": {
|
case WS_SUBTYPES.MOUSE_LOCATION: {
|
||||||
const { pointer, button, username, selectedElementIds } =
|
const { pointer, button, username, selectedElementIds } =
|
||||||
decryptedData.payload;
|
decryptedData.payload;
|
||||||
|
|
||||||
@ -554,8 +555,8 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case WS_SUBTYPES.USER_VIEWPORT_BOUNDS: {
|
case WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS: {
|
||||||
const { bounds, socketId } = decryptedData.payload;
|
const { sceneBounds, socketId } = decryptedData.payload;
|
||||||
|
|
||||||
const appState = this.excalidrawAPI.getAppState();
|
const appState = this.excalidrawAPI.getAppState();
|
||||||
|
|
||||||
@ -579,7 +580,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
this.excalidrawAPI.updateScene({
|
this.excalidrawAPI.updateScene({
|
||||||
appState: zoomToFitBounds({
|
appState: zoomToFitBounds({
|
||||||
appState,
|
appState,
|
||||||
bounds,
|
bounds: sceneBounds,
|
||||||
fitToViewport: true,
|
fitToViewport: true,
|
||||||
viewportZoomFactor: 1,
|
viewportZoomFactor: 1,
|
||||||
}).appState,
|
}).appState,
|
||||||
@ -588,7 +589,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "IDLE_STATUS": {
|
case WS_SUBTYPES.IDLE_STATUS: {
|
||||||
const { userState, socketId, username } = decryptedData.payload;
|
const { userState, socketId, username } = decryptedData.payload;
|
||||||
this.updateCollaborator(socketId, {
|
this.updateCollaborator(socketId, {
|
||||||
userState,
|
userState,
|
||||||
@ -596,6 +597,10 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
assertNever(decryptedData, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -618,7 +623,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
appState: { followedBy: new Set(followedBy) },
|
appState: { followedBy: new Set(followedBy) },
|
||||||
});
|
});
|
||||||
|
|
||||||
this.relayUserViewportBounds({ shouldPerform: true });
|
this.relayVisibleSceneBounds({ force: true });
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -848,25 +853,14 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
CURSOR_SYNC_TIMEOUT,
|
CURSOR_SYNC_TIMEOUT,
|
||||||
);
|
);
|
||||||
|
|
||||||
relayUserViewportBounds = (props?: { shouldPerform: boolean }) => {
|
relayVisibleSceneBounds = (props?: { force: boolean }) => {
|
||||||
const appState = this.excalidrawAPI.getAppState();
|
const appState = this.excalidrawAPI.getAppState();
|
||||||
|
|
||||||
if (
|
if (this.portal.socket && (appState.followedBy.size > 0 || props?.force)) {
|
||||||
this.portal.socket &&
|
this.portal.broadcastVisibleSceneBounds(
|
||||||
(appState.followedBy.size > 0 || props?.shouldPerform)
|
{
|
||||||
) {
|
sceneBounds: getVisibleSceneBounds(appState),
|
||||||
const { x: x1, y: y1 } = viewportCoordsToSceneCoords(
|
},
|
||||||
{ clientX: 0, clientY: 0 },
|
|
||||||
appState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { x: x2, y: y2 } = viewportCoordsToSceneCoords(
|
|
||||||
{ clientX: appState.width, clientY: appState.height },
|
|
||||||
appState,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.portal.broadcastUserViewportBounds(
|
|
||||||
{ bounds: [x1, y1, x2, y2] },
|
|
||||||
`follow@${this.portal.socket.id}`,
|
`follow@${this.portal.socket.id}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -184,7 +184,7 @@ class Portal {
|
|||||||
broadcastIdleChange = (userState: UserIdleState) => {
|
broadcastIdleChange = (userState: UserIdleState) => {
|
||||||
if (this.socket?.id) {
|
if (this.socket?.id) {
|
||||||
const data: SocketUpdateDataSource["IDLE_STATUS"] = {
|
const data: SocketUpdateDataSource["IDLE_STATUS"] = {
|
||||||
type: "IDLE_STATUS",
|
type: WS_SUBTYPES.IDLE_STATUS,
|
||||||
payload: {
|
payload: {
|
||||||
socketId: this.socket.id,
|
socketId: this.socket.id,
|
||||||
userState,
|
userState,
|
||||||
@ -204,7 +204,7 @@ class Portal {
|
|||||||
}) => {
|
}) => {
|
||||||
if (this.socket?.id) {
|
if (this.socket?.id) {
|
||||||
const data: SocketUpdateDataSource["MOUSE_LOCATION"] = {
|
const data: SocketUpdateDataSource["MOUSE_LOCATION"] = {
|
||||||
type: "MOUSE_LOCATION",
|
type: WS_SUBTYPES.MOUSE_LOCATION,
|
||||||
payload: {
|
payload: {
|
||||||
socketId: this.socket.id,
|
socketId: this.socket.id,
|
||||||
pointer: payload.pointer,
|
pointer: payload.pointer,
|
||||||
@ -222,19 +222,19 @@ class Portal {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
broadcastUserViewportBounds = (
|
broadcastVisibleSceneBounds = (
|
||||||
payload: {
|
payload: {
|
||||||
bounds: [number, number, number, number];
|
sceneBounds: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"]["payload"]["sceneBounds"];
|
||||||
},
|
},
|
||||||
roomId: string,
|
roomId: string,
|
||||||
) => {
|
) => {
|
||||||
if (this.socket?.id) {
|
if (this.socket?.id) {
|
||||||
const data: SocketUpdateDataSource["USER_VIEWPORT_BOUNDS"] = {
|
const data: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"] = {
|
||||||
type: WS_SUBTYPES.USER_VIEWPORT_BOUNDS,
|
type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS,
|
||||||
payload: {
|
payload: {
|
||||||
socketId: this.socket.id,
|
socketId: this.socket.id,
|
||||||
username: this.collab.state.username,
|
username: this.collab.state.username,
|
||||||
bounds: payload.bounds,
|
sceneBounds: payload.sceneBounds,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
import { serializeAsJSON } from "../../packages/excalidraw/data/json";
|
import { serializeAsJSON } from "../../packages/excalidraw/data/json";
|
||||||
import { restore } from "../../packages/excalidraw/data/restore";
|
import { restore } from "../../packages/excalidraw/data/restore";
|
||||||
import { ImportedDataState } from "../../packages/excalidraw/data/types";
|
import { ImportedDataState } from "../../packages/excalidraw/data/types";
|
||||||
|
import { SceneBounds } from "../../packages/excalidraw/element/bounds";
|
||||||
import { isInvisiblySmallElement } from "../../packages/excalidraw/element/sizeHelpers";
|
import { isInvisiblySmallElement } from "../../packages/excalidraw/element/sizeHelpers";
|
||||||
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
|
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
|
||||||
import {
|
import {
|
||||||
@ -28,6 +29,7 @@ import {
|
|||||||
DELETED_ELEMENT_TIMEOUT,
|
DELETED_ELEMENT_TIMEOUT,
|
||||||
FILE_UPLOAD_MAX_BYTES,
|
FILE_UPLOAD_MAX_BYTES,
|
||||||
ROOM_ID_BYTES,
|
ROOM_ID_BYTES,
|
||||||
|
WS_SUBTYPES,
|
||||||
} from "../app_constants";
|
} from "../app_constants";
|
||||||
import { encodeFilesForUpload } from "./FileManager";
|
import { encodeFilesForUpload } from "./FileManager";
|
||||||
import { saveFilesToFirebase } from "./firebase";
|
import { saveFilesToFirebase } from "./firebase";
|
||||||
@ -97,20 +99,23 @@ export type EncryptedData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type SocketUpdateDataSource = {
|
export type SocketUpdateDataSource = {
|
||||||
|
INVALID_RESPONSE: {
|
||||||
|
type: WS_SUBTYPES.INVALID_RESPONSE;
|
||||||
|
};
|
||||||
SCENE_INIT: {
|
SCENE_INIT: {
|
||||||
type: "SCENE_INIT";
|
type: WS_SUBTYPES.INIT;
|
||||||
payload: {
|
payload: {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
SCENE_UPDATE: {
|
SCENE_UPDATE: {
|
||||||
type: "SCENE_UPDATE";
|
type: WS_SUBTYPES.UPDATE;
|
||||||
payload: {
|
payload: {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
MOUSE_LOCATION: {
|
MOUSE_LOCATION: {
|
||||||
type: "MOUSE_LOCATION";
|
type: WS_SUBTYPES.MOUSE_LOCATION;
|
||||||
payload: {
|
payload: {
|
||||||
socketId: string;
|
socketId: string;
|
||||||
pointer: { x: number; y: number; tool: "pointer" | "laser" };
|
pointer: { x: number; y: number; tool: "pointer" | "laser" };
|
||||||
@ -119,16 +124,16 @@ export type SocketUpdateDataSource = {
|
|||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
USER_VIEWPORT_BOUNDS: {
|
USER_VISIBLE_SCENE_BOUNDS: {
|
||||||
type: "USER_VIEWPORT_BOUNDS";
|
type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS;
|
||||||
payload: {
|
payload: {
|
||||||
socketId: string;
|
socketId: string;
|
||||||
username: string;
|
username: string;
|
||||||
bounds: [number, number, number, number];
|
sceneBounds: SceneBounds;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
IDLE_STATUS: {
|
IDLE_STATUS: {
|
||||||
type: "IDLE_STATUS";
|
type: WS_SUBTYPES.IDLE_STATUS;
|
||||||
payload: {
|
payload: {
|
||||||
socketId: string;
|
socketId: string;
|
||||||
userState: UserIdleState;
|
userState: UserIdleState;
|
||||||
@ -138,10 +143,7 @@ export type SocketUpdateDataSource = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type SocketUpdateDataIncoming =
|
export type SocketUpdateDataIncoming =
|
||||||
| SocketUpdateDataSource[keyof SocketUpdateDataSource]
|
SocketUpdateDataSource[keyof SocketUpdateDataSource];
|
||||||
| {
|
|
||||||
type: "INVALID_RESPONSE";
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SocketUpdateData =
|
export type SocketUpdateData =
|
||||||
SocketUpdateDataSource[keyof SocketUpdateDataSource] & {
|
SocketUpdateDataSource[keyof SocketUpdateDataSource] & {
|
||||||
|
@ -13,6 +13,8 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area. [#7450](https://github.com/excalidraw/excalidraw/pull/7450)
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336)
|
- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336)
|
||||||
|
@ -20,7 +20,7 @@ import {
|
|||||||
isHandToolActive,
|
isHandToolActive,
|
||||||
} from "../appState";
|
} from "../appState";
|
||||||
import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors";
|
import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors";
|
||||||
import { Bounds } from "../element/bounds";
|
import { SceneBounds } from "../element/bounds";
|
||||||
import { setCursor } from "../cursor";
|
import { setCursor } from "../cursor";
|
||||||
|
|
||||||
export const actionChangeViewBackgroundColor = register({
|
export const actionChangeViewBackgroundColor = register({
|
||||||
@ -211,7 +211,7 @@ export const actionResetZoom = register({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const zoomValueToFitBoundsOnViewport = (
|
const zoomValueToFitBoundsOnViewport = (
|
||||||
bounds: Bounds,
|
bounds: SceneBounds,
|
||||||
viewportDimensions: { width: number; height: number },
|
viewportDimensions: { width: number; height: number },
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = bounds;
|
const [x1, y1, x2, y2] = bounds;
|
||||||
@ -235,7 +235,7 @@ export const zoomToFitBounds = ({
|
|||||||
fitToViewport = false,
|
fitToViewport = false,
|
||||||
viewportZoomFactor = 0.7,
|
viewportZoomFactor = 0.7,
|
||||||
}: {
|
}: {
|
||||||
bounds: readonly [number, number, number, number];
|
bounds: SceneBounds;
|
||||||
appState: Readonly<AppState>;
|
appState: Readonly<AppState>;
|
||||||
/** whether to fit content to viewport (beyond >100%) */
|
/** whether to fit content to viewport (beyond >100%) */
|
||||||
fitToViewport: boolean;
|
fitToViewport: boolean;
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
import { distance2d, rotate, rotatePoint } from "../math";
|
import { distance2d, rotate, rotatePoint } from "../math";
|
||||||
import rough from "roughjs/bin/rough";
|
import rough from "roughjs/bin/rough";
|
||||||
import { Drawable, Op } from "roughjs/bin/core";
|
import { Drawable, Op } from "roughjs/bin/core";
|
||||||
import { Point } from "../types";
|
import { AppState, Point } from "../types";
|
||||||
import { generateRoughOptions } from "../scene/Shape";
|
import { generateRoughOptions } from "../scene/Shape";
|
||||||
import {
|
import {
|
||||||
isArrowElement,
|
isArrowElement,
|
||||||
@ -35,7 +35,9 @@ export type RectangleBox = {
|
|||||||
|
|
||||||
type MaybeQuadraticSolution = [number | null, number | null] | false;
|
type MaybeQuadraticSolution = [number | null, number | null] | false;
|
||||||
|
|
||||||
// x and y position of top left corner, x and y position of bottom right corner
|
/**
|
||||||
|
* x and y position of top left corner, x and y position of bottom right corner
|
||||||
|
*/
|
||||||
export type Bounds = readonly [
|
export type Bounds = readonly [
|
||||||
minX: number,
|
minX: number,
|
||||||
minY: number,
|
minY: number,
|
||||||
@ -43,6 +45,13 @@ export type Bounds = readonly [
|
|||||||
maxY: number,
|
maxY: number,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export type SceneBounds = readonly [
|
||||||
|
sceneX: number,
|
||||||
|
sceneY: number,
|
||||||
|
sceneX2: number,
|
||||||
|
sceneY2: number,
|
||||||
|
];
|
||||||
|
|
||||||
export class ElementBounds {
|
export class ElementBounds {
|
||||||
private static boundsCache = new WeakMap<
|
private static boundsCache = new WeakMap<
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
@ -879,3 +888,21 @@ export const getCommonBoundingBox = (
|
|||||||
midY: (minY + maxY) / 2,
|
midY: (minY + maxY) / 2,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns scene coords of user's editor viewport (visible canvas area) bounds
|
||||||
|
*/
|
||||||
|
export const getVisibleSceneBounds = ({
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
zoom,
|
||||||
|
}: AppState): SceneBounds => {
|
||||||
|
return [
|
||||||
|
-scrollX,
|
||||||
|
-scrollY,
|
||||||
|
-scrollX + width / zoom.value,
|
||||||
|
-scrollY + height / zoom.value,
|
||||||
|
];
|
||||||
|
};
|
||||||
|
@ -249,7 +249,7 @@ export { TTDDialogTrigger } from "./components/TTDDialog/TTDDialogTrigger";
|
|||||||
export { normalizeLink } from "./data/url";
|
export { normalizeLink } from "./data/url";
|
||||||
export { zoomToFitBounds } from "./actions/actionCanvas";
|
export { zoomToFitBounds } from "./actions/actionCanvas";
|
||||||
export { convertToExcalidrawElements } from "./data/transform";
|
export { convertToExcalidrawElements } from "./data/transform";
|
||||||
export { getCommonBounds } from "./element/bounds";
|
export { getCommonBounds, getVisibleSceneBounds } from "./element/bounds";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
elementsOverlappingBBox,
|
elementsOverlappingBBox,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user