Compare commits

..
Author SHA1 Message Date
Milos Vetesnik 6dec38f291 fix: testing headers 2024-09-20 15:59:03 +02:00
Milos Vetesnik 448f3723a3 fix: testing headers fix for LP 2024-09-20 15:54:30 +02:00
Milos Vetesnik fe69b087f6 feat: testing LP 2024-08-09 14:38:48 +02:00
13 changed files with 132 additions and 196 deletions
+68 -76
View File
@@ -332,7 +332,6 @@ import {
isMeasureTextSupported,
isValidTextContainer,
measureText,
normalizeText,
wrapText,
} from "../element/textElement";
import {
@@ -3413,7 +3412,7 @@ class App extends React.Component<AppProps, AppState> {
const lines = isPlainPaste ? [text] : text.split("\n");
const textElements = lines.reduce(
(acc: ExcalidrawTextElement[], line, idx) => {
const originalText = normalizeText(line).trim();
const originalText = line.trim();
if (originalText.length) {
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
x,
@@ -3888,94 +3887,87 @@ class App extends React.Component<AppProps, AppState> {
});
}
if (!isInputLike(event.target)) {
if (event.key === KEYS.ESCAPE && this.flowChartCreator.isCreatingChart) {
this.flowChartCreator.clear();
this.triggerRender(true);
return;
}
const arrowKeyPressed = isArrowKey(event.key);
if (event[KEYS.CTRL_OR_CMD] && arrowKeyPressed && !event.shiftKey) {
event.preventDefault();
const selectedElements = getSelectedElements(
this.scene.getNonDeletedElementsMap(),
this.state,
);
if (
event.key === KEYS.ESCAPE &&
this.flowChartCreator.isCreatingChart
selectedElements.length === 1 &&
isFlowchartNodeElement(selectedElements[0])
) {
this.flowChartCreator.clear();
this.triggerRender(true);
return;
this.flowChartCreator.createNodes(
selectedElements[0],
this.scene.getNonDeletedElementsMap(),
this.state,
getLinkDirectionFromKey(event.key),
);
}
const arrowKeyPressed = isArrowKey(event.key);
return;
}
if (event[KEYS.CTRL_OR_CMD] && arrowKeyPressed && !event.shiftKey) {
if (event.altKey) {
const selectedElements = getSelectedElements(
this.scene.getNonDeletedElementsMap(),
this.state,
);
if (selectedElements.length === 1 && arrowKeyPressed) {
event.preventDefault();
const selectedElements = getSelectedElements(
const nextId = this.flowChartNavigator.exploreByDirection(
selectedElements[0],
this.scene.getNonDeletedElementsMap(),
this.state,
getLinkDirectionFromKey(event.key),
);
if (
selectedElements.length === 1 &&
isFlowchartNodeElement(selectedElements[0])
) {
this.flowChartCreator.createNodes(
selectedElements[0],
this.scene.getNonDeletedElementsMap(),
this.state,
getLinkDirectionFromKey(event.key),
);
}
if (nextId) {
this.setState((prevState) => ({
selectedElementIds: makeNextSelectedElementIds(
{
[nextId]: true,
},
prevState,
),
}));
return;
}
const nextNode = this.scene.getNonDeletedElementsMap().get(nextId);
if (event.altKey) {
const selectedElements = getSelectedElements(
this.scene.getNonDeletedElementsMap(),
this.state,
);
if (selectedElements.length === 1 && arrowKeyPressed) {
event.preventDefault();
const nextId = this.flowChartNavigator.exploreByDirection(
selectedElements[0],
this.scene.getNonDeletedElementsMap(),
getLinkDirectionFromKey(event.key),
);
if (nextId) {
this.setState((prevState) => ({
selectedElementIds: makeNextSelectedElementIds(
{
[nextId]: true,
},
prevState,
),
}));
const nextNode = this.scene
.getNonDeletedElementsMap()
.get(nextId);
if (
nextNode &&
!isElementCompletelyInViewport(
nextNode,
this.canvas.width / window.devicePixelRatio,
this.canvas.height / window.devicePixelRatio,
{
offsetLeft: this.state.offsetLeft,
offsetTop: this.state.offsetTop,
scrollX: this.state.scrollX,
scrollY: this.state.scrollY,
zoom: this.state.zoom,
},
this.scene.getNonDeletedElementsMap(),
)
) {
this.scrollToContent(nextNode, {
animate: true,
duration: 300,
});
}
if (
nextNode &&
!isElementCompletelyInViewport(
nextNode,
this.canvas.width / window.devicePixelRatio,
this.canvas.height / window.devicePixelRatio,
{
offsetLeft: this.state.offsetLeft,
offsetTop: this.state.offsetTop,
scrollX: this.state.scrollX,
scrollY: this.state.scrollY,
zoom: this.state.zoom,
},
this.scene.getNonDeletedElementsMap(),
)
) {
this.scrollToContent(nextNode, {
animate: true,
duration: 300,
});
}
return;
}
return;
}
}
@@ -23,6 +23,7 @@ const handleDimensionChange: DragInputCallbackType<
> = ({
accumulatedChange,
originalElements,
originalElementsMap,
shouldKeepAspectRatio,
shouldChangeByStepSize,
nextValue,
+9 -23
View File
@@ -41,7 +41,6 @@ import {
isElbowArrow,
isFrameLikeElement,
isLinearElement,
isRectangularElement,
isTextElement,
} from "./typeChecks";
import type { ElementUpdate } from "./mutateElement";
@@ -752,7 +751,7 @@ export const bindPointToSnapToElementOutline = (
const aabb = bindableElement && aabbForElement(bindableElement);
if (bindableElement && aabb) {
// TODO: Dirty hacks until tangents are properly calculated
// TODO: Dirty hack until tangents are properly calculated
const intersections = [
...intersectElementWithLine(
bindableElement,
@@ -760,33 +759,20 @@ export const bindPointToSnapToElementOutline = (
[point[0], point[1] + 2 * bindableElement.height],
FIXED_BINDING_DISTANCE,
elementsMap,
).map((i) => {
if (!isRectangularElement(bindableElement)) {
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], i[1]] as Point);
}),
),
...intersectElementWithLine(
bindableElement,
[point[0] - 2 * bindableElement.width, point[1]],
[point[0] + 2 * bindableElement.width, point[1]],
FIXED_BINDING_DISTANCE,
elementsMap,
).map((i) => {
if (!isRectangularElement(bindableElement)) {
return i;
}
const d = distanceToBindableElement(bindableElement, i, elementsMap);
return d >= bindableElement.width / 2 || d < FIXED_BINDING_DISTANCE
? ([-1 * i[0], point[1]] as Point)
: ([i[0], point[1]] as Point);
}),
];
),
].map((i) =>
distanceToBindableElement(bindableElement, i, elementsMap) >
Math.min(bindableElement.width, bindableElement.height) / 2
? ([-1 * i[0], -1 * i[1]] as Point)
: i,
);
const heading = headingForPointFromElement(bindableElement, aabb, point);
const isVertical =
@@ -36,7 +36,7 @@ describe("flow chart creation", () => {
height: 100,
});
API.setElements([rectangle]);
h.elements = [rectangle];
API.setSelectedElements([rectangle]);
});
@@ -166,7 +166,7 @@ describe("flow chart navigation", () => {
height: 100,
});
API.setElements([rectangle]);
h.elements = [rectangle];
API.setSelectedElements([rectangle]);
Keyboard.withModifierKeys({ ctrl: true }, () => {
@@ -234,7 +234,7 @@ describe("flow chart navigation", () => {
height: 100,
});
API.setElements([rectangle]);
h.elements = [rectangle];
API.setSelectedElements([rectangle]);
Keyboard.withModifierKeys({ ctrl: true }, () => {
@@ -325,7 +325,7 @@ describe("flow chart navigation", () => {
height: 100,
});
API.setElements([rectangle]);
h.elements = [rectangle];
API.setSelectedElements([rectangle]);
Keyboard.withModifierKeys({ ctrl: true }, () => {
@@ -741,7 +741,7 @@ export class LinearElementEditor {
}
if (event.altKey && appState.editingLinearElement) {
if (
linearElementEditor.lastUncommittedPoint === null &&
linearElementEditor.lastUncommittedPoint == null &&
!isElbowArrow(element)
) {
mutateElement(element, {
@@ -1447,6 +1447,8 @@ export class LinearElementEditor {
: null;
}
console.warn("movePoints", options?.changedElements);
const mergedElementsMap = options?.changedElements
? toBrandedType<SceneElementsMap>(
new Map([...elementsMap, ...options.changedElements]),
+1 -10
View File
@@ -357,16 +357,7 @@ export const textWysiwyg = ({
};
editable.oninput = () => {
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);
onChange(normalizeText(editable.value));
};
}
-17
View File
@@ -176,23 +176,6 @@ 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 = (
element: ExcalidrawElement | null,
includeLocked = true,
+1 -17
View File
@@ -199,7 +199,7 @@ const generateElementCanvas = (
zoom: Zoom,
renderConfig: StaticCanvasRenderConfig,
appState: StaticCanvasAppState,
): ExcalidrawElementWithCanvas | null => {
): ExcalidrawElementWithCanvas => {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d")!;
const padding = getCanvasPadding(element);
@@ -210,10 +210,6 @@ const generateElementCanvas = (
zoom,
);
if (!width || !height) {
return null;
}
canvas.width = width;
canvas.height = height;
@@ -544,10 +540,6 @@ const generateElementWithCanvas = (
appState,
);
if (!elementWithCanvas) {
return null;
}
elementWithCanvasCache.set(element, elementWithCanvas);
return elementWithCanvas;
@@ -750,10 +742,6 @@ export const renderElement = (
renderConfig,
appState,
);
if (!elementWithCanvas) {
return;
}
drawElementFromCanvas(
elementWithCanvas,
context,
@@ -893,10 +881,6 @@ export const renderElement = (
appState,
);
if (!elementWithCanvas) {
return;
}
const currentImageSmoothingStatus = context.imageSmoothingEnabled;
if (
@@ -1,2 +0,0 @@
export const yellow = (str: string) => `\u001b[33m${str}\u001b[0m`;
export const red = (str: string) => `\u001b[31m${str}\u001b[0m`;
-14
View File
@@ -246,20 +246,6 @@ describe("z-index manipulation", () => {
],
});
// elements should not duplicate
assertZindex({
elements: [
{ id: "A", containerId: "C" },
{ id: "B" },
{ id: "C", isSelected: true },
],
operations: [
[actionSendBackward, ["A", "C", "B"]],
// noop
[actionSendBackward, ["A", "C", "B"]],
],
});
// grouped elements should be atomic
// -------------------------------------------------------------------------
+14 -22
View File
@@ -80,37 +80,29 @@ const getTargetIndexAccountingForBinding = (
direction: "left" | "right",
) => {
if ("containerId" in nextElement && nextElement.containerId) {
const containerElement = Scene.getScene(nextElement)!.getElement(
nextElement.containerId,
);
if (containerElement) {
return direction === "left"
? Math.min(
elements.indexOf(containerElement),
elements.indexOf(nextElement),
)
: Math.max(
elements.indexOf(containerElement),
elements.indexOf(nextElement),
);
if (direction === "left") {
const containerElement = Scene.getScene(nextElement)!.getElement(
nextElement.containerId,
);
if (containerElement) {
return elements.indexOf(containerElement);
}
} else {
return elements.indexOf(nextElement);
}
} else {
const boundElementId = nextElement.boundElements?.find(
(binding) => binding.type !== "arrow",
)?.id;
if (boundElementId) {
if (direction === "left") {
return elements.indexOf(nextElement);
}
const boundTextElement =
Scene.getScene(nextElement)!.getElement(boundElementId);
if (boundTextElement) {
return direction === "left"
? Math.min(
elements.indexOf(boundTextElement),
elements.indexOf(nextElement),
)
: Math.max(
elements.indexOf(boundTextElement),
elements.indexOf(nextElement),
);
return elements.indexOf(boundTextElement);
}
}
}
+6 -9
View File
@@ -5,7 +5,6 @@ import fs from "fs";
import { vi } from "vitest";
import polyfill from "./packages/excalidraw/polyfill";
import { testPolyfills } from "./packages/excalidraw/tests/helpers/polyfills";
import { yellow } from "./packages/excalidraw/tests/helpers/colorize";
Object.assign(globalThis, testPolyfills);
@@ -99,20 +98,18 @@ const element = document.createElement("div");
element.id = "root";
document.body.appendChild(element);
const _consoleError = console.error.bind(console);
const logger = console.error.bind(console);
console.error = (...args) => {
// 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,
// also stripping the actual component stack trace as it's not useful
if (args[0]?.includes("act(")) {
_consoleError(
yellow(
`<<< WARNING: test "${
expect.getState().currentTestName
}" does not wrap some state update in act() >>>`,
),
logger(
`<<< WARNING: test "${
expect.getState().currentTestName
}" does not wrap some state update in act() >>>`,
);
} else {
_consoleError(...args);
logger(...args);
}
};
+25 -1
View File
@@ -2,7 +2,7 @@
"public": true,
"headers": [
{
"source": "/(.*)",
"source": "/",
"headers": [
{
"key": "Access-Control-Allow-Origin",
@@ -22,6 +22,27 @@
}
]
},
{
"source": "/plus/",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "https://plus.excalidraw.com"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "Feature-Policy",
"value": "*"
},
{
"key": "Referrer-Policy",
"value": "origin"
}
]
},
{
"source": "/:file*.woff2",
"headers": [
@@ -49,6 +70,9 @@
]
}
],
"rewrites": [
{"source": "/plus/", "destination": "https://plus.excalidraw.com/"}
],
"redirects": [
{
"source": "/webex/:match*",