Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17477480f3 | ||
|
|
f7b3befd0a | ||
|
|
7b2bee9746 | ||
|
|
88014ace4a | ||
|
|
87a9430809 | ||
|
|
99b91c46f7 | ||
|
|
1ea5b26f25 |
@@ -332,6 +332,7 @@ import {
|
|||||||
isMeasureTextSupported,
|
isMeasureTextSupported,
|
||||||
isValidTextContainer,
|
isValidTextContainer,
|
||||||
measureText,
|
measureText,
|
||||||
|
normalizeText,
|
||||||
wrapText,
|
wrapText,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import {
|
import {
|
||||||
@@ -3412,7 +3413,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const lines = isPlainPaste ? [text] : text.split("\n");
|
const lines = isPlainPaste ? [text] : text.split("\n");
|
||||||
const textElements = lines.reduce(
|
const textElements = lines.reduce(
|
||||||
(acc: ExcalidrawTextElement[], line, idx) => {
|
(acc: ExcalidrawTextElement[], line, idx) => {
|
||||||
const originalText = line.trim();
|
const originalText = normalizeText(line).trim();
|
||||||
if (originalText.length) {
|
if (originalText.length) {
|
||||||
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
||||||
x,
|
x,
|
||||||
@@ -3887,7 +3888,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === KEYS.ESCAPE && this.flowChartCreator.isCreatingChart) {
|
if (!isInputLike(event.target)) {
|
||||||
|
if (
|
||||||
|
event.key === KEYS.ESCAPE &&
|
||||||
|
this.flowChartCreator.isCreatingChart
|
||||||
|
) {
|
||||||
this.flowChartCreator.clear();
|
this.flowChartCreator.clear();
|
||||||
this.triggerRender(true);
|
this.triggerRender(true);
|
||||||
return;
|
return;
|
||||||
@@ -3943,7 +3948,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const nextNode = this.scene.getNonDeletedElementsMap().get(nextId);
|
const nextNode = this.scene
|
||||||
|
.getNonDeletedElementsMap()
|
||||||
|
.get(nextId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
nextNode &&
|
nextNode &&
|
||||||
@@ -3970,6 +3977,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
event[KEYS.CTRL_OR_CMD] &&
|
event[KEYS.CTRL_OR_CMD] &&
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ const handleDimensionChange: DragInputCallbackType<
|
|||||||
> = ({
|
> = ({
|
||||||
accumulatedChange,
|
accumulatedChange,
|
||||||
originalElements,
|
originalElements,
|
||||||
originalElementsMap,
|
|
||||||
shouldKeepAspectRatio,
|
shouldKeepAspectRatio,
|
||||||
shouldChangeByStepSize,
|
shouldChangeByStepSize,
|
||||||
nextValue,
|
nextValue,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import {
|
|||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isFrameLikeElement,
|
isFrameLikeElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
|
isRectangularElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import type { ElementUpdate } from "./mutateElement";
|
import type { ElementUpdate } from "./mutateElement";
|
||||||
@@ -751,7 +752,7 @@ export const bindPointToSnapToElementOutline = (
|
|||||||
const aabb = bindableElement && aabbForElement(bindableElement);
|
const aabb = bindableElement && aabbForElement(bindableElement);
|
||||||
|
|
||||||
if (bindableElement && aabb) {
|
if (bindableElement && aabb) {
|
||||||
// TODO: Dirty hack until tangents are properly calculated
|
// TODO: Dirty hacks until tangents are properly calculated
|
||||||
const intersections = [
|
const intersections = [
|
||||||
...intersectElementWithLine(
|
...intersectElementWithLine(
|
||||||
bindableElement,
|
bindableElement,
|
||||||
@@ -759,24 +760,32 @@ export const bindPointToSnapToElementOutline = (
|
|||||||
[point[0], point[1] + 2 * bindableElement.height],
|
[point[0], point[1] + 2 * bindableElement.height],
|
||||||
FIXED_BINDING_DISTANCE,
|
FIXED_BINDING_DISTANCE,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
).map((i) =>
|
).map((i) => {
|
||||||
distanceToBindableElement(bindableElement, i, elementsMap) >=
|
if (!isRectangularElement(bindableElement)) {
|
||||||
bindableElement.height / 2
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = distanceToBindableElement(bindableElement, i, elementsMap);
|
||||||
|
return d >= bindableElement.height / 2 || d < FIXED_BINDING_DISTANCE
|
||||||
? ([point[0], -1 * i[1]] as Point)
|
? ([point[0], -1 * i[1]] as Point)
|
||||||
: ([point[0], i[1]] as Point),
|
: ([point[0], i[1]] as Point);
|
||||||
),
|
}),
|
||||||
...intersectElementWithLine(
|
...intersectElementWithLine(
|
||||||
bindableElement,
|
bindableElement,
|
||||||
[point[0] - 2 * bindableElement.width, point[1]],
|
[point[0] - 2 * bindableElement.width, point[1]],
|
||||||
[point[0] + 2 * bindableElement.width, point[1]],
|
[point[0] + 2 * bindableElement.width, point[1]],
|
||||||
FIXED_BINDING_DISTANCE,
|
FIXED_BINDING_DISTANCE,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
).map((i) =>
|
).map((i) => {
|
||||||
distanceToBindableElement(bindableElement, i, elementsMap) >=
|
if (!isRectangularElement(bindableElement)) {
|
||||||
bindableElement.width / 2
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = distanceToBindableElement(bindableElement, i, elementsMap);
|
||||||
|
return d >= bindableElement.width / 2 || d < FIXED_BINDING_DISTANCE
|
||||||
? ([-1 * i[0], point[1]] as Point)
|
? ([-1 * i[0], point[1]] as Point)
|
||||||
: ([i[0], point[1]] as Point),
|
: ([i[0], point[1]] as Point);
|
||||||
),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const heading = headingForPointFromElement(bindableElement, aabb, point);
|
const heading = headingForPointFromElement(bindableElement, aabb, point);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ describe("flow chart creation", () => {
|
|||||||
height: 100,
|
height: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
h.elements = [rectangle];
|
API.setElements([rectangle]);
|
||||||
API.setSelectedElements([rectangle]);
|
API.setSelectedElements([rectangle]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ describe("flow chart navigation", () => {
|
|||||||
height: 100,
|
height: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
h.elements = [rectangle];
|
API.setElements([rectangle]);
|
||||||
API.setSelectedElements([rectangle]);
|
API.setSelectedElements([rectangle]);
|
||||||
|
|
||||||
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
||||||
@@ -234,7 +234,7 @@ describe("flow chart navigation", () => {
|
|||||||
height: 100,
|
height: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
h.elements = [rectangle];
|
API.setElements([rectangle]);
|
||||||
API.setSelectedElements([rectangle]);
|
API.setSelectedElements([rectangle]);
|
||||||
|
|
||||||
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
||||||
@@ -325,7 +325,7 @@ describe("flow chart navigation", () => {
|
|||||||
height: 100,
|
height: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
h.elements = [rectangle];
|
API.setElements([rectangle]);
|
||||||
API.setSelectedElements([rectangle]);
|
API.setSelectedElements([rectangle]);
|
||||||
|
|
||||||
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
||||||
|
|||||||
@@ -741,7 +741,7 @@ export class LinearElementEditor {
|
|||||||
}
|
}
|
||||||
if (event.altKey && appState.editingLinearElement) {
|
if (event.altKey && appState.editingLinearElement) {
|
||||||
if (
|
if (
|
||||||
linearElementEditor.lastUncommittedPoint == null &&
|
linearElementEditor.lastUncommittedPoint === null &&
|
||||||
!isElbowArrow(element)
|
!isElbowArrow(element)
|
||||||
) {
|
) {
|
||||||
mutateElement(element, {
|
mutateElement(element, {
|
||||||
|
|||||||
@@ -357,7 +357,16 @@ export const textWysiwyg = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
editable.oninput = () => {
|
editable.oninput = () => {
|
||||||
onChange(normalizeText(editable.value));
|
const normalized = normalizeText(editable.value);
|
||||||
|
if (editable.value !== normalized) {
|
||||||
|
const selectionStart = editable.selectionStart;
|
||||||
|
editable.value = normalized;
|
||||||
|
// put the cursor at some position close to where it was before
|
||||||
|
// normalization (otherwise it'll end up at the end of the text)
|
||||||
|
editable.selectionStart = selectionStart;
|
||||||
|
editable.selectionEnd = selectionStart;
|
||||||
|
}
|
||||||
|
onChange(editable.value);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,6 +176,23 @@ export const isRectanguloidElement = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Remove this when proper distance calculation is introduced
|
||||||
|
// @see binding.ts:distanceToBindableElement()
|
||||||
|
export const isRectangularElement = (
|
||||||
|
element?: ExcalidrawElement | null,
|
||||||
|
): element is ExcalidrawBindableElement => {
|
||||||
|
return (
|
||||||
|
element != null &&
|
||||||
|
(element.type === "rectangle" ||
|
||||||
|
element.type === "image" ||
|
||||||
|
element.type === "text" ||
|
||||||
|
element.type === "iframe" ||
|
||||||
|
element.type === "embeddable" ||
|
||||||
|
element.type === "frame" ||
|
||||||
|
element.type === "magicframe")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const isTextBindableContainer = (
|
export const isTextBindableContainer = (
|
||||||
element: ExcalidrawElement | null,
|
element: ExcalidrawElement | null,
|
||||||
includeLocked = true,
|
includeLocked = true,
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ const generateElementCanvas = (
|
|||||||
zoom: Zoom,
|
zoom: Zoom,
|
||||||
renderConfig: StaticCanvasRenderConfig,
|
renderConfig: StaticCanvasRenderConfig,
|
||||||
appState: StaticCanvasAppState,
|
appState: StaticCanvasAppState,
|
||||||
): ExcalidrawElementWithCanvas => {
|
): ExcalidrawElementWithCanvas | null => {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
const context = canvas.getContext("2d")!;
|
const context = canvas.getContext("2d")!;
|
||||||
const padding = getCanvasPadding(element);
|
const padding = getCanvasPadding(element);
|
||||||
@@ -210,6 +210,10 @@ const generateElementCanvas = (
|
|||||||
zoom,
|
zoom,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!width || !height) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
|
|
||||||
@@ -540,6 +544,10 @@ const generateElementWithCanvas = (
|
|||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!elementWithCanvas) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
elementWithCanvasCache.set(element, elementWithCanvas);
|
elementWithCanvasCache.set(element, elementWithCanvas);
|
||||||
|
|
||||||
return elementWithCanvas;
|
return elementWithCanvas;
|
||||||
@@ -742,6 +750,10 @@ export const renderElement = (
|
|||||||
renderConfig,
|
renderConfig,
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
|
if (!elementWithCanvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
drawElementFromCanvas(
|
drawElementFromCanvas(
|
||||||
elementWithCanvas,
|
elementWithCanvas,
|
||||||
context,
|
context,
|
||||||
@@ -881,6 +893,10 @@ export const renderElement = (
|
|||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!elementWithCanvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const currentImageSmoothingStatus = context.imageSmoothingEnabled;
|
const currentImageSmoothingStatus = context.imageSmoothingEnabled;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export const yellow = (str: string) => `\u001b[33m${str}\u001b[0m`;
|
||||||
|
export const red = (str: string) => `\u001b[31m${str}\u001b[0m`;
|
||||||
+6
-3
@@ -5,6 +5,7 @@ import fs from "fs";
|
|||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
import polyfill from "./packages/excalidraw/polyfill";
|
import polyfill from "./packages/excalidraw/polyfill";
|
||||||
import { testPolyfills } from "./packages/excalidraw/tests/helpers/polyfills";
|
import { testPolyfills } from "./packages/excalidraw/tests/helpers/polyfills";
|
||||||
|
import { yellow } from "./packages/excalidraw/tests/helpers/colorize";
|
||||||
|
|
||||||
Object.assign(globalThis, testPolyfills);
|
Object.assign(globalThis, testPolyfills);
|
||||||
|
|
||||||
@@ -98,18 +99,20 @@ const element = document.createElement("div");
|
|||||||
element.id = "root";
|
element.id = "root";
|
||||||
document.body.appendChild(element);
|
document.body.appendChild(element);
|
||||||
|
|
||||||
const logger = console.error.bind(console);
|
const _consoleError = console.error.bind(console);
|
||||||
console.error = (...args) => {
|
console.error = (...args) => {
|
||||||
// the react's act() warning usually doesn't contain any useful stack trace
|
// the react's act() warning usually doesn't contain any useful stack trace
|
||||||
// so we're catching the log and re-logging the message with the test name,
|
// so we're catching the log and re-logging the message with the test name,
|
||||||
// also stripping the actual component stack trace as it's not useful
|
// also stripping the actual component stack trace as it's not useful
|
||||||
if (args[0]?.includes("act(")) {
|
if (args[0]?.includes("act(")) {
|
||||||
logger(
|
_consoleError(
|
||||||
|
yellow(
|
||||||
`<<< WARNING: test "${
|
`<<< WARNING: test "${
|
||||||
expect.getState().currentTestName
|
expect.getState().currentTestName
|
||||||
}" does not wrap some state update in act() >>>`,
|
}" does not wrap some state update in act() >>>`,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
logger(...args);
|
_consoleError(...args);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user