follow scroll + zoom => follow scrollAndZoom
This commit is contained in:
parent
42a90def41
commit
f255a0835f
@ -225,22 +225,20 @@ const zoomValueToFitBoundsOnViewport = (
|
||||
return clampedZoomValueToFitElements as NormalizedZoomValue;
|
||||
};
|
||||
|
||||
export const zoomToFit = ({
|
||||
targetElements,
|
||||
export const zoomToFitBounds = ({
|
||||
bounds,
|
||||
appState,
|
||||
fitToViewport = false,
|
||||
viewportZoomFactor = 0.7,
|
||||
}: {
|
||||
targetElements: readonly ExcalidrawElement[];
|
||||
bounds: readonly [number, number, number, number];
|
||||
appState: Readonly<AppState>;
|
||||
/** whether to fit content to viewport (beyond >100%) */
|
||||
fitToViewport: boolean;
|
||||
/** zoom content to cover X of the viewport, when fitToViewport=true */
|
||||
viewportZoomFactor?: number;
|
||||
}) => {
|
||||
const commonBounds = getCommonBounds(getNonDeletedElements(targetElements));
|
||||
|
||||
const [x1, y1, x2, y2] = commonBounds;
|
||||
const [x1, y1, x2, y2] = bounds;
|
||||
const centerX = (x1 + x2) / 2;
|
||||
const centerY = (y1 + y2) / 2;
|
||||
|
||||
@ -267,7 +265,7 @@ export const zoomToFit = ({
|
||||
scrollX = (appState.width / 2) * (1 / newZoomValue) - centerX;
|
||||
scrollY = (appState.height / 2) * (1 / newZoomValue) - centerY;
|
||||
} else {
|
||||
newZoomValue = zoomValueToFitBoundsOnViewport(commonBounds, {
|
||||
newZoomValue = zoomValueToFitBoundsOnViewport(bounds, {
|
||||
width: appState.width,
|
||||
height: appState.height,
|
||||
});
|
||||
@ -296,6 +294,29 @@ export const zoomToFit = ({
|
||||
};
|
||||
};
|
||||
|
||||
export const zoomToFit = ({
|
||||
targetElements,
|
||||
appState,
|
||||
fitToViewport,
|
||||
viewportZoomFactor,
|
||||
}: {
|
||||
targetElements: readonly ExcalidrawElement[];
|
||||
appState: Readonly<AppState>;
|
||||
/** whether to fit content to viewport (beyond >100%) */
|
||||
fitToViewport: boolean;
|
||||
/** zoom content to cover X of the viewport, when fitToViewport=true */
|
||||
viewportZoomFactor?: number;
|
||||
}) => {
|
||||
const commonBounds = getCommonBounds(getNonDeletedElements(targetElements));
|
||||
|
||||
return zoomToFitBounds({
|
||||
bounds: commonBounds,
|
||||
appState,
|
||||
fitToViewport,
|
||||
viewportZoomFactor,
|
||||
});
|
||||
};
|
||||
|
||||
// Note, this action differs from actionZoomToFitSelection in that it doesn't
|
||||
// zoom beyond 100%. In other words, if the content is smaller than viewport
|
||||
// size, it won't be zoomed in.
|
||||
|
@ -1500,18 +1500,21 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.refreshDeviceState(this.excalidrawContainerRef.current);
|
||||
}
|
||||
|
||||
// TODO follow-participant
|
||||
// add zoom change
|
||||
|
||||
if (prevState.zoom.value !== this.state.zoom.value) {
|
||||
this.props?.onZoomChange?.(this.state.zoom);
|
||||
this.props?.onScrollAndZoomChange?.({
|
||||
zoom: this.state.zoom,
|
||||
scroll: { x: this.state.scrollX, y: this.state.scrollY },
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
prevState.scrollX !== this.state.scrollX ||
|
||||
prevState.scrollY !== this.state.scrollY
|
||||
) {
|
||||
this.props?.onScrollChange?.(this.state.scrollX, this.state.scrollY);
|
||||
this.props?.onScrollAndZoomChange?.({
|
||||
zoom: this.state.zoom,
|
||||
scroll: { x: this.state.scrollX, y: this.state.scrollY },
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -1,6 +1,6 @@
|
||||
import throttle from "lodash.throttle";
|
||||
import { PureComponent } from "react";
|
||||
import { ExcalidrawImperativeAPI } from "../../types";
|
||||
import { AppState, ExcalidrawImperativeAPI } from "../../types";
|
||||
import { ErrorDialog } from "../../components/ErrorDialog";
|
||||
import { APP_NAME, ENV, EVENT } from "../../constants";
|
||||
import { ImportedDataState } from "../../data/types";
|
||||
@ -16,6 +16,7 @@ import { Collaborator, Gesture } from "../../types";
|
||||
import {
|
||||
preventUnload,
|
||||
resolvablePromise,
|
||||
viewportCoordsToSceneCoords,
|
||||
withBatchedUpdates,
|
||||
} from "../../utils";
|
||||
import {
|
||||
@ -71,6 +72,7 @@ import { resetBrowserStateVersions } from "../data/tabSync";
|
||||
import { LocalData } from "../data/LocalData";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { appJotaiStore } from "../app-jotai";
|
||||
import { zoomToFitBounds } from "../../actions/actionCanvas";
|
||||
|
||||
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
||||
export const collabDialogShownAtom = atom(false);
|
||||
@ -89,8 +91,7 @@ export interface CollabAPI {
|
||||
/** function so that we can access the latest value from stale callbacks */
|
||||
isCollaborating: () => boolean;
|
||||
onPointerUpdate: CollabInstance["onPointerUpdate"];
|
||||
onScrollChange: CollabInstance["onScrollChange"];
|
||||
onZoomChange: CollabInstance["onZoomChange"];
|
||||
onScrollAndZoomChange: CollabInstance["onScrollAndZoomChange"];
|
||||
startCollaboration: CollabInstance["startCollaboration"];
|
||||
stopCollaboration: CollabInstance["stopCollaboration"];
|
||||
syncElements: CollabInstance["syncElements"];
|
||||
@ -164,8 +165,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||
const collabAPI: CollabAPI = {
|
||||
isCollaborating: this.isCollaborating,
|
||||
onPointerUpdate: this.onPointerUpdate,
|
||||
onScrollChange: this.onScrollChange,
|
||||
onZoomChange: this.onZoomChange,
|
||||
onScrollAndZoomChange: this.onScrollAndZoomChange,
|
||||
startCollaboration: this.startCollaboration,
|
||||
syncElements: this.syncElements,
|
||||
fetchImageFilesFromFirebase: this.fetchImageFilesFromFirebase,
|
||||
@ -510,8 +510,6 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||
break;
|
||||
}
|
||||
case WS_SCENE_EVENT_TYPES.UPDATE:
|
||||
console.log("received update", decryptedData);
|
||||
console.log(this.excalidrawAPI.getAppState());
|
||||
this.handleRemoteSceneUpdate(
|
||||
this.reconcileElements(decryptedData.payload.elements),
|
||||
);
|
||||
@ -520,8 +518,6 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||
const { pointer, button, username, selectedElementIds } =
|
||||
decryptedData.payload;
|
||||
|
||||
// console.log({ decryptedData });
|
||||
|
||||
const socketId: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["socketId"] =
|
||||
decryptedData.payload.socketId ||
|
||||
// @ts-ignore legacy, see #2094 (#2097)
|
||||
@ -539,50 +535,22 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||
});
|
||||
break;
|
||||
}
|
||||
// TODO follow-participant
|
||||
// case "SCROLL_LOCATION"
|
||||
// case "ZOOM_VALUE"
|
||||
// if following someone, update scroll and zoom
|
||||
|
||||
case "SCROLL_LOCATION": {
|
||||
const {
|
||||
scroll: { x, y },
|
||||
} = decryptedData.payload;
|
||||
case "SCROLL_AND_ZOOM": {
|
||||
const { bounds } = decryptedData.payload;
|
||||
|
||||
const socketId: SocketUpdateDataSource["SCROLL_LOCATION"]["payload"]["socketId"] =
|
||||
const socketId: SocketUpdateDataSource["SCROLL_AND_ZOOM"]["payload"]["socketId"] =
|
||||
decryptedData.payload.socketId;
|
||||
|
||||
console.log({ decryptedData });
|
||||
|
||||
const appState = this.excalidrawAPI.getAppState();
|
||||
console.log({ appState });
|
||||
|
||||
if (appState.userToFollow === socketId) {
|
||||
this.excalidrawAPI.updateScene({
|
||||
appState: {
|
||||
scrollX: x,
|
||||
scrollY: y,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "ZOOM_VALUE": {
|
||||
const { zoom } = decryptedData.payload;
|
||||
|
||||
console.log({ decryptedData });
|
||||
|
||||
const socketId: SocketUpdateDataSource["ZOOM_VALUE"]["payload"]["socketId"] =
|
||||
decryptedData.payload.socketId;
|
||||
|
||||
const appState = this.excalidrawAPI.getAppState();
|
||||
|
||||
if (appState.userToFollow === socketId) {
|
||||
this.excalidrawAPI.updateScene({
|
||||
appState: { zoom },
|
||||
const _appState = this.excalidrawAPI.getAppState();
|
||||
if (_appState.userToFollow === socketId) {
|
||||
const { appState } = zoomToFitBounds({
|
||||
appState: _appState,
|
||||
bounds,
|
||||
fitToViewport: true,
|
||||
viewportZoomFactor: 1,
|
||||
});
|
||||
this.excalidrawAPI.updateScene({ appState });
|
||||
}
|
||||
|
||||
break;
|
||||
@ -814,7 +782,6 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||
button: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["button"];
|
||||
pointersMap: Gesture["pointers"];
|
||||
}) => {
|
||||
// console.log({ payload });
|
||||
payload.pointersMap.size < 2 &&
|
||||
this.portal.socket &&
|
||||
this.portal.broadcastMouseLocation(payload);
|
||||
@ -822,26 +789,34 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||
CURSOR_SYNC_TIMEOUT,
|
||||
);
|
||||
|
||||
// TODO follow-participant
|
||||
// - onScrollChange
|
||||
// -- broadCastScrollLocation
|
||||
// - onZoomChange
|
||||
// -- broadCastZoomValue
|
||||
onScrollAndZoomChange = throttle(
|
||||
(payload: { zoom: AppState["zoom"]; scroll: { x: number; y: number } }) => {
|
||||
const appState = this.excalidrawAPI.getAppState();
|
||||
|
||||
onZoomChange = throttle(
|
||||
(payload: {
|
||||
zoom: SocketUpdateDataSource["ZOOM_VALUE"]["payload"]["zoom"];
|
||||
}) => {
|
||||
this.portal.socket && this.portal.broadcastZoomValue(payload);
|
||||
},
|
||||
);
|
||||
const { x: x1, y: y1 } = viewportCoordsToSceneCoords(
|
||||
{ clientX: 0, clientY: 0 },
|
||||
{
|
||||
offsetLeft: appState.offsetLeft,
|
||||
offsetTop: appState.offsetTop,
|
||||
scrollX: payload.scroll.x,
|
||||
scrollY: payload.scroll.y,
|
||||
zoom: payload.zoom,
|
||||
},
|
||||
);
|
||||
|
||||
onScrollChange = throttle(
|
||||
(payload: {
|
||||
scrollX: SocketUpdateDataSource["SCROLL_LOCATION"]["payload"]["scroll"]["x"];
|
||||
scrollY: SocketUpdateDataSource["SCROLL_LOCATION"]["payload"]["scroll"]["y"];
|
||||
}) => {
|
||||
this.portal.socket && this.portal.broadcastScrollLocation(payload);
|
||||
const { x: x2, y: y2 } = viewportCoordsToSceneCoords(
|
||||
{ clientX: appState.width, clientY: appState.height },
|
||||
{
|
||||
offsetLeft: appState.offsetLeft,
|
||||
offsetTop: appState.offsetTop,
|
||||
scrollX: payload.scroll.x,
|
||||
scrollY: payload.scroll.y,
|
||||
zoom: payload.zoom,
|
||||
},
|
||||
);
|
||||
|
||||
this.portal.socket &&
|
||||
this.portal.broadcastScrollAndZoom({ bounds: [x1, y1, x2, y2] });
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -214,8 +214,6 @@ class Portal {
|
||||
},
|
||||
};
|
||||
|
||||
// console.log("broadcastMouseLocation data", data);
|
||||
|
||||
return this._broadcastSocketData(
|
||||
data as SocketUpdateData,
|
||||
true, // volatile
|
||||
@ -223,48 +221,19 @@ class Portal {
|
||||
}
|
||||
};
|
||||
|
||||
// TODO follow-participant
|
||||
// - broadCastScrollLocation
|
||||
// - broadCastZoomValue
|
||||
|
||||
broadcastScrollLocation = (payload: {
|
||||
scrollX: SocketUpdateDataSource["SCROLL_LOCATION"]["payload"]["scroll"]["x"];
|
||||
scrollY: SocketUpdateDataSource["SCROLL_LOCATION"]["payload"]["scroll"]["y"];
|
||||
broadcastScrollAndZoom = (payload: {
|
||||
bounds: [number, number, number, number];
|
||||
}) => {
|
||||
if (this.socket?.id) {
|
||||
const data: SocketUpdateDataSource["SCROLL_LOCATION"] = {
|
||||
type: "SCROLL_LOCATION",
|
||||
const data: SocketUpdateDataSource["SCROLL_AND_ZOOM"] = {
|
||||
type: "SCROLL_AND_ZOOM",
|
||||
payload: {
|
||||
socketId: this.socket.id,
|
||||
scroll: { x: payload.scrollX, y: payload.scrollY },
|
||||
username: this.collab.state.username,
|
||||
bounds: payload.bounds,
|
||||
},
|
||||
};
|
||||
|
||||
console.log("broadcastScrollLocation data", data);
|
||||
|
||||
return this._broadcastSocketData(
|
||||
data as SocketUpdateData,
|
||||
true, // volatile
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
broadcastZoomValue = (payload: {
|
||||
zoom: SocketUpdateDataSource["ZOOM_VALUE"]["payload"]["zoom"];
|
||||
}) => {
|
||||
if (this.socket?.id) {
|
||||
const data: SocketUpdateDataSource["ZOOM_VALUE"] = {
|
||||
type: "ZOOM_VALUE",
|
||||
payload: {
|
||||
socketId: this.socket.id,
|
||||
zoom: payload.zoom,
|
||||
username: this.collab.state.username,
|
||||
},
|
||||
};
|
||||
|
||||
console.log("broadcastZoomValue data", data);
|
||||
|
||||
return this._broadcastSocketData(
|
||||
data as SocketUpdateData,
|
||||
true, // volatile
|
||||
|
@ -113,20 +113,12 @@ export type SocketUpdateDataSource = {
|
||||
username: string;
|
||||
};
|
||||
};
|
||||
SCROLL_LOCATION: {
|
||||
type: "SCROLL_LOCATION";
|
||||
SCROLL_AND_ZOOM: {
|
||||
type: "SCROLL_AND_ZOOM";
|
||||
payload: {
|
||||
socketId: string;
|
||||
scroll: { x: number; y: number };
|
||||
username: string;
|
||||
};
|
||||
};
|
||||
ZOOM_VALUE: {
|
||||
type: "ZOOM_VALUE";
|
||||
payload: {
|
||||
socketId: string;
|
||||
zoom: AppState["zoom"];
|
||||
username: string;
|
||||
bounds: [number, number, number, number];
|
||||
};
|
||||
};
|
||||
IDLE_STATUS: {
|
||||
|
@ -649,16 +649,8 @@ const ExcalidrawWrapper = () => {
|
||||
})}
|
||||
>
|
||||
<Excalidraw
|
||||
// TODO follow-participant
|
||||
// add onZoomChange
|
||||
onZoomChange={(zoom) => {
|
||||
console.log({ zoom });
|
||||
collabAPI?.onZoomChange({ zoom });
|
||||
}}
|
||||
onScrollChange={(x, y) => {
|
||||
// console.log({ x, y });
|
||||
|
||||
collabAPI?.onScrollChange({ scrollX: x, scrollY: y });
|
||||
onScrollAndZoomChange={({ zoom, scroll }) => {
|
||||
collabAPI?.onScrollAndZoomChange({ zoom, scroll });
|
||||
}}
|
||||
ref={excalidrawRefCallback}
|
||||
onChange={onChange}
|
||||
|
@ -41,7 +41,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
||||
onLinkOpen,
|
||||
onPointerDown,
|
||||
onScrollChange,
|
||||
onZoomChange,
|
||||
onScrollAndZoomChange,
|
||||
children,
|
||||
} = props;
|
||||
|
||||
@ -116,7 +116,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
||||
onLinkOpen={onLinkOpen}
|
||||
onPointerDown={onPointerDown}
|
||||
onScrollChange={onScrollChange}
|
||||
onZoomChange={onZoomChange}
|
||||
onScrollAndZoomChange={onScrollAndZoomChange}
|
||||
>
|
||||
{children}
|
||||
</App>
|
||||
|
@ -362,6 +362,13 @@ export interface ExcalidrawProps {
|
||||
) => void;
|
||||
onScrollChange?: (scrollX: number, scrollY: number) => void;
|
||||
onZoomChange?: (zoom: Zoom) => void;
|
||||
onScrollAndZoomChange?: ({
|
||||
zoom,
|
||||
scroll,
|
||||
}: {
|
||||
zoom: Zoom;
|
||||
scroll: { x: number; y: number };
|
||||
}) => void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user