mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-12-05 20:14:36 +01: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;
|
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)) {
|
if (this.actionManager.handleKeyDown(event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2030,12 +2044,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
|
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
|
||||||
: ELEMENT_TRANSLATE_AMOUNT);
|
: ELEMENT_TRANSLATE_AMOUNT);
|
||||||
|
|
||||||
const selectedElements = getSelectedElements(
|
|
||||||
this.scene.getNonDeletedElements(),
|
|
||||||
this.state,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let offsetX = 0;
|
let offsetX = 0;
|
||||||
let offsetY = 0;
|
let offsetY = 0;
|
||||||
|
|
||||||
@@ -2049,6 +2057,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
offsetY = step;
|
offsetY = step;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedElements = getSelectedElements(
|
||||||
|
this.scene.getNonDeletedElements(),
|
||||||
|
this.state,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
selectedElements.forEach((element) => {
|
selectedElements.forEach((element) => {
|
||||||
mutateElement(element, {
|
mutateElement(element, {
|
||||||
x: element.x + offsetX,
|
x: element.x + offsetX,
|
||||||
|
|||||||
@@ -230,6 +230,14 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
|||||||
label={t("helpDialog.zoomToSelection")}
|
label={t("helpDialog.zoomToSelection")}
|
||||||
shortcuts={["Shift+2"]}
|
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.fullScreen")} shortcuts={["F"]} />
|
||||||
<Shortcut
|
<Shortcut
|
||||||
label={t("buttons.zenMode")}
|
label={t("buttons.zenMode")}
|
||||||
|
|||||||
@@ -46,12 +46,15 @@ class LocalFileManager extends FileManager {
|
|||||||
const saveDataStateToLocalStorage = (
|
const saveDataStateToLocalStorage = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
appStateOnly = false,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
|
if (!appStateOnly) {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS,
|
STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS,
|
||||||
JSON.stringify(clearElementsForLocalStorage(elements)),
|
JSON.stringify(clearElementsForLocalStorage(elements)),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,
|
STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,
|
||||||
JSON.stringify(clearAppStateForLocalStorage(appState)),
|
JSON.stringify(clearAppStateForLocalStorage(appState)),
|
||||||
@@ -72,8 +75,12 @@ export class LocalData {
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
onFilesSaved: () => void,
|
onFilesSaved: () => void,
|
||||||
|
appStateOnly = false,
|
||||||
) => {
|
) => {
|
||||||
saveDataStateToLocalStorage(elements, appState);
|
saveDataStateToLocalStorage(elements, appState, appStateOnly);
|
||||||
|
if (appStateOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.fileStorage.saveFiles({
|
await this.fileStorage.saveFiles({
|
||||||
elements,
|
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 = () => {
|
static flushSave = () => {
|
||||||
this._save.flush();
|
this._save.flush();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ const initializeScene = async (opts: {
|
|||||||
...restoreAppState(
|
...restoreAppState(
|
||||||
{
|
{
|
||||||
...scene?.appState,
|
...scene?.appState,
|
||||||
theme: localDataState?.appState?.theme || scene?.appState?.theme,
|
...localDataState?.appState,
|
||||||
},
|
},
|
||||||
excalidrawAPI.getAppState(),
|
excalidrawAPI.getAppState(),
|
||||||
),
|
),
|
||||||
@@ -538,6 +538,8 @@ const ExcalidrawWrapper = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
LocalData.saveAppState(appState);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export const KEYS = {
|
|||||||
ARROW_LEFT: "ArrowLeft",
|
ARROW_LEFT: "ArrowLeft",
|
||||||
ARROW_RIGHT: "ArrowRight",
|
ARROW_RIGHT: "ArrowRight",
|
||||||
ARROW_UP: "ArrowUp",
|
ARROW_UP: "ArrowUp",
|
||||||
|
PAGE_UP: "PageUp",
|
||||||
|
PAGE_DOWN: "PageDown",
|
||||||
BACKSPACE: "Backspace",
|
BACKSPACE: "Backspace",
|
||||||
ALT: "Alt",
|
ALT: "Alt",
|
||||||
CTRL_OR_CMD: isDarwin ? "metaKey" : "ctrlKey",
|
CTRL_OR_CMD: isDarwin ? "metaKey" : "ctrlKey",
|
||||||
|
|||||||
@@ -312,7 +312,9 @@
|
|||||||
"view": "View",
|
"view": "View",
|
||||||
"zoomToFit": "Zoom to fit all elements",
|
"zoomToFit": "Zoom to fit all elements",
|
||||||
"zoomToSelection": "Zoom to selection",
|
"zoomToSelection": "Zoom to selection",
|
||||||
"toggleElementLock": "Lock/unlock selection"
|
"toggleElementLock": "Lock/unlock selection",
|
||||||
|
"movePageUpDown": "Move page up/down",
|
||||||
|
"movePageLeftRight": "Move page left/right"
|
||||||
},
|
},
|
||||||
"clearCanvasDialog": {
|
"clearCanvasDialog": {
|
||||||
"title": "Clear canvas"
|
"title": "Clear canvas"
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import {
|
|||||||
} from "./test-utils";
|
} from "./test-utils";
|
||||||
import { Excalidraw } from "../packages/excalidraw/index";
|
import { Excalidraw } from "../packages/excalidraw/index";
|
||||||
import { API } from "./helpers/api";
|
import { API } from "./helpers/api";
|
||||||
|
import { Keyboard } from "./helpers/ui";
|
||||||
|
import { KEYS } from "../keys";
|
||||||
|
import ExcalidrawApp from "../excalidraw-app";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
@@ -50,4 +53,60 @@ describe("appState", () => {
|
|||||||
});
|
});
|
||||||
restoreOriginalGetBoundingClientRect();
|
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 (
|
if (
|
||||||
React.isValidElement(child) &&
|
React.isValidElement(child) &&
|
||||||
typeof child.type !== "string" &&
|
typeof child.type !== "string" &&
|
||||||
child?.type.name
|
//@ts-ignore
|
||||||
|
child?.type.displayName
|
||||||
) {
|
) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
acc[child.type.name] = child;
|
acc[child.type.displayName] = child;
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Partial<T>);
|
}, {} as Partial<T>);
|
||||||
|
|||||||
Reference in New Issue
Block a user