Compare commits

..

1 Commits

Author SHA1 Message Date
5639bb8e87 Updates to point to excalidraw-storage-backend 2025-05-25 22:22:50 -04:00
12 changed files with 25 additions and 310 deletions

View File

@ -1,5 +1,3 @@
MODE="development"
VITE_APP_BACKEND_V2_GET_URL=https://json-dev.excalidraw.com/api/v2/
VITE_APP_BACKEND_V2_POST_URL=https://json-dev.excalidraw.com/api/v2/post/

View File

@ -1,7 +1,5 @@
MODE="production"
VITE_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
VITE_APP_BACKEND_V2_GET_URL=https://ex.dylanbanta.com/api/v2/scenes/
VITE_APP_BACKEND_V2_POST_URL=https://ex.dylanbanta.com/api/v2/scenes/
VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries

View File

@ -34,9 +34,6 @@
<a href="https://discord.gg/UexuTaE">
<img alt="Chat on Discord" src="https://img.shields.io/discord/723672430744174682?color=738ad6&label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&widge=false"/>
</a>
<a href="https://deepwiki.com/excalidraw/excalidraw">
<img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" />
</a>
<a href="https://twitter.com/excalidraw">
<img alt="Follow Excalidraw on Twitter" src="https://img.shields.io/twitter/follow/excalidraw.svg?label=follow+@excalidraw&style=social&logo=twitter"/>
</a>

View File

@ -19,7 +19,7 @@ services:
- ./:/opt/node_app/app:delegated
- ./package.json:/opt/node_app/package.json
- ./yarn.lock:/opt/node_app/yarn.lock
- notused:/opt/node_app/app/node_modules
# - notused:/opt/node_app/app/node_modules
volumes:
notused:
# volumes:
# notused:

View File

@ -926,17 +926,22 @@ const ExcalidrawWrapper = () => {
<ShareDialog
collabAPI={collabAPI}
onExportToBackend={async () => {
if (excalidrawAPI) {
if (!excalidrawAPI) {
return;
}
try {
await onExportToBackend(
const { url, errorMessage } = await exportToBackend(
excalidrawAPI.getSceneElements(),
excalidrawAPI.getAppState(),
excalidrawAPI.getFiles(),
);
if (errorMessage) {
throw new Error(errorMessage);
}
setLatestShareableLink(url);
} catch (error: any) {
setErrorMessage(error.message);
}
}
}}
/>

View File

@ -41,8 +41,8 @@
"prettier": "@excalidraw/prettier-config",
"scripts": {
"build-node": "node ./scripts/build-node.js",
"build:app:docker": "cross-env VITE_APP_DISABLE_SENTRY=true vite build",
"build:app": "cross-env VITE_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA cross-env VITE_APP_ENABLE_TRACKING=true vite build",
"build:app:docker": "vite build",
"build:app": "vite build",
"build:version": "node ../scripts/build-version.js",
"build": "yarn build:app && yarn build:version",
"start": "yarn && vite",

View File

@ -974,25 +974,6 @@ export const updateElbowArrowPoints = (
),
"Elbow arrow segments must be either horizontal or vertical",
);
invariant(
updates.fixedSegments?.find(
(segment) =>
segment.index === 1 &&
pointsEqual(segment.start, (updates.points ?? arrow.points)[0]),
) == null &&
updates.fixedSegments?.find(
(segment) =>
segment.index === (updates.points ?? arrow.points).length - 1 &&
pointsEqual(
segment.end,
(updates.points ?? arrow.points)[
(updates.points ?? arrow.points).length - 1
],
),
) == null,
"The first and last segments cannot be fixed",
);
}
const fixedSegments = updates.fixedSegments ?? arrow.fixedSegments ?? [];

View File

@ -1483,13 +1483,13 @@ const getArrowheadOptions = (flip: boolean) => {
value: "crowfoot_one",
text: t("labels.arrowhead_crowfoot_one"),
icon: <ArrowheadCrowfootOneIcon flip={flip} />,
keyBinding: "x",
keyBinding: "c",
},
{
value: "crowfoot_many",
text: t("labels.arrowhead_crowfoot_many"),
icon: <ArrowheadCrowfootIcon flip={flip} />,
keyBinding: "c",
keyBinding: "x",
},
{
value: "crowfoot_one_or_many",

View File

@ -564,7 +564,7 @@ export const convertElementTypes = (
continue;
}
const fixedSegments: FixedSegment[] = [];
for (let i = 1; i < nextPoints.length - 2; i++) {
for (let i = 0; i < nextPoints.length - 1; i++) {
fixedSegments.push({
start: nextPoints[i],
end: nextPoints[i + 1],
@ -581,7 +581,6 @@ export const convertElementTypes = (
);
mutateElement(element, app.scene.getNonDeletedElementsMap(), {
...updates,
endArrowhead: "arrow",
});
} else {
// if we're converting to non-elbow linear element, check if

View File

@ -7,9 +7,6 @@ import {
} from "@excalidraw/element";
import { resizeSingleElement } from "@excalidraw/element";
import { isImageElement } from "@excalidraw/element";
import { isFrameLikeElement } from "@excalidraw/element";
import { getElementsInResizingFrame } from "@excalidraw/element";
import { replaceAllElementsInFrame } from "@excalidraw/element";
import type { ExcalidrawElement } from "@excalidraw/element/types";
@ -46,8 +43,6 @@ const handleDimensionChange: DragInputCallbackType<
originalAppState,
instantChange,
scene,
app,
setAppState,
}) => {
const elementsMap = scene.getNonDeletedElementsMap();
const origElement = originalElements[0];
@ -189,30 +184,6 @@ const handleDimensionChange: DragInputCallbackType<
},
);
// Handle frame membership update for resized frames
if (isFrameLikeElement(latestElement)) {
const nextElementsInFrame = getElementsInResizingFrame(
scene.getElementsIncludingDeleted(),
latestElement,
originalAppState,
scene.getNonDeletedElementsMap(),
);
const updatedElements = replaceAllElementsInFrame(
scene.getElementsIncludingDeleted(),
nextElementsInFrame,
latestElement,
app,
);
scene.replaceAllElements(updatedElements);
setAppState({
...app.state,
elementsToHighlight: nextElementsInFrame,
});
}
return;
}
const changeInWidth = property === "width" ? accumulatedChange : 0;
@ -259,30 +230,6 @@ const handleDimensionChange: DragInputCallbackType<
shouldMaintainAspectRatio: keepAspectRatio,
},
);
// Handle frame membership update for resized frames
if (isFrameLikeElement(latestElement)) {
const nextElementsInFrame = getElementsInResizingFrame(
scene.getElementsIncludingDeleted(),
latestElement,
originalAppState,
scene.getNonDeletedElementsMap(),
);
const updatedElements = replaceAllElementsInFrame(
scene.getElementsIncludingDeleted(),
nextElementsInFrame,
latestElement,
app,
);
scene.replaceAllElements(updatedElements);
setAppState({
...app.state,
elementsToHighlight: nextElementsInFrame,
});
}
}
};

