scroll the closest element to center (#1670)

Co-authored-by: Sanghyeon Lee <yongdamsh@gmail.com>
pull/1681/head^2
Aakansha Doshi 5 years ago committed by GitHub
parent 0db7ac78c4
commit fa359034c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,7 +4,7 @@ import { getDefaultAppState } from "../appState";
import { trash, zoomIn, zoomOut, resetZoom } from "../components/icons";
import { ToolButton } from "../components/ToolButton";
import { t } from "../i18n";
import { getNormalizedZoom, calculateScrollCenter } from "../scene";
import { getNormalizedZoom, normalizeScroll } from "../scene";
import { KEYS } from "../keys";
import { getShortcutKey } from "../utils";
import useIsMobile from "../is-mobile";
@ -202,15 +202,22 @@ export const actionZoomToFit = register({
name: "zoomToFit",
perform: (elements, appState) => {
const nonDeletedElements = elements.filter((element) => !element.isDeleted);
const scrollCenter = calculateScrollCenter(nonDeletedElements);
const commonBounds = getCommonBounds(nonDeletedElements);
const zoom = calculateZoom(commonBounds, appState.zoom, scrollCenter);
const [x1, y1, x2, y2] = commonBounds;
const centerX = (x1 + x2) / 2;
const centerY = (y1 + y2) / 2;
const scrollX = normalizeScroll(window.innerWidth / 2 - centerX);
const scrollY = normalizeScroll(window.innerHeight / 2 - centerY);
const zoom = calculateZoom(commonBounds, appState.zoom, {
scrollX,
scrollY,
});
return {
appState: {
...appState,
scrollX: scrollCenter.scrollX,
scrollY: scrollCenter.scrollY,
scrollX,
scrollY,
zoom,
},
commitToHistory: false,

@ -847,6 +847,8 @@ class App extends React.Component<any, AppState> {
remoteElements.filter((element: { isDeleted: boolean }) => {
return !element.isDeleted;
}),
this.state,
this.canvas,
),
});
}

@ -256,7 +256,9 @@ const LayerUI = ({
<button
className="scroll-back-to-content"
onClick={() => {
setAppState({ ...calculateScrollCenter(elements) });
setAppState({
...calculateScrollCenter(elements, appState, canvas),
});
}}
>
{t("buttons.scrollBackToContent")}
@ -276,6 +278,7 @@ const LayerUI = ({
onRoomCreate={onRoomCreate}
onRoomDestroy={onRoomDestroy}
onLockToggle={onLockToggle}
canvas={canvas}
/>
) : (
<div className="layer-ui__wrapper">

@ -27,6 +27,7 @@ type MobileMenuProps = {
onUsernameChange: (username: string) => void;
onRoomDestroy: () => void;
onLockToggle: () => void;
canvas: HTMLCanvasElement | null;
};
export const MobileMenu = ({
@ -39,6 +40,7 @@ export const MobileMenu = ({
onUsernameChange,
onRoomDestroy,
onLockToggle,
canvas,
}: MobileMenuProps) => (
<>
{appState.isLoading && <LoadingMessage />}
@ -131,7 +133,9 @@ export const MobileMenu = ({
<button
className="scroll-back-to-content"
onClick={() => {
setAppState({ ...calculateScrollCenter(elements) });
setAppState({
...calculateScrollCenter(elements, appState, canvas),
});
}}
>
{t("buttons.scrollBackToContent")}

@ -271,7 +271,7 @@ export const importFromBackend = async (
}
elements = data.elements || elements;
appState = data.appState || appState;
appState = { ...appState, ...data.appState };
} catch (error) {
window.alert(t("alerts.importBackendFailed"));
console.error(error);

@ -84,6 +84,5 @@ export const restoreFromLocalStorage = () => {
// Do nothing because appState is already null
}
}
return restore(elements, appState);
};

@ -121,7 +121,10 @@ export const restore = (
}, [] as ExcalidrawElement[]);
if (opts?.scrollToContent && savedState) {
savedState = { ...savedState, ...calculateScrollCenter(elements) };
savedState = {
...savedState,
...calculateScrollCenter(elements, savedState, null),
};
}
return {

@ -1,5 +1,5 @@
import { ExcalidrawElement, ExcalidrawLinearElement } from "./types";
import { rotate } from "../math";
import { distance2d, rotate } from "../math";
import rough from "roughjs/bin/rough";
import { Drawable, Op } from "roughjs/bin/core";
import { Point } from "../types";
@ -342,3 +342,27 @@ export const getResizedElementAbsoluteCoords = (
maxY + element.y,
];
};
export const getClosestElementBounds = (
elements: readonly ExcalidrawElement[],
from: { x: number; y: number },
): [number, number, number, number] => {
if (!elements.length) {
return [0, 0, 0, 0];
}
let minDistance = Infinity;
let closestElement = elements[0];
elements.forEach((element) => {
const [x1, y1, x2, y2] = getElementBounds(element);
const distance = distance2d((x1 + x2) / 2, (y1 + y2) / 2, from.x, from.y);
if (distance < minDistance) {
minDistance = distance;
closestElement = element;
}
});
return getElementBounds(closestElement);
};

@ -17,6 +17,7 @@ export {
getCommonBounds,
getDiamondPoints,
getArrowPoints,
getClosestElementBounds,
} from "./bounds";
export {

@ -1,12 +1,43 @@
import { FlooredNumber } from "../types";
import { AppState, FlooredNumber } from "../types";
import { ExcalidrawElement } from "../element/types";
import { getCommonBounds } from "../element";
import { getCommonBounds, getClosestElementBounds } from "../element";
import {
sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords,
} from "../utils";
export const normalizeScroll = (pos: number) =>
Math.floor(pos) as FlooredNumber;
function isOutsideViewPort(
appState: AppState,
canvas: HTMLCanvasElement | null,
cords: Array<number>,
) {
const [x1, y1, x2, y2] = cords;
const { x: viewportX1, y: viewportY1 } = sceneCoordsToViewportCoords(
{ sceneX: x1, sceneY: y1 },
appState,
canvas,
window.devicePixelRatio,
);
const { x: viewportX2, y: viewportY2 } = sceneCoordsToViewportCoords(
{ sceneX: x2, sceneY: y2 },
appState,
canvas,
window.devicePixelRatio,
);
return (
viewportX2 - viewportX1 > window.innerWidth ||
viewportY2 - viewportY1 > window.innerHeight
);
}
export const calculateScrollCenter = (
elements: readonly ExcalidrawElement[],
appState: AppState,
canvas: HTMLCanvasElement | null,
): { scrollX: FlooredNumber; scrollY: FlooredNumber } => {
if (!elements.length) {
return {
@ -14,8 +45,19 @@ export const calculateScrollCenter = (
scrollY: normalizeScroll(0),
};
}
const [x1, y1, x2, y2] = getCommonBounds(elements);
const scale = window.devicePixelRatio;
let [x1, y1, x2, y2] = getCommonBounds(elements);
if (isOutsideViewPort(appState, canvas, [x1, y1, x2, y2])) {
[x1, y1, x2, y2] = getClosestElementBounds(
elements,
viewportCoordsToSceneCoords(
{ clientX: appState.scrollX, clientY: appState.scrollY },
appState,
canvas,
scale,
),
);
}
const centerX = (x1 + x2) / 2;
const centerY = (y1 + y2) / 2;

Loading…
Cancel
Save