mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-09-18 06:50:31 +02:00
Compare commits
3 Commits
zsviczian-
...
fix-collab
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5542e4528a | ||
![]() |
fdd8552637 | ||
![]() |
c8370b394c |
@@ -2008,6 +2008,20 @@ class App extends React.Component<AppProps, AppState> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === KEYS.PAGE_UP || event.key === KEYS.PAGE_DOWN) {
|
||||
let offset =
|
||||
(event.shiftKey ? this.state.width : this.state.height) /
|
||||
this.state.zoom.value;
|
||||
if (event.key === KEYS.PAGE_DOWN) {
|
||||
offset = -offset;
|
||||
}
|
||||
if (event.shiftKey) {
|
||||
this.setState((state) => ({ scrollX: state.scrollX + offset }));
|
||||
} else {
|
||||
this.setState((state) => ({ scrollY: state.scrollY + offset }));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.actionManager.handleKeyDown(event)) {
|
||||
return;
|
||||
}
|
||||
@@ -2030,12 +2044,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
|
||||
: ELEMENT_TRANSLATE_AMOUNT);
|
||||
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
true,
|
||||
);
|
||||
|
||||
let offsetX = 0;
|
||||
let offsetY = 0;
|
||||
|
||||
@@ -2049,6 +2057,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||
offsetY = step;
|
||||
}
|
||||
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
true,
|
||||
);
|
||||
|
||||
selectedElements.forEach((element) => {
|
||||
mutateElement(element, {
|
||||
x: element.x + offsetX,
|
||||
|
@@ -230,6 +230,14 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
||||
label={t("helpDialog.zoomToSelection")}
|
||||
shortcuts={["Shift+2"]}
|
||||
/>
|
||||
<Shortcut
|
||||
label={t("helpDialog.movePageUpDown")}
|
||||
shortcuts={["PgUp/PgDn"]}
|
||||
/>
|
||||
<Shortcut
|
||||
label={t("helpDialog.movePageLeftRight")}
|
||||
shortcuts={["Shift+PgUp/PgDn"]}
|
||||
/>
|
||||
<Shortcut label={t("buttons.fullScreen")} shortcuts={["F"]} />
|
||||
<Shortcut
|
||||
label={t("buttons.zenMode")}
|
||||
|
@@ -46,12 +46,15 @@ class LocalFileManager extends FileManager {
|
||||
const saveDataStateToLocalStorage = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
appStateOnly = false,
|
||||
) => {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS,
|
||||
JSON.stringify(clearElementsForLocalStorage(elements)),
|
||||
);
|
||||
if (!appStateOnly) {
|
||||
localStorage.setItem(
|
||||
STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS,
|
||||
JSON.stringify(clearElementsForLocalStorage(elements)),
|
||||
);
|
||||
}
|
||||
localStorage.setItem(
|
||||
STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,
|
||||
JSON.stringify(clearAppStateForLocalStorage(appState)),
|
||||
@@ -72,8 +75,12 @@ export class LocalData {
|
||||
appState: AppState,
|
||||
files: BinaryFiles,
|
||||
onFilesSaved: () => void,
|
||||
appStateOnly = false,
|
||||
) => {
|
||||
saveDataStateToLocalStorage(elements, appState);
|
||||
saveDataStateToLocalStorage(elements, appState, appStateOnly);
|
||||
if (appStateOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.fileStorage.saveFiles({
|
||||
elements,
|
||||
@@ -97,6 +104,14 @@ export class LocalData {
|
||||
}
|
||||
};
|
||||
|
||||
/** Saves the AppState, only if saving is paused. */
|
||||
static saveAppState = (appState: AppState) => {
|
||||
// we need to make the `isSavePaused` check synchronously (undebounced)
|
||||
if (this.isSavePaused()) {
|
||||
this._save([], appState, {}, () => {}, true);
|
||||
}
|
||||
};
|
||||
|
||||
static flushSave = () => {
|
||||
this._save.flush();
|
||||
};
|
||||
|
@@ -189,7 +189,7 @@ const initializeScene = async (opts: {
|
||||
...restoreAppState(
|
||||
{
|
||||
...scene?.appState,
|
||||
theme: localDataState?.appState?.theme || scene?.appState?.theme,
|
||||
...localDataState?.appState,
|
||||
},
|
||||
excalidrawAPI.getAppState(),
|
||||
),
|
||||
@@ -538,6 +538,8 @@ const ExcalidrawWrapper = () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LocalData.saveAppState(appState);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -29,6 +29,8 @@ export const KEYS = {
|
||||
ARROW_LEFT: "ArrowLeft",
|
||||
ARROW_RIGHT: "ArrowRight",
|
||||
ARROW_UP: "ArrowUp",
|
||||
PAGE_UP: "PageUp",
|
||||
PAGE_DOWN: "PageDown",
|
||||
BACKSPACE: "Backspace",
|
||||
ALT: "Alt",
|
||||
CTRL_OR_CMD: isDarwin ? "metaKey" : "ctrlKey",
|
||||
|
@@ -312,7 +312,9 @@
|
||||
"view": "View",
|
||||
"zoomToFit": "Zoom to fit all elements",
|
||||
"zoomToSelection": "Zoom to selection",
|
||||
"toggleElementLock": "Lock/unlock selection"
|
||||
"toggleElementLock": "Lock/unlock selection",
|
||||
"movePageUpDown": "Move page up/down",
|
||||
"movePageLeftRight": "Move page left/right"
|
||||
},
|
||||
"clearCanvasDialog": {
|
||||
"title": "Clear canvas"
|
||||
|
@@ -6,6 +6,9 @@ import {
|
||||
} from "./test-utils";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { API } from "./helpers/api";
|
||||
import { Keyboard } from "./helpers/ui";
|
||||
import { KEYS } from "../keys";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
@@ -50,4 +53,60 @@ describe("appState", () => {
|
||||
});
|
||||
restoreOriginalGetBoundingClientRect();
|
||||
});
|
||||
|
||||
it("moving by page up/down/left/right", async () => {
|
||||
mockBoundingClientRect();
|
||||
await render(<ExcalidrawApp />, {});
|
||||
|
||||
const scrollTest = () => {
|
||||
const initialScrollY = h.state.scrollY;
|
||||
const initialScrollX = h.state.scrollX;
|
||||
const pageStepY = h.state.height / h.state.zoom.value;
|
||||
const pageStepX = h.state.width / h.state.zoom.value;
|
||||
// Assert the following assertions have meaning
|
||||
expect(pageStepY).toBeGreaterThan(0);
|
||||
expect(pageStepX).toBeGreaterThan(0);
|
||||
// Assert we scroll up
|
||||
Keyboard.keyPress(KEYS.PAGE_UP);
|
||||
expect(h.state.scrollY).toBe(initialScrollY + pageStepY);
|
||||
// x-axis unchanged
|
||||
expect(h.state.scrollX).toBe(initialScrollX);
|
||||
|
||||
// Assert we scroll down
|
||||
Keyboard.keyPress(KEYS.PAGE_DOWN);
|
||||
Keyboard.keyPress(KEYS.PAGE_DOWN);
|
||||
expect(h.state.scrollY).toBe(initialScrollY - pageStepY);
|
||||
// x-axis unchanged
|
||||
expect(h.state.scrollX).toBe(initialScrollX);
|
||||
|
||||
// Assert we scroll left
|
||||
Keyboard.withModifierKeys({ shift: true }, () => {
|
||||
Keyboard.keyPress(KEYS.PAGE_UP);
|
||||
});
|
||||
expect(h.state.scrollX).toBe(initialScrollX + pageStepX);
|
||||
// y-axis unchanged
|
||||
expect(h.state.scrollY).toBe(initialScrollY - pageStepY);
|
||||
|
||||
// Assert we scroll right
|
||||
Keyboard.withModifierKeys({ shift: true }, () => {
|
||||
Keyboard.keyPress(KEYS.PAGE_DOWN);
|
||||
Keyboard.keyPress(KEYS.PAGE_DOWN);
|
||||
});
|
||||
expect(h.state.scrollX).toBe(initialScrollX - pageStepX);
|
||||
// y-axis unchanged
|
||||
expect(h.state.scrollY).toBe(initialScrollY - pageStepY);
|
||||
};
|
||||
|
||||
const zoom = h.state.zoom.value;
|
||||
// Assert we scroll properly when zoomed in
|
||||
h.setState({ zoom: { value: (zoom * 1.1) as typeof zoom } });
|
||||
scrollTest();
|
||||
// Assert we scroll properly when zoomed out
|
||||
h.setState({ zoom: { value: (zoom * 0.9) as typeof zoom } });
|
||||
scrollTest();
|
||||
// Assert we scroll properly with normal zoom
|
||||
h.setState({ zoom: { value: zoom } });
|
||||
scrollTest();
|
||||
restoreOriginalGetBoundingClientRect();
|
||||
});
|
||||
});
|
||||
|
@@ -701,10 +701,11 @@ export const ReactChildrenToObject = <
|
||||
if (
|
||||
React.isValidElement(child) &&
|
||||
typeof child.type !== "string" &&
|
||||
child?.type.name
|
||||
//@ts-ignore
|
||||
child?.type.displayName
|
||||
) {
|
||||
// @ts-ignore
|
||||
acc[child.type.name] = child;
|
||||
acc[child.type.displayName] = child;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Partial<T>);
|
||||
|
Reference in New Issue
Block a user