Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61cf47a403 |
@@ -201,12 +201,23 @@ export const actionChangeStrokeColor = register({
|
|||||||
name: "changeStrokeColor",
|
name: "changeStrokeColor",
|
||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
|
const containers = getSelectedElements(elements, appState, false)
|
||||||
|
.filter((el) => el.boundElements)
|
||||||
|
.map((el) => el.id);
|
||||||
return {
|
return {
|
||||||
...(value.currentItemStrokeColor && {
|
...(value.currentItemStrokeColor && {
|
||||||
elements: changeProperty(
|
elements: changeProperty(
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
(el) => {
|
(el) => {
|
||||||
|
if (
|
||||||
|
isTextElement(el) &&
|
||||||
|
el.containerId &&
|
||||||
|
containers.includes(el.containerId) &&
|
||||||
|
getContainerElement(el)?.strokeColor !== el.strokeColor
|
||||||
|
) {
|
||||||
|
return el;
|
||||||
|
}
|
||||||
return hasStrokeColor(el.type)
|
return hasStrokeColor(el.type)
|
||||||
? newElementWith(el, {
|
? newElementWith(el, {
|
||||||
strokeColor: value.currentItemStrokeColor,
|
strokeColor: value.currentItemStrokeColor,
|
||||||
|
|||||||
+6
-20
@@ -2008,20 +2008,6 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -2044,6 +2030,12 @@ 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;
|
||||||
|
|
||||||
@@ -2057,12 +2049,6 @@ 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,14 +230,6 @@ 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")}
|
||||||
|
|||||||
@@ -325,6 +325,8 @@ export const textWysiwyg = ({
|
|||||||
whiteSpace = "pre-wrap";
|
whiteSpace = "pre-wrap";
|
||||||
wordBreak = "break-word";
|
wordBreak = "break-word";
|
||||||
}
|
}
|
||||||
|
const isContainerArrow = isArrowElement(getContainerElement(element));
|
||||||
|
const background = isContainerArrow ? "#fff" : "transparent";
|
||||||
Object.assign(editable.style, {
|
Object.assign(editable.style, {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
@@ -335,7 +337,7 @@ export const textWysiwyg = ({
|
|||||||
border: 0,
|
border: 0,
|
||||||
outline: 0,
|
outline: 0,
|
||||||
resize: "none",
|
resize: "none",
|
||||||
background: "transparent",
|
background,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
// must be specified because in dark mode canvas creates a stacking context
|
// must be specified because in dark mode canvas creates a stacking context
|
||||||
zIndex: "var(--zIndex-wysiwyg)",
|
zIndex: "var(--zIndex-wysiwyg)",
|
||||||
|
|||||||
@@ -46,15 +46,12 @@ 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)),
|
||||||
@@ -75,12 +72,8 @@ export class LocalData {
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
onFilesSaved: () => void,
|
onFilesSaved: () => void,
|
||||||
appStateOnly = false,
|
|
||||||
) => {
|
) => {
|
||||||
saveDataStateToLocalStorage(elements, appState, appStateOnly);
|
saveDataStateToLocalStorage(elements, appState);
|
||||||
if (appStateOnly) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.fileStorage.saveFiles({
|
await this.fileStorage.saveFiles({
|
||||||
elements,
|
elements,
|
||||||
@@ -104,14 +97,6 @@ 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,
|
||||||
...localDataState?.appState,
|
theme: localDataState?.appState?.theme || scene?.appState?.theme,
|
||||||
},
|
},
|
||||||
excalidrawAPI.getAppState(),
|
excalidrawAPI.getAppState(),
|
||||||
),
|
),
|
||||||
@@ -538,8 +538,6 @@ const ExcalidrawWrapper = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
LocalData.saveAppState(appState);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ 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",
|
||||||
|
|||||||
+1
-3
@@ -312,9 +312,7 @@
|
|||||||
"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"
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`Test Linear Elements Test bound text element should match styles for text editor 1`] = `
|
|
||||||
<textarea
|
|
||||||
class="excalidraw-wysiwyg"
|
|
||||||
data-type="wysiwyg"
|
|
||||||
dir="auto"
|
|
||||||
style="position: absolute; display: inline-block; min-height: 1em; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 0px; height: 0px; left: 40px; top: 20px; transform: translate(0px, 0px) scale(1) rotate(0deg); text-align: center; vertical-align: middle; color: rgb(0, 0, 0); opacity: 1; filter: var(--theme-filter); max-height: -20px; font: Emoji 20px 20px; line-height: 0px; font-family: Virgil, Segoe UI Emoji;"
|
|
||||||
tabindex="0"
|
|
||||||
wrap="off"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -829,15 +829,6 @@ describe("Test Linear Elements", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should match styles for text editor", () => {
|
|
||||||
createTwoPointerLinearElement("arrow");
|
|
||||||
Keyboard.keyPress(KEYS.ENTER);
|
|
||||||
const editor = document.querySelector(
|
|
||||||
".excalidraw-textEditorContainer > textarea",
|
|
||||||
) as HTMLTextAreaElement;
|
|
||||||
expect(editor).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should bind text to arrow when double clicked", async () => {
|
it("should bind text to arrow when double clicked", async () => {
|
||||||
createTwoPointerLinearElement("arrow");
|
createTwoPointerLinearElement("arrow");
|
||||||
const arrow = h.elements[0] as ExcalidrawLinearElement;
|
const arrow = h.elements[0] as ExcalidrawLinearElement;
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ 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;
|
||||||
|
|
||||||
@@ -53,60 +50,4 @@ 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();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-3
@@ -701,11 +701,10 @@ export const ReactChildrenToObject = <
|
|||||||
if (
|
if (
|
||||||
React.isValidElement(child) &&
|
React.isValidElement(child) &&
|
||||||
typeof child.type !== "string" &&
|
typeof child.type !== "string" &&
|
||||||
//@ts-ignore
|
child?.type.name
|
||||||
child?.type.displayName
|
|
||||||
) {
|
) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
acc[child.type.displayName] = child;
|
acc[child.type.name] = child;
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Partial<T>);
|
}, {} as Partial<T>);
|
||||||
|
|||||||
Reference in New Issue
Block a user