Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ed78d4895 | |||
| 5ba2798306 | |||
| b8aae34e32 | |||
| 6317ac16ee | |||
| 9ad75b8375 | |||
| 4c9ad1a22f | |||
| 023895b49b | |||
| eb37be953a |
@@ -0,0 +1,27 @@
|
|||||||
|
name: Playwright Tests
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, master ]
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
timeout-minutes: 60
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm install -g yarn && yarn
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: yarn playwright install --with-deps
|
||||||
|
- name: Run Playwright tests
|
||||||
|
run: yarn playwright test
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: playwright-report/
|
||||||
|
retention-days: 30
|
||||||
@@ -27,3 +27,9 @@ dev-dist
|
|||||||
html
|
html
|
||||||
meta*.json
|
meta*.json
|
||||||
.claude
|
.claude
|
||||||
|
|
||||||
|
# Playwright
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
|||||||
@@ -615,52 +615,6 @@ export default function ExampleApp({
|
|||||||
const renderMenu = () => {
|
const renderMenu = () => {
|
||||||
return (
|
return (
|
||||||
<MainMenu>
|
<MainMenu>
|
||||||
<MainMenu.Sub>
|
|
||||||
<MainMenu.Sub.Trigger
|
|
||||||
title="Custom trigger"
|
|
||||||
icon={
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
strokeWidth={1.5}
|
|
||||||
stroke="currentColor"
|
|
||||||
className="w-6 h-6"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M15.042 21.672L13.684 16.6m0 0l-2.51 2.225.569-9.47 5.227 7.917-3.286-.672zm-7.518-.267A8.25 8.25 0 1120.25 10.5M8.288 14.212A5.25 5.25 0 1117.25 10.5"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Submenu trigger
|
|
||||||
</MainMenu.Sub.Trigger>
|
|
||||||
<MainMenu.Sub.Content>
|
|
||||||
<MainMenu.Sub.Item
|
|
||||||
icon={
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
strokeWidth={1.5}
|
|
||||||
stroke="currentColor"
|
|
||||||
className="w-6 h-6"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M12 7.5h1.5m-1.5 3h1.5m-7.5 3h7.5m-7.5 3h7.5m3-9h3.375c.621 0 1.125.504 1.125 1.125V18a2.25 2.25 0 01-2.25 2.25M16.5 7.5V18a2.25 2.25 0 002.25 2.25M16.5 7.5V4.875c0-.621-.504-1.125-1.125-1.125H4.125C3.504 3.75 3 4.254 3 4.875V18a2.25 2.25 0 002.25 2.25h13.5M6 7.5h3v3H6v-3z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
}
|
|
||||||
onSelect={() => window.alert("You clicked on sub item")}
|
|
||||||
>
|
|
||||||
Sub item
|
|
||||||
</MainMenu.Sub.Item>
|
|
||||||
</MainMenu.Sub.Content>
|
|
||||||
</MainMenu.Sub>
|
|
||||||
<MainMenu.DefaultItems.SaveAsImage />
|
<MainMenu.DefaultItems.SaveAsImage />
|
||||||
<MainMenu.DefaultItems.Export />
|
<MainMenu.DefaultItems.Export />
|
||||||
<MainMenu.Separator />
|
<MainMenu.Separator />
|
||||||
@@ -668,57 +622,10 @@ export default function ExampleApp({
|
|||||||
isCollaborating={isCollaborating}
|
isCollaborating={isCollaborating}
|
||||||
onSelect={() => window.alert("You clicked on collab button")}
|
onSelect={() => window.alert("You clicked on collab button")}
|
||||||
/>
|
/>
|
||||||
<MainMenu.Sub>
|
|
||||||
<MainMenu.Sub.Trigger>Trigger</MainMenu.Sub.Trigger>
|
|
||||||
<MainMenu.Sub.Content>
|
|
||||||
<MainMenu.Sub.Item
|
|
||||||
onSelect={() => window.alert("You clicked on sub item")}
|
|
||||||
>
|
|
||||||
Sub item
|
|
||||||
</MainMenu.Sub.Item>
|
|
||||||
</MainMenu.Sub.Content>
|
|
||||||
</MainMenu.Sub>
|
|
||||||
<MainMenu.Group title="Excalidraw links">
|
<MainMenu.Group title="Excalidraw links">
|
||||||
<MainMenu.DefaultItems.Socials />
|
<MainMenu.DefaultItems.Socials />
|
||||||
</MainMenu.Group>
|
</MainMenu.Group>
|
||||||
{/* <MainMenu.Separator /> */}
|
<MainMenu.Separator />
|
||||||
<MainMenu.Sub>
|
|
||||||
<MainMenu.Sub.Trigger className="custom-classname">
|
|
||||||
Another submenu trigger
|
|
||||||
</MainMenu.Sub.Trigger>
|
|
||||||
<MainMenu.Sub.Content className="custom-classname-for-content">
|
|
||||||
<MainMenu.Sub.Item
|
|
||||||
title="Sub item"
|
|
||||||
onSelect={() => window.alert("You clicked on sub item")}
|
|
||||||
>
|
|
||||||
Sub item
|
|
||||||
</MainMenu.Sub.Item>
|
|
||||||
</MainMenu.Sub.Content>
|
|
||||||
</MainMenu.Sub>
|
|
||||||
<MainMenu.Sub>
|
|
||||||
<MainMenu.Sub.Trigger>Trigger me</MainMenu.Sub.Trigger>
|
|
||||||
<MainMenu.Sub.Content>
|
|
||||||
<MainMenu.Sub>
|
|
||||||
<MainMenu.Sub.Trigger>Trigger me inside</MainMenu.Sub.Trigger>
|
|
||||||
<MainMenu.Sub.Content>
|
|
||||||
<MainMenu.Sub.Item
|
|
||||||
onSelect={() => {
|
|
||||||
alert("wow, nested submenus!");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Item wow
|
|
||||||
</MainMenu.Sub.Item>
|
|
||||||
</MainMenu.Sub.Content>
|
|
||||||
</MainMenu.Sub>
|
|
||||||
<MainMenu.Sub.Item
|
|
||||||
onSelect={() => {
|
|
||||||
alert("wow, nested submenus! very cool");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Another one
|
|
||||||
</MainMenu.Sub.Item>
|
|
||||||
</MainMenu.Sub.Content>
|
|
||||||
</MainMenu.Sub>
|
|
||||||
<MainMenu.ItemCustom>
|
<MainMenu.ItemCustom>
|
||||||
<button
|
<button
|
||||||
style={{ height: "2rem" }}
|
style={{ height: "2rem" }}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
APP_NAME,
|
APP_NAME,
|
||||||
EVENT,
|
EVENT,
|
||||||
THEME,
|
THEME,
|
||||||
|
TITLE_TIMEOUT,
|
||||||
VERSION_TIMEOUT,
|
VERSION_TIMEOUT,
|
||||||
debounce,
|
debounce,
|
||||||
getVersion,
|
getVersion,
|
||||||
@@ -73,6 +74,7 @@ import type {
|
|||||||
import type { ResolutionType } from "@excalidraw/common/utility-types";
|
import type { ResolutionType } from "@excalidraw/common/utility-types";
|
||||||
import type { ResolvablePromise } from "@excalidraw/common/utils";
|
import type { ResolvablePromise } from "@excalidraw/common/utils";
|
||||||
|
|
||||||
|
import "./record";
|
||||||
import CustomStats from "./CustomStats";
|
import CustomStats from "./CustomStats";
|
||||||
import {
|
import {
|
||||||
Provider,
|
Provider,
|
||||||
@@ -498,6 +500,11 @@ const ExcalidrawWrapper = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const titleTimeout = setTimeout(
|
||||||
|
() => (document.title = APP_NAME),
|
||||||
|
TITLE_TIMEOUT,
|
||||||
|
);
|
||||||
|
|
||||||
const syncData = debounce(() => {
|
const syncData = debounce(() => {
|
||||||
if (isTestEnv()) {
|
if (isTestEnv()) {
|
||||||
return;
|
return;
|
||||||
@@ -588,6 +595,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
visibilityChange,
|
visibilityChange,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
clearTimeout(titleTimeout);
|
||||||
};
|
};
|
||||||
}, [isCollabDisabled, collabAPI, excalidrawAPI, setLangCode]);
|
}, [isCollabDisabled, collabAPI, excalidrawAPI, setLangCode]);
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ export const AppMainMenu: React.FC<{
|
|||||||
<MainMenu.DefaultItems.SearchMenu />
|
<MainMenu.DefaultItems.SearchMenu />
|
||||||
<MainMenu.DefaultItems.Help />
|
<MainMenu.DefaultItems.Help />
|
||||||
<MainMenu.DefaultItems.ClearCanvas />
|
<MainMenu.DefaultItems.ClearCanvas />
|
||||||
<MainMenu.DefaultItems.Preferences />
|
|
||||||
<MainMenu.Separator />
|
<MainMenu.Separator />
|
||||||
<MainMenu.ItemLink
|
<MainMenu.ItemLink
|
||||||
icon={ExcalLogo}
|
icon={ExcalLogo}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Excalidraw Whiteboard</title>
|
<title>
|
||||||
|
Free, collaborative whiteboard • Hand-drawn look & feel | Excalidraw
|
||||||
|
</title>
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, shrink-to-fit=no"
|
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, shrink-to-fit=no"
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
"build:version": "node ../scripts/build-version.js",
|
"build:version": "node ../scripts/build-version.js",
|
||||||
"build": "yarn build:app && yarn build:version",
|
"build": "yarn build:app && yarn build:version",
|
||||||
"start": "yarn && vite",
|
"start": "yarn && vite",
|
||||||
|
"start:test": "yarn && vite --mode test",
|
||||||
"start:production": "yarn build && yarn serve",
|
"start:production": "yarn build && yarn serve",
|
||||||
"serve": "npx http-server build -a localhost -p 5001 -o",
|
"serve": "npx http-server build -a localhost -p 5001 -o",
|
||||||
"build:preview": "yarn build && vite preview --port 5000"
|
"build:preview": "yarn build && vite preview --port 5000"
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
import { isDevEnv } from "@excalidraw/common";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
record: typeof Record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Record {
|
||||||
|
private static recording: boolean = false;
|
||||||
|
private static events: string = "";
|
||||||
|
private static timestamp: number = 0;
|
||||||
|
|
||||||
|
public static get isRecording() {
|
||||||
|
return Record.recording;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static header() {
|
||||||
|
Record.events += " await page.addInitScript(() => {\n";
|
||||||
|
Record.events += " Math.random = () => 0.42;\n\n";
|
||||||
|
|
||||||
|
// Capture LocalStorage, which is essential to re-establish state
|
||||||
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
|
const key = localStorage.key(i);
|
||||||
|
if (key != null) {
|
||||||
|
const value = JSON.stringify(localStorage.getItem(key));
|
||||||
|
Record.events += ` localStorage.getItem("${key}");\n`;
|
||||||
|
Record.events += ` localStorage.setItem("${key}", ${value});\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Record.events += " });\n";
|
||||||
|
Record.events += ` await page.setViewportSize({ width: ${window.innerWidth}, height: ${window.innerHeight} });\n`;
|
||||||
|
Record.events += ` await page.goto("http://localhost:3000");\n`;
|
||||||
|
Record.events += ` await page.waitForLoadState("load");\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static restart() {
|
||||||
|
if (!Record.recording) {
|
||||||
|
Record.start();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Record.events += `});\n\n`;
|
||||||
|
Record.events += `test("${
|
||||||
|
Date.now() + Math.floor(Math.random() * Date.now()).toString(36)
|
||||||
|
}", async ({ page }) => {\n`;
|
||||||
|
|
||||||
|
Record.header();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static start() {
|
||||||
|
Record.recording = true;
|
||||||
|
|
||||||
|
// Record header
|
||||||
|
this.header();
|
||||||
|
|
||||||
|
// Set up the events
|
||||||
|
Record.timestamp = performance.now();
|
||||||
|
|
||||||
|
window.addEventListener("mousemove", this.onMouseMove);
|
||||||
|
window.addEventListener("mousedown", this.onMouseDown);
|
||||||
|
window.addEventListener("mouseup", this.onMouseUp);
|
||||||
|
window.addEventListener("keydown", this.onKeyDown);
|
||||||
|
window.addEventListener("keyup", this.onKeyUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static stop() {
|
||||||
|
window.removeEventListener("mousemove", this.onMouseMove);
|
||||||
|
window.removeEventListener("mousedown", this.onMouseDown);
|
||||||
|
window.removeEventListener("mouseup", this.onMouseUp);
|
||||||
|
window.removeEventListener("keydown", this.onKeyDown);
|
||||||
|
window.removeEventListener("keyup", this.onKeyUp);
|
||||||
|
Record.recording = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays a window as an absolutely positioned DIV with the generated
|
||||||
|
/// events within <pre> tags as formatted JSON, so it can be copied easily.
|
||||||
|
public static showGeneratedEvents() {
|
||||||
|
if (Record.recording) {
|
||||||
|
Record.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.style.position = "absolute";
|
||||||
|
div.style.top = "10px";
|
||||||
|
div.style.right = "10px";
|
||||||
|
div.style.left = "10px";
|
||||||
|
div.style.height = "60vh";
|
||||||
|
div.style.backgroundColor = "gray";
|
||||||
|
div.style.padding = "10px";
|
||||||
|
div.style.zIndex = "10000";
|
||||||
|
|
||||||
|
const pre = document.createElement("pre");
|
||||||
|
|
||||||
|
let textContent = `import { expect, test } from "@playwright/test";\n\n`;
|
||||||
|
textContent += `test("${
|
||||||
|
Date.now() + Math.floor(Math.random() * Date.now()).toString(36)
|
||||||
|
}", async ({ page }) => {\n`;
|
||||||
|
textContent += Record.events;
|
||||||
|
textContent += `});\n`;
|
||||||
|
|
||||||
|
pre.textContent = textContent;
|
||||||
|
//pre.textContent = Record.events;
|
||||||
|
|
||||||
|
pre.style.marginTop = "18px";
|
||||||
|
pre.style.maxHeight = "60vh";
|
||||||
|
pre.style.overflow = "auto";
|
||||||
|
div.appendChild(pre);
|
||||||
|
|
||||||
|
const copyBtn = document.createElement("button");
|
||||||
|
copyBtn.textContent = "Copy";
|
||||||
|
copyBtn.title = "Copy generated events to clipboard";
|
||||||
|
copyBtn.setAttribute("aria-label", "Copy generated events to clipboard");
|
||||||
|
copyBtn.style.position = "absolute";
|
||||||
|
copyBtn.style.top = "4px";
|
||||||
|
copyBtn.style.left = "4px";
|
||||||
|
copyBtn.style.border = "none";
|
||||||
|
copyBtn.style.background = "transparent";
|
||||||
|
copyBtn.style.fontSize = "12px";
|
||||||
|
copyBtn.style.lineHeight = "1";
|
||||||
|
copyBtn.style.cursor = "pointer";
|
||||||
|
copyBtn.style.padding = "4px 8px";
|
||||||
|
copyBtn.addEventListener("click", async () => {
|
||||||
|
const text = pre.textContent ?? "";
|
||||||
|
try {
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
} else {
|
||||||
|
const ta = document.createElement("textarea");
|
||||||
|
ta.value = text;
|
||||||
|
document.body.appendChild(ta);
|
||||||
|
ta.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(ta);
|
||||||
|
}
|
||||||
|
const orig = copyBtn.textContent;
|
||||||
|
copyBtn.textContent = "Copied";
|
||||||
|
setTimeout(() => (copyBtn.textContent = orig), 1000);
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
div.appendChild(copyBtn);
|
||||||
|
|
||||||
|
const closeBtn = document.createElement("button");
|
||||||
|
closeBtn.textContent = "×";
|
||||||
|
closeBtn.title = "Close";
|
||||||
|
closeBtn.style.position = "absolute";
|
||||||
|
closeBtn.style.top = "4px";
|
||||||
|
closeBtn.style.right = "4px";
|
||||||
|
closeBtn.style.border = "none";
|
||||||
|
closeBtn.style.background = "transparent";
|
||||||
|
closeBtn.style.fontSize = "18px";
|
||||||
|
closeBtn.style.lineHeight = "1";
|
||||||
|
closeBtn.style.cursor = "pointer";
|
||||||
|
closeBtn.addEventListener("click", () => {
|
||||||
|
// remove the dialog from DOM
|
||||||
|
if (div.parentNode) {
|
||||||
|
div.parentNode.removeChild(div);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
div.appendChild(closeBtn);
|
||||||
|
|
||||||
|
document.body.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static onMouseMove(event: MouseEvent) {
|
||||||
|
if (
|
||||||
|
event.clientX < 0 ||
|
||||||
|
event.clientX > window.innerWidth ||
|
||||||
|
event.clientY < 0 ||
|
||||||
|
event.clientY > window.innerHeight
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = event.timeStamp || performance.now();
|
||||||
|
const delay = now - Record.timestamp;
|
||||||
|
Record.timestamp = now;
|
||||||
|
|
||||||
|
if (delay > 0) {
|
||||||
|
Record.events += ` await page.waitForTimeout(${delay});\n`;
|
||||||
|
}
|
||||||
|
Record.events += ` await page.mouse.move(${event.clientX}, ${event.clientY});\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static onMouseDown(event: MouseEvent) {
|
||||||
|
const now = event.timeStamp || performance.now();
|
||||||
|
const delay = now - Record.timestamp;
|
||||||
|
Record.timestamp = now;
|
||||||
|
|
||||||
|
if (delay > 0) {
|
||||||
|
Record.events += ` await page.waitForTimeout(${delay});\n`;
|
||||||
|
}
|
||||||
|
const button =
|
||||||
|
event.button === 0 ? "left" : event.button === 1 ? "middle" : "right";
|
||||||
|
Record.events += ` await page.mouse.down({ button: "${button}" });\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static onMouseUp(event: MouseEvent) {
|
||||||
|
const now = event.timeStamp || performance.now();
|
||||||
|
const delay = now - Record.timestamp;
|
||||||
|
Record.timestamp = now;
|
||||||
|
|
||||||
|
if (delay > 0) {
|
||||||
|
Record.events += ` await page.waitForTimeout(${delay});\n`;
|
||||||
|
}
|
||||||
|
const button =
|
||||||
|
event.button === 0 ? "left" : event.button === 1 ? "middle" : "right";
|
||||||
|
Record.events += ` await page.mouse.up({ button: "${button}" });\n`;
|
||||||
|
|
||||||
|
Record.events += " await expect(page).toHaveScreenshot({\n";
|
||||||
|
Record.events += " maxDiffPixels: 100,\n";
|
||||||
|
Record.events += " maxDiffPixelRatio: 0.01,\n";
|
||||||
|
Record.events += " });\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static onKeyDown(event: KeyboardEvent) {
|
||||||
|
// Only record if the recording key is not pressed
|
||||||
|
if (event.key !== "F2") {
|
||||||
|
const now = event.timeStamp || performance.now();
|
||||||
|
const delay = now - Record.timestamp;
|
||||||
|
Record.timestamp = now;
|
||||||
|
|
||||||
|
if (delay > 0) {
|
||||||
|
Record.events += ` await page.waitForTimeout(${delay});\n`;
|
||||||
|
}
|
||||||
|
Record.events += ` await page.keyboard.down("${event.key}");\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static onKeyUp(event: KeyboardEvent) {
|
||||||
|
// Only record if the recording key is not pressed
|
||||||
|
if (event.key !== "F2") {
|
||||||
|
const now = event.timeStamp || performance.now();
|
||||||
|
const delay = now - Record.timestamp;
|
||||||
|
Record.timestamp = now;
|
||||||
|
|
||||||
|
if (delay > 0) {
|
||||||
|
Record.events += ` await page.waitForTimeout(${delay});\n`;
|
||||||
|
}
|
||||||
|
Record.events += ` await page.keyboard.up("${event.key}");\n`;
|
||||||
|
|
||||||
|
Record.events += " await expect(page).toHaveScreenshot({\n";
|
||||||
|
Record.events += " maxDiffPixels: 100,\n";
|
||||||
|
Record.events += " maxDiffPixelRatio: 0.01,\n";
|
||||||
|
Record.events += " });\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDevEnv()) {
|
||||||
|
window.record = Record;
|
||||||
|
|
||||||
|
window.addEventListener("keyup", (event) => {
|
||||||
|
if (event.key === "F2") {
|
||||||
|
if (Record.isRecording) {
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
console.info("Stopping Playwright recording");
|
||||||
|
Record.stop();
|
||||||
|
} else {
|
||||||
|
Record.restart();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.info("Starting Playwright recording");
|
||||||
|
Record.start();
|
||||||
|
}
|
||||||
|
} else if (event.key === "Enter" && event.ctrlKey) {
|
||||||
|
Record.showGeneratedEvents();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,716 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
test("17562123239901g67cqde", async ({ page }) => {
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
Math.random = () => 0.42;
|
||||||
|
|
||||||
|
localStorage.getItem("i18nextLng");
|
||||||
|
localStorage.setItem("i18nextLng", "en");
|
||||||
|
localStorage.getItem("excalidraw-collab");
|
||||||
|
localStorage.setItem("excalidraw-collab", '{"username":""}');
|
||||||
|
localStorage.getItem("excalidraw-debug");
|
||||||
|
localStorage.setItem("excalidraw-debug", '{"enabled":true}');
|
||||||
|
localStorage.getItem("excalidraw-theme");
|
||||||
|
localStorage.setItem("excalidraw-theme", "dark");
|
||||||
|
localStorage.getItem("version-files");
|
||||||
|
localStorage.setItem("version-files", "1756212319038");
|
||||||
|
localStorage.getItem("version-dataState");
|
||||||
|
localStorage.setItem("version-dataState", "1756212319038");
|
||||||
|
localStorage.getItem("excalidraw-state");
|
||||||
|
localStorage.setItem(
|
||||||
|
"excalidraw-state",
|
||||||
|
'{"showWelcomeScreen":true,"theme":"dark","currentChartType":"bar","currentItemBackgroundColor":"#a5d8ff","currentItemEndArrowhead":"arrow","currentItemFillStyle":"solid","currentItemFontFamily":5,"currentItemFontSize":20,"currentItemOpacity":100,"currentItemRoughness":2,"currentItemStartArrowhead":null,"currentItemStrokeColor":"#1e1e1e","currentItemRoundness":"round","currentItemArrowType":"round","currentItemStrokeStyle":"solid","currentItemStrokeWidth":2,"currentItemTextAlign":"left","cursorButton":"up","editingGroupId":null,"activeTool":{"type":"arrow","customType":null,"locked":false,"fromSelection":false,"lastActiveTool":null},"penMode":false,"penDetected":false,"exportBackground":true,"exportScale":1,"exportEmbedScene":false,"exportWithDarkMode":false,"gridSize":20,"gridStep":5,"gridModeEnabled":false,"defaultSidebarDockedPreference":false,"lastPointerDownWith":"mouse","name":"Untitled-2025-07-28-1603","openMenu":null,"openSidebar":null,"previousSelectedElementIds":{},"scrolledOutside":false,"scrollX":688.1079394457738,"scrollY":349.585883261872,"selectedElementIds":{},"selectedGroupIds":{},"shouldCacheIgnoreZoom":false,"stats":{"open":true,"panels":3},"viewBackgroundColor":"#ffffff","zenModeEnabled":false,"zoom":{"value":1.331666},"selectedLinearElement":null,"objectsSnapModeEnabled":false,"lockedMultiSelections":{}}',
|
||||||
|
);
|
||||||
|
localStorage.getItem("excalidraw");
|
||||||
|
localStorage.setItem(
|
||||||
|
"excalidraw",
|
||||||
|
'[{"id":"hJuTw4QcwTsFtadNNnkLj","type":"rectangle","x":-100,"y":-100,"width":200,"height":200,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"#a5d8ff","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"index":"a1","roundness":{"type":3},"seed":43277494,"version":839,"versionNonce":1923298088,"isDeleted":false,"boundElements":[{"id":"0xSZCPMN8RzKiJOpvGaKB","type":"arrow"}],"updated":1756212298830,"link":null,"locked":false},{"id":"qXw5KqKvAjHRr5uwPi9B-","type":"rectangle","x":-523.1841597523046,"y":-129.52451989693097,"width":200,"height":200,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"#a5d8ff","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"index":"a2","roundness":{"type":3},"seed":392633439,"version":963,"versionNonce":210394408,"isDeleted":false,"boundElements":[{"id":"0xSZCPMN8RzKiJOpvGaKB","type":"arrow"}],"updated":1756212298830,"link":null,"locked":false}]',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await page.setViewportSize({ width: 1280, height: 1001 });
|
||||||
|
await page.goto("http://localhost:3000");
|
||||||
|
await page.waitForLoadState("load");
|
||||||
|
await page.waitForTimeout(3.599999999627471);
|
||||||
|
await page.mouse.move(425, 390);
|
||||||
|
await page.waitForTimeout(9.900000000372529);
|
||||||
|
await page.mouse.move(424, 390);
|
||||||
|
await page.waitForTimeout(51.09999999962747);
|
||||||
|
await page.mouse.move(423, 390);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(423, 392);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(422, 392);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(420, 393);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(420, 395);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(418, 395);
|
||||||
|
await page.waitForTimeout(6);
|
||||||
|
await page.mouse.move(417, 396);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(416, 396);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(416, 397);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(414, 397);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(414, 399);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(413, 399);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(413, 401);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(413, 402);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(411, 402);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(411, 403);
|
||||||
|
await page.waitForTimeout(32);
|
||||||
|
await page.mouse.move(412, 403);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(412, 403);
|
||||||
|
await page.waitForTimeout(4);
|
||||||
|
await page.mouse.move(412, 402);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(413, 402);
|
||||||
|
await page.waitForTimeout(0.900000000372529);
|
||||||
|
await page.mouse.move(413, 400);
|
||||||
|
await page.waitForTimeout(1.099999999627471);
|
||||||
|
await page.mouse.move(415, 400);
|
||||||
|
await page.waitForTimeout(7);
|
||||||
|
await page.mouse.move(416, 400);
|
||||||
|
await page.waitForTimeout(71);
|
||||||
|
await page.mouse.move(417, 400);
|
||||||
|
await page.waitForTimeout(450);
|
||||||
|
await page.mouse.move(417, 399);
|
||||||
|
await page.waitForTimeout(6);
|
||||||
|
await page.mouse.move(418, 399);
|
||||||
|
await page.waitForTimeout(6.1000000005587935);
|
||||||
|
await page.mouse.move(419, 399);
|
||||||
|
await page.mouse.down({ button: "left" });
|
||||||
|
await page.waitForTimeout(11.899999999441206);
|
||||||
|
await page.mouse.move(420, 399);
|
||||||
|
await page.waitForTimeout(69);
|
||||||
|
await page.mouse.move(421, 399);
|
||||||
|
await page.waitForTimeout(13);
|
||||||
|
await page.mouse.move(422, 399);
|
||||||
|
await page.waitForTimeout(7.1000000005587935);
|
||||||
|
await page.mouse.move(423, 399);
|
||||||
|
await page.waitForTimeout(5.3999999994412065);
|
||||||
|
await page.mouse.move(425, 399);
|
||||||
|
await page.waitForTimeout(0.5);
|
||||||
|
await page.mouse.move(427, 401);
|
||||||
|
await page.waitForTimeout(4);
|
||||||
|
await page.mouse.move(428, 401);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(428, 403);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(430, 403);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(432, 403);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(432, 404);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(433, 404);
|
||||||
|
await page.waitForTimeout(1.8999999994412065);
|
||||||
|
await page.mouse.move(434, 404);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(436, 406);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(437, 406);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(438, 407);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(440, 407);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(442, 407);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(443, 409);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(445, 409);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(447, 411);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(451, 411);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(455, 411);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(457, 413);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(459, 413);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(461, 415);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(463, 417);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(467, 419);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(469, 419);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(471, 420);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(473, 424);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(477, 424);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(477, 426);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(478, 426);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(482, 428);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(484, 430);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(486, 430);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(488, 430);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(488, 432);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(489, 432);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(491, 432);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(493, 434);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(495, 434);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(499, 436);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(501, 436);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(502, 436);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(504, 438);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(506, 440);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(508, 440);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(510, 442);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(512, 444);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(514, 444);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(514, 445);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(515, 445);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(517, 445);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(517, 447);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(519, 448);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(521, 448);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(522, 450);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(524, 450);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(525, 450);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(527, 450);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(529, 450);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(530, 450);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(532, 450);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(533, 451);
|
||||||
|
await page.waitForTimeout(6);
|
||||||
|
await page.mouse.move(534, 451);
|
||||||
|
await page.waitForTimeout(3.1000000005587935);
|
||||||
|
await page.mouse.move(535, 451);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(535, 453);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(537, 453);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(538, 453);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(540, 453);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(540, 454);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(541, 454);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(542, 454);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(543, 454);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(544, 454);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(544, 455);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(546, 455);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(547, 455);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(548, 455);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(549, 455);
|
||||||
|
await page.waitForTimeout(1.8999999994412065);
|
||||||
|
await page.mouse.move(551, 455);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(552, 455);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(554, 455);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(555, 455);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(557, 455);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(559, 457);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(560, 457);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(562, 459);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(564, 459);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(566, 459);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(567, 459);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(569, 459);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(570, 459);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(574, 460);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(575, 460);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(575, 462);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(577, 464);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(578, 464);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(580, 464);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(582, 465);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(584, 465);
|
||||||
|
await page.waitForTimeout(1.8999999994412065);
|
||||||
|
await page.mouse.move(585, 465);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(586, 465);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(588, 467);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(590, 467);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(591, 467);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(591, 468);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(594, 468);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(595, 468);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(597, 468);
|
||||||
|
await page.waitForTimeout(2.7000000001862645);
|
||||||
|
await page.mouse.move(601, 470);
|
||||||
|
await page.waitForTimeout(1.400000000372529);
|
||||||
|
await page.mouse.move(602, 470);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(603, 470);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(605, 470);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(607, 470);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(608, 470);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(610, 470);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(612, 470);
|
||||||
|
await page.waitForTimeout(2.8999999994412065);
|
||||||
|
await page.mouse.move(613, 470);
|
||||||
|
await page.waitForTimeout(1.2000000001862645);
|
||||||
|
await page.mouse.move(613, 471);
|
||||||
|
await page.waitForTimeout(1.7999999998137355);
|
||||||
|
await page.mouse.move(614, 471);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(616, 473);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(618, 473);
|
||||||
|
await page.waitForTimeout(1.8999999994412065);
|
||||||
|
await page.mouse.move(621, 473);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(623, 473);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(625, 473);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(626, 473);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(628, 473);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(629, 473);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(631, 473);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(634, 475);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(636, 475);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(638, 475);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(639, 475);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(640, 475);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(642, 475);
|
||||||
|
await page.waitForTimeout(1.8999999994412065);
|
||||||
|
await page.mouse.move(644, 475);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(647, 475);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(649, 475);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(650, 475);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(652, 475);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(653, 475);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(655, 475);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(656, 476);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(658, 476);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(659, 476);
|
||||||
|
await page.waitForTimeout(1.8999999994412065);
|
||||||
|
await page.mouse.move(660, 476);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(662, 476);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(663, 476);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(664, 476);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(666, 474);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(668, 474);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(670, 474);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(671, 474);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(673, 476);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(675, 476);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(678, 476);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(680, 476);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(682, 476);
|
||||||
|
await page.waitForTimeout(4);
|
||||||
|
await page.mouse.move(683, 476);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(684, 476);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(686, 476);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(690, 476);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(694, 478);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(696, 478);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(698, 478);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(699, 478);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(703, 478);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(705, 478);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(707, 478);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(708, 478);
|
||||||
|
await page.waitForTimeout(1.8999999994412065);
|
||||||
|
await page.mouse.move(711, 478);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(713, 478);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(714, 478);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(716, 478);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(717, 478);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(719, 478);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(722, 478);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(724, 478);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(725, 478);
|
||||||
|
await page.waitForTimeout(1.8999999994412065);
|
||||||
|
await page.mouse.move(726, 478);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(727, 478);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(733, 480);
|
||||||
|
await page.waitForTimeout(2.8999999994412065);
|
||||||
|
await page.mouse.move(734, 480);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(735, 480);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(737, 480);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(739, 480);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(740, 480);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(742, 480);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(744, 480);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(744, 482);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(745, 482);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(747, 482);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(748, 482);
|
||||||
|
await page.waitForTimeout(4);
|
||||||
|
await page.mouse.move(749, 482);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(751, 482);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(753, 482);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(755, 482);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(758, 482);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(760, 482);
|
||||||
|
await page.waitForTimeout(6);
|
||||||
|
await page.mouse.move(761, 483);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(763, 484);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(765, 484);
|
||||||
|
await page.waitForTimeout(2.8999999994412065);
|
||||||
|
await page.mouse.move(766, 485);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(767, 485);
|
||||||
|
await page.waitForTimeout(11);
|
||||||
|
await page.mouse.move(768, 485);
|
||||||
|
await page.waitForTimeout(12.100000000558794);
|
||||||
|
await page.mouse.move(769, 485);
|
||||||
|
await page.waitForTimeout(51.89999999944121);
|
||||||
|
await page.mouse.move(770, 485);
|
||||||
|
await page.waitForTimeout(9);
|
||||||
|
await page.mouse.move(771, 485);
|
||||||
|
await page.waitForTimeout(3.1000000005587935);
|
||||||
|
await page.mouse.move(772, 485);
|
||||||
|
await page.waitForTimeout(5);
|
||||||
|
await page.mouse.move(773, 485);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(774, 485);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(776, 487);
|
||||||
|
await page.waitForTimeout(4);
|
||||||
|
await page.mouse.move(777, 487);
|
||||||
|
await page.waitForTimeout(4.1000000005587935);
|
||||||
|
await page.mouse.move(778, 487);
|
||||||
|
await page.waitForTimeout(1.8999999994412065);
|
||||||
|
await page.mouse.move(779, 487);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(780, 488);
|
||||||
|
await page.waitForTimeout(2.8999999994412065);
|
||||||
|
await page.mouse.move(781, 488);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(782, 488);
|
||||||
|
await page.waitForTimeout(1.8999999994412065);
|
||||||
|
await page.mouse.move(784, 488);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(785, 488);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(786, 488);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(788, 488);
|
||||||
|
await page.waitForTimeout(8.899999999441206);
|
||||||
|
await page.mouse.move(789, 488);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(790, 488);
|
||||||
|
await page.waitForTimeout(5);
|
||||||
|
await page.mouse.move(791, 488);
|
||||||
|
await page.waitForTimeout(4);
|
||||||
|
await page.mouse.move(792, 488);
|
||||||
|
await page.waitForTimeout(6.1000000005587935);
|
||||||
|
await page.mouse.move(793, 488);
|
||||||
|
await page.waitForTimeout(3.8999999994412065);
|
||||||
|
await page.mouse.move(793, 489);
|
||||||
|
await page.waitForTimeout(6);
|
||||||
|
await page.mouse.move(794, 489);
|
||||||
|
await page.waitForTimeout(4.1000000005587935);
|
||||||
|
await page.mouse.move(795, 489);
|
||||||
|
await page.waitForTimeout(7.599999999627471);
|
||||||
|
await page.mouse.move(796, 489);
|
||||||
|
await page.waitForTimeout(3.400000000372529);
|
||||||
|
await page.mouse.move(797, 489);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(798, 489);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(799, 489);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(800, 489);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(801, 489);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(802, 489);
|
||||||
|
await page.waitForTimeout(1.8999999994412065);
|
||||||
|
await page.mouse.move(803, 489);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(804, 489);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(805, 489);
|
||||||
|
await page.waitForTimeout(5.8999999994412065);
|
||||||
|
await page.mouse.move(806, 490);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(807, 490);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(809, 490);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(810, 490);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(812, 490);
|
||||||
|
await page.waitForTimeout(1.8999999994412065);
|
||||||
|
await page.mouse.move(813, 490);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(815, 490);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(816, 492);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(818, 492);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(819, 492);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(820, 492);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(822, 492);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(823, 492);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(825, 492);
|
||||||
|
await page.waitForTimeout(2.8999999994412065);
|
||||||
|
await page.mouse.move(826, 492);
|
||||||
|
await page.waitForTimeout(1.1000000005587935);
|
||||||
|
await page.mouse.move(827, 492);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(828, 492);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(830, 492);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(831, 492);
|
||||||
|
await page.waitForTimeout(2.1000000005587935);
|
||||||
|
await page.mouse.move(832, 492);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(833, 493);
|
||||||
|
await page.waitForTimeout(6.1000000005587935);
|
||||||
|
await page.mouse.move(835, 493);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(836, 493);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(838, 493);
|
||||||
|
await page.waitForTimeout(0.8999999994412065);
|
||||||
|
await page.mouse.move(839, 493);
|
||||||
|
await page.waitForTimeout(4.1000000005587935);
|
||||||
|
await page.mouse.move(841, 493);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(842, 493);
|
||||||
|
await page.waitForTimeout(3.8999999994412065);
|
||||||
|
await page.mouse.move(843, 493);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(843, 492);
|
||||||
|
await page.waitForTimeout(4.1000000005587935);
|
||||||
|
await page.mouse.move(844, 492);
|
||||||
|
await page.waitForTimeout(4);
|
||||||
|
await page.mouse.move(844, 493);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(845, 493);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(846, 493);
|
||||||
|
await page.waitForTimeout(4);
|
||||||
|
await page.mouse.move(847, 494);
|
||||||
|
await page.waitForTimeout(24.899999999441206);
|
||||||
|
await page.mouse.move(847, 495);
|
||||||
|
await page.waitForTimeout(29.100000000558794);
|
||||||
|
await page.mouse.move(848, 495);
|
||||||
|
await page.waitForTimeout(297.8999999994412);
|
||||||
|
await page.mouse.move(848, 495);
|
||||||
|
await page.waitForTimeout(5.7000000001862645);
|
||||||
|
await page.mouse.up({ button: "left" });
|
||||||
|
await expect(page).toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
maxDiffPixelRatio: 0.01,
|
||||||
|
});
|
||||||
|
await page.waitForTimeout(11.299999999813735);
|
||||||
|
await page.mouse.move(847, 495);
|
||||||
|
await page.waitForTimeout(199.1000000005588);
|
||||||
|
await page.mouse.move(847, 495);
|
||||||
|
await page.waitForTimeout(249.29999999981374);
|
||||||
|
await page.mouse.move(846, 495);
|
||||||
|
await page.waitForTimeout(5.7000000001862645);
|
||||||
|
await page.mouse.move(846, 496);
|
||||||
|
await page.waitForTimeout(6);
|
||||||
|
await page.mouse.move(845, 496);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(845, 497);
|
||||||
|
await page.waitForTimeout(9);
|
||||||
|
await page.mouse.move(845, 498);
|
||||||
|
await page.waitForTimeout(3.8999999994412065);
|
||||||
|
await page.mouse.move(844, 498);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(844, 500);
|
||||||
|
await page.waitForTimeout(8);
|
||||||
|
await page.mouse.move(844, 501);
|
||||||
|
await page.waitForTimeout(3.1000000005587935);
|
||||||
|
await page.mouse.move(843, 501);
|
||||||
|
await page.waitForTimeout(137);
|
||||||
|
await page.mouse.move(844, 501);
|
||||||
|
await page.waitForTimeout(68.09999999962747);
|
||||||
|
await page.mouse.move(845, 501);
|
||||||
|
await page.waitForTimeout(7.7999999998137355);
|
||||||
|
await page.mouse.move(845, 500);
|
||||||
|
await page.waitForTimeout(1);
|
||||||
|
await page.mouse.move(846, 500);
|
||||||
|
await page.waitForTimeout(4);
|
||||||
|
await page.mouse.move(847, 500);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(848, 500);
|
||||||
|
await page.waitForTimeout(3.1000000005587935);
|
||||||
|
await page.mouse.move(849, 500);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(850, 500);
|
||||||
|
await page.waitForTimeout(5);
|
||||||
|
await page.mouse.move(851, 500);
|
||||||
|
await page.waitForTimeout(2);
|
||||||
|
await page.mouse.move(852, 499);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(854, 499);
|
||||||
|
await page.waitForTimeout(5);
|
||||||
|
await page.mouse.move(855, 499);
|
||||||
|
await page.waitForTimeout(3);
|
||||||
|
await page.mouse.move(856, 499);
|
||||||
|
await page.waitForTimeout(6);
|
||||||
|
await page.mouse.move(857, 499);
|
||||||
|
await page.waitForTimeout(12);
|
||||||
|
await page.mouse.move(858, 499);
|
||||||
|
await page.waitForTimeout(24);
|
||||||
|
await page.mouse.move(858, 498);
|
||||||
|
await page.waitForTimeout(4);
|
||||||
|
await page.mouse.move(858, 498);
|
||||||
|
await page.waitForTimeout(59.200000000186265);
|
||||||
|
await page.keyboard.down("Control");
|
||||||
|
});
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
@@ -11,9 +11,11 @@
|
|||||||
"@babel/preset-env": "7.26.9",
|
"@babel/preset-env": "7.26.9",
|
||||||
"@excalidraw/eslint-config": "1.0.3",
|
"@excalidraw/eslint-config": "1.0.3",
|
||||||
"@excalidraw/prettier-config": "1.0.2",
|
"@excalidraw/prettier-config": "1.0.2",
|
||||||
|
"@playwright/test": "1.55.0",
|
||||||
"@types/chai": "4.3.0",
|
"@types/chai": "4.3.0",
|
||||||
"@types/jest": "27.4.0",
|
"@types/jest": "27.4.0",
|
||||||
"@types/lodash.throttle": "4.1.7",
|
"@types/lodash.throttle": "4.1.7",
|
||||||
|
"@types/node": "24.3.0",
|
||||||
"@types/react": "19.0.10",
|
"@types/react": "19.0.10",
|
||||||
"@types/react-dom": "19.0.4",
|
"@types/react-dom": "19.0.4",
|
||||||
"@types/socket.io-client": "3.0.0",
|
"@types/socket.io-client": "3.0.0",
|
||||||
@@ -62,6 +64,7 @@
|
|||||||
"build:preview": "yarn --cwd ./excalidraw-app build:preview",
|
"build:preview": "yarn --cwd ./excalidraw-app build:preview",
|
||||||
"start": "yarn --cwd ./excalidraw-app start",
|
"start": "yarn --cwd ./excalidraw-app start",
|
||||||
"start:production": "yarn --cwd ./excalidraw-app start:production",
|
"start:production": "yarn --cwd ./excalidraw-app start:production",
|
||||||
|
"start:test": "yarn --cwd ./excalidraw-app start:test",
|
||||||
"start:example": "yarn build:packages && yarn --cwd ./examples/with-script-in-browser start",
|
"start:example": "yarn build:packages && yarn --cwd ./examples/with-script-in-browser start",
|
||||||
"test:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watch=false",
|
"test:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watch=false",
|
||||||
"test:app": "vitest",
|
"test:app": "vitest",
|
||||||
|
|||||||
@@ -28,9 +28,11 @@ export const isBrave = () =>
|
|||||||
export const isMobile =
|
export const isMobile =
|
||||||
isIOS ||
|
isIOS ||
|
||||||
/android|webos|ipod|blackberry|iemobile|opera mini/i.test(
|
/android|webos|ipod|blackberry|iemobile|opera mini/i.test(
|
||||||
navigator.userAgent,
|
navigator.userAgent.toLowerCase(),
|
||||||
) ||
|
) ||
|
||||||
/android|ios|ipod|blackberry|windows phone/i.test(navigator.platform);
|
/android|ios|ipod|blackberry|windows phone/i.test(
|
||||||
|
navigator.platform.toLowerCase(),
|
||||||
|
);
|
||||||
|
|
||||||
export const supportsResizeObserver =
|
export const supportsResizeObserver =
|
||||||
typeof window !== "undefined" && "ResizeObserver" in window;
|
typeof window !== "undefined" && "ResizeObserver" in window;
|
||||||
@@ -522,5 +524,3 @@ export enum UserIdleState {
|
|||||||
* the start and end points)
|
* the start and end points)
|
||||||
*/
|
*/
|
||||||
export const LINE_POLYGON_POINT_MERGE_DISTANCE = 20;
|
export const LINE_POLYGON_POINT_MERGE_DISTANCE = 20;
|
||||||
|
|
||||||
export const DOUBLE_TAP_POSITION_THRESHOLD = 35;
|
|
||||||
|
|||||||
@@ -1111,16 +1111,16 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
inserted,
|
inserted,
|
||||||
}: Delta<ElementPartial>) =>
|
}: Delta<ElementPartial>) =>
|
||||||
!!(
|
!!(
|
||||||
|
deleted.version &&
|
||||||
|
inserted.version &&
|
||||||
// versions are required integers
|
// versions are required integers
|
||||||
(
|
Number.isInteger(deleted.version) &&
|
||||||
Number.isInteger(deleted.version) &&
|
Number.isInteger(inserted.version) &&
|
||||||
Number.isInteger(inserted.version) &&
|
// versions should be positive, zero included
|
||||||
// versions should be positive, zero included
|
deleted.version >= 0 &&
|
||||||
deleted.version! >= 0 &&
|
inserted.version >= 0 &&
|
||||||
inserted.version! >= 0 &&
|
// versions should never be the same
|
||||||
// versions should never be the same
|
deleted.version !== inserted.version
|
||||||
deleted.version !== inserted.version
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
private static satisfiesUniqueInvariants = (
|
private static satisfiesUniqueInvariants = (
|
||||||
@@ -1191,10 +1191,9 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
ElementsDelta.stripIrrelevantProps,
|
ElementsDelta.stripIrrelevantProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ignore updates which would "delete" already deleted element
|
||||||
if (!prevElement.isDeleted) {
|
if (!prevElement.isDeleted) {
|
||||||
removed[prevElement.id] = delta;
|
removed[prevElement.id] = delta;
|
||||||
} else {
|
|
||||||
updated[prevElement.id] = delta;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1222,8 +1221,6 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
// ignore updates which would "delete" already deleted element
|
// ignore updates which would "delete" already deleted element
|
||||||
if (!nextElement.isDeleted) {
|
if (!nextElement.isDeleted) {
|
||||||
added[nextElement.id] = delta;
|
added[nextElement.id] = delta;
|
||||||
} else {
|
|
||||||
updated[nextElement.id] = delta;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -1253,7 +1250,15 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
updated[nextElement.id] = delta;
|
const strippedDeleted = ElementsDelta.stripVersionProps(delta.deleted);
|
||||||
|
const strippedInserted = ElementsDelta.stripVersionProps(
|
||||||
|
delta.inserted,
|
||||||
|
);
|
||||||
|
|
||||||
|
// making sure there are at least some changes and only changed version & versionNonce does not count!
|
||||||
|
if (Delta.isInnerDifferent(strippedDeleted, strippedInserted, true)) {
|
||||||
|
updated[nextElement.id] = delta;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1367,8 +1372,15 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
latestDelta = delta;
|
latestDelta = delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const strippedDeleted = ElementsDelta.stripVersionProps(
|
||||||
|
latestDelta.deleted,
|
||||||
|
);
|
||||||
|
const strippedInserted = ElementsDelta.stripVersionProps(
|
||||||
|
latestDelta.inserted,
|
||||||
|
);
|
||||||
|
|
||||||
// it might happen that after applying latest changes the delta itself does not contain any changes
|
// it might happen that after applying latest changes the delta itself does not contain any changes
|
||||||
if (Delta.isInnerDifferent(latestDelta.deleted, latestDelta.inserted)) {
|
if (Delta.isInnerDifferent(strippedDeleted, strippedInserted)) {
|
||||||
modifiedDeltas[id] = latestDelta;
|
modifiedDeltas[id] = latestDelta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2063,4 +2075,12 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
|
|
||||||
return strippedPartial;
|
return strippedPartial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static stripVersionProps(
|
||||||
|
partial: Partial<OrderedExcalidrawElement>,
|
||||||
|
): ElementPartial {
|
||||||
|
const { version, versionNonce, ...strippedPartial } = partial;
|
||||||
|
|
||||||
|
return strippedPartial;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -359,12 +359,6 @@ const handleSegmentRelease = (
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!restoredPoints || restoredPoints.length < 2) {
|
|
||||||
throw new Error(
|
|
||||||
"Property 'points' is required in the update returned by normalizeArrowElementUpdate()",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextPoints: GlobalPoint[] = [];
|
const nextPoints: GlobalPoint[] = [];
|
||||||
|
|
||||||
// First part of the arrow are the old points
|
// First part of the arrow are the old points
|
||||||
@@ -712,7 +706,7 @@ const handleEndpointDrag = (
|
|||||||
endGlobalPoint: GlobalPoint,
|
endGlobalPoint: GlobalPoint,
|
||||||
hoveredStartElement: ExcalidrawBindableElement | null,
|
hoveredStartElement: ExcalidrawBindableElement | null,
|
||||||
hoveredEndElement: ExcalidrawBindableElement | null,
|
hoveredEndElement: ExcalidrawBindableElement | null,
|
||||||
): ElementUpdate<ExcalidrawElbowArrowElement> => {
|
) => {
|
||||||
let startIsSpecial = arrow.startIsSpecial ?? null;
|
let startIsSpecial = arrow.startIsSpecial ?? null;
|
||||||
let endIsSpecial = arrow.endIsSpecial ?? null;
|
let endIsSpecial = arrow.endIsSpecial ?? null;
|
||||||
const globalUpdatedPoints = updatedPoints.map((p, i) =>
|
const globalUpdatedPoints = updatedPoints.map((p, i) =>
|
||||||
@@ -747,15 +741,8 @@ const handleEndpointDrag = (
|
|||||||
|
|
||||||
// Calculate the moving second point connection and add the start point
|
// Calculate the moving second point connection and add the start point
|
||||||
{
|
{
|
||||||
const secondPoint = globalUpdatedPoints.at(startIsSpecial ? 2 : 1);
|
const secondPoint = globalUpdatedPoints[startIsSpecial ? 2 : 1];
|
||||||
const thirdPoint = globalUpdatedPoints.at(startIsSpecial ? 3 : 2);
|
const thirdPoint = globalUpdatedPoints[startIsSpecial ? 3 : 2];
|
||||||
|
|
||||||
if (!secondPoint || !thirdPoint) {
|
|
||||||
throw new Error(
|
|
||||||
`Second and third points must exist when handling endpoint drag (${startIsSpecial})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const startIsHorizontal = headingIsHorizontal(startHeading);
|
const startIsHorizontal = headingIsHorizontal(startHeading);
|
||||||
const secondIsHorizontal = headingIsHorizontal(
|
const secondIsHorizontal = headingIsHorizontal(
|
||||||
vectorToHeading(vectorFromPoint(secondPoint, thirdPoint)),
|
vectorToHeading(vectorFromPoint(secondPoint, thirdPoint)),
|
||||||
@@ -814,19 +801,10 @@ const handleEndpointDrag = (
|
|||||||
|
|
||||||
// Calculate the moving second to last point connection
|
// Calculate the moving second to last point connection
|
||||||
{
|
{
|
||||||
const secondToLastPoint = globalUpdatedPoints.at(
|
const secondToLastPoint =
|
||||||
globalUpdatedPoints.length - (endIsSpecial ? 3 : 2),
|
globalUpdatedPoints[globalUpdatedPoints.length - (endIsSpecial ? 3 : 2)];
|
||||||
);
|
const thirdToLastPoint =
|
||||||
const thirdToLastPoint = globalUpdatedPoints.at(
|
globalUpdatedPoints[globalUpdatedPoints.length - (endIsSpecial ? 4 : 3)];
|
||||||
globalUpdatedPoints.length - (endIsSpecial ? 4 : 3),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!secondToLastPoint || !thirdToLastPoint) {
|
|
||||||
throw new Error(
|
|
||||||
`Second and third to last points must exist when handling endpoint drag (${endIsSpecial})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const endIsHorizontal = headingIsHorizontal(endHeading);
|
const endIsHorizontal = headingIsHorizontal(endHeading);
|
||||||
const secondIsHorizontal = headingForPointIsHorizontal(
|
const secondIsHorizontal = headingForPointIsHorizontal(
|
||||||
thirdToLastPoint,
|
thirdToLastPoint,
|
||||||
@@ -2093,7 +2071,16 @@ const normalizeArrowElementUpdate = (
|
|||||||
nextFixedSegments: readonly FixedSegment[] | null,
|
nextFixedSegments: readonly FixedSegment[] | null,
|
||||||
startIsSpecial?: ExcalidrawElbowArrowElement["startIsSpecial"],
|
startIsSpecial?: ExcalidrawElbowArrowElement["startIsSpecial"],
|
||||||
endIsSpecial?: ExcalidrawElbowArrowElement["startIsSpecial"],
|
endIsSpecial?: ExcalidrawElbowArrowElement["startIsSpecial"],
|
||||||
): ElementUpdate<ExcalidrawElbowArrowElement> => {
|
): {
|
||||||
|
points: LocalPoint[];
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
fixedSegments: readonly FixedSegment[] | null;
|
||||||
|
startIsSpecial?: ExcalidrawElbowArrowElement["startIsSpecial"];
|
||||||
|
endIsSpecial?: ExcalidrawElbowArrowElement["startIsSpecial"];
|
||||||
|
} => {
|
||||||
const offsetX = global[0][0];
|
const offsetX = global[0][0];
|
||||||
const offsetY = global[0][1];
|
const offsetY = global[0][1];
|
||||||
let points = global.map((p) =>
|
let points = global.map((p) =>
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import {
|
|||||||
getContainerElement,
|
getContainerElement,
|
||||||
handleBindTextResize,
|
handleBindTextResize,
|
||||||
getBoundTextMaxWidth,
|
getBoundTextMaxWidth,
|
||||||
computeBoundTextPosition,
|
|
||||||
} from "./textElement";
|
} from "./textElement";
|
||||||
import {
|
import {
|
||||||
getMinTextElementWidth,
|
getMinTextElementWidth,
|
||||||
@@ -226,16 +225,7 @@ const rotateSingleElement = (
|
|||||||
scene.getElement<ExcalidrawTextElementWithContainer>(boundTextElementId);
|
scene.getElement<ExcalidrawTextElementWithContainer>(boundTextElementId);
|
||||||
|
|
||||||
if (textElement && !isArrowElement(element)) {
|
if (textElement && !isArrowElement(element)) {
|
||||||
const { x, y } = computeBoundTextPosition(
|
scene.mutateElement(textElement, { angle });
|
||||||
element,
|
|
||||||
textElement,
|
|
||||||
scene.getNonDeletedElementsMap(),
|
|
||||||
);
|
|
||||||
scene.mutateElement(textElement, {
|
|
||||||
angle,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -426,15 +416,9 @@ const rotateMultipleElements = (
|
|||||||
|
|
||||||
const boundText = getBoundTextElement(element, elementsMap);
|
const boundText = getBoundTextElement(element, elementsMap);
|
||||||
if (boundText && !isArrowElement(element)) {
|
if (boundText && !isArrowElement(element)) {
|
||||||
const { x, y } = computeBoundTextPosition(
|
|
||||||
element,
|
|
||||||
boundText,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
scene.mutateElement(boundText, {
|
scene.mutateElement(boundText, {
|
||||||
x,
|
x: boundText.x + (rotatedCX - cx),
|
||||||
y,
|
y: boundText.y + (rotatedCY - cy),
|
||||||
angle: normalizeRadians((centerAngle + origAngle) as Radians),
|
angle: normalizeRadians((centerAngle + origAngle) as Radians),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
assertNever,
|
assertNever,
|
||||||
COLOR_PALETTE,
|
COLOR_PALETTE,
|
||||||
LINE_POLYGON_POINT_MERGE_DISTANCE,
|
LINE_POLYGON_POINT_MERGE_DISTANCE,
|
||||||
|
isTestEnv,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import { RoughGenerator } from "roughjs/bin/generator";
|
import { RoughGenerator } from "roughjs/bin/generator";
|
||||||
@@ -182,7 +183,7 @@ export const generateRoughOptions = (
|
|||||||
continuousPath = false,
|
continuousPath = false,
|
||||||
): Options => {
|
): Options => {
|
||||||
const options: Options = {
|
const options: Options = {
|
||||||
seed: element.seed,
|
seed: isTestEnv() ? 1 : element.seed,
|
||||||
strokeLineDash:
|
strokeLineDash:
|
||||||
element.strokeStyle === "dashed"
|
element.strokeStyle === "dashed"
|
||||||
? getDashArrayDashed(element.strokeWidth)
|
? getDashArrayDashed(element.strokeWidth)
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import {
|
|||||||
invariant,
|
invariant,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import { pointFrom, pointRotateRads, type Radians } from "@excalidraw/math";
|
|
||||||
|
|
||||||
import type { AppState } from "@excalidraw/excalidraw/types";
|
import type { AppState } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import type { ExtractSetType } from "@excalidraw/common/utility-types";
|
import type { ExtractSetType } from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
|
import type { Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
resetOriginalContainerCache,
|
resetOriginalContainerCache,
|
||||||
updateOriginalContainerCache,
|
updateOriginalContainerCache,
|
||||||
@@ -254,26 +254,6 @@ export const computeBoundTextPosition = (
|
|||||||
x =
|
x =
|
||||||
containerCoords.x + (maxContainerWidth / 2 - boundTextElement.width / 2);
|
containerCoords.x + (maxContainerWidth / 2 - boundTextElement.width / 2);
|
||||||
}
|
}
|
||||||
const angle = (container.angle ?? 0) as Radians;
|
|
||||||
|
|
||||||
if (angle !== 0) {
|
|
||||||
const contentCenter = pointFrom(
|
|
||||||
containerCoords.x + maxContainerWidth / 2,
|
|
||||||
containerCoords.y + maxContainerHeight / 2,
|
|
||||||
);
|
|
||||||
const textCenter = pointFrom(
|
|
||||||
x + boundTextElement.width / 2,
|
|
||||||
y + boundTextElement.height / 2,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [rx, ry] = pointRotateRads(textCenter, contentCenter, angle);
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: rx - boundTextElement.width / 2,
|
|
||||||
y: ry - boundTextElement.height / 2,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { x, y };
|
return { x, y };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { AppStateDelta, Delta, ElementsDelta } from "../src/delta";
|
|||||||
|
|
||||||
describe("ElementsDelta", () => {
|
describe("ElementsDelta", () => {
|
||||||
describe("elements delta calculation", () => {
|
describe("elements delta calculation", () => {
|
||||||
it("should not throw when element gets removed but was already deleted", () => {
|
it("should not create removed delta when element gets removed but was already deleted", () => {
|
||||||
const element = API.createElement({
|
const element = API.createElement({
|
||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
x: 100,
|
x: 100,
|
||||||
@@ -19,12 +19,12 @@ describe("ElementsDelta", () => {
|
|||||||
const prevElements = new Map([[element.id, element]]);
|
const prevElements = new Map([[element.id, element]]);
|
||||||
const nextElements = new Map();
|
const nextElements = new Map();
|
||||||
|
|
||||||
expect(() =>
|
const delta = ElementsDelta.calculate(prevElements, nextElements);
|
||||||
ElementsDelta.calculate(prevElements, nextElements),
|
|
||||||
).not.toThrow();
|
expect(delta.isEmpty()).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not throw when adding element as already deleted", () => {
|
it("should not create added delta when adding element as already deleted", () => {
|
||||||
const element = API.createElement({
|
const element = API.createElement({
|
||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
x: 100,
|
x: 100,
|
||||||
@@ -35,12 +35,12 @@ describe("ElementsDelta", () => {
|
|||||||
const prevElements = new Map();
|
const prevElements = new Map();
|
||||||
const nextElements = new Map([[element.id, element]]);
|
const nextElements = new Map([[element.id, element]]);
|
||||||
|
|
||||||
expect(() =>
|
const delta = ElementsDelta.calculate(prevElements, nextElements);
|
||||||
ElementsDelta.calculate(prevElements, nextElements),
|
|
||||||
).not.toThrow();
|
expect(delta.isEmpty()).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create updated delta even when there is only version and versionNonce change", () => {
|
it("should not create updated delta when there is only version and versionNonce change", () => {
|
||||||
const baseElement = API.createElement({
|
const baseElement = API.createElement({
|
||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
x: 100,
|
x: 100,
|
||||||
@@ -65,24 +65,7 @@ describe("ElementsDelta", () => {
|
|||||||
nextElements as SceneElementsMap,
|
nextElements as SceneElementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(delta).toEqual(
|
expect(delta.isEmpty()).toBeTruthy();
|
||||||
ElementsDelta.create(
|
|
||||||
{},
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
[baseElement.id]: Delta.create(
|
|
||||||
{
|
|
||||||
version: baseElement.version,
|
|
||||||
versionNonce: baseElement.versionNonce,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
version: baseElement.version + 1,
|
|
||||||
versionNonce: baseElement.versionNonce + 1,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { getLineHeight } from "@excalidraw/common";
|
import { getLineHeight } from "@excalidraw/common";
|
||||||
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
||||||
|
|
||||||
import { FONT_FAMILY, TEXT_ALIGN, VERTICAL_ALIGN } from "@excalidraw/common";
|
import { FONT_FAMILY } from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
computeContainerDimensionForBoundText,
|
computeContainerDimensionForBoundText,
|
||||||
getContainerCoords,
|
getContainerCoords,
|
||||||
getBoundTextMaxWidth,
|
getBoundTextMaxWidth,
|
||||||
getBoundTextMaxHeight,
|
getBoundTextMaxHeight,
|
||||||
computeBoundTextPosition,
|
|
||||||
} from "../src/textElement";
|
} from "../src/textElement";
|
||||||
import { detectLineHeight, getLineHeightInPx } from "../src/textMeasurements";
|
import { detectLineHeight, getLineHeightInPx } from "../src/textMeasurements";
|
||||||
|
|
||||||
@@ -208,172 +207,3 @@ describe("Test getDefaultLineHeight", () => {
|
|||||||
expect(getLineHeight(FONT_FAMILY.Cascadia)).toBe(1.2);
|
expect(getLineHeight(FONT_FAMILY.Cascadia)).toBe(1.2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Test computeBoundTextPosition", () => {
|
|
||||||
const createMockElementsMap = () => new Map();
|
|
||||||
|
|
||||||
// Helper function to create rectangle test case with 90-degree rotation
|
|
||||||
const createRotatedRectangleTestCase = (
|
|
||||||
textAlign: string,
|
|
||||||
verticalAlign: string,
|
|
||||||
) => {
|
|
||||||
const container = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
x: 100,
|
|
||||||
y: 100,
|
|
||||||
width: 200,
|
|
||||||
height: 100,
|
|
||||||
angle: (Math.PI / 2) as any, // 90 degrees
|
|
||||||
});
|
|
||||||
|
|
||||||
const boundTextElement = API.createElement({
|
|
||||||
type: "text",
|
|
||||||
width: 80,
|
|
||||||
height: 40,
|
|
||||||
text: "hello darkness my old friend",
|
|
||||||
textAlign: textAlign as any,
|
|
||||||
verticalAlign: verticalAlign as any,
|
|
||||||
containerId: container.id,
|
|
||||||
}) as ExcalidrawTextElementWithContainer;
|
|
||||||
|
|
||||||
const elementsMap = createMockElementsMap();
|
|
||||||
|
|
||||||
return { container, boundTextElement, elementsMap };
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("90-degree rotation with all alignment combinations", () => {
|
|
||||||
// Test all 9 combinations of horizontal (left, center, right) and vertical (top, middle, bottom) alignment
|
|
||||||
|
|
||||||
it("should position text with LEFT + TOP alignment at 90-degree rotation", () => {
|
|
||||||
const { container, boundTextElement, elementsMap } =
|
|
||||||
createRotatedRectangleTestCase(TEXT_ALIGN.LEFT, VERTICAL_ALIGN.TOP);
|
|
||||||
|
|
||||||
const result = computeBoundTextPosition(
|
|
||||||
container,
|
|
||||||
boundTextElement,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.x).toBeCloseTo(185, 1);
|
|
||||||
expect(result.y).toBeCloseTo(75, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should position text with LEFT + MIDDLE alignment at 90-degree rotation", () => {
|
|
||||||
const { container, boundTextElement, elementsMap } =
|
|
||||||
createRotatedRectangleTestCase(TEXT_ALIGN.LEFT, VERTICAL_ALIGN.MIDDLE);
|
|
||||||
|
|
||||||
const result = computeBoundTextPosition(
|
|
||||||
container,
|
|
||||||
boundTextElement,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.x).toBeCloseTo(160, 1);
|
|
||||||
expect(result.y).toBeCloseTo(75, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should position text with LEFT + BOTTOM alignment at 90-degree rotation", () => {
|
|
||||||
const { container, boundTextElement, elementsMap } =
|
|
||||||
createRotatedRectangleTestCase(TEXT_ALIGN.LEFT, VERTICAL_ALIGN.BOTTOM);
|
|
||||||
|
|
||||||
const result = computeBoundTextPosition(
|
|
||||||
container,
|
|
||||||
boundTextElement,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.x).toBeCloseTo(135, 1);
|
|
||||||
expect(result.y).toBeCloseTo(75, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should position text with CENTER + TOP alignment at 90-degree rotation", () => {
|
|
||||||
const { container, boundTextElement, elementsMap } =
|
|
||||||
createRotatedRectangleTestCase(TEXT_ALIGN.CENTER, VERTICAL_ALIGN.TOP);
|
|
||||||
|
|
||||||
const result = computeBoundTextPosition(
|
|
||||||
container,
|
|
||||||
boundTextElement,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.x).toBeCloseTo(185, 1);
|
|
||||||
expect(result.y).toBeCloseTo(130, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should position text with CENTER + MIDDLE alignment at 90-degree rotation", () => {
|
|
||||||
const { container, boundTextElement, elementsMap } =
|
|
||||||
createRotatedRectangleTestCase(
|
|
||||||
TEXT_ALIGN.CENTER,
|
|
||||||
VERTICAL_ALIGN.MIDDLE,
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = computeBoundTextPosition(
|
|
||||||
container,
|
|
||||||
boundTextElement,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.x).toBeCloseTo(160, 1);
|
|
||||||
expect(result.y).toBeCloseTo(130, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should position text with CENTER + BOTTOM alignment at 90-degree rotation", () => {
|
|
||||||
const { container, boundTextElement, elementsMap } =
|
|
||||||
createRotatedRectangleTestCase(
|
|
||||||
TEXT_ALIGN.CENTER,
|
|
||||||
VERTICAL_ALIGN.BOTTOM,
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = computeBoundTextPosition(
|
|
||||||
container,
|
|
||||||
boundTextElement,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.x).toBeCloseTo(135, 1);
|
|
||||||
expect(result.y).toBeCloseTo(130, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should position text with RIGHT + TOP alignment at 90-degree rotation", () => {
|
|
||||||
const { container, boundTextElement, elementsMap } =
|
|
||||||
createRotatedRectangleTestCase(TEXT_ALIGN.RIGHT, VERTICAL_ALIGN.TOP);
|
|
||||||
|
|
||||||
const result = computeBoundTextPosition(
|
|
||||||
container,
|
|
||||||
boundTextElement,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.x).toBeCloseTo(185, 1);
|
|
||||||
expect(result.y).toBeCloseTo(185, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should position text with RIGHT + MIDDLE alignment at 90-degree rotation", () => {
|
|
||||||
const { container, boundTextElement, elementsMap } =
|
|
||||||
createRotatedRectangleTestCase(TEXT_ALIGN.RIGHT, VERTICAL_ALIGN.MIDDLE);
|
|
||||||
|
|
||||||
const result = computeBoundTextPosition(
|
|
||||||
container,
|
|
||||||
boundTextElement,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.x).toBeCloseTo(160, 1);
|
|
||||||
expect(result.y).toBeCloseTo(185, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should position text with RIGHT + BOTTOM alignment at 90-degree rotation", () => {
|
|
||||||
const { container, boundTextElement, elementsMap } =
|
|
||||||
createRotatedRectangleTestCase(TEXT_ALIGN.RIGHT, VERTICAL_ALIGN.BOTTOM);
|
|
||||||
|
|
||||||
const result = computeBoundTextPosition(
|
|
||||||
container,
|
|
||||||
boundTextElement,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.x).toBeCloseTo(135, 1);
|
|
||||||
expect(result.y).toBeCloseTo(185, 1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -54,8 +54,7 @@ export type ShortcutName =
|
|||||||
| "saveScene"
|
| "saveScene"
|
||||||
| "imageExport"
|
| "imageExport"
|
||||||
| "commandPalette"
|
| "commandPalette"
|
||||||
| "searchMenu"
|
| "searchMenu";
|
||||||
| "toolLock";
|
|
||||||
|
|
||||||
const shortcutMap: Record<ShortcutName, string[]> = {
|
const shortcutMap: Record<ShortcutName, string[]> = {
|
||||||
toggleTheme: [getShortcutKey("Shift+Alt+D")],
|
toggleTheme: [getShortcutKey("Shift+Alt+D")],
|
||||||
@@ -117,7 +116,6 @@ const shortcutMap: Record<ShortcutName, string[]> = {
|
|||||||
toggleShortcuts: [getShortcutKey("?")],
|
toggleShortcuts: [getShortcutKey("?")],
|
||||||
searchMenu: [getShortcutKey("CtrlOrCmd+F")],
|
searchMenu: [getShortcutKey("CtrlOrCmd+F")],
|
||||||
wrapSelectionInFrame: [],
|
wrapSelectionInFrame: [],
|
||||||
toolLock: [getShortcutKey("Q")],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getShortcutFromShortcutName = (name: ShortcutName, idx = 0) => {
|
export const getShortcutFromShortcutName = (name: ShortcutName, idx = 0) => {
|
||||||
|
|||||||
@@ -397,7 +397,6 @@ export const ShapesSwitcher = ({
|
|||||||
onClickOutside={() => setIsExtraToolsMenuOpen(false)}
|
onClickOutside={() => setIsExtraToolsMenuOpen(false)}
|
||||||
onSelect={() => setIsExtraToolsMenuOpen(false)}
|
onSelect={() => setIsExtraToolsMenuOpen(false)}
|
||||||
className="App-toolbar__extra-tools-dropdown"
|
className="App-toolbar__extra-tools-dropdown"
|
||||||
align="end"
|
|
||||||
>
|
>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
onSelect={() => app.setActiveTool({ type: "frame" })}
|
onSelect={() => app.setActiveTool({ type: "frame" })}
|
||||||
@@ -451,10 +450,10 @@ export const ShapesSwitcher = ({
|
|||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
onSelect={() => app.onMagicframeToolSelect()}
|
onSelect={() => app.onMagicframeToolSelect()}
|
||||||
icon={MagicIcon}
|
icon={MagicIcon}
|
||||||
badge={<DropdownMenu.Item.Badge>AI</DropdownMenu.Item.Badge>}
|
|
||||||
data-testid="toolbar-magicframe"
|
data-testid="toolbar-magicframe"
|
||||||
>
|
>
|
||||||
{t("toolBar.magicframe")}
|
{t("toolBar.magicframe")}
|
||||||
|
<DropdownMenu.Item.Badge>AI</DropdownMenu.Item.Badge>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -102,7 +102,6 @@ import {
|
|||||||
Emitter,
|
Emitter,
|
||||||
isMobile,
|
isMobile,
|
||||||
MINIMUM_ARROW_SIZE,
|
MINIMUM_ARROW_SIZE,
|
||||||
DOUBLE_TAP_POSITION_THRESHOLD,
|
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -532,7 +531,6 @@ export const useExcalidrawActionManager = () =>
|
|||||||
|
|
||||||
let didTapTwice: boolean = false;
|
let didTapTwice: boolean = false;
|
||||||
let tappedTwiceTimer = 0;
|
let tappedTwiceTimer = 0;
|
||||||
let firstTapPosition: { x: number; y: number } | null = null;
|
|
||||||
let isHoldingSpace: boolean = false;
|
let isHoldingSpace: boolean = false;
|
||||||
let isPanning: boolean = false;
|
let isPanning: boolean = false;
|
||||||
let isDraggingScrollBar: boolean = false;
|
let isDraggingScrollBar: boolean = false;
|
||||||
@@ -2991,7 +2989,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
private static resetTapTwice() {
|
private static resetTapTwice() {
|
||||||
didTapTwice = false;
|
didTapTwice = false;
|
||||||
firstTapPosition = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onTouchStart = (event: TouchEvent) => {
|
private onTouchStart = (event: TouchEvent) => {
|
||||||
@@ -3002,13 +2999,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
if (!didTapTwice) {
|
if (!didTapTwice) {
|
||||||
didTapTwice = true;
|
didTapTwice = true;
|
||||||
|
|
||||||
if (event.touches.length === 1) {
|
|
||||||
firstTapPosition = {
|
|
||||||
x: event.touches[0].clientX,
|
|
||||||
y: event.touches[0].clientY,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
clearTimeout(tappedTwiceTimer);
|
clearTimeout(tappedTwiceTimer);
|
||||||
tappedTwiceTimer = window.setTimeout(
|
tappedTwiceTimer = window.setTimeout(
|
||||||
App.resetTapTwice,
|
App.resetTapTwice,
|
||||||
@@ -3016,29 +3006,15 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// insert text only if we tapped twice with a single finger
|
||||||
// insert text only if we tapped twice with a single finger at approximately the same position
|
|
||||||
// event.touches.length === 1 will also prevent inserting text when user's zooming
|
// event.touches.length === 1 will also prevent inserting text when user's zooming
|
||||||
if (didTapTwice && event.touches.length === 1 && firstTapPosition) {
|
if (didTapTwice && event.touches.length === 1) {
|
||||||
const touch = event.touches[0];
|
const touch = event.touches[0];
|
||||||
const distance = pointDistance(
|
// @ts-ignore
|
||||||
pointFrom(touch.clientX, touch.clientY),
|
this.handleCanvasDoubleClick({
|
||||||
pointFrom(firstTapPosition.x, firstTapPosition.y),
|
clientX: touch.clientX,
|
||||||
);
|
clientY: touch.clientY,
|
||||||
|
});
|
||||||
// only create text if the second tap is within the threshold of the first tap
|
|
||||||
// this prevents accidental text creation during dragging/selection
|
|
||||||
if (distance <= DOUBLE_TAP_POSITION_THRESHOLD) {
|
|
||||||
// end lasso trail and deselect elements just in case
|
|
||||||
this.lassoTrail.endPath();
|
|
||||||
this.deselectElements();
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
this.handleCanvasDoubleClick({
|
|
||||||
clientX: touch.clientX,
|
|
||||||
clientY: touch.clientY,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
didTapTwice = false;
|
didTapTwice = false;
|
||||||
clearTimeout(tappedTwiceTimer);
|
clearTimeout(tappedTwiceTimer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface ButtonProps
|
|||||||
HTMLButtonElement
|
HTMLButtonElement
|
||||||
> {
|
> {
|
||||||
type?: "button" | "submit" | "reset";
|
type?: "button" | "submit" | "reset";
|
||||||
onSelect: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => any;
|
onSelect: () => any;
|
||||||
/** whether button is in active state */
|
/** whether button is in active state */
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -34,7 +34,7 @@ export const Button = ({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={composeEventHandlers(rest.onClick, (event) => {
|
onClick={composeEventHandlers(rest.onClick, (event) => {
|
||||||
onSelect(event);
|
onSelect();
|
||||||
})}
|
})}
|
||||||
type={type}
|
type={type}
|
||||||
className={clsx("excalidraw-button", className, { selected })}
|
className={clsx("excalidraw-button", className, { selected })}
|
||||||
|
|||||||
@@ -30,18 +30,6 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#canvas-bg-color-picker-container {
|
|
||||||
.color-picker__top-picks {
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-picker-container {
|
|
||||||
@include isMobile {
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-picker__button {
|
.color-picker__button {
|
||||||
--radius: 4px;
|
--radius: 4px;
|
||||||
--size: 1.375rem;
|
--size: 1.375rem;
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ import { PropertiesPopover } from "../PropertiesPopover";
|
|||||||
import { QuickSearch } from "../QuickSearch";
|
import { QuickSearch } from "../QuickSearch";
|
||||||
import { ScrollableList } from "../ScrollableList";
|
import { ScrollableList } from "../ScrollableList";
|
||||||
import DropdownMenuGroup from "../dropdownMenu/DropdownMenuGroup";
|
import DropdownMenuGroup from "../dropdownMenu/DropdownMenuGroup";
|
||||||
|
import DropdownMenuItem, {
|
||||||
|
DropDownMenuItemBadgeType,
|
||||||
|
DropDownMenuItemBadge,
|
||||||
|
} from "../dropdownMenu/DropdownMenuItem";
|
||||||
import {
|
import {
|
||||||
FontFamilyCodeIcon,
|
FontFamilyCodeIcon,
|
||||||
FontFamilyHeadingIcon,
|
FontFamilyHeadingIcon,
|
||||||
@@ -32,15 +36,8 @@ import {
|
|||||||
FreedrawIcon,
|
FreedrawIcon,
|
||||||
} from "../icons";
|
} from "../icons";
|
||||||
|
|
||||||
import { Ellipsify } from "../Ellipsify";
|
|
||||||
|
|
||||||
import { fontPickerKeyHandler } from "./keyboardNavHandlers";
|
import { fontPickerKeyHandler } from "./keyboardNavHandlers";
|
||||||
|
|
||||||
import {
|
|
||||||
FontPickerListItem,
|
|
||||||
FontPickerListItemBadgeType,
|
|
||||||
} from "./FontPickerListItem";
|
|
||||||
|
|
||||||
import type { JSX } from "react";
|
import type { JSX } from "react";
|
||||||
|
|
||||||
export interface FontDescriptor {
|
export interface FontDescriptor {
|
||||||
@@ -49,7 +46,7 @@ export interface FontDescriptor {
|
|||||||
text: string;
|
text: string;
|
||||||
deprecated?: true;
|
deprecated?: true;
|
||||||
badge?: {
|
badge?: {
|
||||||
type: ValueOf<typeof FontPickerListItemBadgeType>;
|
type: ValueOf<typeof DropDownMenuItemBadgeType>;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -115,7 +112,7 @@ export const FontPickerList = React.memo(
|
|||||||
Object.assign(fontDescriptor, {
|
Object.assign(fontDescriptor, {
|
||||||
deprecated: metadata.deprecated,
|
deprecated: metadata.deprecated,
|
||||||
badge: {
|
badge: {
|
||||||
type: FontPickerListItemBadgeType.RED,
|
type: DropDownMenuItemBadgeType.RED,
|
||||||
placeholder: t("fontList.badge.old"),
|
placeholder: t("fontList.badge.old"),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -230,7 +227,7 @@ export const FontPickerList = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderFont = (font: FontDescriptor, index: number) => (
|
const renderFont = (font: FontDescriptor, index: number) => (
|
||||||
<FontPickerListItem
|
<DropdownMenuItem
|
||||||
key={font.value}
|
key={font.value}
|
||||||
icon={font.icon}
|
icon={font.icon}
|
||||||
value={font.value}
|
value={font.value}
|
||||||
@@ -242,8 +239,8 @@ export const FontPickerList = React.memo(
|
|||||||
selected={font.value === selectedFontFamily}
|
selected={font.value === selectedFontFamily}
|
||||||
// allow to tab between search and selected font
|
// allow to tab between search and selected font
|
||||||
tabIndex={font.value === selectedFontFamily ? 0 : -1}
|
tabIndex={font.value === selectedFontFamily ? 0 : -1}
|
||||||
onSelect={() => {
|
onClick={(e) => {
|
||||||
onSelect(font.value);
|
onSelect(Number(e.currentTarget.value));
|
||||||
}}
|
}}
|
||||||
onMouseMove={() => {
|
onMouseMove={() => {
|
||||||
if (hoveredFont?.value !== font.value) {
|
if (hoveredFont?.value !== font.value) {
|
||||||
@@ -251,13 +248,13 @@ export const FontPickerList = React.memo(
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Ellipsify>{font.text}</Ellipsify>
|
{font.text}
|
||||||
{font.badge && (
|
{font.badge && (
|
||||||
<FontPickerListItem.Badge type={font.badge.type}>
|
<DropDownMenuItemBadge type={font.badge.type}>
|
||||||
{font.badge.placeholder}
|
{font.badge.placeholder}
|
||||||
</FontPickerListItem.Badge>
|
</DropDownMenuItemBadge>
|
||||||
)}
|
)}
|
||||||
</FontPickerListItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
|
|
||||||
const groups = [];
|
const groups = [];
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
|
||||||
|
|
||||||
import { THEME } from "@excalidraw/common";
|
|
||||||
|
|
||||||
import type { ValueOf } from "@excalidraw/common/utility-types";
|
|
||||||
|
|
||||||
import { Button } from "../Button";
|
|
||||||
|
|
||||||
import { useExcalidrawAppState } from "../App";
|
|
||||||
|
|
||||||
import { useDevice } from "../App";
|
|
||||||
|
|
||||||
import { getDropdownMenuItemClassName } from "../dropdownMenu/common";
|
|
||||||
|
|
||||||
import type { JSX } from "react";
|
|
||||||
|
|
||||||
const MenuItemContent = ({
|
|
||||||
textStyle,
|
|
||||||
icon,
|
|
||||||
shortcut,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
icon?: React.ReactNode;
|
|
||||||
shortcut?: string;
|
|
||||||
textStyle?: React.CSSProperties;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
const device = useDevice();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{icon && <div className="dropdown-menu-item__icon">{icon}</div>}
|
|
||||||
<div style={textStyle} className="dropdown-menu-item__text">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
{shortcut && !device.editor.isMobile && (
|
|
||||||
<div className="dropdown-menu-item__shortcut">{shortcut}</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FontPickerListItem = ({
|
|
||||||
icon,
|
|
||||||
value,
|
|
||||||
order,
|
|
||||||
children,
|
|
||||||
shortcut,
|
|
||||||
className,
|
|
||||||
hovered,
|
|
||||||
selected,
|
|
||||||
textStyle,
|
|
||||||
onSelect,
|
|
||||||
onClick,
|
|
||||||
...rest
|
|
||||||
}: {
|
|
||||||
icon?: JSX.Element;
|
|
||||||
value?: string | number | undefined;
|
|
||||||
order?: number;
|
|
||||||
onSelect: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
|
||||||
children: React.ReactNode;
|
|
||||||
shortcut?: string;
|
|
||||||
hovered?: boolean;
|
|
||||||
selected?: boolean;
|
|
||||||
textStyle?: React.CSSProperties;
|
|
||||||
className?: string;
|
|
||||||
} & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onSelect">) => {
|
|
||||||
const ref = useRef<HTMLButtonElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (hovered) {
|
|
||||||
if (order === 0) {
|
|
||||||
// scroll into the first item differently, so it's visible what is above (i.e. group title)
|
|
||||||
ref.current?.scrollIntoView({ block: "end" });
|
|
||||||
} else {
|
|
||||||
ref.current?.scrollIntoView({ block: "nearest" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [hovered, order]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="radix-menu-item">
|
|
||||||
<Button
|
|
||||||
{...rest}
|
|
||||||
ref={ref}
|
|
||||||
onSelect={onSelect}
|
|
||||||
className={getDropdownMenuItemClassName(className, selected, hovered)}
|
|
||||||
title={rest.title ?? rest["aria-label"]}
|
|
||||||
>
|
|
||||||
<MenuItemContent textStyle={textStyle} icon={icon} shortcut={shortcut}>
|
|
||||||
{children}
|
|
||||||
</MenuItemContent>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
FontPickerListItem.displayName = "FontPickerListItem";
|
|
||||||
|
|
||||||
export const FontPickerListItemBadgeType = {
|
|
||||||
GREEN: "green",
|
|
||||||
RED: "red",
|
|
||||||
BLUE: "blue",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const FontPickerListItemBadge = ({
|
|
||||||
type = FontPickerListItemBadgeType.BLUE,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
type?: ValueOf<typeof FontPickerListItemBadgeType>;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
const { theme } = useExcalidrawAppState();
|
|
||||||
const style = {
|
|
||||||
display: "inline-flex",
|
|
||||||
marginLeft: "auto",
|
|
||||||
padding: "2px 4px",
|
|
||||||
borderRadius: 6,
|
|
||||||
fontSize: 9,
|
|
||||||
fontFamily: "Cascadia, monospace",
|
|
||||||
border: theme === THEME.LIGHT ? "1.5px solid white" : "none",
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case FontPickerListItemBadgeType.GREEN:
|
|
||||||
Object.assign(style, {
|
|
||||||
backgroundColor: "var(--background-color-badge)",
|
|
||||||
color: "var(--color-badge)",
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case FontPickerListItemBadgeType.RED:
|
|
||||||
Object.assign(style, {
|
|
||||||
backgroundColor: "pink",
|
|
||||||
color: "darkred",
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case FontPickerListItemBadgeType.BLUE:
|
|
||||||
default:
|
|
||||||
Object.assign(style, {
|
|
||||||
background: "var(--color-promo)",
|
|
||||||
color: "var(--color-surface-lowest)",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="DropDownMenuItemBadge" style={style}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
FontPickerListItemBadge.displayName = "DropdownMenuItemBadge";
|
|
||||||
|
|
||||||
FontPickerListItem.Badge = FontPickerListItemBadge;
|
|
||||||
@@ -238,10 +238,7 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
|||||||
shortcuts={[getShortcutKey("Enter"), getShortcutKey("Escape")]}
|
shortcuts={[getShortcutKey("Enter"), getShortcutKey("Escape")]}
|
||||||
isOr={true}
|
isOr={true}
|
||||||
/>
|
/>
|
||||||
<Shortcut
|
<Shortcut label={t("toolBar.lock")} shortcuts={[KEYS.Q]} />
|
||||||
label={t("toolBar.lock")}
|
|
||||||
shortcuts={[getShortcutFromShortcutName("toolLock")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
<Shortcut
|
||||||
label={t("helpDialog.preventBinding")}
|
label={t("helpDialog.preventBinding")}
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd")]}
|
shortcuts={[getShortcutKey("CtrlOrCmd")]}
|
||||||
|
|||||||
@@ -194,7 +194,6 @@ export const LibraryDropdownMenuButton: React.FC<{
|
|||||||
<DropdownMenu open={isLibraryMenuOpen}>
|
<DropdownMenu open={isLibraryMenuOpen}>
|
||||||
<DropdownMenu.Trigger
|
<DropdownMenu.Trigger
|
||||||
onToggle={() => setIsLibraryMenuOpen(!isLibraryMenuOpen)}
|
onToggle={() => setIsLibraryMenuOpen(!isLibraryMenuOpen)}
|
||||||
aria-label="Library menu"
|
|
||||||
>
|
>
|
||||||
{DotsIcon}
|
{DotsIcon}
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
@@ -202,7 +201,6 @@ export const LibraryDropdownMenuButton: React.FC<{
|
|||||||
onClickOutside={() => setIsLibraryMenuOpen(false)}
|
onClickOutside={() => setIsLibraryMenuOpen(false)}
|
||||||
onSelect={() => setIsLibraryMenuOpen(false)}
|
onSelect={() => setIsLibraryMenuOpen(false)}
|
||||||
className="library-menu"
|
className="library-menu"
|
||||||
align="end"
|
|
||||||
>
|
>
|
||||||
{!itemsSelected && (
|
{!itemsSelected && (
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ export const TTDDialogTrigger = ({
|
|||||||
setAppState({ openDialog: { name: "ttd", tab: "text-to-diagram" } });
|
setAppState({ openDialog: { name: "ttd", tab: "text-to-diagram" } });
|
||||||
}}
|
}}
|
||||||
icon={icon ?? brainIcon}
|
icon={icon ?? brainIcon}
|
||||||
badge={<DropdownMenu.Item.Badge>AI</DropdownMenu.Item.Badge>}
|
|
||||||
>
|
>
|
||||||
{children ?? t("labels.textToDiagram")}
|
{children ?? t("labels.textToDiagram")}
|
||||||
|
<DropdownMenu.Item.Badge>AI</DropdownMenu.Item.Badge>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
</TTDDialogTriggerTunnel.In>
|
</TTDDialogTriggerTunnel.In>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,45 +1,20 @@
|
|||||||
@import "../../css/variables.module";
|
@import "../../css/variables.module.scss";
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
[data-dropdown-menu-trigger] + [data-radix-popper-content-wrapper] {
|
|
||||||
z-index: 2 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
max-width: 16rem;
|
position: absolute;
|
||||||
margin-top: 0.25rem;
|
top: 100%;
|
||||||
|
margin-top: 0.5rem;
|
||||||
&__submenu-trigger {
|
|
||||||
&[aria-expanded="true"] {
|
|
||||||
.dropdown-menu-item {
|
|
||||||
background-color: var(--button-hover-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__submenu-trigger-icon {
|
|
||||||
margin-left: auto;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radix-menu-item {
|
|
||||||
&:focus-visible {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-submenu {
|
|
||||||
margin-left: -0.75rem;
|
|
||||||
min-width: 16rem;
|
|
||||||
max-width: 20rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--mobile {
|
&--mobile {
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
row-gap: 0.75rem;
|
||||||
|
|
||||||
.dropdown-menu-container {
|
.dropdown-menu-container {
|
||||||
grid-template-columns: minmax(0, 1fr);
|
|
||||||
padding: 8px 8px;
|
padding: 8px 8px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: var(--island-bg-color);
|
// background-color: var(--island-bg-color);
|
||||||
box-shadow: var(--shadow-island);
|
box-shadow: var(--shadow-island);
|
||||||
border-radius: var(--border-radius-lg);
|
border-radius: var(--border-radius-lg);
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -55,14 +30,13 @@
|
|||||||
|
|
||||||
.dropdown-menu-container {
|
.dropdown-menu-container {
|
||||||
background-color: var(--island-bg-color);
|
background-color: var(--island-bg-color);
|
||||||
max-height: var(--radix-popper-available-height);
|
max-height: calc(100vh - 150px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
--gap: 2;
|
--gap: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu-item-base {
|
.dropdown-menu-item-base {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0 0.625rem;
|
|
||||||
column-gap: 0.625rem;
|
column-gap: 0.625rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: var(--color-on-surface);
|
color: var(--color-on-surface);
|
||||||
@@ -70,7 +44,6 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.manual-hover {
|
&.manual-hover {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
||||||
|
|
||||||
import DropdownMenuContent from "./DropdownMenuContent";
|
import DropdownMenuContent from "./DropdownMenuContent";
|
||||||
import DropdownMenuGroup from "./DropdownMenuGroup";
|
import DropdownMenuGroup from "./DropdownMenuGroup";
|
||||||
import DropdownMenuItem from "./DropdownMenuItem";
|
import DropdownMenuItem from "./DropdownMenuItem";
|
||||||
@@ -25,12 +23,11 @@ const DropdownMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const MenuTriggerComp = getMenuTriggerComponent(children);
|
const MenuTriggerComp = getMenuTriggerComponent(children);
|
||||||
const MenuContentComp = getMenuContentComponent(children);
|
const MenuContentComp = getMenuContentComponent(children);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Root open={open} modal={false}>
|
<>
|
||||||
{MenuTriggerComp}
|
{MenuTriggerComp}
|
||||||
{MenuContentComp}
|
{open && MenuContentComp}
|
||||||
</DropdownMenuPrimitive.Root>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import React, { useEffect, useRef } from "react";
|
|||||||
|
|
||||||
import { EVENT, KEYS } from "@excalidraw/common";
|
import { EVENT, KEYS } from "@excalidraw/common";
|
||||||
|
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
||||||
|
|
||||||
import { useOutsideClick } from "../../hooks/useOutsideClick";
|
import { useOutsideClick } from "../../hooks/useOutsideClick";
|
||||||
import { useStable } from "../../hooks/useStable";
|
import { useStable } from "../../hooks/useStable";
|
||||||
import { useDevice } from "../App";
|
import { useDevice } from "../App";
|
||||||
@@ -19,9 +17,6 @@ const MenuContent = ({
|
|||||||
className = "",
|
className = "",
|
||||||
onSelect,
|
onSelect,
|
||||||
style,
|
style,
|
||||||
sideOffset = 4,
|
|
||||||
align = "start",
|
|
||||||
collisionPadding,
|
|
||||||
}: {
|
}: {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
onClickOutside?: () => void;
|
onClickOutside?: () => void;
|
||||||
@@ -31,11 +26,6 @@ const MenuContent = ({
|
|||||||
*/
|
*/
|
||||||
onSelect?: (event: Event) => void;
|
onSelect?: (event: Event) => void;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
sideOffset?: number;
|
|
||||||
align?: "start" | "center" | "end";
|
|
||||||
collisionPadding?:
|
|
||||||
| number
|
|
||||||
| Partial<Record<"top" | "right" | "bottom" | "left", number>>;
|
|
||||||
}) => {
|
}) => {
|
||||||
const device = useDevice();
|
const device = useDevice();
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -72,15 +62,11 @@ const MenuContent = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuContentPropsContext.Provider value={{ onSelect }}>
|
<DropdownMenuContentPropsContext.Provider value={{ onSelect }}>
|
||||||
<DropdownMenuPrimitive.Content
|
<div
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
className={classNames}
|
className={classNames}
|
||||||
style={style}
|
style={style}
|
||||||
data-testid="dropdown-menu"
|
data-testid="dropdown-menu"
|
||||||
side="bottom"
|
|
||||||
sideOffset={sideOffset}
|
|
||||||
align={align}
|
|
||||||
collisionPadding={collisionPadding}
|
|
||||||
>
|
>
|
||||||
{/* the zIndex ensures this menu has higher stacking order,
|
{/* the zIndex ensures this menu has higher stacking order,
|
||||||
see https://github.com/excalidraw/excalidraw/pull/1445 */}
|
see https://github.com/excalidraw/excalidraw/pull/1445 */}
|
||||||
@@ -95,7 +81,7 @@ const MenuContent = ({
|
|||||||
{children}
|
{children}
|
||||||
</Island>
|
</Island>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuPrimitive.Content>
|
</div>
|
||||||
</DropdownMenuContentPropsContext.Provider>
|
</DropdownMenuContentPropsContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
import React, { useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
|
|
||||||
import { THEME } from "@excalidraw/common";
|
import { THEME } from "@excalidraw/common";
|
||||||
|
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
||||||
|
|
||||||
import type { ValueOf } from "@excalidraw/common/utility-types";
|
import type { ValueOf } from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
import { Button } from "../Button";
|
|
||||||
|
|
||||||
import { useExcalidrawAppState } from "../App";
|
import { useExcalidrawAppState } from "../App";
|
||||||
|
|
||||||
import MenuItemContent from "./DropdownMenuItemContent";
|
import MenuItemContent from "./DropdownMenuItemContent";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getDropdownMenuItemClassName,
|
getDropdownMenuItemClassName,
|
||||||
useHandleDropdownMenuItemClick,
|
useHandleDropdownMenuItemClick,
|
||||||
@@ -22,45 +17,55 @@ import type { JSX } from "react";
|
|||||||
const DropdownMenuItem = ({
|
const DropdownMenuItem = ({
|
||||||
icon,
|
icon,
|
||||||
value,
|
value,
|
||||||
badge,
|
|
||||||
order,
|
order,
|
||||||
children,
|
children,
|
||||||
shortcut,
|
shortcut,
|
||||||
className,
|
className,
|
||||||
|
hovered,
|
||||||
selected,
|
selected,
|
||||||
|
textStyle,
|
||||||
onSelect,
|
onSelect,
|
||||||
onClick,
|
onClick,
|
||||||
...rest
|
...rest
|
||||||
}: {
|
}: {
|
||||||
icon?: JSX.Element;
|
icon?: JSX.Element;
|
||||||
badge?: React.ReactNode;
|
|
||||||
value?: string | number | undefined;
|
value?: string | number | undefined;
|
||||||
order?: number;
|
order?: number;
|
||||||
onSelect?: (event: Event) => void;
|
onSelect?: (event: Event) => void;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
shortcut?: string;
|
shortcut?: string;
|
||||||
|
hovered?: boolean;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
|
textStyle?: React.CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
} & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onSelect">) => {
|
} & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onSelect">) => {
|
||||||
const handleClick = useHandleDropdownMenuItemClick(onClick, onSelect);
|
const handleClick = useHandleDropdownMenuItemClick(onClick, onSelect);
|
||||||
const ref = useRef<HTMLButtonElement>(null);
|
const ref = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hovered) {
|
||||||
|
if (order === 0) {
|
||||||
|
// scroll into the first item differently, so it's visible what is above (i.e. group title)
|
||||||
|
ref.current?.scrollIntoView({ block: "end" });
|
||||||
|
} else {
|
||||||
|
ref.current?.scrollIntoView({ block: "nearest" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [hovered, order]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Item className="radix-menu-item">
|
<button
|
||||||
<Button
|
{...rest}
|
||||||
{...rest}
|
ref={ref}
|
||||||
ref={ref}
|
value={value}
|
||||||
onSelect={handleClick}
|
onClick={handleClick}
|
||||||
className={getDropdownMenuItemClassName(className)}
|
className={getDropdownMenuItemClassName(className, selected, hovered)}
|
||||||
title={rest.title ?? rest["aria-label"]}
|
title={rest.title ?? rest["aria-label"]}
|
||||||
>
|
>
|
||||||
<MenuItemContent icon={icon} shortcut={shortcut} badge={badge}>
|
<MenuItemContent textStyle={textStyle} icon={icon} shortcut={shortcut}>
|
||||||
{children}
|
{children}
|
||||||
</MenuItemContent>
|
</MenuItemContent>
|
||||||
</Button>
|
</button>
|
||||||
</DropdownMenuPrimitive.Item>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
DropdownMenuItem.displayName = "DropdownMenuItem";
|
DropdownMenuItem.displayName = "DropdownMenuItem";
|
||||||
|
|||||||
@@ -2,24 +2,25 @@ import { useDevice } from "../App";
|
|||||||
|
|
||||||
import { Ellipsify } from "../Ellipsify";
|
import { Ellipsify } from "../Ellipsify";
|
||||||
|
|
||||||
|
import type { JSX } from "react";
|
||||||
|
|
||||||
const MenuItemContent = ({
|
const MenuItemContent = ({
|
||||||
|
textStyle,
|
||||||
icon,
|
icon,
|
||||||
badge,
|
|
||||||
shortcut,
|
shortcut,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
icon?: React.ReactNode;
|
icon?: JSX.Element;
|
||||||
shortcut?: string;
|
shortcut?: string;
|
||||||
|
textStyle?: React.CSSProperties;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
badge?: React.ReactNode;
|
|
||||||
}) => {
|
}) => {
|
||||||
const device = useDevice();
|
const device = useDevice();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{icon && <div className="dropdown-menu-item__icon">{icon}</div>}
|
{icon && <div className="dropdown-menu-item__icon">{icon}</div>}
|
||||||
<div className="dropdown-menu-item__text">
|
<div style={textStyle} className="dropdown-menu-item__text">
|
||||||
<Ellipsify>{children}</Ellipsify>
|
<Ellipsify>{children}</Ellipsify>
|
||||||
{badge}
|
|
||||||
</div>
|
</div>
|
||||||
{shortcut && !device.editor.isMobile && (
|
{shortcut && !device.editor.isMobile && (
|
||||||
<div className="dropdown-menu-item__shortcut">{shortcut}</div>
|
<div className="dropdown-menu-item__shortcut">{shortcut}</div>
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
||||||
|
|
||||||
import {
|
|
||||||
getSubMenuContentComponent,
|
|
||||||
getSubMenuTriggerComponent,
|
|
||||||
} from "./dropdownMenuUtils";
|
|
||||||
import DropdownMenuSubTrigger from "./DropdownMenuSubTrigger";
|
|
||||||
import DropdownMenuSubContent from "./DropdownMenuSubContent";
|
|
||||||
import DropdownMenuSubItem from "./DropdownMenuSubItem";
|
|
||||||
|
|
||||||
const DropdownMenuSub = ({ children }: { children?: React.ReactNode }) => {
|
|
||||||
const MenuTriggerComp = getSubMenuTriggerComponent(children);
|
|
||||||
const MenuContentComp = getSubMenuContentComponent(children);
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.Sub>
|
|
||||||
{MenuTriggerComp}
|
|
||||||
{MenuContentComp}
|
|
||||||
</DropdownMenuPrimitive.Sub>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
DropdownMenuSub.Trigger = DropdownMenuSubTrigger;
|
|
||||||
DropdownMenuSub.Content = DropdownMenuSubContent;
|
|
||||||
DropdownMenuSub.Item = DropdownMenuSubItem;
|
|
||||||
|
|
||||||
export default DropdownMenuSub;
|
|
||||||
DropdownMenuSub.displayName = "DropdownMenuSub";
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
||||||
|
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
import { useDevice } from "../App";
|
|
||||||
import Stack from "../Stack";
|
|
||||||
import { Island } from "../Island";
|
|
||||||
|
|
||||||
const DropdownMenuSubContent = ({
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
children?: React.ReactNode;
|
|
||||||
className?: string;
|
|
||||||
}) => {
|
|
||||||
const device = useDevice();
|
|
||||||
|
|
||||||
const classNames = clsx(`dropdown-menu dropdown-submenu ${className}`, {
|
|
||||||
"dropdown-menu--mobile": device.editor.isMobile,
|
|
||||||
}).trim();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.SubContent
|
|
||||||
className={classNames}
|
|
||||||
sideOffset={8}
|
|
||||||
alignOffset={-4}
|
|
||||||
>
|
|
||||||
{device.editor.isMobile ? (
|
|
||||||
<Stack.Col className="dropdown-menu-container">{children}</Stack.Col>
|
|
||||||
) : (
|
|
||||||
<Island
|
|
||||||
className="dropdown-menu-container"
|
|
||||||
padding={1}
|
|
||||||
style={{ zIndex: 1 }}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Island>
|
|
||||||
)}
|
|
||||||
</DropdownMenuPrimitive.SubContent>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DropdownMenuSubContent;
|
|
||||||
DropdownMenuSubContent.displayName = "DropdownMenuSubContent";
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
||||||
|
|
||||||
import { Button } from "../Button";
|
|
||||||
|
|
||||||
import MenuItemContent from "./DropdownMenuItemContent";
|
|
||||||
import {
|
|
||||||
getDropdownMenuItemClassName,
|
|
||||||
useHandleDropdownMenuItemClick,
|
|
||||||
} from "./common";
|
|
||||||
|
|
||||||
const DropdownMenuSubItem = ({
|
|
||||||
icon,
|
|
||||||
onSelect,
|
|
||||||
children,
|
|
||||||
shortcut,
|
|
||||||
className,
|
|
||||||
...rest
|
|
||||||
}: {
|
|
||||||
icon?: React.ReactNode;
|
|
||||||
onSelect: (event: Event) => void;
|
|
||||||
children: React.ReactNode;
|
|
||||||
shortcut?: string;
|
|
||||||
className?: string;
|
|
||||||
} & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onSelect">) => {
|
|
||||||
const handleClick = useHandleDropdownMenuItemClick(rest.onClick, onSelect);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.Item className="radix-menu-item">
|
|
||||||
<Button
|
|
||||||
{...rest}
|
|
||||||
onSelect={handleClick}
|
|
||||||
type="button"
|
|
||||||
className={getDropdownMenuItemClassName(className)}
|
|
||||||
title={rest.title ?? rest["aria-label"]}
|
|
||||||
>
|
|
||||||
<MenuItemContent icon={icon} shortcut={shortcut}>
|
|
||||||
{children}
|
|
||||||
</MenuItemContent>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuPrimitive.Item>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DropdownMenuSubItem;
|
|
||||||
DropdownMenuSubItem.displayName = "DropdownMenuSubItem";
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { ChevronRight } from "../icons";
|
|
||||||
|
|
||||||
import MenuItemContent from "./DropdownMenuItemContent";
|
|
||||||
import { getDropdownMenuItemClassName } from "./common";
|
|
||||||
|
|
||||||
import type { JSX } from "react";
|
|
||||||
|
|
||||||
const DropdownMenuSubTrigger = ({
|
|
||||||
children,
|
|
||||||
icon,
|
|
||||||
className,
|
|
||||||
...rest
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
icon?: JSX.Element;
|
|
||||||
className?: string;
|
|
||||||
} & React.HTMLAttributes<HTMLDivElement>) => {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.SubTrigger className="radix-menu-item dropdown-menu__submenu-trigger">
|
|
||||||
<div
|
|
||||||
{...rest}
|
|
||||||
className={getDropdownMenuItemClassName(className)}
|
|
||||||
title={rest.title ?? rest["aria-label"]}
|
|
||||||
>
|
|
||||||
<MenuItemContent icon={icon}>{children}</MenuItemContent>
|
|
||||||
<div className="dropdown-menu__submenu-trigger-icon">
|
|
||||||
{ChevronRight}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DropdownMenuPrimitive.SubTrigger>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DropdownMenuSubTrigger;
|
|
||||||
DropdownMenuSubTrigger.displayName = "DropdownMenuSubTrigger";
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
||||||
|
|
||||||
import { useDevice } from "../App";
|
import { useDevice } from "../App";
|
||||||
|
|
||||||
const MenuTrigger = ({
|
const MenuTrigger = ({
|
||||||
@@ -25,8 +23,7 @@ const MenuTrigger = ({
|
|||||||
},
|
},
|
||||||
).trim();
|
).trim();
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Trigger
|
<button
|
||||||
data-dropdown-menu-trigger
|
|
||||||
data-prevent-outside-click
|
data-prevent-outside-click
|
||||||
className={classNames}
|
className={classNames}
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
@@ -36,7 +33,7 @@ const MenuTrigger = ({
|
|||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.Trigger>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const getMenuComponent = (component: string) => (children: React.ReactNode) => {
|
export const getMenuTriggerComponent = (children: React.ReactNode) => {
|
||||||
const comp = React.Children.toArray(children).find(
|
const comp = React.Children.toArray(children).find(
|
||||||
(child) =>
|
(child) =>
|
||||||
React.isValidElement(child) &&
|
React.isValidElement(child) &&
|
||||||
@@ -8,7 +8,7 @@ const getMenuComponent = (component: string) => (children: React.ReactNode) => {
|
|||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
child?.type.displayName &&
|
child?.type.displayName &&
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
child.type.displayName === component,
|
child.type.displayName === "DropdownMenuTrigger",
|
||||||
);
|
);
|
||||||
if (!comp) {
|
if (!comp) {
|
||||||
return null;
|
return null;
|
||||||
@@ -17,11 +17,19 @@ const getMenuComponent = (component: string) => (children: React.ReactNode) => {
|
|||||||
return comp;
|
return comp;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMenuTriggerComponent = getMenuComponent("DropdownMenuTrigger");
|
export const getMenuContentComponent = (children: React.ReactNode) => {
|
||||||
export const getMenuContentComponent = getMenuComponent("DropdownMenuContent");
|
const comp = React.Children.toArray(children).find(
|
||||||
export const getSubMenuTriggerComponent = getMenuComponent(
|
(child) =>
|
||||||
"DropdownMenuSubTrigger",
|
React.isValidElement(child) &&
|
||||||
);
|
typeof child.type !== "string" &&
|
||||||
export const getSubMenuContentComponent = getMenuComponent(
|
//@ts-ignore
|
||||||
"DropdownMenuSubContent",
|
child?.type.displayName &&
|
||||||
);
|
//@ts-ignore
|
||||||
|
child.type.displayName === "DropdownMenuContent",
|
||||||
|
);
|
||||||
|
if (!comp) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
return comp;
|
||||||
|
};
|
||||||
|
|||||||
@@ -72,15 +72,6 @@ const modifiedTablerIconProps: Opts = {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
//tabler-icons: chevron-right
|
|
||||||
export const ChevronRight = createIcon(
|
|
||||||
<g strokeWidth="1.5">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<polyline points="9 6 15 12 9 18" />
|
|
||||||
</g>,
|
|
||||||
tablerIconProps,
|
|
||||||
);
|
|
||||||
|
|
||||||
// tabler-icons: present
|
// tabler-icons: present
|
||||||
export const PlusPromoIcon = createIcon(
|
export const PlusPromoIcon = createIcon(
|
||||||
<g strokeWidth="1.5">
|
<g strokeWidth="1.5">
|
||||||
@@ -2278,21 +2269,3 @@ export const elementLinkIcon = createIcon(
|
|||||||
</g>,
|
</g>,
|
||||||
tablerIconProps,
|
tablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const settingsIcon = createIcon(
|
|
||||||
<g strokeWidth={1.25}>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M14 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
|
||||||
<path d="M4 6l8 0" />
|
|
||||||
<path d="M16 6l4 0" />
|
|
||||||
<path d="M8 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
|
||||||
<path d="M4 12l2 0" />
|
|
||||||
<path d="M10 12l10 0" />
|
|
||||||
<path d="M17 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
|
||||||
<path d="M4 18l11 0" />
|
|
||||||
<path d="M19 18l1 0" />
|
|
||||||
</g>,
|
|
||||||
tablerIconProps,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const emptyIcon = <div style={{ width: "1rem", height: "1rem" }} />;
|
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ import {
|
|||||||
actionLoadScene,
|
actionLoadScene,
|
||||||
actionSaveToActiveFile,
|
actionSaveToActiveFile,
|
||||||
actionShortcuts,
|
actionShortcuts,
|
||||||
actionToggleGridMode,
|
|
||||||
actionToggleObjectsSnapMode,
|
|
||||||
actionToggleSearchMenu,
|
actionToggleSearchMenu,
|
||||||
actionToggleTheme,
|
actionToggleTheme,
|
||||||
actionToggleZenMode,
|
|
||||||
} from "../../actions";
|
} from "../../actions";
|
||||||
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
||||||
import { trackEvent } from "../../analytics";
|
import { trackEvent } from "../../analytics";
|
||||||
@@ -26,23 +23,13 @@ import {
|
|||||||
useExcalidrawActionManager,
|
useExcalidrawActionManager,
|
||||||
useExcalidrawElements,
|
useExcalidrawElements,
|
||||||
useAppProps,
|
useAppProps,
|
||||||
useApp,
|
|
||||||
} from "../App";
|
} from "../App";
|
||||||
import { openConfirmModal } from "../OverwriteConfirm/OverwriteConfirmState";
|
import { openConfirmModal } from "../OverwriteConfirm/OverwriteConfirmState";
|
||||||
import Trans from "../Trans";
|
import Trans from "../Trans";
|
||||||
import DropdownMenuItem from "../dropdownMenu/DropdownMenuItem";
|
import DropdownMenuItem from "../dropdownMenu/DropdownMenuItem";
|
||||||
import DropdownMenuItemContentRadio from "../dropdownMenu/DropdownMenuItemContentRadio";
|
import DropdownMenuItemContentRadio from "../dropdownMenu/DropdownMenuItemContentRadio";
|
||||||
import DropdownMenuItemLink from "../dropdownMenu/DropdownMenuItemLink";
|
import DropdownMenuItemLink from "../dropdownMenu/DropdownMenuItemLink";
|
||||||
import DropdownMenuSub from "../dropdownMenu/DropdownMenuSub";
|
import { GithubIcon, DiscordIcon, XBrandIcon } from "../icons";
|
||||||
import { actionToggleViewMode } from "../../actions/actionToggleViewMode";
|
|
||||||
import {
|
|
||||||
GithubIcon,
|
|
||||||
DiscordIcon,
|
|
||||||
XBrandIcon,
|
|
||||||
settingsIcon,
|
|
||||||
checkIcon,
|
|
||||||
emptyIcon,
|
|
||||||
} from "../icons";
|
|
||||||
import {
|
import {
|
||||||
boltIcon,
|
boltIcon,
|
||||||
DeviceDesktopIcon,
|
DeviceDesktopIcon,
|
||||||
@@ -326,10 +313,7 @@ export const ChangeCanvasBackground = () => {
|
|||||||
>
|
>
|
||||||
{t("labels.canvasBackground")}
|
{t("labels.canvasBackground")}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div style={{ padding: "0 0.625rem" }}>
|
||||||
style={{ padding: "0 0.625rem" }}
|
|
||||||
id="canvas-bg-color-picker-container"
|
|
||||||
>
|
|
||||||
{actionManager.renderAction("changeViewBackgroundColor")}
|
{actionManager.renderAction("changeViewBackgroundColor")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -409,73 +393,3 @@ export const LiveCollaborationTrigger = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
LiveCollaborationTrigger.displayName = "LiveCollaborationTrigger";
|
LiveCollaborationTrigger.displayName = "LiveCollaborationTrigger";
|
||||||
|
|
||||||
export const Preferences = ({ children }: { children?: React.ReactNode }) => {
|
|
||||||
const { t } = useI18n();
|
|
||||||
const actionManager = useExcalidrawActionManager();
|
|
||||||
const appState = useUIAppState();
|
|
||||||
const app = useApp();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenuSub>
|
|
||||||
<DropdownMenuSub.Trigger icon={settingsIcon}>
|
|
||||||
{t("labels.preferences")}
|
|
||||||
</DropdownMenuSub.Trigger>
|
|
||||||
<DropdownMenuSub.Content className="excalidraw-main-menu-preferences-submenu">
|
|
||||||
<DropdownMenuSub.Item
|
|
||||||
icon={appState.activeTool.locked ? checkIcon : emptyIcon}
|
|
||||||
shortcut={getShortcutFromShortcutName("toolLock")}
|
|
||||||
onSelect={(event) => {
|
|
||||||
app.toggleLock();
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("labels.preferences_toolLock")}
|
|
||||||
</DropdownMenuSub.Item>
|
|
||||||
<DropdownMenuSub.Item
|
|
||||||
icon={appState.objectsSnapModeEnabled ? checkIcon : emptyIcon}
|
|
||||||
shortcut={getShortcutFromShortcutName("objectsSnapMode")}
|
|
||||||
onSelect={(event) => {
|
|
||||||
actionManager.executeAction(actionToggleObjectsSnapMode);
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("buttons.objectsSnapMode")}
|
|
||||||
</DropdownMenuSub.Item>
|
|
||||||
<DropdownMenuSub.Item
|
|
||||||
icon={appState.gridModeEnabled ? checkIcon : emptyIcon}
|
|
||||||
shortcut={getShortcutFromShortcutName("gridMode")}
|
|
||||||
onSelect={(event) => {
|
|
||||||
actionManager.executeAction(actionToggleGridMode);
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("labels.toggleGrid")}
|
|
||||||
</DropdownMenuSub.Item>
|
|
||||||
<DropdownMenuSub.Item
|
|
||||||
icon={appState.zenModeEnabled ? checkIcon : emptyIcon}
|
|
||||||
shortcut={getShortcutFromShortcutName("zenMode")}
|
|
||||||
onSelect={(event) => {
|
|
||||||
actionManager.executeAction(actionToggleZenMode);
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("buttons.zenMode")}
|
|
||||||
</DropdownMenuSub.Item>
|
|
||||||
<DropdownMenuSub.Item
|
|
||||||
icon={appState.viewModeEnabled ? checkIcon : emptyIcon}
|
|
||||||
shortcut={getShortcutFromShortcutName("viewMode")}
|
|
||||||
onSelect={(event) => {
|
|
||||||
actionManager.executeAction(actionToggleViewMode);
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("labels.viewMode")}
|
|
||||||
</DropdownMenuSub.Item>
|
|
||||||
{children}
|
|
||||||
</DropdownMenuSub.Content>
|
|
||||||
</DropdownMenuSub>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Preferences.displayName = "Preferences";
|
|
||||||
|
|||||||
@@ -2,12 +2,8 @@ import React from "react";
|
|||||||
|
|
||||||
import { composeEventHandlers } from "@excalidraw/common";
|
import { composeEventHandlers } from "@excalidraw/common";
|
||||||
|
|
||||||
import * as Portal from "@radix-ui/react-portal";
|
|
||||||
|
|
||||||
import { useTunnels } from "../../context/tunnels";
|
import { useTunnels } from "../../context/tunnels";
|
||||||
import { useUIAppState } from "../../context/ui-appState";
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
import DropdownMenuSub from "../dropdownMenu/DropdownMenuSub";
|
|
||||||
|
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../i18n";
|
||||||
import { useDevice, useExcalidrawSetAppState } from "../App";
|
import { useDevice, useExcalidrawSetAppState } from "../App";
|
||||||
import { UserList } from "../UserList";
|
import { UserList } from "../UserList";
|
||||||
@@ -40,17 +36,6 @@ const MainMenu = Object.assign(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MainMenuTunnel.In>
|
<MainMenuTunnel.In>
|
||||||
{appState.openMenu === "canvas" && device.editor.isMobile && (
|
|
||||||
<Portal.Root
|
|
||||||
style={{
|
|
||||||
backgroundColor: "rgba(18, 18, 18, 0.2)",
|
|
||||||
position: "fixed",
|
|
||||||
inset: "0px",
|
|
||||||
// zIndex: "var(--zIndex-layerUI)",
|
|
||||||
}}
|
|
||||||
onClick={() => setAppState({ openMenu: null })}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<DropdownMenu open={appState.openMenu === "canvas"}>
|
<DropdownMenu open={appState.openMenu === "canvas"}>
|
||||||
<DropdownMenu.Trigger
|
<DropdownMenu.Trigger
|
||||||
onToggle={() => {
|
onToggle={() => {
|
||||||
@@ -59,27 +44,15 @@ const MainMenu = Object.assign(
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
data-testid="main-menu-trigger"
|
data-testid="main-menu-trigger"
|
||||||
aria-label="Main menu"
|
|
||||||
className="main-menu-trigger"
|
className="main-menu-trigger"
|
||||||
>
|
>
|
||||||
{HamburgerMenuIcon}
|
{HamburgerMenuIcon}
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
sideOffset={device.editor.isMobile ? 20 : undefined}
|
|
||||||
className="main-menu-content"
|
|
||||||
onClickOutside={onClickOutside}
|
onClickOutside={onClickOutside}
|
||||||
onSelect={composeEventHandlers(onSelect, () => {
|
onSelect={composeEventHandlers(onSelect, () => {
|
||||||
setAppState({ openMenu: null });
|
setAppState({ openMenu: null });
|
||||||
})}
|
})}
|
||||||
collisionPadding={
|
|
||||||
// accounting for
|
|
||||||
// - editor footer on desktop
|
|
||||||
// - toolbar on mobile
|
|
||||||
// we probably don't want the menu to overlay these elements
|
|
||||||
!device.editor.isMobile
|
|
||||||
? { bottom: 90, top: 10 }
|
|
||||||
: { top: 90, bottom: 10 }
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{device.editor.isMobile && appState.collaborators.size > 0 && (
|
{device.editor.isMobile && appState.collaborators.size > 0 && (
|
||||||
@@ -105,7 +78,6 @@ const MainMenu = Object.assign(
|
|||||||
ItemCustom: DropdownMenu.ItemCustom,
|
ItemCustom: DropdownMenu.ItemCustom,
|
||||||
Group: DropdownMenu.Group,
|
Group: DropdownMenu.Group,
|
||||||
Separator: DropdownMenu.Separator,
|
Separator: DropdownMenu.Separator,
|
||||||
Sub: DropdownMenuSub,
|
|
||||||
DefaultItems,
|
DefaultItems,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -144,7 +144,6 @@
|
|||||||
--color-logo-icon: var(--color-primary);
|
--color-logo-icon: var(--color-primary);
|
||||||
--color-logo-text: #190064;
|
--color-logo-text: #190064;
|
||||||
|
|
||||||
--border-radius-sm: 0.25rem;
|
|
||||||
--border-radius-md: 0.375rem;
|
--border-radius-md: 0.375rem;
|
||||||
--border-radius-lg: 0.5rem;
|
--border-radius-lg: 0.5rem;
|
||||||
|
|
||||||
|
|||||||
@@ -387,10 +387,7 @@ export const restoreElement = (
|
|||||||
elbowed: true,
|
elbowed: true,
|
||||||
startBinding: repairBinding(element, element.startBinding),
|
startBinding: repairBinding(element, element.startBinding),
|
||||||
endBinding: repairBinding(element, element.endBinding),
|
endBinding: repairBinding(element, element.endBinding),
|
||||||
fixedSegments:
|
fixedSegments: element.fixedSegments,
|
||||||
element.fixedSegments?.length && base.points.length >= 4
|
|
||||||
? element.fixedSegments
|
|
||||||
: null,
|
|
||||||
startIsSpecial: element.startIsSpecial,
|
startIsSpecial: element.startIsSpecial,
|
||||||
endIsSpecial: element.endIsSpecial,
|
endIsSpecial: element.endIsSpecial,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -171,9 +171,7 @@
|
|||||||
"linkToElement": "Link to object",
|
"linkToElement": "Link to object",
|
||||||
"wrapSelectionInFrame": "Wrap selection in frame",
|
"wrapSelectionInFrame": "Wrap selection in frame",
|
||||||
"tab": "Tab",
|
"tab": "Tab",
|
||||||
"shapeSwitch": "Switch shape",
|
"shapeSwitch": "Switch shape"
|
||||||
"preferences": "Preferences",
|
|
||||||
"preferences_toolLock": "Tool lock"
|
|
||||||
},
|
},
|
||||||
"elementLink": {
|
"elementLink": {
|
||||||
"title": "Link to object",
|
"title": "Link to object",
|
||||||
|
|||||||
@@ -81,13 +81,11 @@
|
|||||||
"@braintree/sanitize-url": "6.0.2",
|
"@braintree/sanitize-url": "6.0.2",
|
||||||
"@excalidraw/common": "0.18.0",
|
"@excalidraw/common": "0.18.0",
|
||||||
"@excalidraw/element": "0.18.0",
|
"@excalidraw/element": "0.18.0",
|
||||||
"@excalidraw/laser-pointer": "1.3.1",
|
|
||||||
"@excalidraw/math": "0.18.0",
|
"@excalidraw/math": "0.18.0",
|
||||||
"@excalidraw/mermaid-to-excalidraw": "1.1.3",
|
"@excalidraw/laser-pointer": "1.3.1",
|
||||||
|
"@excalidraw/mermaid-to-excalidraw": "1.1.2",
|
||||||
"@excalidraw/random-username": "1.1.0",
|
"@excalidraw/random-username": "1.1.0",
|
||||||
"@radix-ui/react-dropdown-menu": "2.1.16",
|
|
||||||
"@radix-ui/react-popover": "1.1.6",
|
"@radix-ui/react-popover": "1.1.6",
|
||||||
"@radix-ui/react-portal": "1.1.9",
|
|
||||||
"@radix-ui/react-tabs": "1.1.3",
|
"@radix-ui/react-tabs": "1.1.3",
|
||||||
"browser-fs-access": "0.29.1",
|
"browser-fs-access": "0.29.1",
|
||||||
"canvas-roundrect-polyfill": "0.0.1",
|
"canvas-roundrect-polyfill": "0.0.1",
|
||||||
@@ -99,8 +97,8 @@
|
|||||||
"image-blob-reduce": "3.0.1",
|
"image-blob-reduce": "3.0.1",
|
||||||
"jotai": "2.11.0",
|
"jotai": "2.11.0",
|
||||||
"jotai-scope": "0.7.2",
|
"jotai-scope": "0.7.2",
|
||||||
"lodash.debounce": "4.0.8",
|
|
||||||
"lodash.throttle": "4.1.1",
|
"lodash.throttle": "4.1.1",
|
||||||
|
"lodash.debounce": "4.0.8",
|
||||||
"nanoid": "3.3.3",
|
"nanoid": "3.3.3",
|
||||||
"open-color": "1.9.1",
|
"open-color": "1.9.1",
|
||||||
"pako": "2.0.3",
|
"pako": "2.0.3",
|
||||||
|
|||||||
@@ -282,14 +282,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"added": {},
|
"added": {},
|
||||||
"removed": {},
|
"removed": {},
|
||||||
"updated": {
|
"updated": {
|
||||||
"id0": {
|
|
||||||
"deleted": {
|
|
||||||
"version": 12,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"version": 11,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id1": {
|
"id1": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
@@ -404,14 +396,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"version": 12,
|
"version": 12,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id15": {
|
|
||||||
"deleted": {
|
|
||||||
"version": 10,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"version": 9,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id4": {
|
"id4": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"height": "99.19972",
|
"height": "99.19972",
|
||||||
@@ -853,14 +837,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"added": {},
|
"added": {},
|
||||||
"removed": {},
|
"removed": {},
|
||||||
"updated": {
|
"updated": {
|
||||||
"id0": {
|
|
||||||
"deleted": {
|
|
||||||
"version": 13,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"version": 12,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id1": {
|
"id1": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
@@ -2656,7 +2632,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"height": 100,
|
"height": 100,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"isDeleted": false,
|
"isDeleted": true,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -2705,7 +2681,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"textAlign": "left",
|
"textAlign": "left",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 8,
|
"version": 6,
|
||||||
"verticalAlign": "top",
|
"verticalAlign": "top",
|
||||||
"width": 100,
|
"width": 100,
|
||||||
"x": 15,
|
"x": 15,
|
||||||
@@ -2719,7 +2695,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"autoResize": true,
|
"autoResize": true,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id0",
|
"containerId": null,
|
||||||
"customData": undefined,
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 5,
|
"fontFamily": 5,
|
||||||
@@ -2766,12 +2742,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"elements": {
|
"elements": {
|
||||||
"added": {},
|
"added": {
|
||||||
"removed": {},
|
|
||||||
"updated": {
|
|
||||||
"id0": {
|
"id0": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"isDeleted": false,
|
"isDeleted": true,
|
||||||
"version": 9,
|
"version": 9,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
@@ -2800,21 +2774,16 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"y": 10,
|
"y": 10,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id1": {
|
},
|
||||||
"deleted": {
|
"removed": {},
|
||||||
"containerId": null,
|
"updated": {
|
||||||
"version": 8,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"containerId": null,
|
|
||||||
"version": 7,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id5": {
|
"id5": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
|
"containerId": null,
|
||||||
"version": 7,
|
"version": 7,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
|
"containerId": "id0",
|
||||||
"version": 6,
|
"version": 6,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -3127,14 +3096,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"version": 8,
|
"version": 8,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id5": {
|
|
||||||
"deleted": {
|
|
||||||
"version": 7,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"version": 6,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id": "id9",
|
"id": "id9",
|
||||||
@@ -4684,15 +4645,15 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id1": {
|
"id1": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"version": 8,
|
"version": 4,
|
||||||
"x": 15,
|
"x": 15,
|
||||||
"y": 15,
|
"y": 15,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"angle": 0,
|
"angle": 90,
|
||||||
"version": 7,
|
"version": 3,
|
||||||
"x": 15,
|
"x": 205,
|
||||||
"y": 15,
|
"y": 205,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -4886,8 +4847,8 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"version": 6,
|
"version": 6,
|
||||||
"verticalAlign": "top",
|
"verticalAlign": "top",
|
||||||
"width": 80,
|
"width": 80,
|
||||||
"x": "241.29526",
|
"x": 205,
|
||||||
"y": "247.59241",
|
"y": 205,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -5671,12 +5632,12 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
|
|||||||
"updated": {
|
"updated": {
|
||||||
"id1": {
|
"id1": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"frameId": null,
|
"frameId": "id0",
|
||||||
"version": 9,
|
"version": 5,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"version": 8,
|
"version": 6,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -5823,7 +5784,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 6,
|
"version": 5,
|
||||||
"width": 100,
|
"width": 100,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
@@ -5855,7 +5816,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 5,
|
"version": 4,
|
||||||
"width": 100,
|
"width": 100,
|
||||||
"x": 100,
|
"x": 100,
|
||||||
"y": 100,
|
"y": 100,
|
||||||
@@ -5891,74 +5852,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"elements": {
|
"elements": {
|
||||||
"added": {},
|
"added": {},
|
||||||
"removed": {},
|
"removed": {},
|
||||||
"updated": {
|
"updated": {},
|
||||||
"id0": {
|
|
||||||
"deleted": {
|
|
||||||
"angle": 0,
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"boundElements": null,
|
|
||||||
"customData": undefined,
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"frameId": null,
|
|
||||||
"groupIds": [
|
|
||||||
"A",
|
|
||||||
],
|
|
||||||
"height": 100,
|
|
||||||
"index": "a0",
|
|
||||||
"isDeleted": true,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"opacity": 100,
|
|
||||||
"roughness": 1,
|
|
||||||
"roundness": null,
|
|
||||||
"strokeColor": "#1e1e1e",
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 5,
|
|
||||||
"width": 100,
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"isDeleted": true,
|
|
||||||
"version": 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id1": {
|
|
||||||
"deleted": {
|
|
||||||
"angle": 0,
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"boundElements": null,
|
|
||||||
"customData": undefined,
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"frameId": null,
|
|
||||||
"groupIds": [
|
|
||||||
"A",
|
|
||||||
],
|
|
||||||
"height": 100,
|
|
||||||
"index": "a1",
|
|
||||||
"isDeleted": true,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"opacity": 100,
|
|
||||||
"roughness": 1,
|
|
||||||
"roundness": null,
|
|
||||||
"strokeColor": "#1e1e1e",
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 5,
|
|
||||||
"width": 100,
|
|
||||||
"x": 100,
|
|
||||||
"y": 100,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"isDeleted": true,
|
|
||||||
"version": 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"id": "id13",
|
"id": "id13",
|
||||||
},
|
},
|
||||||
@@ -6178,7 +6072,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 9,
|
"version": 8,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 20,
|
"x": 20,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
@@ -6208,7 +6102,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 9,
|
"version": 8,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 50,
|
"x": 50,
|
||||||
"y": 50,
|
"y": 50,
|
||||||
@@ -6293,39 +6187,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"elements": {
|
"elements": {
|
||||||
"added": {},
|
"added": {},
|
||||||
"removed": {},
|
"removed": {},
|
||||||
"updated": {
|
"updated": {},
|
||||||
"id3": {
|
|
||||||
"deleted": {
|
|
||||||
"angle": 0,
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"boundElements": null,
|
|
||||||
"customData": undefined,
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"frameId": null,
|
|
||||||
"groupIds": [],
|
|
||||||
"height": 10,
|
|
||||||
"index": "a1",
|
|
||||||
"isDeleted": true,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"opacity": 100,
|
|
||||||
"roughness": 1,
|
|
||||||
"roundness": null,
|
|
||||||
"strokeColor": "#1e1e1e",
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 8,
|
|
||||||
"width": 10,
|
|
||||||
"x": 20,
|
|
||||||
"y": 0,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"isDeleted": true,
|
|
||||||
"version": 7,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"id": "id18",
|
"id": "id18",
|
||||||
},
|
},
|
||||||
@@ -6343,11 +6205,11 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"backgroundColor": "#ffc9c9",
|
"backgroundColor": "#ffc9c9",
|
||||||
"version": 9,
|
"version": 8,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"version": 8,
|
"version": 7,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -6372,39 +6234,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"elements": {
|
"elements": {
|
||||||
"added": {},
|
"added": {},
|
||||||
"removed": {},
|
"removed": {},
|
||||||
"updated": {
|
"updated": {},
|
||||||
"id8": {
|
|
||||||
"deleted": {
|
|
||||||
"angle": 0,
|
|
||||||
"backgroundColor": "#ffc9c9",
|
|
||||||
"boundElements": null,
|
|
||||||
"customData": undefined,
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"frameId": null,
|
|
||||||
"groupIds": [],
|
|
||||||
"height": 10,
|
|
||||||
"index": "a2",
|
|
||||||
"isDeleted": true,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"opacity": 100,
|
|
||||||
"roughness": 1,
|
|
||||||
"roundness": null,
|
|
||||||
"strokeColor": "#1e1e1e",
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 8,
|
|
||||||
"width": 10,
|
|
||||||
"x": 30,
|
|
||||||
"y": 30,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"isDeleted": true,
|
|
||||||
"version": 7,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"id": "id20",
|
"id": "id20",
|
||||||
},
|
},
|
||||||
@@ -6421,12 +6251,12 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"updated": {
|
"updated": {
|
||||||
"id8": {
|
"id8": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"version": 9,
|
"version": 8,
|
||||||
"x": 50,
|
"x": 50,
|
||||||
"y": 50,
|
"y": 50,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"version": 8,
|
"version": 7,
|
||||||
"x": 30,
|
"x": 30,
|
||||||
"y": 30,
|
"y": 30,
|
||||||
},
|
},
|
||||||
@@ -7274,7 +7104,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 9,
|
"version": 8,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
@@ -7305,60 +7135,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"elements": {
|
"elements": {
|
||||||
"added": {},
|
"added": {},
|
||||||
"removed": {},
|
"removed": {},
|
||||||
"updated": {
|
"updated": {},
|
||||||
"id0": {
|
|
||||||
"deleted": {
|
|
||||||
"angle": 0,
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"boundElements": null,
|
|
||||||
"customData": undefined,
|
|
||||||
"elbowed": false,
|
|
||||||
"endArrowhead": "arrow",
|
|
||||||
"endBinding": null,
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"frameId": null,
|
|
||||||
"groupIds": [],
|
|
||||||
"height": 10,
|
|
||||||
"index": "a0",
|
|
||||||
"isDeleted": true,
|
|
||||||
"lastCommittedPoint": [
|
|
||||||
10,
|
|
||||||
10,
|
|
||||||
],
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"opacity": 100,
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
10,
|
|
||||||
10,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"roughness": 1,
|
|
||||||
"roundness": {
|
|
||||||
"type": 2,
|
|
||||||
},
|
|
||||||
"startArrowhead": null,
|
|
||||||
"startBinding": null,
|
|
||||||
"strokeColor": "#1e1e1e",
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"type": "arrow",
|
|
||||||
"version": 9,
|
|
||||||
"width": 10,
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"isDeleted": true,
|
|
||||||
"version": 8,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"id": "id13",
|
"id": "id13",
|
||||||
},
|
},
|
||||||
@@ -7567,7 +7344,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 9,
|
"version": 8,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 10,
|
"x": 10,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
@@ -7598,39 +7375,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"elements": {
|
"elements": {
|
||||||
"added": {},
|
"added": {},
|
||||||
"removed": {},
|
"removed": {},
|
||||||
"updated": {
|
"updated": {},
|
||||||
"id0": {
|
|
||||||
"deleted": {
|
|
||||||
"angle": 0,
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"boundElements": null,
|
|
||||||
"customData": undefined,
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"frameId": null,
|
|
||||||
"groupIds": [],
|
|
||||||
"height": 10,
|
|
||||||
"index": "a0",
|
|
||||||
"isDeleted": true,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"opacity": 100,
|
|
||||||
"roughness": 1,
|
|
||||||
"roundness": null,
|
|
||||||
"strokeColor": "#1e1e1e",
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 8,
|
|
||||||
"width": 10,
|
|
||||||
"x": 10,
|
|
||||||
"y": 0,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"isDeleted": true,
|
|
||||||
"version": 7,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"id": "id7",
|
"id": "id7",
|
||||||
},
|
},
|
||||||
@@ -7648,11 +7393,11 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"id0": {
|
"id0": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"backgroundColor": "#ffec99",
|
"backgroundColor": "#ffec99",
|
||||||
"version": 9,
|
"version": 8,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"version": 8,
|
"version": 7,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -10581,7 +10326,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme
|
|||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 9,
|
"version": 8,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 10,
|
"x": 10,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
@@ -10664,18 +10409,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme
|
|||||||
"elements": {
|
"elements": {
|
||||||
"added": {},
|
"added": {},
|
||||||
"removed": {},
|
"removed": {},
|
||||||
"updated": {
|
"updated": {},
|
||||||
"id0": {
|
|
||||||
"deleted": {
|
|
||||||
"isDeleted": false,
|
|
||||||
"version": 9,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"isDeleted": false,
|
|
||||||
"version": 8,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"id": "id8",
|
"id": "id8",
|
||||||
},
|
},
|
||||||
@@ -16041,14 +15775,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"version": 5,
|
"version": 5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id1": {
|
|
||||||
"deleted": {
|
|
||||||
"version": 5,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"version": 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id2": {
|
"id2": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
@@ -17010,14 +16736,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"version": 5,
|
"version": 5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id1": {
|
|
||||||
"deleted": {
|
|
||||||
"version": 6,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"version": 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id2": {
|
"id2": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
@@ -17643,14 +17361,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"version": 9,
|
"version": 9,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id1": {
|
|
||||||
"deleted": {
|
|
||||||
"version": 10,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"version": 9,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id2": {
|
"id2": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
@@ -18012,14 +17722,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"version": 7,
|
"version": 7,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id2": {
|
|
||||||
"deleted": {
|
|
||||||
"version": 4,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"version": 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id": "id21",
|
"id": "id21",
|
||||||
|
|||||||
@@ -2216,16 +2216,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] undo
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"updated": {
|
"updated": {},
|
||||||
"id0": {
|
|
||||||
"deleted": {
|
|
||||||
"version": 5,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"version": 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"id": "id6",
|
"id": "id6",
|
||||||
},
|
},
|
||||||
@@ -10901,32 +10892,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"updated": {
|
"updated": {},
|
||||||
"id0": {
|
|
||||||
"deleted": {
|
|
||||||
"version": 6,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"version": 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id3": {
|
|
||||||
"deleted": {
|
|
||||||
"version": 6,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"version": 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id6": {
|
|
||||||
"deleted": {
|
|
||||||
"version": 6,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"version": 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"id": "id21",
|
"id": "id21",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
import { queryByText, queryByTestId } from "@testing-library/react";
|
import { queryByText, queryByTestId } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import { THEME } from "@excalidraw/common";
|
import { THEME } from "@excalidraw/common";
|
||||||
|
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { Excalidraw, Footer } from "..";
|
import { Excalidraw, Footer, MainMenu } from "../index";
|
||||||
import MainMenu from "../components/main-menu/MainMenu";
|
|
||||||
|
|
||||||
import {
|
import { fireEvent, GlobalTestState, toggleMenu, render } from "./test-utils";
|
||||||
render,
|
|
||||||
togglePopover,
|
|
||||||
fireEvent,
|
|
||||||
GlobalTestState,
|
|
||||||
} from "./test-utils";
|
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
@@ -20,7 +15,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
const menu = document.querySelector(".dropdown-menu");
|
const menu = document.querySelector(".dropdown-menu");
|
||||||
if (menu) {
|
if (menu) {
|
||||||
togglePopover("Main menu");
|
toggleMenu(document.querySelector(".excalidraw")!);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -141,7 +136,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
<Excalidraw UIOptions={undefined} />,
|
<Excalidraw UIOptions={undefined} />,
|
||||||
);
|
);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
expect(queryByTestId(container, "dropdown-menu")).toMatchSnapshot();
|
expect(queryByTestId(container, "dropdown-menu")).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -150,7 +145,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
<Excalidraw UIOptions={{ canvasActions: { clearCanvas: false } }} />,
|
<Excalidraw UIOptions={{ canvasActions: { clearCanvas: false } }} />,
|
||||||
);
|
);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
expect(queryByTestId(container, "clear-canvas-button")).toBeNull();
|
expect(queryByTestId(container, "clear-canvas-button")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -159,7 +154,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
<Excalidraw UIOptions={{ canvasActions: { export: false } }} />,
|
<Excalidraw UIOptions={{ canvasActions: { export: false } }} />,
|
||||||
);
|
);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
expect(queryByTestId(container, "json-export-button")).toBeNull();
|
expect(queryByTestId(container, "json-export-button")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -168,7 +163,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
<Excalidraw UIOptions={{ canvasActions: { saveAsImage: false } }} />,
|
<Excalidraw UIOptions={{ canvasActions: { saveAsImage: false } }} />,
|
||||||
);
|
);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
expect(queryByTestId(container, "image-export-button")).toBeNull();
|
expect(queryByTestId(container, "image-export-button")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -187,7 +182,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
expect(queryByTestId(container, "save-as-button")).toBeNull();
|
expect(queryByTestId(container, "save-as-button")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -198,7 +193,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
expect(queryByTestId(container, "save-button")).toBeNull();
|
expect(queryByTestId(container, "save-button")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -209,7 +204,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
expect(queryByTestId(container, "canvas-background-label")).toBeNull();
|
expect(queryByTestId(container, "canvas-background-label")).toBeNull();
|
||||||
expect(queryByTestId(container, "canvas-background-picker")).toBeNull();
|
expect(queryByTestId(container, "canvas-background-picker")).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -225,7 +220,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
</Excalidraw>,
|
</Excalidraw>,
|
||||||
);
|
);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
expect(queryByTestId(container, "canvas-background-label")).toBeNull();
|
expect(queryByTestId(container, "canvas-background-label")).toBeNull();
|
||||||
expect(queryByTestId(container, "canvas-background-picker")).toBeNull();
|
expect(queryByTestId(container, "canvas-background-picker")).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -235,7 +230,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
<Excalidraw UIOptions={{ canvasActions: { toggleTheme: false } }} />,
|
<Excalidraw UIOptions={{ canvasActions: { toggleTheme: false } }} />,
|
||||||
);
|
);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
expect(queryByTestId(container, "toggle-dark-mode")).toBeNull();
|
expect(queryByTestId(container, "toggle-dark-mode")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -256,8 +251,8 @@ describe("<Excalidraw/>", () => {
|
|||||||
</Excalidraw>,
|
</Excalidraw>,
|
||||||
);
|
);
|
||||||
//open menu
|
//open menu
|
||||||
|
toggleMenu(container);
|
||||||
// load button shouldn't be rendered since `UIActions.canvasActions.loadScene` is `false`
|
// load button shouldn't be rendered since `UIActions.canvasActions.loadScene` is `false`
|
||||||
togglePopover("Main menu");
|
|
||||||
expect(queryByTestId(container, "load-button")).toBeNull();
|
expect(queryByTestId(container, "load-button")).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -268,7 +263,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
const { container } = await render(<Excalidraw />);
|
const { container } = await render(<Excalidraw />);
|
||||||
expect(h.state.theme).toBe(THEME.LIGHT);
|
expect(h.state.theme).toBe(THEME.LIGHT);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
|
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
|
||||||
expect(darkModeToggle).toBeTruthy();
|
expect(darkModeToggle).toBeTruthy();
|
||||||
});
|
});
|
||||||
@@ -278,7 +273,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
|
|
||||||
expect(h.state.theme).toBe(THEME.DARK);
|
expect(h.state.theme).toBe(THEME.DARK);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
expect(queryByTestId(container, "toggle-dark-mode")).toBe(null);
|
expect(queryByTestId(container, "toggle-dark-mode")).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -291,7 +286,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
);
|
);
|
||||||
expect(h.state.theme).toBe(THEME.DARK);
|
expect(h.state.theme).toBe(THEME.DARK);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
|
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
|
||||||
expect(darkModeToggle).toBeTruthy();
|
expect(darkModeToggle).toBeTruthy();
|
||||||
});
|
});
|
||||||
@@ -305,7 +300,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
);
|
);
|
||||||
expect(h.state.theme).toBe(THEME.DARK);
|
expect(h.state.theme).toBe(THEME.DARK);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
|
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
|
||||||
expect(darkModeToggle).toBe(null);
|
expect(darkModeToggle).toBe(null);
|
||||||
});
|
});
|
||||||
@@ -315,7 +310,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
it("should allow editing name", async () => {
|
it("should allow editing name", async () => {
|
||||||
const { container } = await render(<Excalidraw />);
|
const { container } = await render(<Excalidraw />);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
fireEvent.click(queryByTestId(container, "image-export-button")!);
|
fireEvent.click(queryByTestId(container, "image-export-button")!);
|
||||||
const textInput: HTMLInputElement | null = document.querySelector(
|
const textInput: HTMLInputElement | null = document.querySelector(
|
||||||
".ImageExportModal .ImageExportModal__preview__filename .TextInput",
|
".ImageExportModal .ImageExportModal__preview__filename .TextInput",
|
||||||
@@ -328,7 +323,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
const name = "test";
|
const name = "test";
|
||||||
const { container } = await render(<Excalidraw name={name} />);
|
const { container } = await render(<Excalidraw name={name} />);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
await fireEvent.click(queryByTestId(container, "image-export-button")!);
|
await fireEvent.click(queryByTestId(container, "image-export-button")!);
|
||||||
const textInput = document.querySelector(
|
const textInput = document.querySelector(
|
||||||
".ImageExportModal .ImageExportModal__preview__filename .TextInput",
|
".ImageExportModal .ImageExportModal__preview__filename .TextInput",
|
||||||
@@ -380,7 +375,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
</Excalidraw>,
|
</Excalidraw>,
|
||||||
);
|
);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
expect(queryByTestId(container, "dropdown-menu")).toMatchSnapshot();
|
expect(queryByTestId(container, "dropdown-menu")).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -399,7 +394,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
|
|
||||||
const { container } = await render(<CustomExcalidraw />);
|
const { container } = await render(<CustomExcalidraw />);
|
||||||
//open menu
|
//open menu
|
||||||
togglePopover("Main menu");
|
toggleMenu(container);
|
||||||
|
|
||||||
expect(h.state.theme).toBe(THEME.LIGHT);
|
expect(h.state.theme).toBe(THEME.LIGHT);
|
||||||
|
|
||||||
|
|||||||
@@ -4055,7 +4055,7 @@ describe("history", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: container.id,
|
id: container.id,
|
||||||
boundElements: [{ id: remoteText.id, type: "text" }],
|
boundElements: [{ id: remoteText.id, type: "text" }],
|
||||||
isDeleted: false,
|
isDeleted: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: text.id,
|
id: text.id,
|
||||||
@@ -4064,7 +4064,8 @@ describe("history", () => {
|
|||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: remoteText.id,
|
id: remoteText.id,
|
||||||
containerId: container.id,
|
// unbound
|
||||||
|
containerId: null,
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@@ -4354,8 +4355,8 @@ describe("history", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
...textProps,
|
...textProps,
|
||||||
// text element got redrawn!
|
// text element got redrawn!
|
||||||
x: 241.295259647664,
|
x: 205,
|
||||||
y: 247.59240920619527,
|
y: 205,
|
||||||
angle: 90,
|
angle: 90,
|
||||||
id: text.id,
|
id: text.id,
|
||||||
containerId: container.id,
|
containerId: container.id,
|
||||||
@@ -4398,8 +4399,8 @@ describe("history", () => {
|
|||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
...textProps,
|
...textProps,
|
||||||
x: 241.295259647664,
|
x: 205,
|
||||||
y: 247.59240920619527,
|
y: 205,
|
||||||
angle: 90,
|
angle: 90,
|
||||||
id: text.id,
|
id: text.id,
|
||||||
containerId: container.id,
|
containerId: container.id,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { queryByTestId } from "@testing-library/react";
|
import { act, queryByTestId } from "@testing-library/react";
|
||||||
import { act } from "@testing-library/react";
|
import React from "react";
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
|
||||||
import { MIME_TYPES, ORIG_ID } from "@excalidraw/common";
|
import { MIME_TYPES, ORIG_ID } from "@excalidraw/common";
|
||||||
@@ -13,11 +13,9 @@ import { serializeLibraryAsJSON } from "../data/json";
|
|||||||
import { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
import { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
||||||
import { Excalidraw } from "../index";
|
import { Excalidraw } from "../index";
|
||||||
|
|
||||||
import { fireEvent, render, togglePopover, waitFor } from "./test-utils";
|
|
||||||
|
|
||||||
import { API } from "./helpers/api";
|
import { API } from "./helpers/api";
|
||||||
import { UI } from "./helpers/ui";
|
import { UI } from "./helpers/ui";
|
||||||
import { getCloneByOrigId } from "./test-utils";
|
import { fireEvent, getCloneByOrigId, render, waitFor } from "./test-utils";
|
||||||
|
|
||||||
import type { LibraryItem, LibraryItems } from "../types";
|
import type { LibraryItem, LibraryItems } from "../types";
|
||||||
|
|
||||||
@@ -217,13 +215,12 @@ describe("library menu", () => {
|
|||||||
const libraryButton = container.querySelector(".sidebar-trigger");
|
const libraryButton = container.querySelector(".sidebar-trigger");
|
||||||
|
|
||||||
fireEvent.click(libraryButton!);
|
fireEvent.click(libraryButton!);
|
||||||
togglePopover("Library menu");
|
fireEvent.click(
|
||||||
// fireEvent.click(
|
queryByTestId(
|
||||||
// queryByTestId(
|
container.querySelector(".layer-ui__library")!,
|
||||||
// container.querySelector(".layer-ui__library")!,
|
"dropdown-menu-button",
|
||||||
// "dropdown-menu-button",
|
)!,
|
||||||
// )!,
|
);
|
||||||
// );
|
|
||||||
fireEvent.click(queryByTestId(container, "lib-dropdown--load")!);
|
fireEvent.click(queryByTestId(container, "lib-dropdown--load")!);
|
||||||
|
|
||||||
const libraryItems = parseLibraryJSON(await libraryJSONPromise);
|
const libraryItems = parseLibraryJSON(await libraryJSONPromise);
|
||||||
|
|||||||
@@ -2,46 +2,26 @@
|
|||||||
|
|
||||||
exports[`<Excalidraw/> > <MainMenu/> > should render main menu with host menu items if passed from host 1`] = `
|
exports[`<Excalidraw/> > <MainMenu/> > should render main menu with host menu items if passed from host 1`] = `
|
||||||
<div
|
<div
|
||||||
aria-labelledby="radix-:r65:"
|
class="dropdown-menu"
|
||||||
aria-orientation="vertical"
|
|
||||||
class="dropdown-menu main-menu-content"
|
|
||||||
data-align="start"
|
|
||||||
data-orientation="vertical"
|
|
||||||
data-radix-menu-content=""
|
|
||||||
data-side="bottom"
|
|
||||||
data-state="open"
|
|
||||||
data-testid="dropdown-menu"
|
data-testid="dropdown-menu"
|
||||||
dir="ltr"
|
|
||||||
id="radix-:r66:"
|
|
||||||
role="menu"
|
|
||||||
style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); animation: none;"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="Island dropdown-menu-container"
|
class="Island dropdown-menu-container"
|
||||||
style="--padding: 1; z-index: 2;"
|
style="--padding: 2; z-index: 2;"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
class="radix-menu-item"
|
class="dropdown-menu-item dropdown-menu-item-base"
|
||||||
data-orientation="vertical"
|
type="button"
|
||||||
data-radix-collection-item=""
|
|
||||||
role="menuitem"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
>
|
||||||
<button
|
<div
|
||||||
class="excalidraw-button dropdown-menu-item dropdown-menu-item-base"
|
class="dropdown-menu-item__icon"
|
||||||
type="button"
|
/>
|
||||||
|
<div
|
||||||
|
class="dropdown-menu-item__text"
|
||||||
>
|
>
|
||||||
<div
|
Click me
|
||||||
class="dropdown-menu-item__icon"
|
</div>
|
||||||
/>
|
</button>
|
||||||
<div
|
|
||||||
class="dropdown-menu-item__text"
|
|
||||||
>
|
|
||||||
Click me
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<a
|
<a
|
||||||
class="dropdown-menu-item dropdown-menu-item-base"
|
class="dropdown-menu-item dropdown-menu-item-base"
|
||||||
href="https://plus.excalidraw.com/blog"
|
href="https://plus.excalidraw.com/blog"
|
||||||
@@ -66,361 +46,301 @@ exports[`<Excalidraw/> > <MainMenu/> > should render main menu with host menu it
|
|||||||
custom menu item
|
custom menu item
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<button
|
||||||
class="radix-menu-item"
|
aria-label="Help"
|
||||||
data-orientation="vertical"
|
class="dropdown-menu-item dropdown-menu-item-base"
|
||||||
data-radix-collection-item=""
|
data-testid="help-menu-item"
|
||||||
role="menuitem"
|
title="Help"
|
||||||
tabindex="-1"
|
type="button"
|
||||||
>
|
>
|
||||||
<button
|
<div
|
||||||
aria-label="Help"
|
class="dropdown-menu-item__icon"
|
||||||
class="excalidraw-button dropdown-menu-item dropdown-menu-item-base"
|
|
||||||
data-testid="help-menu-item"
|
|
||||||
title="Help"
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<div
|
<svg
|
||||||
class="dropdown-menu-item__icon"
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<svg
|
<g
|
||||||
aria-hidden="true"
|
stroke-width="1.5"
|
||||||
class=""
|
|
||||||
fill="none"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
>
|
||||||
<g
|
<path
|
||||||
stroke-width="1.5"
|
d="M0 0h24v24H0z"
|
||||||
>
|
fill="none"
|
||||||
<path
|
stroke="none"
|
||||||
d="M0 0h24v24H0z"
|
/>
|
||||||
fill="none"
|
<circle
|
||||||
stroke="none"
|
cx="12"
|
||||||
/>
|
cy="12"
|
||||||
<circle
|
r="9"
|
||||||
cx="12"
|
/>
|
||||||
cy="12"
|
<line
|
||||||
r="9"
|
x1="12"
|
||||||
/>
|
x2="12"
|
||||||
<line
|
y1="17"
|
||||||
x1="12"
|
y2="17.01"
|
||||||
x2="12"
|
/>
|
||||||
y1="17"
|
<path
|
||||||
y2="17.01"
|
d="M12 13.5a1.5 1.5 0 0 1 1 -1.5a2.6 2.6 0 1 0 -3 -4"
|
||||||
/>
|
/>
|
||||||
<path
|
</g>
|
||||||
d="M12 13.5a1.5 1.5 0 0 1 1 -1.5a2.6 2.6 0 1 0 -3 -4"
|
</svg>
|
||||||
/>
|
</div>
|
||||||
</g>
|
<div
|
||||||
</svg>
|
class="dropdown-menu-item__text"
|
||||||
</div>
|
>
|
||||||
<div
|
Help
|
||||||
class="dropdown-menu-item__text"
|
</div>
|
||||||
>
|
<div
|
||||||
Help
|
class="dropdown-menu-item__shortcut"
|
||||||
</div>
|
>
|
||||||
<div
|
?
|
||||||
class="dropdown-menu-item__shortcut"
|
</div>
|
||||||
>
|
</button>
|
||||||
?
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<Excalidraw/> > Test UIOptions prop > Test canvasActions > should render menu with default items when "UIOPtions" is "undefined" 1`] = `
|
exports[`<Excalidraw/> > Test UIOptions prop > Test canvasActions > should render menu with default items when "UIOPtions" is "undefined" 1`] = `
|
||||||
<div
|
<div
|
||||||
aria-labelledby="radix-:rq:"
|
class="dropdown-menu"
|
||||||
aria-orientation="vertical"
|
|
||||||
class="dropdown-menu main-menu-content"
|
|
||||||
data-align="start"
|
|
||||||
data-orientation="vertical"
|
|
||||||
data-radix-menu-content=""
|
|
||||||
data-side="bottom"
|
|
||||||
data-state="open"
|
|
||||||
data-testid="dropdown-menu"
|
data-testid="dropdown-menu"
|
||||||
dir="ltr"
|
|
||||||
id="radix-:rr:"
|
|
||||||
role="menu"
|
|
||||||
style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); animation: none;"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="Island dropdown-menu-container"
|
class="Island dropdown-menu-container"
|
||||||
style="--padding: 1; z-index: 2;"
|
style="--padding: 2; z-index: 2;"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
class="radix-menu-item"
|
aria-label="Open"
|
||||||
data-orientation="vertical"
|
class="dropdown-menu-item dropdown-menu-item-base"
|
||||||
data-radix-collection-item=""
|
data-testid="load-button"
|
||||||
role="menuitem"
|
title="Open"
|
||||||
tabindex="-1"
|
type="button"
|
||||||
>
|
>
|
||||||
<button
|
<div
|
||||||
aria-label="Open"
|
class="dropdown-menu-item__icon"
|
||||||
class="excalidraw-button dropdown-menu-item dropdown-menu-item-base"
|
|
||||||
data-testid="load-button"
|
|
||||||
title="Open"
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<div
|
<svg
|
||||||
class="dropdown-menu-item__icon"
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
>
|
>
|
||||||
<svg
|
<path
|
||||||
aria-hidden="true"
|
d="m9.257 6.351.183.183H15.819c.34 0 .727.182 1.051.506.323.323.505.708.505 1.05v5.819c0 .316-.183.7-.52 1.035-.337.338-.723.522-1.037.522H4.182c-.352 0-.74-.181-1.058-.5-.318-.318-.499-.705-.499-1.057V5.182c0-.351.181-.736.5-1.054.32-.321.71-.503 1.057-.503H6.53l2.726 2.726Z"
|
||||||
class=""
|
stroke-width="1.25"
|
||||||
fill="none"
|
/>
|
||||||
focusable="false"
|
</svg>
|
||||||
role="img"
|
</div>
|
||||||
stroke="currentColor"
|
<div
|
||||||
stroke-linecap="round"
|
class="dropdown-menu-item__text"
|
||||||
stroke-linejoin="round"
|
>
|
||||||
viewBox="0 0 20 20"
|
Open
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="dropdown-menu-item__shortcut"
|
||||||
|
>
|
||||||
|
Ctrl+O
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Save to..."
|
||||||
|
class="dropdown-menu-item dropdown-menu-item-base"
|
||||||
|
data-testid="json-export-button"
|
||||||
|
title="Save to..."
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="dropdown-menu-item__icon"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3.333 14.167v1.666c0 .92.747 1.667 1.667 1.667h10c.92 0 1.667-.746 1.667-1.667v-1.666M5.833 9.167 10 13.333l4.167-4.166M10 3.333v10"
|
||||||
|
stroke-width="1.25"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="dropdown-menu-item__text"
|
||||||
|
>
|
||||||
|
Save to...
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Export image..."
|
||||||
|
class="dropdown-menu-item dropdown-menu-item-base"
|
||||||
|
data-testid="image-export-button"
|
||||||
|
title="Export image..."
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="dropdown-menu-item__icon"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
stroke-width="1.25"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="m9.257 6.351.183.183H15.819c.34 0 .727.182 1.051.506.323.323.505.708.505 1.05v5.819c0 .316-.183.7-.52 1.035-.337.338-.723.522-1.037.522H4.182c-.352 0-.74-.181-1.058-.5-.318-.318-.499-.705-.499-1.057V5.182c0-.351.181-.736.5-1.054.32-.321.71-.503 1.057-.503H6.53l2.726 2.726Z"
|
d="M0 0h24v24H0z"
|
||||||
stroke-width="1.25"
|
fill="none"
|
||||||
|
stroke="none"
|
||||||
/>
|
/>
|
||||||
</svg>
|
<path
|
||||||
</div>
|
d="M15 8h.01"
|
||||||
<div
|
/>
|
||||||
class="dropdown-menu-item__text"
|
<path
|
||||||
>
|
d="M12 20h-5a3 3 0 0 1 -3 -3v-10a3 3 0 0 1 3 -3h10a3 3 0 0 1 3 3v5"
|
||||||
Open
|
/>
|
||||||
</div>
|
<path
|
||||||
<div
|
d="M4 15l4 -4c.928 -.893 2.072 -.893 3 0l4 4"
|
||||||
class="dropdown-menu-item__shortcut"
|
/>
|
||||||
>
|
<path
|
||||||
Ctrl+O
|
d="M14 14l1 -1c.617 -.593 1.328 -.793 2.009 -.598"
|
||||||
</div>
|
/>
|
||||||
</button>
|
<path
|
||||||
</div>
|
d="M19 16v6"
|
||||||
<div
|
/>
|
||||||
class="radix-menu-item"
|
<path
|
||||||
data-orientation="vertical"
|
d="M22 19l-3 3l-3 -3"
|
||||||
data-radix-collection-item=""
|
/>
|
||||||
role="menuitem"
|
</g>
|
||||||
tabindex="-1"
|
</svg>
|
||||||
>
|
</div>
|
||||||
<button
|
<div
|
||||||
aria-label="Save to..."
|
class="dropdown-menu-item__text"
|
||||||
class="excalidraw-button dropdown-menu-item dropdown-menu-item-base"
|
|
||||||
data-testid="json-export-button"
|
|
||||||
title="Save to..."
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<div
|
Export image...
|
||||||
class="dropdown-menu-item__icon"
|
</div>
|
||||||
|
<div
|
||||||
|
class="dropdown-menu-item__shortcut"
|
||||||
|
>
|
||||||
|
Ctrl+Shift+E
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Help"
|
||||||
|
class="dropdown-menu-item dropdown-menu-item-base"
|
||||||
|
data-testid="help-menu-item"
|
||||||
|
title="Help"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="dropdown-menu-item__icon"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<svg
|
<g
|
||||||
aria-hidden="true"
|
stroke-width="1.5"
|
||||||
class=""
|
|
||||||
fill="none"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M3.333 14.167v1.666c0 .92.747 1.667 1.667 1.667h10c.92 0 1.667-.746 1.667-1.667v-1.666M5.833 9.167 10 13.333l4.167-4.166M10 3.333v10"
|
d="M0 0h24v24H0z"
|
||||||
stroke-width="1.25"
|
fill="none"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="9"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="12"
|
||||||
|
x2="12"
|
||||||
|
y1="17"
|
||||||
|
y2="17.01"
|
||||||
/>
|
/>
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="dropdown-menu-item__text"
|
|
||||||
>
|
|
||||||
Save to...
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="radix-menu-item"
|
|
||||||
data-orientation="vertical"
|
|
||||||
data-radix-collection-item=""
|
|
||||||
role="menuitem"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
aria-label="Export image..."
|
|
||||||
class="excalidraw-button dropdown-menu-item dropdown-menu-item-base"
|
|
||||||
data-testid="image-export-button"
|
|
||||||
title="Export image..."
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="dropdown-menu-item__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class=""
|
|
||||||
fill="none"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<g
|
|
||||||
stroke-width="1.25"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M0 0h24v24H0z"
|
|
||||||
fill="none"
|
|
||||||
stroke="none"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M15 8h.01"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M12 20h-5a3 3 0 0 1 -3 -3v-10a3 3 0 0 1 3 -3h10a3 3 0 0 1 3 3v5"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M4 15l4 -4c.928 -.893 2.072 -.893 3 0l4 4"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M14 14l1 -1c.617 -.593 1.328 -.793 2.009 -.598"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M19 16v6"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M22 19l-3 3l-3 -3"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="dropdown-menu-item__text"
|
|
||||||
>
|
|
||||||
Export image...
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="dropdown-menu-item__shortcut"
|
|
||||||
>
|
|
||||||
Ctrl+Shift+E
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="radix-menu-item"
|
|
||||||
data-orientation="vertical"
|
|
||||||
data-radix-collection-item=""
|
|
||||||
role="menuitem"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
aria-label="Help"
|
|
||||||
class="excalidraw-button dropdown-menu-item dropdown-menu-item-base"
|
|
||||||
data-testid="help-menu-item"
|
|
||||||
title="Help"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="dropdown-menu-item__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class=""
|
|
||||||
fill="none"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<g
|
|
||||||
stroke-width="1.5"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M0 0h24v24H0z"
|
|
||||||
fill="none"
|
|
||||||
stroke="none"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
cx="12"
|
|
||||||
cy="12"
|
|
||||||
r="9"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
x1="12"
|
|
||||||
x2="12"
|
|
||||||
y1="17"
|
|
||||||
y2="17.01"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M12 13.5a1.5 1.5 0 0 1 1 -1.5a2.6 2.6 0 1 0 -3 -4"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="dropdown-menu-item__text"
|
|
||||||
>
|
|
||||||
Help
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="dropdown-menu-item__shortcut"
|
|
||||||
>
|
|
||||||
?
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="radix-menu-item"
|
|
||||||
data-orientation="vertical"
|
|
||||||
data-radix-collection-item=""
|
|
||||||
role="menuitem"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
aria-label="Reset the canvas"
|
|
||||||
class="excalidraw-button dropdown-menu-item dropdown-menu-item-base"
|
|
||||||
data-testid="clear-canvas-button"
|
|
||||||
title="Reset the canvas"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="dropdown-menu-item__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class=""
|
|
||||||
fill="none"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
d="M3.333 5.833h13.334M8.333 9.167v5M11.667 9.167v5M4.167 5.833l.833 10c0 .92.746 1.667 1.667 1.667h6.666c.92 0 1.667-.746 1.667-1.667l.833-10M7.5 5.833v-2.5c0-.46.373-.833.833-.833h3.334c.46 0 .833.373.833.833v2.5"
|
d="M12 13.5a1.5 1.5 0 0 1 1 -1.5a2.6 2.6 0 1 0 -3 -4"
|
||||||
stroke-width="1.25"
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</g>
|
||||||
</div>
|
</svg>
|
||||||
<div
|
</div>
|
||||||
class="dropdown-menu-item__text"
|
<div
|
||||||
|
class="dropdown-menu-item__text"
|
||||||
|
>
|
||||||
|
Help
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="dropdown-menu-item__shortcut"
|
||||||
|
>
|
||||||
|
?
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Reset the canvas"
|
||||||
|
class="dropdown-menu-item dropdown-menu-item-base"
|
||||||
|
data-testid="clear-canvas-button"
|
||||||
|
title="Reset the canvas"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="dropdown-menu-item__icon"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
>
|
>
|
||||||
Reset the canvas
|
<path
|
||||||
</div>
|
d="M3.333 5.833h13.334M8.333 9.167v5M11.667 9.167v5M4.167 5.833l.833 10c0 .92.746 1.667 1.667 1.667h6.666c.92 0 1.667-.746 1.667-1.667l.833-10M7.5 5.833v-2.5c0-.46.373-.833.833-.833h3.334c.46 0 .833.373.833.833v2.5"
|
||||||
</button>
|
stroke-width="1.25"
|
||||||
</div>
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="dropdown-menu-item__text"
|
||||||
|
>
|
||||||
|
Reset the canvas
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
<div
|
<div
|
||||||
style="height: 1px; margin: .5rem 0px;"
|
style="height: 1px; margin: .5rem 0px;"
|
||||||
/>
|
/>
|
||||||
@@ -553,53 +473,45 @@ exports[`<Excalidraw/> > Test UIOptions prop > Test canvasActions > should rende
|
|||||||
<div
|
<div
|
||||||
style="height: 1px; margin: .5rem 0px;"
|
style="height: 1px; margin: .5rem 0px;"
|
||||||
/>
|
/>
|
||||||
<div
|
<button
|
||||||
class="radix-menu-item"
|
aria-label="Dark mode"
|
||||||
data-orientation="vertical"
|
class="dropdown-menu-item dropdown-menu-item-base"
|
||||||
data-radix-collection-item=""
|
data-testid="toggle-dark-mode"
|
||||||
role="menuitem"
|
title="Dark mode"
|
||||||
tabindex="-1"
|
type="button"
|
||||||
>
|
>
|
||||||
<button
|
<div
|
||||||
aria-label="Dark mode"
|
class="dropdown-menu-item__icon"
|
||||||
class="excalidraw-button dropdown-menu-item dropdown-menu-item-base"
|
|
||||||
data-testid="toggle-dark-mode"
|
|
||||||
title="Dark mode"
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<div
|
<svg
|
||||||
class="dropdown-menu-item__icon"
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
>
|
>
|
||||||
<svg
|
<path
|
||||||
aria-hidden="true"
|
clip-rule="evenodd"
|
||||||
class=""
|
d="M10 2.5h.328a6.25 6.25 0 0 0 6.6 10.372A7.5 7.5 0 1 1 10 2.493V2.5Z"
|
||||||
fill="none"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linecap="round"
|
/>
|
||||||
stroke-linejoin="round"
|
</svg>
|
||||||
viewBox="0 0 20 20"
|
</div>
|
||||||
>
|
<div
|
||||||
<path
|
class="dropdown-menu-item__text"
|
||||||
clip-rule="evenodd"
|
>
|
||||||
d="M10 2.5h.328a6.25 6.25 0 0 0 6.6 10.372A7.5 7.5 0 1 1 10 2.493V2.5Z"
|
Dark mode
|
||||||
stroke="currentColor"
|
</div>
|
||||||
/>
|
<div
|
||||||
</svg>
|
class="dropdown-menu-item__shortcut"
|
||||||
</div>
|
>
|
||||||
<div
|
Shift+Alt+D
|
||||||
class="dropdown-menu-item__text"
|
</div>
|
||||||
>
|
</button>
|
||||||
Dark mode
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="dropdown-menu-item__shortcut"
|
|
||||||
>
|
|
||||||
Shift+Alt+D
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
style="margin-top: 0.5rem;"
|
style="margin-top: 0.5rem;"
|
||||||
>
|
>
|
||||||
@@ -610,7 +522,6 @@ exports[`<Excalidraw/> > Test UIOptions prop > Test canvasActions > should rende
|
|||||||
Canvas background
|
Canvas background
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="canvas-bg-color-picker-container"
|
|
||||||
style="padding: 0px 0.625rem;"
|
style="padding: 0px 0.625rem;"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
@@ -682,7 +593,7 @@ exports[`<Excalidraw/> > Test UIOptions prop > Test canvasActions > should rende
|
|||||||
style="width: 1px; height: 100%; margin: 0px auto;"
|
style="width: 1px; height: 100%; margin: 0px auto;"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
aria-controls="radix-:r12:"
|
aria-controls="radix-:r0:"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-haspopup="dialog"
|
aria-haspopup="dialog"
|
||||||
aria-label="Canvas background"
|
aria-label="Canvas background"
|
||||||
|
|||||||
@@ -215,12 +215,11 @@ export const textWysiwyg = ({
|
|||||||
);
|
);
|
||||||
app.scene.mutateElement(container, { height: targetContainerHeight });
|
app.scene.mutateElement(container, { height: targetContainerHeight });
|
||||||
} else {
|
} else {
|
||||||
const { x, y } = computeBoundTextPosition(
|
const { y } = computeBoundTextPosition(
|
||||||
container,
|
container,
|
||||||
updatedTextElement as ExcalidrawTextElementWithContainer,
|
updatedTextElement as ExcalidrawTextElementWithContainer,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
coordX = x;
|
|
||||||
coordY = y;
|
coordY = y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// import dotenv from 'dotenv';
|
||||||
|
// import path from 'path';
|
||||||
|
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./excalidraw-app/tests/regression",
|
||||||
|
snapshotPathTemplate:
|
||||||
|
"{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{ext}",
|
||||||
|
/* Run tests in files in parallel */
|
||||||
|
fullyParallel: true,
|
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
/* Retry on CI only */
|
||||||
|
retries: process.env.CI ? 2 : 1,
|
||||||
|
/* Opt out of parallel tests on CI. */
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
reporter: "html",
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
actionTimeout: 0,
|
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
baseURL: "http://localhost:3000",
|
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: "on-first-retry",
|
||||||
|
headless: true,
|
||||||
|
},
|
||||||
|
timeout: 1200000,
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium",
|
||||||
|
use: {
|
||||||
|
...devices["Desktop Chrome"],
|
||||||
|
launchOptions: {
|
||||||
|
args: ["--disable-font-subpixel-positioning", "--disable-gpu"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// {
|
||||||
|
// name: "firefox",
|
||||||
|
// use: { ...devices["Desktop Firefox"] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
// {
|
||||||
|
// name: "webkit",
|
||||||
|
// use: { ...devices["Desktop Safari"] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: { ...devices['Pixel 5'] },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: { ...devices['iPhone 12'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */
|
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
webServer: {
|
||||||
|
command: "yarn start --no-open",
|
||||||
|
url: "http://localhost:3000",
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user