View File

@ -11,7 +11,7 @@ import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types";
import type { Scene } from "@excalidraw/element";
import { useApp, useExcalidrawSetAppState } from "../App";
import { useApp } from "../App";
import { InlineIcon } from "../InlineIcon";
import { SMALLEST_DELTA } from "./utils";
@ -36,8 +36,6 @@ export type DragInputCallbackType<
property: P;
originalAppState: AppState;
setInputValue: (value: number) => void;
app: ReturnType<typeof useApp>;
setAppState: ReturnType<typeof useExcalidrawSetAppState>;
}) => void;
interface StatsDragInputProps<
@ -75,7 +73,6 @@ const StatsDragInput = <
sensitivity = 1,
}: StatsDragInputProps<T, E>) => {
const app = useApp();
const setAppState = useExcalidrawSetAppState();
const inputRef = useRef<HTMLInputElement>(null);
const labelRef = useRef<HTMLDivElement>(null);
@ -140,8 +137,6 @@ const StatsDragInput = <
property,
originalAppState: appState,
setInputValue: (value) => setInputValue(String(value)),
app,
setAppState,
});
app.syncActionResult({
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
@ -268,8 +263,6 @@ const StatsDragInput = <
scene,
originalAppState,
setInputValue: (value) => setInputValue(String(value)),
app,
setAppState,
});
stepChange = 0;
@ -294,11 +287,6 @@ const StatsDragInput = <
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
// Clear frame highlighting
setAppState({
elementsToHighlight: null,
});
lastPointer = null;
accumulatedChange = 0;
stepChange = 0;
@ -353,11 +341,6 @@ const StatsDragInput = <
stateRef.current.originalAppState = cloneJSON(appState);
}}
onBlur={(event) => {
// Clear frame highlighting when input loses focus
setAppState({
elementsToHighlight: null,
});
if (!inputValue) {
setInputValue(value.toString());
} else if (editable) {

View File

@ -728,196 +728,3 @@ describe("stats for multiple elements", () => {
expect(newGroupHeight).toBeCloseTo(500, 4);
});
});
describe("frame resizing behavior", () => {
beforeEach(async () => {
localStorage.clear();
renderStaticScene.mockClear();
reseed(7);
setDateTimeForTests("201933152653");
await render(<Excalidraw handleKeyboardGlobally={true} />);
API.setElements([]);
fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
button: 2,
clientX: 1,
clientY: 1,
});
const contextMenu = UI.queryContextMenu();
fireEvent.click(queryByTestId(contextMenu!, "stats")!);
stats = UI.queryStats();
});
beforeAll(() => {
mockBoundingClientRect();
});
afterAll(() => {
restoreOriginalGetBoundingClientRect();
});
it("should add shapes to frame when resizing frame to encompass them", () => {
// Create a frame
const frame = API.createElement({
type: "frame",
x: 0,
y: 0,
width: 100,
height: 100,
});
// Create a rectangle outside the frame
const rectangle = API.createElement({
type: "rectangle",
x: 150,
y: 50,
width: 50,
height: 50,
});
API.setElements([frame, rectangle]);
// Initially, rectangle should not be in the frame
expect(rectangle.frameId).toBe(null);
// Select the frame
API.setAppState({
selectedElementIds: {
[frame.id]: true,
},
});
elementStats = stats?.querySelector("#elementStats");
// Find the width input and update it to encompass the rectangle
const widthInput = UI.queryStatsProperty("W")?.querySelector(
".drag-input",
) as HTMLInputElement;
expect(widthInput).toBeDefined();
expect(widthInput.value).toBe("100");
// Resize frame to width 250, which should encompass the rectangle
UI.updateInput(widthInput, "250");
// After resizing, the rectangle should now be part of the frame
expect(h.elements.find((el) => el.id === rectangle.id)?.frameId).toBe(
frame.id,
);
});
it("should add multiple shapes when frame encompasses them through height resize", () => {
const frame = API.createElement({
type: "frame",
x: 0,
y: 0,
width: 200,
height: 100,
});
const rectangle1 = API.createElement({
type: "rectangle",
x: 50,
y: 150,
width: 50,
height: 50,
});
const rectangle2 = API.createElement({
type: "rectangle",
x: 100,
y: 180,
width: 40,
height: 40,
});
API.setElements([frame, rectangle1, rectangle2]);
// Initially, rectangles should not be in the frame
expect(rectangle1.frameId).toBe(null);
expect(rectangle2.frameId).toBe(null);
// Select the frame
API.setAppState({
selectedElementIds: {
[frame.id]: true,
},
});
elementStats = stats?.querySelector("#elementStats");
// Resize frame height to encompass both rectangles
const heightInput = UI.queryStatsProperty("H")?.querySelector(
".drag-input",
) as HTMLInputElement;
// Resize frame to height 250, which should encompass both rectangles
UI.updateInput(heightInput, "250");
// After resizing, both rectangles should now be part of the frame
expect(h.elements.find((el) => el.id === rectangle1.id)?.frameId).toBe(
frame.id,
);
expect(h.elements.find((el) => el.id === rectangle2.id)?.frameId).toBe(
frame.id,
);
});
it("should not affect shapes that remain outside frame after resize", () => {
const frame = API.createElement({
type: "frame",
x: 0,
y: 0,
width: 100,
height: 100,
});
const insideRect = API.createElement({
type: "rectangle",
x: 120,
y: 50,
width: 30,
height: 30,
});
const outsideRect = API.createElement({
type: "rectangle",
x: 300,
y: 50,
width: 30,
height: 30,
});
API.setElements([frame, insideRect, outsideRect]);
// Initially, both rectangles should not be in the frame
expect(insideRect.frameId).toBe(null);
expect(outsideRect.frameId).toBe(null);
// Select the frame
API.setAppState({
selectedElementIds: {
[frame.id]: true,
},
});
elementStats = stats?.querySelector("#elementStats");
// Resize frame width to 200, which should only encompass insideRect
const widthInput = UI.queryStatsProperty("W")?.querySelector(
".drag-input",
) as HTMLInputElement;
UI.updateInput(widthInput, "200");
// After resizing, only insideRect should be in the frame
expect(h.elements.find((el) => el.id === insideRect.id)?.frameId).toBe(
frame.id,
);
expect(h.elements.find((el) => el.id === outsideRect.id)?.frameId).toBe(
null,
);
});
});