mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-11-11 08:14:17 +01:00
Compare commits
1 Commits
mrazator/f
...
dwelle/cli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
348912f32f |
@@ -25,9 +25,6 @@
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/LICENSE">
|
||||
<img alt="Excalidraw is released under the MIT license." src="https://img.shields.io/badge/license-MIT-blue.svg" />
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@excalidraw/excalidraw">
|
||||
<img alt="npm downloads/month" src="https://img.shields.io/npm/dm/@excalidraw/excalidraw" />
|
||||
</a>
|
||||
<a href="https://docs.excalidraw.com/docs/introduction/contributing">
|
||||
<img alt="PRs welcome!" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" />
|
||||
</a>
|
||||
|
||||
@@ -23,7 +23,7 @@ You can pass a `ref` when you want to access some excalidraw APIs. We expose the
|
||||
| ready | `boolean` | This is set to true once Excalidraw is rendered |
|
||||
| [readyPromise](#readypromise) | `function` | This promise will be resolved with the api once excalidraw has rendered. This will be helpful when you want do some action on the host app once this promise resolves. For this to work you will have to pass ref as shown [here](#readypromise) |
|
||||
| [updateScene](#updatescene) | `function` | updates the scene with the sceneData |
|
||||
| [updateLibrary](#updatelibrary) | `function` | updates the the library |
|
||||
| [updateLibrary](#updatelibrary) | `function` | updates the scene with the sceneData |
|
||||
| [addFiles](#addfiles) | `function` | add files data to the appState |
|
||||
| [resetScene](#resetscene) | `function` | Resets the scene. If `resetLoadingState` is passed as true then it will also force set the loading state to false. |
|
||||
| [getSceneElementsIncludingDeleted](#getsceneelementsincludingdeleted) | `function` | Returns all the elements including the deleted in the scene |
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* - DataState refers to full state of the app: appState, elements, images,
|
||||
* though some state is saved separately (collab username, library) for one
|
||||
* reason or another. We also save different data to different storage
|
||||
* reason or another. We also save different data to different sotrage
|
||||
* (localStorage, indexedDB).
|
||||
*/
|
||||
|
||||
|
||||
@@ -131,5 +131,5 @@ export class Debug {
|
||||
};
|
||||
};
|
||||
}
|
||||
//@ts-ignore
|
||||
|
||||
window.debug = Debug;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "6.0.2",
|
||||
"@excalidraw/mermaid-to-excalidraw": "0.1.2",
|
||||
"@excalidraw/laser-pointer": "1.2.0",
|
||||
"@excalidraw/random-username": "1.0.0",
|
||||
"@radix-ui/react-popover": "1.0.3",
|
||||
@@ -126,7 +125,7 @@
|
||||
"test": "yarn test:app",
|
||||
"test:coverage": "vitest --coverage",
|
||||
"test:coverage:watch": "vitest --coverage --watch",
|
||||
"test:ui": "yarn test --ui --coverage.enabled=true",
|
||||
"test:ui": "yarn test --ui",
|
||||
"autorelease": "node scripts/autorelease.js",
|
||||
"prerelease": "node scripts/prerelease.js",
|
||||
"build:preview": "yarn build && vite preview --port 5000",
|
||||
|
||||
@@ -3,10 +3,8 @@ import { register } from "./register";
|
||||
import {
|
||||
copyTextToSystemClipboard,
|
||||
copyToClipboard,
|
||||
createPasteEvent,
|
||||
probablySupportsClipboardBlob,
|
||||
probablySupportsClipboardWriteText,
|
||||
readSystemClipboard,
|
||||
} from "../clipboard";
|
||||
import { actionDeleteSelected } from "./actionDeleteSelected";
|
||||
import { exportCanvas } from "../data/index";
|
||||
@@ -17,7 +15,7 @@ import { isFirefox } from "../constants";
|
||||
export const actionCopy = register({
|
||||
name: "copy",
|
||||
trackEvent: { category: "element" },
|
||||
perform: async (elements, appState, event: ClipboardEvent | null, app) => {
|
||||
perform: async (elements, appState, _, app) => {
|
||||
const elementsToCopy = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
@@ -25,7 +23,7 @@ export const actionCopy = register({
|
||||
});
|
||||
|
||||
try {
|
||||
await copyToClipboard(elementsToCopy, app.files, event);
|
||||
await copyToClipboard(elementsToCopy, app.files);
|
||||
} catch (error: any) {
|
||||
return {
|
||||
commitToHistory: false,
|
||||
@@ -49,50 +47,86 @@ export const actionPaste = register({
|
||||
name: "paste",
|
||||
trackEvent: { category: "element" },
|
||||
perform: async (elements, appState, data, app) => {
|
||||
let types;
|
||||
const MIME_TYPES: Record<string, string> = {};
|
||||
try {
|
||||
types = await readSystemClipboard();
|
||||
try {
|
||||
const clipboardItems = await navigator.clipboard?.read();
|
||||
for (const item of clipboardItems) {
|
||||
for (const type of item.types) {
|
||||
try {
|
||||
const blob = await item.getType(type);
|
||||
MIME_TYPES[type] = await blob.text();
|
||||
} catch (error: any) {
|
||||
console.warn(
|
||||
`Cannot retrieve ${type} from clipboardItem: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(MIME_TYPES).length === 0) {
|
||||
console.warn(
|
||||
"No clipboard data found from clipboard.read(). Falling back to clipboard.readText()",
|
||||
);
|
||||
// throw so we fall back onto clipboard.readText()
|
||||
throw new Error("No clipboard data found");
|
||||
}
|
||||
} catch (error: any) {
|
||||
try {
|
||||
MIME_TYPES["text/plain"] = await navigator.clipboard?.readText();
|
||||
} catch (error: any) {
|
||||
console.warn(`Cannot readText() from clipboard: ${error.message}`);
|
||||
if (isFirefox) {
|
||||
return {
|
||||
commitToHistory: false,
|
||||
appState: {
|
||||
...appState,
|
||||
errorMessage: t("hints.firefox_clipboard_write"),
|
||||
},
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.name === "AbortError" || error.name === "NotAllowedError") {
|
||||
// user probably aborted the action. Though not 100% sure, it's best
|
||||
// to not annoy them with an error message.
|
||||
return false;
|
||||
}
|
||||
|
||||
console.error(`actionPaste ${error.name}: ${error.message}`);
|
||||
|
||||
if (isFirefox) {
|
||||
return {
|
||||
commitToHistory: false,
|
||||
appState: {
|
||||
...appState,
|
||||
errorMessage: t("hints.firefox_clipboard_write"),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
console.error(`actionPaste: ${error.message}`);
|
||||
return {
|
||||
commitToHistory: false,
|
||||
appState: {
|
||||
...appState,
|
||||
errorMessage: t("errors.asyncPasteFailedOnRead"),
|
||||
errorMessage: error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
app.pasteFromClipboard(createPasteEvent({ types }));
|
||||
console.log("actionPaste (1)", { MIME_TYPES });
|
||||
const event = new ClipboardEvent("paste", {
|
||||
clipboardData: new DataTransfer(),
|
||||
});
|
||||
for (const [type, value] of Object.entries(MIME_TYPES)) {
|
||||
try {
|
||||
event.clipboardData?.setData(type, value);
|
||||
} catch (error: any) {
|
||||
console.warn(
|
||||
`Cannot set ${type} as clipboardData item: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
event.clipboardData?.types.forEach((type) => {
|
||||
console.log(
|
||||
`actionPaste (2) event.clipboardData?.getData(${type})`,
|
||||
event.clipboardData?.getData(type),
|
||||
);
|
||||
});
|
||||
app.pasteFromClipboard(event);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return {
|
||||
commitToHistory: false,
|
||||
appState: {
|
||||
...appState,
|
||||
errorMessage: t("errors.asyncPasteFailedOnParse"),
|
||||
errorMessage: error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
commitToHistory: false,
|
||||
};
|
||||
@@ -105,10 +139,13 @@ export const actionPaste = register({
|
||||
export const actionCut = register({
|
||||
name: "cut",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState, event: ClipboardEvent | null, app) => {
|
||||
actionCopy.perform(elements, appState, event, app);
|
||||
perform: (elements, appState, data, app) => {
|
||||
actionCopy.perform(elements, appState, data, app);
|
||||
return actionDeleteSelected.perform(elements, appState);
|
||||
},
|
||||
predicate: (elements, appState, appProps, app) => {
|
||||
return app.device.isMobile && !!navigator.clipboard;
|
||||
},
|
||||
contextItemLabel: "labels.cut",
|
||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.X,
|
||||
});
|
||||
|
||||
@@ -119,10 +119,10 @@ export class ActionManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
executeAction<T extends Action>(
|
||||
action: T,
|
||||
executeAction(
|
||||
action: Action,
|
||||
source: ActionSource = "api",
|
||||
value: Parameters<T["perform"]>[2] = null,
|
||||
value: any = null,
|
||||
) {
|
||||
const elements = this.getElementsIncludingDeleted();
|
||||
const appState = this.getAppState();
|
||||
|
||||
@@ -1,196 +1,22 @@
|
||||
import {
|
||||
createPasteEvent,
|
||||
parseClipboard,
|
||||
serializeAsClipboardJSON,
|
||||
} from "./clipboard";
|
||||
import { API } from "./tests/helpers/api";
|
||||
import { parseClipboard } from "./clipboard";
|
||||
import { createPasteEvent } from "./tests/test-utils";
|
||||
|
||||
describe("parseClipboard()", () => {
|
||||
it("should parse JSON as plaintext if not excalidraw-api/clipboard data", async () => {
|
||||
let text;
|
||||
let clipboardData;
|
||||
// -------------------------------------------------------------------------
|
||||
describe("Test parseClipboard", () => {
|
||||
it("should parse valid json correctly", async () => {
|
||||
let text = "123";
|
||||
|
||||
text = "123";
|
||||
clipboardData = await parseClipboard(
|
||||
createPasteEvent({ types: { "text/plain": text } }),
|
||||
let clipboardData = await parseClipboard(
|
||||
createPasteEvent({ "text/plain": text }),
|
||||
);
|
||||
expect(clipboardData.text).toBe(text);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
expect(clipboardData.text).toBe(text);
|
||||
|
||||
text = "[123]";
|
||||
|
||||
clipboardData = await parseClipboard(
|
||||
createPasteEvent({ types: { "text/plain": text } }),
|
||||
createPasteEvent({ "text/plain": text }),
|
||||
);
|
||||
|
||||
expect(clipboardData.text).toBe(text);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
text = JSON.stringify({ val: 42 });
|
||||
clipboardData = await parseClipboard(
|
||||
createPasteEvent({ types: { "text/plain": text } }),
|
||||
);
|
||||
expect(clipboardData.text).toBe(text);
|
||||
});
|
||||
|
||||
it("should parse valid excalidraw JSON if inside text/plain", async () => {
|
||||
const rect = API.createElement({ type: "rectangle" });
|
||||
|
||||
const json = serializeAsClipboardJSON({ elements: [rect], files: null });
|
||||
const clipboardData = await parseClipboard(
|
||||
createPasteEvent({
|
||||
types: {
|
||||
"text/plain": json,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(clipboardData.elements).toEqual([rect]);
|
||||
});
|
||||
|
||||
it("should parse valid excalidraw JSON if inside text/html", async () => {
|
||||
const rect = API.createElement({ type: "rectangle" });
|
||||
|
||||
let json;
|
||||
let clipboardData;
|
||||
// -------------------------------------------------------------------------
|
||||
json = serializeAsClipboardJSON({ elements: [rect], files: null });
|
||||
clipboardData = await parseClipboard(
|
||||
createPasteEvent({
|
||||
types: {
|
||||
"text/html": json,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(clipboardData.elements).toEqual([rect]);
|
||||
// -------------------------------------------------------------------------
|
||||
json = serializeAsClipboardJSON({ elements: [rect], files: null });
|
||||
clipboardData = await parseClipboard(
|
||||
createPasteEvent({
|
||||
types: {
|
||||
"text/html": `<div> ${json}</div>`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(clipboardData.elements).toEqual([rect]);
|
||||
// -------------------------------------------------------------------------
|
||||
});
|
||||
|
||||
it("should parse <image> `src` urls out of text/html", async () => {
|
||||
let clipboardData;
|
||||
// -------------------------------------------------------------------------
|
||||
clipboardData = await parseClipboard(
|
||||
createPasteEvent({
|
||||
types: {
|
||||
"text/html": `<img src="https://example.com/image.png" />`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(clipboardData.mixedContent).toEqual([
|
||||
{
|
||||
type: "imageUrl",
|
||||
value: "https://example.com/image.png",
|
||||
},
|
||||
]);
|
||||
// -------------------------------------------------------------------------
|
||||
clipboardData = await parseClipboard(
|
||||
createPasteEvent({
|
||||
types: {
|
||||
"text/html": `<div><img src="https://example.com/image.png" /></div><a><img src="https://example.com/image2.png" /></a>`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(clipboardData.mixedContent).toEqual([
|
||||
{
|
||||
type: "imageUrl",
|
||||
value: "https://example.com/image.png",
|
||||
},
|
||||
{
|
||||
type: "imageUrl",
|
||||
value: "https://example.com/image2.png",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should parse text content alongside <image> `src` urls out of text/html", async () => {
|
||||
const clipboardData = await parseClipboard(
|
||||
createPasteEvent({
|
||||
types: {
|
||||
"text/html": `<a href="https://example.com">hello </a><div><img src="https://example.com/image.png" /></div><b>my friend!</b>`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(clipboardData.mixedContent).toEqual([
|
||||
{
|
||||
type: "text",
|
||||
// trimmed
|
||||
value: "hello",
|
||||
},
|
||||
{
|
||||
type: "imageUrl",
|
||||
value: "https://example.com/image.png",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
value: "my friend!",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should parse spreadsheet from either text/plain and text/html", async () => {
|
||||
let clipboardData;
|
||||
// -------------------------------------------------------------------------
|
||||
clipboardData = await parseClipboard(
|
||||
createPasteEvent({
|
||||
types: {
|
||||
"text/plain": `a b
|
||||
1 2
|
||||
4 5
|
||||
7 10`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(clipboardData.spreadsheet).toEqual({
|
||||
title: "b",
|
||||
labels: ["1", "4", "7"],
|
||||
values: [2, 5, 10],
|
||||
});
|
||||
// -------------------------------------------------------------------------
|
||||
clipboardData = await parseClipboard(
|
||||
createPasteEvent({
|
||||
types: {
|
||||
"text/html": `a b
|
||||
1 2
|
||||
4 5
|
||||
7 10`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(clipboardData.spreadsheet).toEqual({
|
||||
title: "b",
|
||||
labels: ["1", "4", "7"],
|
||||
values: [2, 5, 10],
|
||||
});
|
||||
// -------------------------------------------------------------------------
|
||||
clipboardData = await parseClipboard(
|
||||
createPasteEvent({
|
||||
types: {
|
||||
"text/html": `<html>
|
||||
<body>
|
||||
<!--StartFragment--><google-sheets-html-origin><style type="text/css"><!--td {border: 1px solid #cccccc;}br {mso-data-placement:same-cell;}--></style><table xmlns="http://www.w3.org/1999/xhtml" cellspacing="0" cellpadding="0" dir="ltr" border="1" style="table-layout:fixed;font-size:10pt;font-family:Arial;width:0px;border-collapse:collapse;border:none"><colgroup><col width="100"/><col width="100"/></colgroup><tbody><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{"1":2,"2":"a"}">a</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{"1":2,"2":"b"}">b</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{"1":3,"3":1}">1</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{"1":3,"3":2}">2</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{"1":3,"3":4}">4</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{"1":3,"3":5}">5</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{"1":3,"3":7}">7</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{"1":3,"3":10}">10</td></tr></tbody></table><!--EndFragment-->
|
||||
</body>
|
||||
</html>`,
|
||||
"text/plain": `a b
|
||||
1 2
|
||||
4 5
|
||||
7 10`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(clipboardData.spreadsheet).toEqual({
|
||||
title: "b",
|
||||
labels: ["1", "4", "7"],
|
||||
values: [2, 5, 10],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
290
src/clipboard.ts
290
src/clipboard.ts
@@ -3,18 +3,14 @@ import {
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "./element/types";
|
||||
import { BinaryFiles } from "./types";
|
||||
import { SVG_EXPORT_TAG } from "./scene/export";
|
||||
import { tryParseSpreadsheet, Spreadsheet, VALID_SPREADSHEET } from "./charts";
|
||||
import {
|
||||
ALLOWED_PASTE_MIME_TYPES,
|
||||
EXPORT_DATA_TYPES,
|
||||
MIME_TYPES,
|
||||
} from "./constants";
|
||||
import { EXPORT_DATA_TYPES, MIME_TYPES } from "./constants";
|
||||
import { isInitializedImageElement } from "./element/typeChecks";
|
||||
import { deepCopyElement } from "./element/newElement";
|
||||
import { mutateElement } from "./element/mutateElement";
|
||||
import { getContainingFrame } from "./frame";
|
||||
import { isMemberOf, isPromiseLike } from "./utils";
|
||||
import { t } from "./i18n";
|
||||
import { isPromiseLike, isTestEnv } from "./utils";
|
||||
|
||||
type ElementsClipboard = {
|
||||
type: typeof EXPORT_DATA_TYPES.excalidrawClipboard;
|
||||
@@ -34,11 +30,8 @@ export interface ClipboardData {
|
||||
programmaticAPI?: boolean;
|
||||
}
|
||||
|
||||
type AllowedPasteMimeTypes = typeof ALLOWED_PASTE_MIME_TYPES[number];
|
||||
|
||||
type ParsedClipboardEvent =
|
||||
| { type: "text"; value: string }
|
||||
| { type: "mixedContent"; value: PastedMixedContent };
|
||||
let CLIPBOARD = "";
|
||||
let PREFER_APP_CLIPBOARD = false;
|
||||
|
||||
export const probablySupportsClipboardReadText =
|
||||
"clipboard" in navigator && "readText" in navigator.clipboard;
|
||||
@@ -68,61 +61,10 @@ const clipboardContainsElements = (
|
||||
return false;
|
||||
};
|
||||
|
||||
export const createPasteEvent = ({
|
||||
types,
|
||||
files,
|
||||
}: {
|
||||
types?: { [key in AllowedPasteMimeTypes]?: string };
|
||||
files?: File[];
|
||||
}) => {
|
||||
if (!types && !files) {
|
||||
console.warn("createPasteEvent: no types or files provided");
|
||||
}
|
||||
|
||||
const event = new ClipboardEvent("paste", {
|
||||
clipboardData: new DataTransfer(),
|
||||
});
|
||||
|
||||
if (types) {
|
||||
for (const [type, value] of Object.entries(types)) {
|
||||
try {
|
||||
event.clipboardData?.setData(type, value);
|
||||
if (event.clipboardData?.getData(type) !== value) {
|
||||
throw new Error(`Failed to set "${type}" as clipboardData item`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (files) {
|
||||
let idx = -1;
|
||||
for (const file of files) {
|
||||
idx++;
|
||||
try {
|
||||
event.clipboardData?.items.add(file);
|
||||
if (event.clipboardData?.files[idx] !== file) {
|
||||
throw new Error(
|
||||
`Failed to set file "${file.name}" as clipboardData item`,
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return event;
|
||||
};
|
||||
|
||||
export const serializeAsClipboardJSON = ({
|
||||
elements,
|
||||
files,
|
||||
}: {
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
files: BinaryFiles | null;
|
||||
}) => {
|
||||
export const copyToClipboard = async (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
files: BinaryFiles | null,
|
||||
) => {
|
||||
const framesToCopy = new Set(
|
||||
elements.filter((element) => element.type === "frame"),
|
||||
);
|
||||
@@ -144,7 +86,7 @@ export const serializeAsClipboardJSON = ({
|
||||
);
|
||||
}
|
||||
|
||||
// select bound text elements when copying
|
||||
// select binded text elements when copying
|
||||
const contents: ElementsClipboard = {
|
||||
type: EXPORT_DATA_TYPES.excalidrawClipboard,
|
||||
elements: elements.map((element) => {
|
||||
@@ -163,20 +105,34 @@ export const serializeAsClipboardJSON = ({
|
||||
}),
|
||||
files: files ? _files : undefined,
|
||||
};
|
||||
const json = JSON.stringify(contents);
|
||||
|
||||
return JSON.stringify(contents);
|
||||
if (isTestEnv()) {
|
||||
return json;
|
||||
}
|
||||
|
||||
CLIPBOARD = json;
|
||||
|
||||
try {
|
||||
PREFER_APP_CLIPBOARD = false;
|
||||
await copyTextToSystemClipboard(json);
|
||||
} catch (error: any) {
|
||||
PREFER_APP_CLIPBOARD = true;
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const copyToClipboard = async (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
files: BinaryFiles | null,
|
||||
/** supply if available to make the operation more certain to succeed */
|
||||
clipboardEvent?: ClipboardEvent | null,
|
||||
) => {
|
||||
await copyTextToSystemClipboard(
|
||||
serializeAsClipboardJSON({ elements, files }),
|
||||
clipboardEvent,
|
||||
);
|
||||
const getAppClipboard = (): Partial<ElementsClipboard> => {
|
||||
if (!CLIPBOARD) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(CLIPBOARD);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const parsePotentialSpreadsheet = (
|
||||
@@ -210,9 +166,7 @@ function parseHTMLTree(el: ChildNode) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const maybeParseHTMLPaste = (
|
||||
event: ClipboardEvent,
|
||||
): { type: "mixedContent"; value: PastedMixedContent } | null => {
|
||||
const maybeParseHTMLPaste = (event: ClipboardEvent) => {
|
||||
const html = event.clipboardData?.getData("text/html");
|
||||
|
||||
if (!html) {
|
||||
@@ -225,7 +179,7 @@ const maybeParseHTMLPaste = (
|
||||
const content = parseHTMLTree(doc.body);
|
||||
|
||||
if (content.length) {
|
||||
return { type: "mixedContent", value: content };
|
||||
return content;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`error in parseHTMLFromPaste: ${error.message}`);
|
||||
@@ -234,85 +188,21 @@ const maybeParseHTMLPaste = (
|
||||
return null;
|
||||
};
|
||||
|
||||
export const readSystemClipboard = async () => {
|
||||
const types: { [key in AllowedPasteMimeTypes]?: string } = {};
|
||||
|
||||
try {
|
||||
if (navigator.clipboard?.readText) {
|
||||
return { "text/plain": await navigator.clipboard?.readText() };
|
||||
}
|
||||
} catch (error: any) {
|
||||
// @ts-ignore
|
||||
if (navigator.clipboard?.read) {
|
||||
console.warn(
|
||||
`navigator.clipboard.readText() failed (${error.message}). Failling back to navigator.clipboard.read()`,
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
let clipboardItems: ClipboardItems;
|
||||
|
||||
try {
|
||||
clipboardItems = await navigator.clipboard?.read();
|
||||
} catch (error: any) {
|
||||
if (error.name === "DataError") {
|
||||
console.warn(
|
||||
`navigator.clipboard.read() error, clipboard is probably empty: ${error.message}`,
|
||||
);
|
||||
return types;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
for (const item of clipboardItems) {
|
||||
for (const type of item.types) {
|
||||
if (!isMemberOf(ALLOWED_PASTE_MIME_TYPES, type)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
types[type] = await (await item.getType(type)).text();
|
||||
} catch (error: any) {
|
||||
console.warn(
|
||||
`Cannot retrieve ${type} from clipboardItem: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(types).length === 0) {
|
||||
console.warn("No clipboard data found from clipboard.read().");
|
||||
return types;
|
||||
}
|
||||
|
||||
return types;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses "paste" ClipboardEvent.
|
||||
* Retrieves content from system clipboard (either from ClipboardEvent or
|
||||
* via async clipboard API if supported)
|
||||
*/
|
||||
const parseClipboardEvent = async (
|
||||
const getSystemClipboard = async (
|
||||
event: ClipboardEvent,
|
||||
isPlainPaste = false,
|
||||
): Promise<ParsedClipboardEvent> => {
|
||||
): Promise<
|
||||
| { type: "text"; value: string }
|
||||
| { type: "mixedContent"; value: PastedMixedContent }
|
||||
> => {
|
||||
try {
|
||||
const mixedContent = !isPlainPaste && event && maybeParseHTMLPaste(event);
|
||||
|
||||
if (mixedContent) {
|
||||
if (mixedContent.value.every((item) => item.type === "text")) {
|
||||
return {
|
||||
type: "text",
|
||||
value:
|
||||
event.clipboardData?.getData("text/plain") ||
|
||||
mixedContent.value
|
||||
.map((item) => item.value)
|
||||
.join("\n")
|
||||
.trim(),
|
||||
};
|
||||
}
|
||||
|
||||
return mixedContent;
|
||||
return { type: "mixedContent", value: mixedContent };
|
||||
}
|
||||
|
||||
const text = event.clipboardData?.getData("text/plain");
|
||||
@@ -330,29 +220,37 @@ export const parseClipboard = async (
|
||||
event: ClipboardEvent,
|
||||
isPlainPaste = false,
|
||||
): Promise<ClipboardData> => {
|
||||
const parsedEventData = await parseClipboardEvent(event, isPlainPaste);
|
||||
const systemClipboard = await getSystemClipboard(event, isPlainPaste);
|
||||
|
||||
if (parsedEventData.type === "mixedContent") {
|
||||
if (systemClipboard.type === "mixedContent") {
|
||||
return {
|
||||
mixedContent: parsedEventData.value,
|
||||
mixedContent: systemClipboard.value,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// if system clipboard contains spreadsheet, use it even though it's
|
||||
// technically possible it's staler than in-app clipboard
|
||||
const spreadsheetResult =
|
||||
!isPlainPaste && parsePotentialSpreadsheet(parsedEventData.value);
|
||||
|
||||
if (spreadsheetResult) {
|
||||
return spreadsheetResult;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
// if system clipboard empty, couldn't be resolved, or contains previously
|
||||
// copied excalidraw scene as SVG, fall back to previously copied excalidraw
|
||||
// elements
|
||||
if (
|
||||
!systemClipboard ||
|
||||
(!isPlainPaste && systemClipboard.value.includes(SVG_EXPORT_TAG))
|
||||
) {
|
||||
return getAppClipboard();
|
||||
}
|
||||
|
||||
// if system clipboard contains spreadsheet, use it even though it's
|
||||
// technically possible it's staler than in-app clipboard
|
||||
const spreadsheetResult =
|
||||
!isPlainPaste && parsePotentialSpreadsheet(systemClipboard.value);
|
||||
|
||||
if (spreadsheetResult) {
|
||||
return spreadsheetResult;
|
||||
}
|
||||
|
||||
const appClipboardData = getAppClipboard();
|
||||
|
||||
try {
|
||||
const systemClipboardData = JSON.parse(parsedEventData.value);
|
||||
const systemClipboardData = JSON.parse(systemClipboard.value);
|
||||
const programmaticAPI =
|
||||
systemClipboardData.type === EXPORT_DATA_TYPES.excalidrawClipboardWithAPI;
|
||||
if (clipboardContainsElements(systemClipboardData)) {
|
||||
@@ -365,9 +263,18 @@ export const parseClipboard = async (
|
||||
programmaticAPI,
|
||||
};
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return { text: parsedEventData.value };
|
||||
} catch (e) {}
|
||||
// system clipboard doesn't contain excalidraw elements → return plaintext
|
||||
// unless we set a flag to prefer in-app clipboard because browser didn't
|
||||
// support storing to system clipboard on copy
|
||||
return PREFER_APP_CLIPBOARD && appClipboardData.elements
|
||||
? {
|
||||
...appClipboardData,
|
||||
text: isPlainPaste
|
||||
? JSON.stringify(appClipboardData.elements, null, 2)
|
||||
: undefined,
|
||||
}
|
||||
: { text: systemClipboard.value };
|
||||
};
|
||||
|
||||
export const copyBlobToClipboardAsPng = async (blob: Blob | Promise<Blob>) => {
|
||||
@@ -400,49 +307,28 @@ export const copyBlobToClipboardAsPng = async (blob: Blob | Promise<Blob>) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const copyTextToSystemClipboard = async (
|
||||
text: string | null,
|
||||
clipboardEvent?: ClipboardEvent | null,
|
||||
) => {
|
||||
// (1) first try using Async Clipboard API
|
||||
export const copyTextToSystemClipboard = async (text: string | null) => {
|
||||
let copied = false;
|
||||
if (probablySupportsClipboardWriteText) {
|
||||
try {
|
||||
// NOTE: doesn't work on FF on non-HTTPS domains, or when document
|
||||
// not focused
|
||||
await navigator.clipboard.writeText(text || "");
|
||||
return;
|
||||
copied = true;
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// (2) if fails and we have access to ClipboardEvent, use plain old setData()
|
||||
try {
|
||||
if (clipboardEvent) {
|
||||
clipboardEvent.clipboardData?.setData("text/plain", text || "");
|
||||
if (clipboardEvent.clipboardData?.getData("text/plain") !== text) {
|
||||
throw new Error("Failed to setData on clipboardEvent");
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
// (3) if that fails, use document.execCommand
|
||||
if (!copyTextViaExecCommand(text)) {
|
||||
throw new Error(t("errors.copyToSystemClipboardFailed"));
|
||||
// Note that execCommand doesn't allow copying empty strings, so if we're
|
||||
// clearing clipboard using this API, we must copy at least an empty char
|
||||
if (!copied && !copyTextViaExecCommand(text || " ")) {
|
||||
throw new Error("couldn't copy");
|
||||
}
|
||||
};
|
||||
|
||||
// adapted from https://github.com/zenorocha/clipboard.js/blob/ce79f170aa655c408b6aab33c9472e8e4fa52e19/src/clipboard-action.js#L48
|
||||
const copyTextViaExecCommand = (text: string | null) => {
|
||||
// execCommand doesn't allow copying empty strings, so if we're
|
||||
// clearing clipboard using this API, we must copy at least an empty char
|
||||
if (!text) {
|
||||
text = " ";
|
||||
}
|
||||
|
||||
const copyTextViaExecCommand = (text: string) => {
|
||||
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
|
||||
|
||||
const textarea = document.createElement("textarea");
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
EmbedIcon,
|
||||
extraToolsIcon,
|
||||
frameToolIcon,
|
||||
mermaidLogoIcon,
|
||||
laserPointerToolIcon,
|
||||
} from "./icons";
|
||||
import { KEYS } from "../keys";
|
||||
@@ -224,6 +223,7 @@ export const ShapesSwitcher = ({
|
||||
app: AppClassProperties;
|
||||
}) => {
|
||||
const [isExtraToolsMenuOpen, setIsExtraToolsMenuOpen] = useState(false);
|
||||
const device = useDevice();
|
||||
|
||||
const frameToolSelected = activeTool.type === "frame";
|
||||
const laserToolSelected = activeTool.type === "laser";
|
||||
@@ -273,63 +273,111 @@ export const ShapesSwitcher = ({
|
||||
);
|
||||
})}
|
||||
<div className="App-toolbar__divider" />
|
||||
|
||||
<DropdownMenu open={isExtraToolsMenuOpen}>
|
||||
<DropdownMenu.Trigger
|
||||
className={clsx("App-toolbar__extra-tools-trigger", {
|
||||
"App-toolbar__extra-tools-trigger--selected":
|
||||
frameToolSelected ||
|
||||
embeddableToolSelected ||
|
||||
// in collab we're already highlighting the laser button
|
||||
// outside toolbar, so let's not highlight extra-tools button
|
||||
// on top of it
|
||||
(laserToolSelected && !app.props.isCollaborating),
|
||||
})}
|
||||
onToggle={() => setIsExtraToolsMenuOpen(!isExtraToolsMenuOpen)}
|
||||
title={t("toolBar.extraTools")}
|
||||
>
|
||||
{extraToolsIcon}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
onClickOutside={() => setIsExtraToolsMenuOpen(false)}
|
||||
onSelect={() => setIsExtraToolsMenuOpen(false)}
|
||||
className="App-toolbar__extra-tools-dropdown"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => app.setActiveTool({ type: "frame" })}
|
||||
{/* TEMP HACK because dropdown doesn't work well inside mobile toolbar */}
|
||||
{device.isMobile ? (
|
||||
<>
|
||||
<ToolButton
|
||||
className={clsx("Shape", { fillable: false })}
|
||||
type="radio"
|
||||
icon={frameToolIcon}
|
||||
shortcut={KEYS.F.toLocaleUpperCase()}
|
||||
data-testid="toolbar-frame"
|
||||
selected={frameToolSelected}
|
||||
>
|
||||
{t("toolBar.frame")}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => app.setActiveTool({ type: "embeddable" })}
|
||||
checked={activeTool.type === "frame"}
|
||||
name="editor-current-shape"
|
||||
title={`${capitalizeString(
|
||||
t("toolBar.frame"),
|
||||
)} — ${KEYS.F.toLocaleUpperCase()}`}
|
||||
keyBindingLabel={KEYS.F.toLocaleUpperCase()}
|
||||
aria-label={capitalizeString(t("toolBar.frame"))}
|
||||
aria-keyshortcuts={KEYS.F.toLocaleUpperCase()}
|
||||
data-testid={`toolbar-frame`}
|
||||
onPointerDown={({ pointerType }) => {
|
||||
if (!appState.penDetected && pointerType === "pen") {
|
||||
app.togglePenMode(true);
|
||||
}
|
||||
}}
|
||||
onChange={({ pointerType }) => {
|
||||
trackEvent("toolbar", "frame", "ui");
|
||||
app.setActiveTool({ type: "frame" });
|
||||
}}
|
||||
selected={activeTool.type === "frame"}
|
||||
/>
|
||||
<ToolButton
|
||||
className={clsx("Shape", { fillable: false })}
|
||||
type="radio"
|
||||
icon={EmbedIcon}
|
||||
data-testid="toolbar-embeddable"
|
||||
selected={embeddableToolSelected}
|
||||
checked={activeTool.type === "embeddable"}
|
||||
name="editor-current-shape"
|
||||
title={capitalizeString(t("toolBar.embeddable"))}
|
||||
aria-label={capitalizeString(t("toolBar.embeddable"))}
|
||||
data-testid={`toolbar-embeddable`}
|
||||
onPointerDown={({ pointerType }) => {
|
||||
if (!appState.penDetected && pointerType === "pen") {
|
||||
app.togglePenMode(true);
|
||||
}
|
||||
}}
|
||||
onChange={({ pointerType }) => {
|
||||
trackEvent("toolbar", "embeddable", "ui");
|
||||
app.setActiveTool({ type: "embeddable" });
|
||||
}}
|
||||
selected={activeTool.type === "embeddable"}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<DropdownMenu open={isExtraToolsMenuOpen}>
|
||||
<DropdownMenu.Trigger
|
||||
className={clsx("App-toolbar__extra-tools-trigger", {
|
||||
"App-toolbar__extra-tools-trigger--selected":
|
||||
frameToolSelected ||
|
||||
embeddableToolSelected ||
|
||||
// in collab we're already highlighting the laser button
|
||||
// outside toolbar, so let's not highlight extra-tools button
|
||||
// on top of it
|
||||
(laserToolSelected && !app.props.isCollaborating),
|
||||
})}
|
||||
onToggle={() => setIsExtraToolsMenuOpen(!isExtraToolsMenuOpen)}
|
||||
title={t("toolBar.extraTools")}
|
||||
>
|
||||
{t("toolBar.embeddable")}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => app.setActiveTool({ type: "laser" })}
|
||||
icon={laserPointerToolIcon}
|
||||
data-testid="toolbar-laser"
|
||||
selected={laserToolSelected}
|
||||
shortcut={KEYS.K.toLocaleUpperCase()}
|
||||
{extraToolsIcon}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
onClickOutside={() => setIsExtraToolsMenuOpen(false)}
|
||||
onSelect={() => setIsExtraToolsMenuOpen(false)}
|
||||
className="App-toolbar__extra-tools-dropdown"
|
||||
>
|
||||
{t("toolBar.laser")}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => app.setOpenDialog("mermaid")}
|
||||
icon={mermaidLogoIcon}
|
||||
data-testid="toolbar-embeddable"
|
||||
>
|
||||
{t("toolBar.mermaidToExcalidraw")}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => {
|
||||
app.setActiveTool({ type: "frame" });
|
||||
}}
|
||||
icon={frameToolIcon}
|
||||
shortcut={KEYS.F.toLocaleUpperCase()}
|
||||
data-testid="toolbar-frame"
|
||||
selected={frameToolSelected}
|
||||
>
|
||||
{t("toolBar.frame")}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => {
|
||||
app.setActiveTool({ type: "embeddable" });
|
||||
}}
|
||||
icon={EmbedIcon}
|
||||
data-testid="toolbar-embeddable"
|
||||
selected={embeddableToolSelected}
|
||||
>
|
||||
{t("toolBar.embeddable")}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => {
|
||||
app.setActiveTool({ type: "laser" });
|
||||
}}
|
||||
icon={laserPointerToolIcon}
|
||||
data-testid="toolbar-laser"
|
||||
selected={laserToolSelected}
|
||||
shortcut={KEYS.K.toLocaleUpperCase()}
|
||||
>
|
||||
{t("toolBar.laser")}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -366,7 +366,6 @@ import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
|
||||
import { StaticCanvas, InteractiveCanvas } from "./canvases";
|
||||
import { Renderer } from "../scene/Renderer";
|
||||
import { ShapeCache } from "../scene/ShapeCache";
|
||||
import MermaidToExcalidraw from "./MermaidToExcalidraw";
|
||||
import { LaserToolOverlay } from "./LaserTool/LaserTool";
|
||||
import { LaserPathManager } from "./LaserTool/LaserPathManager";
|
||||
import {
|
||||
@@ -1246,11 +1245,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
isCollaborating={this.props.isCollaborating}
|
||||
>
|
||||
{this.props.children}
|
||||
{this.state.openDialog === "mermaid" && (
|
||||
<MermaidToExcalidraw />
|
||||
)}
|
||||
</LayerUI>
|
||||
|
||||
<div className="excalidraw-textEditorContainer" />
|
||||
<div className="excalidraw-contextMenuContainer" />
|
||||
<div className="excalidraw-eye-dropper-container" />
|
||||
@@ -1280,10 +1275,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
top={this.state.contextMenu.top}
|
||||
left={this.state.contextMenu.left}
|
||||
actionManager={this.actionManager}
|
||||
onClose={(callback) => {
|
||||
onClose={(cb) => {
|
||||
this.setState({ contextMenu: null }, () => {
|
||||
this.focusContainer();
|
||||
callback?.();
|
||||
cb?.();
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -2121,7 +2116,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (!isExcalidrawActive || isWritableElement(event.target)) {
|
||||
return;
|
||||
}
|
||||
this.actionManager.executeAction(actionCut, "keyboard", event);
|
||||
this.cutAll();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
@@ -2133,11 +2128,19 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (!isExcalidrawActive || isWritableElement(event.target)) {
|
||||
return;
|
||||
}
|
||||
this.actionManager.executeAction(actionCopy, "keyboard", event);
|
||||
this.copyAll();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
private cutAll = () => {
|
||||
this.actionManager.executeAction(actionCut, "keyboard");
|
||||
};
|
||||
|
||||
private copyAll = () => {
|
||||
this.actionManager.executeAction(actionCopy, "keyboard");
|
||||
};
|
||||
|
||||
private static resetTapTwice() {
|
||||
didTapTwice = false;
|
||||
}
|
||||
@@ -2199,13 +2202,20 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
public pasteFromClipboard = withBatchedUpdates(
|
||||
async (event: ClipboardEvent) => {
|
||||
const isPlainPaste = !!IS_PLAIN_PASTE;
|
||||
const isPlainPaste = !!(IS_PLAIN_PASTE && event);
|
||||
|
||||
console.warn(
|
||||
"pasteFromClipboard",
|
||||
event?.clipboardData?.types,
|
||||
event?.clipboardData?.getData("text/plain"),
|
||||
);
|
||||
|
||||
// #686
|
||||
const target = document.activeElement;
|
||||
const isExcalidrawActive =
|
||||
this.excalidrawContainerRef.current?.contains(target);
|
||||
if (event && !isExcalidrawActive) {
|
||||
console.log("exit (1)");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2218,6 +2228,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
(!(elementUnderCursor instanceof HTMLCanvasElement) ||
|
||||
isWritableElement(target))
|
||||
) {
|
||||
console.log("exit (2)");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2329,12 +2340,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
);
|
||||
|
||||
addElementsFromPasteOrLibrary = (opts: {
|
||||
private addElementsFromPasteOrLibrary = (opts: {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
files: BinaryFiles | null;
|
||||
position: { clientX: number; clientY: number } | "cursor" | "center";
|
||||
retainSeed?: boolean;
|
||||
fitToContent?: boolean;
|
||||
}) => {
|
||||
const elements = restoreElements(opts.elements, null, undefined);
|
||||
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
||||
@@ -2439,12 +2449,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
);
|
||||
this.setActiveTool({ type: "selection" });
|
||||
|
||||
if (opts.fitToContent) {
|
||||
this.scrollToContent(newElements, {
|
||||
fitToContent: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// TODO rewrite this to paste both text & images at the same time if
|
||||
@@ -3320,10 +3324,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
});
|
||||
};
|
||||
|
||||
setOpenDialog = (dialogType: AppState["openDialog"]) => {
|
||||
this.setState({ openDialog: dialogType });
|
||||
};
|
||||
|
||||
private setCursor = (cursor: string) => {
|
||||
setCursor(this.interactiveCanvas, cursor);
|
||||
};
|
||||
@@ -4274,7 +4274,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
);
|
||||
|
||||
this.hitLinkElement = this.getElementLinkAtPosition(
|
||||
scenePointer,
|
||||
hitElement,
|
||||
|
||||
@@ -21,7 +21,7 @@ type ContextMenuProps = {
|
||||
items: ContextMenuItems;
|
||||
top: number;
|
||||
left: number;
|
||||
onClose: (callback?: () => void) => void;
|
||||
onClose: (cb?: () => void) => void;
|
||||
};
|
||||
|
||||
export const CONTEXT_MENU_SEPARATOR = "separator";
|
||||
@@ -50,9 +50,7 @@ export const ContextMenu = React.memo(
|
||||
|
||||
return (
|
||||
<Popover
|
||||
onCloseRequest={() => {
|
||||
onClose();
|
||||
}}
|
||||
onCloseRequest={() => onClose()}
|
||||
top={top}
|
||||
left={left}
|
||||
fitInViewport={true}
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
@import "../css/variables.module";
|
||||
|
||||
$verticalBreakpoint: 860px;
|
||||
|
||||
.excalidraw {
|
||||
.dialog-mermaid {
|
||||
&-title {
|
||||
margin-bottom: 5px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
&-desc {
|
||||
font-size: 15px;
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.Modal__content .Island {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@at-root .excalidraw:not(.excalidraw--mobile)#{&} {
|
||||
padding: 1.25rem;
|
||||
|
||||
.Modal__content {
|
||||
height: 100%;
|
||||
max-height: 750px;
|
||||
|
||||
@media screen and (max-width: $verticalBreakpoint) {
|
||||
height: auto;
|
||||
// When vertical, we want the height to span whole viewport.
|
||||
// This is also important for the children not to overflow the
|
||||
// modal/viewport (for some reason).
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.Island {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
|
||||
.Dialog__content {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-mermaid-body {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr auto;
|
||||
height: 100%;
|
||||
column-gap: 4rem;
|
||||
|
||||
@media screen and (max-width: $verticalBreakpoint) {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-mermaid-panels {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
justify-content: space-between;
|
||||
gap: 4rem;
|
||||
|
||||
grid-row: 1;
|
||||
grid-column: 1 / 3;
|
||||
|
||||
@media screen and (max-width: $verticalBreakpoint) {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
margin-left: 4px;
|
||||
|
||||
@media screen and (max-width: $verticalBreakpoint) {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
textarea {
|
||||
width: 20rem;
|
||||
height: 100%;
|
||||
resize: none;
|
||||
border-radius: var(--border-radius-lg);
|
||||
border: 1px solid var(--dialog-border-color);
|
||||
white-space: pre-wrap;
|
||||
padding: 0.85rem;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
font-family: monospace;
|
||||
|
||||
@media screen and (max-width: $verticalBreakpoint) {
|
||||
width: auto;
|
||||
height: 10rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-preview-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.85rem;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
// acts as min-height
|
||||
height: 200px;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
|
||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==")
|
||||
left center;
|
||||
border-radius: var(--border-radius-lg);
|
||||
border: 1px solid var(--dialog-border-color);
|
||||
|
||||
@media screen and (max-width: $verticalBreakpoint) {
|
||||
// acts as min-height
|
||||
height: 400px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
canvas {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-preview-canvas-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mermaid-error {
|
||||
color: red;
|
||||
font-weight: 800;
|
||||
font-size: 30px;
|
||||
word-break: break-word;
|
||||
overflow: auto;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
|
||||
p {
|
||||
font-weight: 500;
|
||||
font-family: Cascadia;
|
||||
text-align: left;
|
||||
white-space: pre-wrap;
|
||||
font-size: 0.875rem;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-mermaid-buttons {
|
||||
grid-column: 2;
|
||||
|
||||
.dialog-mermaid-insert {
|
||||
&.excalidraw-button {
|
||||
font-family: "Assistant";
|
||||
font-weight: 600;
|
||||
height: 2.5rem;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.3em;
|
||||
width: 7.5rem;
|
||||
font-size: 12px;
|
||||
color: $oc-white;
|
||||
background-color: var(--color-primary);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-darker);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-primary-darkest);
|
||||
}
|
||||
|
||||
@media screen and (max-width: $verticalBreakpoint) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@at-root .excalidraw.theme--dark#{&} {
|
||||
color: var(--color-gray-100);
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
padding-left: 0.5rem;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
import { useState, useRef, useEffect, useDeferredValue } from "react";
|
||||
import { BinaryFiles } from "../types";
|
||||
import { useApp } from "./App";
|
||||
import { Button } from "./Button";
|
||||
import { Dialog } from "./Dialog";
|
||||
import { DEFAULT_EXPORT_PADDING, DEFAULT_FONT_SIZE } from "../constants";
|
||||
import {
|
||||
convertToExcalidrawElements,
|
||||
exportToCanvas,
|
||||
} from "../packages/excalidraw/index";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { canvasToBlob } from "../data/blob";
|
||||
import { ArrowRightIcon } from "./icons";
|
||||
import Spinner from "./Spinner";
|
||||
import "./MermaidToExcalidraw.scss";
|
||||
|
||||
import { MermaidToExcalidrawResult } from "@excalidraw/mermaid-to-excalidraw/dist/interfaces";
|
||||
import type { MermaidOptions } from "@excalidraw/mermaid-to-excalidraw";
|
||||
import { t } from "../i18n";
|
||||
import Trans from "./Trans";
|
||||
|
||||
const LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW = "mermaid-to-excalidraw";
|
||||
const MERMAID_EXAMPLE =
|
||||
"flowchart TD\n A[Christmas] -->|Get money| B(Go shopping)\n B --> C{Let me think}\n C -->|One| D[Laptop]\n C -->|Two| E[iPhone]\n C -->|Three| F[Car]";
|
||||
|
||||
const saveMermaidDataToStorage = (data: string) => {
|
||||
try {
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW, data);
|
||||
} catch (error: any) {
|
||||
// Unable to access window.localStorage
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const importMermaidDataFromStorage = () => {
|
||||
try {
|
||||
const data = localStorage.getItem(LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW);
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Unable to access localStorage
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const ErrorComp = ({ error }: { error: string }) => {
|
||||
return (
|
||||
<div data-testid="mermaid-error" className="mermaid-error">
|
||||
Error! <p>{error}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MermaidToExcalidraw = () => {
|
||||
const [mermaidToExcalidrawLib, setMermaidToExcalidrawLib] = useState<{
|
||||
loaded: boolean;
|
||||
api: {
|
||||
parseMermaidToExcalidraw: (
|
||||
defination: string,
|
||||
options: MermaidOptions,
|
||||
) => Promise<MermaidToExcalidrawResult>;
|
||||
} | null;
|
||||
}>({ loaded: false, api: null });
|
||||
|
||||
const [text, setText] = useState("");
|
||||
const deferredText = useDeferredValue(text.trim());
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const canvasRef = useRef<HTMLDivElement>(null);
|
||||
const data = useRef<{
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
files: BinaryFiles | null;
|
||||
}>({ elements: [], files: null });
|
||||
|
||||
const app = useApp();
|
||||
|
||||
const resetPreview = () => {
|
||||
const canvasNode = canvasRef.current;
|
||||
|
||||
if (!canvasNode) {
|
||||
return;
|
||||
}
|
||||
const parent = canvasNode.parentElement;
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
parent.style.background = "";
|
||||
setError(null);
|
||||
canvasNode.replaceChildren();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadMermaidToExcalidrawLib = async () => {
|
||||
const api = await import(
|
||||
/* webpackChunkName:"mermaid-to-excalidraw" */ "@excalidraw/mermaid-to-excalidraw"
|
||||
);
|
||||
setMermaidToExcalidrawLib({ loaded: true, api });
|
||||
};
|
||||
loadMermaidToExcalidrawLib();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const data = importMermaidDataFromStorage() || MERMAID_EXAMPLE;
|
||||
setText(data);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const renderExcalidrawPreview = async () => {
|
||||
const canvasNode = canvasRef.current;
|
||||
const parent = canvasNode?.parentElement;
|
||||
if (
|
||||
!mermaidToExcalidrawLib.loaded ||
|
||||
!canvasNode ||
|
||||
!parent ||
|
||||
!mermaidToExcalidrawLib.api
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!deferredText) {
|
||||
resetPreview();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { elements, files } =
|
||||
await mermaidToExcalidrawLib.api.parseMermaidToExcalidraw(
|
||||
deferredText,
|
||||
{
|
||||
fontSize: DEFAULT_FONT_SIZE,
|
||||
},
|
||||
);
|
||||
setError(null);
|
||||
|
||||
data.current = {
|
||||
elements: convertToExcalidrawElements(elements, {
|
||||
regenerateIds: true,
|
||||
}),
|
||||
files,
|
||||
};
|
||||
|
||||
const canvas = await exportToCanvas({
|
||||
elements: data.current.elements,
|
||||
files: data.current.files,
|
||||
exportPadding: DEFAULT_EXPORT_PADDING,
|
||||
maxWidthOrHeight:
|
||||
Math.max(parent.offsetWidth, parent.offsetHeight) *
|
||||
window.devicePixelRatio,
|
||||
});
|
||||
// if converting to blob fails, there's some problem that will
|
||||
// likely prevent preview and export (e.g. canvas too big)
|
||||
await canvasToBlob(canvas);
|
||||
parent.style.background = "var(--default-bg-color)";
|
||||
canvasNode.replaceChildren(canvas);
|
||||
} catch (e: any) {
|
||||
parent.style.background = "var(--default-bg-color)";
|
||||
if (deferredText) {
|
||||
setError(e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
renderExcalidrawPreview();
|
||||
}, [deferredText, mermaidToExcalidrawLib]);
|
||||
|
||||
const onClose = () => {
|
||||
app.setOpenDialog(null);
|
||||
saveMermaidDataToStorage(text);
|
||||
};
|
||||
|
||||
const onSelect = () => {
|
||||
const { elements: newElements, files } = data.current;
|
||||
app.addElementsFromPasteOrLibrary({
|
||||
elements: newElements,
|
||||
files,
|
||||
position: "center",
|
||||
fitToContent: true,
|
||||
});
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
className="dialog-mermaid"
|
||||
onCloseRequest={onClose}
|
||||
size={1200}
|
||||
title={
|
||||
<>
|
||||
<p className="dialog-mermaid-title">{t("mermaid.title")}</p>
|
||||
<span className="dialog-mermaid-desc">
|
||||
<Trans
|
||||
i18nKey="mermaid.description"
|
||||
flowchartLink={(el) => (
|
||||
<a href="https://mermaid.js.org/syntax/flowchart.html">{el}</a>
|
||||
)}
|
||||
sequenceLink={(el) => (
|
||||
<a href="https://mermaid.js.org/syntax/sequenceDiagram.html">
|
||||
{el}
|
||||
</a>
|
||||
)}
|
||||
/>
|
||||
<br />
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="dialog-mermaid-body">
|
||||
<div className="dialog-mermaid-panels">
|
||||
<div className="dialog-mermaid-panels-text">
|
||||
<label>{t("mermaid.syntax")}</label>
|
||||
|
||||
<textarea
|
||||
onChange={(event) => setText(event.target.value)}
|
||||
value={text}
|
||||
/>
|
||||
</div>
|
||||
<div className="dialog-mermaid-panels-preview">
|
||||
<label>{t("mermaid.preview")}</label>
|
||||
<div className="dialog-mermaid-panels-preview-wrapper">
|
||||
{error && <ErrorComp error={error} />}
|
||||
{mermaidToExcalidrawLib.loaded ? (
|
||||
<div
|
||||
ref={canvasRef}
|
||||
style={{ opacity: error ? "0.15" : 1 }}
|
||||
className="dialog-mermaid-panels-preview-canvas-container"
|
||||
/>
|
||||
) : (
|
||||
<Spinner size="2rem" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dialog-mermaid-buttons">
|
||||
<Button className="dialog-mermaid-insert" onSelect={onSelect}>
|
||||
{t("mermaid.button")}
|
||||
<span>{ArrowRightIcon}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export default MermaidToExcalidraw;
|
||||
@@ -160,15 +160,6 @@
|
||||
width: var(--lg-button-size);
|
||||
height: var(--lg-button-size);
|
||||
|
||||
@media screen and (max-width: 450px) {
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
}
|
||||
@media screen and (max-width: 379px) {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: var(--lg-icon-size);
|
||||
height: var(--lg-icon-size);
|
||||
|
||||
@@ -16,10 +16,6 @@
|
||||
align-self: center;
|
||||
background-color: var(--default-border-color);
|
||||
margin: 0 0.25rem;
|
||||
|
||||
@include isMobile {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +41,5 @@
|
||||
margin-top: 0.375rem;
|
||||
right: 0;
|
||||
min-width: 11.875rem;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
margin-top: 0.25rem;
|
||||
|
||||
&--mobile {
|
||||
bottom: 55px;
|
||||
top: auto;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
row-gap: 0.75rem;
|
||||
|
||||
@@ -1654,22 +1654,6 @@ export const frameToolIcon = createIcon(
|
||||
tablerIconProps,
|
||||
);
|
||||
|
||||
export const mermaidLogoIcon = createIcon(
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M407.48,111.18C335.587,108.103 269.573,152.338 245.08,220C220.587,152.338 154.573,108.103 82.68,111.18C80.285,168.229 107.577,222.632 154.74,254.82C178.908,271.419 193.35,298.951 193.27,328.27L193.27,379.13L296.9,379.13L296.9,328.27C296.816,298.953 311.255,271.42 335.42,254.82C382.596,222.644 409.892,168.233 407.48,111.18Z"
|
||||
/>,
|
||||
);
|
||||
|
||||
export const ArrowRightIcon = createIcon(
|
||||
<g strokeWidth="1.25">
|
||||
<path d="M4.16602 10H15.8327" />
|
||||
<path d="M12.5 13.3333L15.8333 10" />
|
||||
<path d="M12.5 6.66666L15.8333 9.99999" />
|
||||
</g>,
|
||||
modifiedTablerIconProps,
|
||||
);
|
||||
|
||||
export const laserPointerToolIcon = createIcon(
|
||||
<g
|
||||
fill="none"
|
||||
|
||||
@@ -148,8 +148,6 @@ export const IMAGE_MIME_TYPES = {
|
||||
jfif: "image/jfif",
|
||||
} as const;
|
||||
|
||||
export const ALLOWED_PASTE_MIME_TYPES = ["text/plain", "text/html"] as const;
|
||||
|
||||
export const MIME_TYPES = {
|
||||
json: "application/json",
|
||||
// excalidraw data
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
.buttonList label:focus-within,
|
||||
input:focus-visible {
|
||||
outline: transparent;
|
||||
box-shadow: 0 0 0 1px var(--color-brand-hover);
|
||||
box-shadow: 0 0 0 2px var(--focus-highlight-color);
|
||||
}
|
||||
|
||||
.buttonList {
|
||||
@@ -280,11 +280,6 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
|
||||
.dropdown-menu--mobile {
|
||||
bottom: 55px;
|
||||
top: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.App-mobile-menu {
|
||||
@@ -542,13 +537,13 @@
|
||||
|
||||
&:not(:focus) {
|
||||
&:hover {
|
||||
border-color: var(--color-brand-hover);
|
||||
background-color: var(--input-hover-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-brand-hover);
|
||||
box-shadow: 0 0 0 2px var(--focus-highlight-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,8 +592,6 @@
|
||||
background-color: var(--island-bg-color);
|
||||
|
||||
.ToolIcon__icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@@ -608,8 +601,8 @@
|
||||
}
|
||||
|
||||
.App-toolbar--mobile {
|
||||
overflow: visible;
|
||||
max-width: 98vw;
|
||||
overflow-x: auto;
|
||||
max-width: 90vw;
|
||||
|
||||
.ToolIcon__keybinding {
|
||||
display: none;
|
||||
|
||||
@@ -6,11 +6,11 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"backgroundColor": "#d8f5a2",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id45",
|
||||
"id": "id41",
|
||||
"type": "arrow",
|
||||
},
|
||||
{
|
||||
"id": "id46",
|
||||
"id": "id42",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
@@ -45,7 +45,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id46",
|
||||
"id": "id42",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
@@ -110,7 +110,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id47",
|
||||
"elementId": "id43",
|
||||
"focus": -0.08139534883720931,
|
||||
"gap": 1,
|
||||
},
|
||||
@@ -186,7 +186,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id45",
|
||||
"id": "id41",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
@@ -222,7 +222,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"baseline": 0,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id48",
|
||||
"id": "id44",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
@@ -266,7 +266,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"baseline": 0,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id48",
|
||||
"id": "id44",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
@@ -309,7 +309,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id49",
|
||||
"id": "id45",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
@@ -367,7 +367,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"backgroundColor": "transparent",
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id48",
|
||||
"containerId": "id44",
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@@ -401,6 +401,173 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
`;
|
||||
|
||||
exports[`Test Transform > Test arrow bindings > should bind arrows to shapes when start / end provided without ids 1`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id34",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id36",
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 0,
|
||||
"id": Any<String>,
|
||||
"isDeleted": false,
|
||||
"lastCommittedPoint": null,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"points": [
|
||||
[
|
||||
0.5,
|
||||
0,
|
||||
],
|
||||
[
|
||||
99.5,
|
||||
0,
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id35",
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 3,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 255,
|
||||
"y": 239,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test Transform > Test arrow bindings > should bind arrows to shapes when start / end provided without ids 2`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id33",
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 25,
|
||||
"id": Any<String>,
|
||||
"isDeleted": false,
|
||||
"lineHeight": 1.25,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"originalText": "HELLO WORLD!!",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"text": "HELLO WORLD!!",
|
||||
"textAlign": "center",
|
||||
"type": "text",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": Any<Number>,
|
||||
"verticalAlign": "middle",
|
||||
"width": 130,
|
||||
"x": 240,
|
||||
"y": 226.5,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test Transform > Test arrow bindings > should bind arrows to shapes when start / end provided without ids 3`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id33",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 100,
|
||||
"id": Any<String>,
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 155,
|
||||
"y": 189,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test Transform > Test arrow bindings > should bind arrows to shapes when start / end provided without ids 4`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id33",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 100,
|
||||
"id": Any<String>,
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "ellipse",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 355,
|
||||
"y": 189,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test Transform > Test arrow bindings > should bind arrows to text when start / end provided without ids 1`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
@@ -458,180 +625,13 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test Transform > Test arrow bindings > should bind arrows to shapes when start / end provided without ids 2`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id37",
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 25,
|
||||
"id": Any<String>,
|
||||
"isDeleted": false,
|
||||
"lineHeight": 1.25,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"originalText": "HELLO WORLD!!",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"text": "HELLO WORLD!!",
|
||||
"textAlign": "center",
|
||||
"type": "text",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": Any<Number>,
|
||||
"verticalAlign": "middle",
|
||||
"width": 130,
|
||||
"x": 240,
|
||||
"y": 226.5,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test Transform > Test arrow bindings > should bind arrows to shapes when start / end provided without ids 3`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id37",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 100,
|
||||
"id": Any<String>,
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 155,
|
||||
"y": 189,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test Transform > Test arrow bindings > should bind arrows to shapes when start / end provided without ids 4`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id37",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 100,
|
||||
"id": Any<String>,
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "ellipse",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 355,
|
||||
"y": 189,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test Transform > Test arrow bindings > should bind arrows to text when start / end provided without ids 1`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id42",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id44",
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 0,
|
||||
"id": Any<String>,
|
||||
"isDeleted": false,
|
||||
"lastCommittedPoint": null,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"points": [
|
||||
[
|
||||
0.5,
|
||||
0,
|
||||
],
|
||||
[
|
||||
99.5,
|
||||
0,
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id43",
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 3,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 255,
|
||||
"y": 239,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Test Transform > Test arrow bindings > should bind arrows to text when start / end provided without ids 2`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id41",
|
||||
"containerId": "id37",
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@@ -671,7 +671,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
"baseline": 0,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id41",
|
||||
"id": "id37",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
@@ -715,7 +715,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
"baseline": 0,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id41",
|
||||
"id": "id37",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
@@ -1335,7 +1335,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 0,
|
||||
"height": 130,
|
||||
"id": Any<String>,
|
||||
"isDeleted": false,
|
||||
"lastCommittedPoint": null,
|
||||
@@ -1362,7 +1362,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 100,
|
||||
@@ -1385,7 +1385,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 0,
|
||||
"height": 130,
|
||||
"id": Any<String>,
|
||||
"isDeleted": false,
|
||||
"lastCommittedPoint": null,
|
||||
@@ -1412,7 +1412,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 100,
|
||||
|
||||
@@ -309,90 +309,6 @@ describe("Test Transform", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test Frames", () => {
|
||||
it("should transform frames and update frame ids when regenerated", () => {
|
||||
const elementsSkeleton: ExcalidrawElementSkeleton[] = [
|
||||
{
|
||||
type: "rectangle",
|
||||
x: 10,
|
||||
y: 10,
|
||||
strokeWidth: 2,
|
||||
id: "1",
|
||||
},
|
||||
{
|
||||
type: "diamond",
|
||||
x: 120,
|
||||
y: 20,
|
||||
backgroundColor: "#fff3bf",
|
||||
strokeWidth: 2,
|
||||
label: {
|
||||
text: "HELLO EXCALIDRAW",
|
||||
strokeColor: "#099268",
|
||||
fontSize: 30,
|
||||
},
|
||||
id: "2",
|
||||
},
|
||||
{
|
||||
type: "frame",
|
||||
children: ["1", "2"],
|
||||
name: "My frame",
|
||||
},
|
||||
];
|
||||
const excaldrawElements = convertToExcalidrawElements(
|
||||
elementsSkeleton,
|
||||
opts,
|
||||
);
|
||||
expect(excaldrawElements.length).toBe(4);
|
||||
|
||||
excaldrawElements.forEach((ele) => {
|
||||
expect(ele).toMatchObject({
|
||||
seed: expect.any(Number),
|
||||
versionNonce: expect.any(Number),
|
||||
id: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should consider max of calculated and frame dimensions when provided", () => {
|
||||
const elementsSkeleton: ExcalidrawElementSkeleton[] = [
|
||||
{
|
||||
type: "rectangle",
|
||||
x: 10,
|
||||
y: 10,
|
||||
strokeWidth: 2,
|
||||
id: "1",
|
||||
},
|
||||
{
|
||||
type: "diamond",
|
||||
x: 120,
|
||||
y: 20,
|
||||
backgroundColor: "#fff3bf",
|
||||
strokeWidth: 2,
|
||||
label: {
|
||||
text: "HELLO EXCALIDRAW",
|
||||
strokeColor: "#099268",
|
||||
fontSize: 30,
|
||||
},
|
||||
id: "2",
|
||||
},
|
||||
{
|
||||
type: "frame",
|
||||
children: ["1", "2"],
|
||||
name: "My frame",
|
||||
width: 800,
|
||||
height: 100,
|
||||
},
|
||||
];
|
||||
const excaldrawElements = convertToExcalidrawElements(
|
||||
elementsSkeleton,
|
||||
opts,
|
||||
);
|
||||
const frame = excaldrawElements.find((ele) => ele.type === "frame")!;
|
||||
expect(frame.width).toBe(800);
|
||||
expect(frame.height).toBe(126);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test arrow bindings", () => {
|
||||
it("should bind arrows to shapes when start / end provided without ids", () => {
|
||||
const elements = [
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
VERTICAL_ALIGN,
|
||||
} from "../constants";
|
||||
import {
|
||||
getCommonBounds,
|
||||
newElement,
|
||||
newLinearElement,
|
||||
redrawTextBoundingBox,
|
||||
@@ -13,7 +12,6 @@ import {
|
||||
import { bindLinearElement } from "../element/binding";
|
||||
import {
|
||||
ElementConstructorOpts,
|
||||
newFrameElement,
|
||||
newImageElement,
|
||||
newTextElement,
|
||||
} from "../element/newElement";
|
||||
@@ -137,7 +135,9 @@ export type ValidContainer =
|
||||
export type ExcalidrawElementSkeleton =
|
||||
| Extract<
|
||||
Exclude<ExcalidrawElement, ExcalidrawSelectionElement>,
|
||||
ExcalidrawEmbeddableElement | ExcalidrawFreeDrawElement
|
||||
| ExcalidrawEmbeddableElement
|
||||
| ExcalidrawFreeDrawElement
|
||||
| ExcalidrawFrameElement
|
||||
>
|
||||
| ({
|
||||
type: Extract<ExcalidrawLinearElement["type"], "line">;
|
||||
@@ -158,12 +158,7 @@ export type ExcalidrawElementSkeleton =
|
||||
x: number;
|
||||
y: number;
|
||||
fileId: FileId;
|
||||
} & Partial<ExcalidrawImageElement>)
|
||||
| ({
|
||||
type: "frame";
|
||||
children: readonly ExcalidrawElement["id"][];
|
||||
name?: string;
|
||||
} & Partial<ExcalidrawFrameElement>);
|
||||
} & Partial<ExcalidrawImageElement>);
|
||||
|
||||
const DEFAULT_LINEAR_ELEMENT_PROPS = {
|
||||
width: 100,
|
||||
@@ -442,6 +437,7 @@ export const convertToExcalidrawElements = (
|
||||
const elements: ExcalidrawElementSkeleton[] = JSON.parse(
|
||||
JSON.stringify(elementsSkeleton),
|
||||
);
|
||||
|
||||
const elementStore = new ElementStore();
|
||||
const elementsWithIds = new Map<string, ExcalidrawElementSkeleton>();
|
||||
const oldToNewElementIdMap = new Map<string, string>();
|
||||
@@ -540,15 +536,8 @@ export const convertToExcalidrawElements = (
|
||||
|
||||
break;
|
||||
}
|
||||
case "frame": {
|
||||
excalidrawElement = newFrameElement({
|
||||
x: 0,
|
||||
y: 0,
|
||||
...element,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "freedraw":
|
||||
case "frame":
|
||||
case "embeddable": {
|
||||
excalidrawElement = element;
|
||||
break;
|
||||
@@ -652,60 +641,5 @@ export const convertToExcalidrawElements = (
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Once all the excalidraw elements are created, we can add frames since we
|
||||
// need to calculate coordinates and dimensions of frame which is possibe after all
|
||||
// frame children are processed.
|
||||
for (const [id, element] of elementsWithIds) {
|
||||
if (element.type !== "frame") {
|
||||
continue;
|
||||
}
|
||||
const frame = elementStore.getElement(id);
|
||||
|
||||
if (!frame) {
|
||||
throw new Error(`Excalidraw element with id ${id} doesn't exist`);
|
||||
}
|
||||
const childrenElements: ExcalidrawElement[] = [];
|
||||
|
||||
element.children.forEach((id) => {
|
||||
const newElementId = oldToNewElementIdMap.get(id);
|
||||
if (!newElementId) {
|
||||
throw new Error(`Element with ${id} wasn't mapped correctly`);
|
||||
}
|
||||
|
||||
const elementInFrame = elementStore.getElement(newElementId);
|
||||
if (!elementInFrame) {
|
||||
throw new Error(`Frame element with id ${newElementId} doesn't exist`);
|
||||
}
|
||||
Object.assign(elementInFrame, { frameId: frame.id });
|
||||
|
||||
elementInFrame?.boundElements?.forEach((boundElement) => {
|
||||
const ele = elementStore.getElement(boundElement.id);
|
||||
if (!ele) {
|
||||
throw new Error(
|
||||
`Bound element with id ${boundElement.id} doesn't exist`,
|
||||
);
|
||||
}
|
||||
Object.assign(ele, { frameId: frame.id });
|
||||
childrenElements.push(ele);
|
||||
});
|
||||
|
||||
childrenElements.push(elementInFrame);
|
||||
});
|
||||
|
||||
let [minX, minY, maxX, maxY] = getCommonBounds(childrenElements);
|
||||
|
||||
const PADDING = 10;
|
||||
minX = minX - PADDING;
|
||||
minY = minY - PADDING;
|
||||
maxX = maxX + PADDING;
|
||||
maxY = maxY + PADDING;
|
||||
|
||||
// Take the max of calculated and provided frame dimensions, whichever is higher
|
||||
const width = Math.max(frame?.width, maxX - minX);
|
||||
const height = Math.max(frame?.height, maxY - minY);
|
||||
Object.assign(frame, { x: minX, y: minY, width, height });
|
||||
}
|
||||
|
||||
return elementStore.getElements();
|
||||
};
|
||||
|
||||
@@ -144,15 +144,13 @@ export const newEmbeddableElement = (
|
||||
};
|
||||
|
||||
export const newFrameElement = (
|
||||
opts: {
|
||||
name?: string;
|
||||
} & ElementConstructorOpts,
|
||||
opts: ElementConstructorOpts,
|
||||
): NonDeleted<ExcalidrawFrameElement> => {
|
||||
const frameElement = newElementWith(
|
||||
{
|
||||
..._newElementBase<ExcalidrawFrameElement>("frame", opts),
|
||||
type: "frame",
|
||||
name: opts?.name || null,
|
||||
name: null,
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
@@ -91,7 +91,7 @@ export const redrawTextBoundingBox = (
|
||||
);
|
||||
const maxContainerWidth = getBoundTextMaxWidth(container);
|
||||
|
||||
if (!isArrowElement(container) && metrics.height > maxContainerHeight) {
|
||||
if (metrics.height > maxContainerHeight) {
|
||||
const nextHeight = computeContainerDimensionForBoundText(
|
||||
metrics.height,
|
||||
container.type,
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { API } from "../tests/helpers/api";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import { getOriginalContainerHeightFromCache } from "./textWysiwyg";
|
||||
import { getTextEditor, updateTextEditor } from "../tests/queries/dom";
|
||||
import { getTextEditor } from "../tests/queries/dom";
|
||||
|
||||
// Unmount ReactDOM from root
|
||||
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
||||
@@ -26,7 +26,10 @@ ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
||||
const tab = " ";
|
||||
const mouse = new Pointer("mouse");
|
||||
|
||||
const textEditorSelector = ".excalidraw-textEditorContainer > textarea";
|
||||
const updateTextEditor = (editor: HTMLTextAreaElement, value: string) => {
|
||||
fireEvent.change(editor, { target: { value } });
|
||||
editor.dispatchEvent(new Event("input"));
|
||||
};
|
||||
|
||||
describe("textWysiwyg", () => {
|
||||
describe("start text editing", () => {
|
||||
@@ -192,7 +195,7 @@ describe("textWysiwyg", () => {
|
||||
|
||||
mouse.clickAt(text.x + 50, text.y + 50);
|
||||
|
||||
const editor = await getTextEditor(textEditorSelector, false);
|
||||
const editor = await getTextEditor(false);
|
||||
|
||||
expect(editor).not.toBe(null);
|
||||
expect(h.state.editingElement?.id).toBe(text.id);
|
||||
@@ -214,26 +217,12 @@ describe("textWysiwyg", () => {
|
||||
|
||||
mouse.doubleClickAt(text.x + 50, text.y + 50);
|
||||
|
||||
const editor = await getTextEditor(textEditorSelector, false);
|
||||
const editor = await getTextEditor(false);
|
||||
|
||||
expect(editor).not.toBe(null);
|
||||
expect(h.state.editingElement?.id).toBe(text.id);
|
||||
expect(h.elements.length).toBe(1);
|
||||
});
|
||||
|
||||
// FIXME too flaky. No one knows why.
|
||||
it.skip("should bump the version of a labeled arrow when the label is updated", async () => {
|
||||
const arrow = UI.createElement("arrow", {
|
||||
width: 300,
|
||||
height: 0,
|
||||
});
|
||||
await UI.editText(arrow, "Hello");
|
||||
const { version } = arrow;
|
||||
|
||||
await UI.editText(arrow, "Hello\nworld!");
|
||||
|
||||
expect(arrow.version).toEqual(version + 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test container-unbound text", () => {
|
||||
@@ -255,7 +244,7 @@ describe("textWysiwyg", () => {
|
||||
textElement = UI.createElement("text");
|
||||
|
||||
mouse.clickOn(textElement);
|
||||
textarea = await getTextEditor(textEditorSelector, true);
|
||||
textarea = await getTextEditor(true);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@@ -465,7 +454,7 @@ describe("textWysiwyg", () => {
|
||||
UI.clickTool("text");
|
||||
mouse.clickAt(750, 300);
|
||||
|
||||
textarea = await getTextEditor(textEditorSelector, true);
|
||||
textarea = await getTextEditor(true);
|
||||
updateTextEditor(
|
||||
textarea,
|
||||
"Excalidraw is an opensource virtual collaborative whiteboard for sketching hand-drawn like diagrams!",
|
||||
@@ -517,7 +506,7 @@ describe("textWysiwyg", () => {
|
||||
{ id: text.id, type: "text" },
|
||||
]);
|
||||
mouse.down();
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
|
||||
updateTextEditor(editor, "Hello World!");
|
||||
|
||||
@@ -545,7 +534,7 @@ describe("textWysiwyg", () => {
|
||||
]);
|
||||
expect(text.angle).toBe(rectangle.angle);
|
||||
mouse.down();
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
|
||||
updateTextEditor(editor, "Hello World!");
|
||||
|
||||
@@ -572,7 +561,7 @@ describe("textWysiwyg", () => {
|
||||
API.setSelectedElements([diamond]);
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
const value = new Array(1000).fill("1").join("\n");
|
||||
@@ -607,7 +596,7 @@ describe("textWysiwyg", () => {
|
||||
expect(text.type).toBe("text");
|
||||
expect(text.containerId).toBe(null);
|
||||
mouse.down();
|
||||
let editor = await getTextEditor(textEditorSelector, true);
|
||||
let editor = await getTextEditor(true);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
editor.blur();
|
||||
|
||||
@@ -622,7 +611,7 @@ describe("textWysiwyg", () => {
|
||||
expect(text.containerId).toBe(rectangle.id);
|
||||
|
||||
mouse.down();
|
||||
editor = await getTextEditor(textEditorSelector, true);
|
||||
editor = await getTextEditor(true);
|
||||
|
||||
updateTextEditor(editor, "Hello World!");
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
@@ -644,7 +633,7 @@ describe("textWysiwyg", () => {
|
||||
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||
expect(text.type).toBe("text");
|
||||
expect(text.containerId).toBe(rectangle.id);
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
@@ -679,7 +668,7 @@ describe("textWysiwyg", () => {
|
||||
{ id: text.id, type: "text" },
|
||||
]);
|
||||
mouse.down();
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
updateTextEditor(editor, "Hello World!");
|
||||
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
@@ -704,7 +693,7 @@ describe("textWysiwyg", () => {
|
||||
freedraw.y + freedraw.height / 2,
|
||||
);
|
||||
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
updateTextEditor(editor, "Hello World!");
|
||||
fireEvent.keyDown(editor, { key: KEYS.ESCAPE });
|
||||
|
||||
@@ -738,7 +727,7 @@ describe("textWysiwyg", () => {
|
||||
expect(text.type).toBe("text");
|
||||
expect(text.containerId).toBe(null);
|
||||
mouse.down();
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
|
||||
updateTextEditor(editor, "Hello World!");
|
||||
|
||||
@@ -753,7 +742,7 @@ describe("textWysiwyg", () => {
|
||||
|
||||
UI.clickTool("text");
|
||||
mouse.clickAt(20, 30);
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
|
||||
updateTextEditor(
|
||||
editor,
|
||||
@@ -798,7 +787,7 @@ describe("textWysiwyg", () => {
|
||||
mouse.down();
|
||||
|
||||
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||
let editor = await getTextEditor(textEditorSelector, true);
|
||||
let editor = await getTextEditor(true);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
updateTextEditor(editor, "Hello World!");
|
||||
@@ -811,7 +800,7 @@ describe("textWysiwyg", () => {
|
||||
rectangle.y + rectangle.height / 2,
|
||||
);
|
||||
mouse.down();
|
||||
editor = await getTextEditor(textEditorSelector, true);
|
||||
editor = await getTextEditor(true);
|
||||
|
||||
editor.select();
|
||||
fireEvent.click(screen.getByTitle(/code/i));
|
||||
@@ -844,7 +833,7 @@ describe("textWysiwyg", () => {
|
||||
|
||||
Keyboard.keyDown(KEYS.ENTER);
|
||||
let text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||
let editor = await getTextEditor(textEditorSelector, true);
|
||||
let editor = await getTextEditor(true);
|
||||
|
||||
updateTextEditor(editor, "Hello World!");
|
||||
|
||||
@@ -865,7 +854,7 @@ describe("textWysiwyg", () => {
|
||||
mouse.select(rectangle);
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
|
||||
editor = await getTextEditor(textEditorSelector, true);
|
||||
editor = await getTextEditor(true);
|
||||
updateTextEditor(editor, "Hello");
|
||||
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
@@ -894,7 +883,7 @@ describe("textWysiwyg", () => {
|
||||
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||
expect(text.containerId).toBe(rectangle.id);
|
||||
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
@@ -931,7 +920,7 @@ describe("textWysiwyg", () => {
|
||||
// Bind first text
|
||||
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||
expect(text.containerId).toBe(rectangle.id);
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
updateTextEditor(editor, "Hello World!");
|
||||
editor.blur();
|
||||
@@ -952,7 +941,7 @@ describe("textWysiwyg", () => {
|
||||
it("should respect text alignment when resizing", async () => {
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
|
||||
let editor = await getTextEditor(textEditorSelector, true);
|
||||
let editor = await getTextEditor(true);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
updateTextEditor(editor, "Hello");
|
||||
editor.blur();
|
||||
@@ -969,7 +958,7 @@ describe("textWysiwyg", () => {
|
||||
mouse.select(rectangle);
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
|
||||
editor = await getTextEditor(textEditorSelector, true);
|
||||
editor = await getTextEditor(true);
|
||||
|
||||
editor.select();
|
||||
|
||||
@@ -992,7 +981,7 @@ describe("textWysiwyg", () => {
|
||||
|
||||
mouse.select(rectangle);
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
editor = await getTextEditor(textEditorSelector, true);
|
||||
editor = await getTextEditor(true);
|
||||
|
||||
editor.select();
|
||||
|
||||
@@ -1030,7 +1019,7 @@ describe("textWysiwyg", () => {
|
||||
expect(text.type).toBe("text");
|
||||
expect(text.containerId).toBe(rectangle.id);
|
||||
mouse.down();
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
|
||||
updateTextEditor(editor, "Hello World!");
|
||||
|
||||
@@ -1045,7 +1034,7 @@ describe("textWysiwyg", () => {
|
||||
it("should scale font size correctly when resizing using shift", async () => {
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
updateTextEditor(editor, "Hello");
|
||||
editor.blur();
|
||||
@@ -1065,7 +1054,7 @@ describe("textWysiwyg", () => {
|
||||
it("should bind text correctly when container duplicated with alt-drag", async () => {
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
updateTextEditor(editor, "Hello");
|
||||
editor.blur();
|
||||
@@ -1097,7 +1086,7 @@ describe("textWysiwyg", () => {
|
||||
|
||||
it("undo should work", async () => {
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
updateTextEditor(editor, "Hello");
|
||||
editor.blur();
|
||||
@@ -1134,7 +1123,7 @@ describe("textWysiwyg", () => {
|
||||
|
||||
it("should not allow bound text with only whitespaces", async () => {
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
updateTextEditor(editor, " ");
|
||||
@@ -1189,7 +1178,7 @@ describe("textWysiwyg", () => {
|
||||
it("should reset the container height cache when resizing", async () => {
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
|
||||
let editor = await getTextEditor(textEditorSelector, true);
|
||||
let editor = await getTextEditor(true);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
updateTextEditor(editor, "Hello");
|
||||
editor.blur();
|
||||
@@ -1201,7 +1190,7 @@ describe("textWysiwyg", () => {
|
||||
mouse.select(rectangle);
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
|
||||
editor = await getTextEditor(textEditorSelector, true);
|
||||
editor = await getTextEditor(true);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
editor.blur();
|
||||
@@ -1217,7 +1206,7 @@ describe("textWysiwyg", () => {
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
|
||||
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
updateTextEditor(editor, "Hello World!");
|
||||
editor.blur();
|
||||
|
||||
@@ -1242,7 +1231,7 @@ describe("textWysiwyg", () => {
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
|
||||
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
updateTextEditor(editor, "Hello World!");
|
||||
editor.blur();
|
||||
expect(
|
||||
@@ -1274,12 +1263,12 @@ describe("textWysiwyg", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
editor = await getTextEditor(textEditorSelector, true);
|
||||
editor = await getTextEditor(true);
|
||||
updateTextEditor(editor, "Hello");
|
||||
editor.blur();
|
||||
mouse.select(rectangle);
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
editor = await getTextEditor(textEditorSelector, true);
|
||||
editor = await getTextEditor(true);
|
||||
editor.select();
|
||||
});
|
||||
|
||||
@@ -1390,7 +1379,7 @@ describe("textWysiwyg", () => {
|
||||
it("should wrap text in a container when wrap text in container triggered from context menu", async () => {
|
||||
UI.clickTool("text");
|
||||
mouse.clickAt(20, 30);
|
||||
const editor = await getTextEditor(textEditorSelector, true);
|
||||
const editor = await getTextEditor(true);
|
||||
|
||||
updateTextEditor(
|
||||
editor,
|
||||
@@ -1478,7 +1467,7 @@ describe("textWysiwyg", () => {
|
||||
// Bind first text
|
||||
let text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||
expect(text.containerId).toBe(rectangle.id);
|
||||
let editor = await getTextEditor(textEditorSelector, true);
|
||||
let editor = await getTextEditor(true);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
updateTextEditor(editor, "Hello!");
|
||||
expect(
|
||||
@@ -1503,7 +1492,7 @@ describe("textWysiwyg", () => {
|
||||
rectangle.x + rectangle.width / 2,
|
||||
rectangle.y + rectangle.height / 2,
|
||||
);
|
||||
editor = await getTextEditor(textEditorSelector, true);
|
||||
editor = await getTextEditor(true);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
updateTextEditor(editor, "Excalidraw");
|
||||
editor.blur();
|
||||
@@ -1517,4 +1506,18 @@ describe("textWysiwyg", () => {
|
||||
expect(text.text).toBe("Excalidraw");
|
||||
});
|
||||
});
|
||||
|
||||
it("should bump the version of a labeled arrow when the label is updated", async () => {
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
const arrow = UI.createElement("arrow", {
|
||||
width: 300,
|
||||
height: 0,
|
||||
});
|
||||
await UI.editText(arrow, "Hello");
|
||||
const { version } = arrow;
|
||||
|
||||
await UI.editText(arrow, "Hello\nworld!");
|
||||
|
||||
expect(arrow.version).toEqual(version + 1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,13 +17,9 @@ export const useCreatePortalContainer = (opts?: {
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (div) {
|
||||
div.className = "";
|
||||
div.classList.add("excalidraw", ...(opts?.className?.split(/\s+/) || []));
|
||||
div.classList.toggle("excalidraw--mobile", device.isMobile);
|
||||
div.classList.toggle("excalidraw--mobile", isMobileRef.current);
|
||||
div.classList.toggle("theme--dark", theme === "dark");
|
||||
}
|
||||
}, [div, theme, device.isMobile, opts?.className]);
|
||||
}, [div, device.isMobile]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const container = opts?.parentSelector
|
||||
@@ -36,6 +32,10 @@ export const useCreatePortalContainer = (opts?: {
|
||||
|
||||
const div = document.createElement("div");
|
||||
|
||||
div.classList.add("excalidraw", ...(opts?.className?.split(/\s+/) || []));
|
||||
div.classList.toggle("excalidraw--mobile", isMobileRef.current);
|
||||
div.classList.toggle("theme--dark", theme === "dark");
|
||||
|
||||
container.appendChild(div);
|
||||
|
||||
setDiv(div);
|
||||
@@ -43,7 +43,7 @@ export const useCreatePortalContainer = (opts?: {
|
||||
return () => {
|
||||
container.removeChild(div);
|
||||
};
|
||||
}, [excalidrawContainer, opts?.parentSelector]);
|
||||
}, [excalidrawContainer, theme, opts?.className, opts?.parentSelector]);
|
||||
|
||||
return div;
|
||||
};
|
||||
|
||||
@@ -218,10 +218,7 @@
|
||||
"libraryElementTypeError": {
|
||||
"embeddable": "Embeddable elements cannot be added to the library.",
|
||||
"image": "Support for adding images to the library coming soon!"
|
||||
},
|
||||
"asyncPasteFailedOnRead": "Couldn't paste (couldn't read from system clipboard).",
|
||||
"asyncPasteFailedOnParse": "Couldn't paste.",
|
||||
"copyToSystemClipboardFailed": "Couldn't copy to clipboard."
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Selection",
|
||||
@@ -242,8 +239,7 @@
|
||||
"embeddable": "Web Embed",
|
||||
"laser": "Laser pointer",
|
||||
"hand": "Hand (panning tool)",
|
||||
"extraTools": "More tools",
|
||||
"mermaidToExcalidraw": "Mermaid to Excalidraw"
|
||||
"extraTools": "More tools"
|
||||
},
|
||||
"headings": {
|
||||
"canvasActions": "Canvas actions",
|
||||
@@ -502,12 +498,5 @@
|
||||
"description": "Loading external drawing will <bold>replace your existing content</bold>.<br></br>You can back up your drawing first by using one of the options below."
|
||||
}
|
||||
}
|
||||
},
|
||||
"mermaid": {
|
||||
"title": "Mermaid to Excalidraw",
|
||||
"button": "Insert",
|
||||
"description": "Currently only <flowchartLink>Flowcharts</flowchartLink> and <sequenceLink>Sequence Diagrams</sequenceLink> are supported. The other types will be rendered as image in Excalidraw.",
|
||||
"syntax": "Mermaid Syntax",
|
||||
"preview": "Preview"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"path": "dist/excalidraw.production.min.js",
|
||||
"limit": "325 kB"
|
||||
"limit": "320 kB"
|
||||
},
|
||||
{
|
||||
"path": "dist/excalidraw-assets/locales",
|
||||
@@ -11,6 +11,6 @@
|
||||
{
|
||||
"path": "dist/excalidraw-assets/vendor-*.js",
|
||||
"name": "dist/excalidraw-assets/vendor*.js",
|
||||
"limit": "900 kB"
|
||||
"limit": "30 kB"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -7,7 +7,6 @@ const elements: ExcalidrawElementSkeleton[] = [
|
||||
x: 10,
|
||||
y: 10,
|
||||
strokeWidth: 2,
|
||||
id: "1",
|
||||
},
|
||||
{
|
||||
type: "diamond",
|
||||
@@ -20,7 +19,6 @@ const elements: ExcalidrawElementSkeleton[] = [
|
||||
strokeColor: "#099268",
|
||||
fontSize: 30,
|
||||
},
|
||||
id: "2",
|
||||
},
|
||||
{
|
||||
type: "arrow",
|
||||
@@ -38,11 +36,6 @@ const elements: ExcalidrawElementSkeleton[] = [
|
||||
height: 230,
|
||||
fileId: "rocket" as FileId,
|
||||
},
|
||||
{
|
||||
type: "frame",
|
||||
children: ["1", "2"],
|
||||
name: "My frame",
|
||||
},
|
||||
];
|
||||
export default {
|
||||
elements,
|
||||
|
||||
@@ -41,14 +41,6 @@ module.exports = {
|
||||
"sass-loader",
|
||||
],
|
||||
},
|
||||
// So that type module works with webpack
|
||||
// https://github.com/webpack/webpack/issues/11467#issuecomment-691873586
|
||||
{
|
||||
test: /\.m?js/,
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(ts|tsx|js|jsx|mjs)$/,
|
||||
exclude:
|
||||
|
||||
@@ -44,14 +44,6 @@ module.exports = {
|
||||
"sass-loader",
|
||||
],
|
||||
},
|
||||
// So that type module works with webpack
|
||||
// https://github.com/webpack/webpack/issues/11467#issuecomment-691873586
|
||||
{
|
||||
test: /\.m?js/,
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(ts|tsx|js|jsx|mjs)$/,
|
||||
exclude:
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "../element/image";
|
||||
import Scene from "./Scene";
|
||||
|
||||
const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
|
||||
export const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
|
||||
|
||||
export const exportToCanvas = async (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
|
||||
@@ -3,9 +3,6 @@ import "vitest-canvas-mock";
|
||||
import "@testing-library/jest-dom";
|
||||
import { vi } from "vitest";
|
||||
import polyfill from "./polyfill";
|
||||
import { testPolyfills } from "./tests/helpers/polyfills";
|
||||
|
||||
Object.assign(globalThis, testPolyfills);
|
||||
|
||||
require("fake-indexeddb/auto");
|
||||
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
import { act, fireEvent, render } from "./test-utils";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import React from "react";
|
||||
import { expect, vi } from "vitest";
|
||||
import * as MermaidToExcalidraw from "@excalidraw/mermaid-to-excalidraw";
|
||||
import { getTextEditor, updateTextEditor } from "./queries/dom";
|
||||
|
||||
vi.mock("@excalidraw/mermaid-to-excalidraw", async (importActual) => {
|
||||
const module = (await importActual()) as any;
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...module,
|
||||
};
|
||||
});
|
||||
const parseMermaidToExcalidrawSpy = vi.spyOn(
|
||||
MermaidToExcalidraw,
|
||||
"parseMermaidToExcalidraw",
|
||||
);
|
||||
|
||||
parseMermaidToExcalidrawSpy.mockImplementation(
|
||||
async (
|
||||
definition: string,
|
||||
options?: MermaidToExcalidraw.MermaidOptions | undefined,
|
||||
) => {
|
||||
const firstLine = definition.split("\n")[0];
|
||||
return new Promise((resolve, reject) => {
|
||||
if (firstLine === "flowchart TD") {
|
||||
resolve({
|
||||
elements: [
|
||||
{
|
||||
id: "Start",
|
||||
type: "rectangle",
|
||||
groupIds: [],
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 69.703125,
|
||||
height: 44,
|
||||
strokeWidth: 2,
|
||||
label: {
|
||||
groupIds: [],
|
||||
text: "Start",
|
||||
fontSize: 20,
|
||||
},
|
||||
link: null,
|
||||
},
|
||||
{
|
||||
id: "Stop",
|
||||
type: "rectangle",
|
||||
groupIds: [],
|
||||
x: 2.7109375,
|
||||
y: 94,
|
||||
width: 64.28125,
|
||||
height: 44,
|
||||
strokeWidth: 2,
|
||||
label: {
|
||||
groupIds: [],
|
||||
text: "Stop",
|
||||
fontSize: 20,
|
||||
},
|
||||
link: null,
|
||||
},
|
||||
{
|
||||
id: "Start_Stop",
|
||||
type: "arrow",
|
||||
groupIds: [],
|
||||
x: 34.852,
|
||||
y: 44,
|
||||
strokeWidth: 2,
|
||||
points: [
|
||||
[0, 0],
|
||||
[0, 50],
|
||||
],
|
||||
roundness: {
|
||||
type: 2,
|
||||
},
|
||||
start: {
|
||||
id: "Start",
|
||||
},
|
||||
end: {
|
||||
id: "Stop",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
reject(new Error("ERROR"));
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
vi.spyOn(React, "useRef").mockReturnValue({
|
||||
current: {
|
||||
parseMermaidToExcalidraw: parseMermaidToExcalidrawSpy,
|
||||
},
|
||||
});
|
||||
|
||||
describe("Test <MermaidToExcalidraw/>", () => {
|
||||
beforeEach(async () => {
|
||||
await render(
|
||||
<Excalidraw
|
||||
initialData={{
|
||||
appState: {
|
||||
openDialog: "mermaid",
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it("should open mermaid popup when active tool is mermaid", async () => {
|
||||
const dialog = document.querySelector(".dialog-mermaid")!;
|
||||
|
||||
expect(dialog.outerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should close the popup and set the tool to selection when close button clicked", () => {
|
||||
const dialog = document.querySelector(".dialog-mermaid")!;
|
||||
const closeBtn = dialog.querySelector(".Dialog__close")!;
|
||||
fireEvent.click(closeBtn);
|
||||
expect(document.querySelector(".dialog-mermaid")).toBe(null);
|
||||
expect(window.h.state.activeTool).toStrictEqual({
|
||||
customType: null,
|
||||
lastActiveTool: null,
|
||||
locked: false,
|
||||
type: "selection",
|
||||
});
|
||||
});
|
||||
|
||||
it("should show error in preview when mermaid library throws error", async () => {
|
||||
const dialog = document.querySelector(".dialog-mermaid")!;
|
||||
const selector = ".dialog-mermaid-panels-text textarea";
|
||||
let editor = await getTextEditor(selector, false);
|
||||
|
||||
expect(dialog.querySelector('[data-testid="mermaid-error"]')).toBeNull();
|
||||
|
||||
expect(editor.textContent).toMatchInlineSnapshot(`
|
||||
"flowchart TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[Car]"
|
||||
`);
|
||||
|
||||
await act(async () => {
|
||||
updateTextEditor(editor, "flowchart TD1");
|
||||
await new Promise((cb) => setTimeout(cb, 0));
|
||||
});
|
||||
editor = await getTextEditor(selector, false);
|
||||
|
||||
expect(editor.textContent).toBe("flowchart TD1");
|
||||
expect(dialog.querySelector('[data-testid="mermaid-error"]'))
|
||||
.toMatchInlineSnapshot(`
|
||||
<div
|
||||
class="mermaid-error"
|
||||
data-testid="mermaid-error"
|
||||
>
|
||||
Error!
|
||||
<p>
|
||||
ERROR
|
||||
</p>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Test <MermaidToExcalidraw/> > should open mermaid popup when active tool is mermaid 1`] = `
|
||||
"<div class=\\"Modal Dialog dialog-mermaid\\" role=\\"dialog\\" aria-modal=\\"true\\" aria-labelledby=\\"dialog-title\\" data-prevent-outside-click=\\"true\\"><div class=\\"Modal__background\\"></div><div class=\\"Modal__content\\" style=\\"--max-width: 1200px;\\" tabindex=\\"0\\"><div class=\\"Island\\"><h2 id=\\"test-id-dialog-title\\" class=\\"Dialog__title\\"><span class=\\"Dialog__titleContent\\"><p class=\\"dialog-mermaid-title\\">Mermaid to Excalidraw</p><span class=\\"dialog-mermaid-desc\\">Currently only <a href=\\"https://mermaid.js.org/syntax/flowchart.html\\">Flowcharts</a> and <a href=\\"https://mermaid.js.org/syntax/sequenceDiagram.html\\">Sequence Diagrams</a> are supported. The other types will be rendered as image in Excalidraw.<br></span></span></h2><button class=\\"Dialog__close\\" title=\\"Close\\" aria-label=\\"Close\\"><svg aria-hidden=\\"true\\" focusable=\\"false\\" role=\\"img\\" viewBox=\\"0 0 20 20\\" class=\\"\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\"><g clip-path=\\"url(#a)\\" stroke=\\"currentColor\\" stroke-width=\\"1.25\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\"><path d=\\"M15 5 5 15M5 5l10 10\\"></path></g><defs><clipPath id=\\"a\\"><path fill=\\"#fff\\" d=\\"M0 0h20v20H0z\\"></path></clipPath></defs></svg></button><div class=\\"Dialog__content\\"><div class=\\"dialog-mermaid-body\\"><div class=\\"dialog-mermaid-panels\\"><div class=\\"dialog-mermaid-panels-text\\"><label>Mermaid Syntax</label><textarea>flowchart TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[Car]</textarea></div><div class=\\"dialog-mermaid-panels-preview\\"><label>Preview</label><div class=\\"dialog-mermaid-panels-preview-wrapper\\"><div style=\\"opacity: 1;\\" class=\\"dialog-mermaid-panels-preview-canvas-container\\"></div></div></div></div><div class=\\"dialog-mermaid-buttons\\"><button type=\\"button\\" class=\\"excalidraw-button dialog-mermaid-insert\\">Insert<span><svg aria-hidden=\\"true\\" focusable=\\"false\\" role=\\"img\\" viewBox=\\"0 0 20 20\\" class=\\"\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\"><g stroke-width=\\"1.25\\"><path d=\\"M4.16602 10H15.8327\\"></path><path d=\\"M12.5 13.3333L15.8333 10\\"></path><path d=\\"M12.5 6.66666L15.8333 9.99999\\"></path></g></svg></span></button></div></div></div></div></div></div>"
|
||||
`;
|
||||
@@ -17,6 +17,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
||||
"keyTest": [Function],
|
||||
"name": "cut",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -26,6 +27,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
||||
"keyTest": undefined,
|
||||
"name": "copy",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -35,6 +37,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
||||
"keyTest": undefined,
|
||||
"name": "paste",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -4601,6 +4604,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"keyTest": [Function],
|
||||
"name": "cut",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -4610,6 +4614,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"keyTest": undefined,
|
||||
"name": "copy",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -4619,6 +4624,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"keyTest": undefined,
|
||||
"name": "paste",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -5181,6 +5187,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"keyTest": [Function],
|
||||
"name": "cut",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -5190,6 +5197,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"keyTest": undefined,
|
||||
"name": "copy",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -5199,6 +5207,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"keyTest": undefined,
|
||||
"name": "paste",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -5846,6 +5855,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
||||
"keyTest": undefined,
|
||||
"name": "paste",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -6099,6 +6109,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
||||
"keyTest": [Function],
|
||||
"name": "cut",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -6108,6 +6119,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
||||
"keyTest": undefined,
|
||||
"name": "copy",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -6117,6 +6129,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
||||
"keyTest": undefined,
|
||||
"name": "paste",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -6473,6 +6486,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
||||
"keyTest": [Function],
|
||||
"name": "cut",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -6482,6 +6496,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
||||
"keyTest": undefined,
|
||||
"name": "copy",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
@@ -6491,6 +6506,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
||||
"keyTest": undefined,
|
||||
"name": "paste",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { vi } from "vitest";
|
||||
import ReactDOM from "react-dom";
|
||||
import { render, waitFor, GlobalTestState } from "./test-utils";
|
||||
import {
|
||||
render,
|
||||
waitFor,
|
||||
GlobalTestState,
|
||||
createPasteEvent,
|
||||
} from "./test-utils";
|
||||
import { Pointer, Keyboard } from "./helpers/ui";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { KEYS } from "../keys";
|
||||
@@ -11,7 +16,7 @@ import {
|
||||
import { getElementBounds } from "../element";
|
||||
import { NormalizedZoomValue } from "../types";
|
||||
import { API } from "./helpers/api";
|
||||
import { createPasteEvent, serializeAsClipboardJSON } from "../clipboard";
|
||||
import { copyToClipboard } from "../clipboard";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
@@ -32,9 +37,7 @@ vi.mock("../keys.ts", async (importOriginal) => {
|
||||
|
||||
const sendPasteEvent = (text: string) => {
|
||||
const clipboardEvent = createPasteEvent({
|
||||
types: {
|
||||
"text/plain": text,
|
||||
},
|
||||
"text/plain": text,
|
||||
});
|
||||
document.dispatchEvent(clipboardEvent);
|
||||
};
|
||||
@@ -83,10 +86,7 @@ beforeEach(async () => {
|
||||
describe("general paste behavior", () => {
|
||||
it("should randomize seed on paste", async () => {
|
||||
const rectangle = API.createElement({ type: "rectangle" });
|
||||
const clipboardJSON = await serializeAsClipboardJSON({
|
||||
elements: [rectangle],
|
||||
files: null,
|
||||
});
|
||||
const clipboardJSON = (await copyToClipboard([rectangle], null))!;
|
||||
pasteWithCtrlCmdV(clipboardJSON);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -97,10 +97,7 @@ describe("general paste behavior", () => {
|
||||
|
||||
it("should retain seed on shift-paste", async () => {
|
||||
const rectangle = API.createElement({ type: "rectangle" });
|
||||
const clipboardJSON = await serializeAsClipboardJSON({
|
||||
elements: [rectangle],
|
||||
files: null,
|
||||
});
|
||||
const clipboardJSON = (await copyToClipboard([rectangle], null))!;
|
||||
|
||||
// assert we don't randomize seed on shift-paste
|
||||
pasteWithCtrlCmdShiftV(clipboardJSON);
|
||||
|
||||
@@ -83,7 +83,6 @@ describe("contextMenu element", () => {
|
||||
const contextMenuOptions =
|
||||
contextMenu?.querySelectorAll(".context-menu li");
|
||||
const expectedShortcutNames: ShortcutName[] = [
|
||||
"paste",
|
||||
"selectAll",
|
||||
"gridMode",
|
||||
"zenMode",
|
||||
@@ -115,9 +114,6 @@ describe("contextMenu element", () => {
|
||||
const contextMenuOptions =
|
||||
contextMenu?.querySelectorAll(".context-menu li");
|
||||
const expectedShortcutNames: ShortcutName[] = [
|
||||
"cut",
|
||||
"copy",
|
||||
"paste",
|
||||
"copyStyles",
|
||||
"pasteStyles",
|
||||
"deleteSelectedElements",
|
||||
@@ -207,9 +203,6 @@ describe("contextMenu element", () => {
|
||||
const contextMenuOptions =
|
||||
contextMenu?.querySelectorAll(".context-menu li");
|
||||
const expectedShortcutNames: ShortcutName[] = [
|
||||
"cut",
|
||||
"copy",
|
||||
"paste",
|
||||
"copyStyles",
|
||||
"pasteStyles",
|
||||
"deleteSelectedElements",
|
||||
@@ -263,9 +256,6 @@ describe("contextMenu element", () => {
|
||||
const contextMenuOptions =
|
||||
contextMenu?.querySelectorAll(".context-menu li");
|
||||
const expectedShortcutNames: ShortcutName[] = [
|
||||
"cut",
|
||||
"copy",
|
||||
"paste",
|
||||
"copyStyles",
|
||||
"pasteStyles",
|
||||
"deleteSelectedElements",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import {
|
||||
createPasteEvent,
|
||||
fireEvent,
|
||||
GlobalTestState,
|
||||
render,
|
||||
@@ -26,7 +27,6 @@ import { vi } from "vitest";
|
||||
import * as blob from "../data/blob";
|
||||
import { KEYS } from "../keys";
|
||||
import { getBoundTextElementPosition } from "../element/textElement";
|
||||
import { createPasteEvent } from "../clipboard";
|
||||
|
||||
const { h } = window;
|
||||
const mouse = new Pointer("mouse");
|
||||
@@ -727,7 +727,7 @@ describe("freedraw", () => {
|
||||
describe("image", () => {
|
||||
const createImage = async () => {
|
||||
const sendPasteEvent = (file?: File) => {
|
||||
const clipboardEvent = createPasteEvent({ files: file ? [file] : [] });
|
||||
const clipboardEvent = createPasteEvent({}, file ? [file] : []);
|
||||
document.dispatchEvent(clipboardEvent);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
class ClipboardEvent {
|
||||
constructor(
|
||||
type: "paste" | "copy",
|
||||
eventInitDict: {
|
||||
clipboardData: DataTransfer;
|
||||
},
|
||||
) {
|
||||
return Object.assign(
|
||||
new Event("paste", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
}),
|
||||
{
|
||||
clipboardData: eventInitDict.clipboardData,
|
||||
},
|
||||
) as any as ClipboardEvent;
|
||||
}
|
||||
}
|
||||
|
||||
type DataKind = "string" | "file";
|
||||
|
||||
class DataTransferItem {
|
||||
kind: DataKind;
|
||||
type: string;
|
||||
data: string | Blob;
|
||||
|
||||
constructor(kind: DataKind, type: string, data: string | Blob) {
|
||||
this.kind = kind;
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
getAsString(callback: (data: string) => void): void {
|
||||
if (this.kind === "string") {
|
||||
callback(this.data as string);
|
||||
}
|
||||
}
|
||||
|
||||
getAsFile(): File | null {
|
||||
if (this.kind === "file" && this.data instanceof File) {
|
||||
return this.data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class DataTransferList {
|
||||
items: DataTransferItem[] = [];
|
||||
|
||||
add(data: string | File, type: string = ""): void {
|
||||
if (typeof data === "string") {
|
||||
this.items.push(new DataTransferItem("string", type, data));
|
||||
} else if (data instanceof File) {
|
||||
this.items.push(new DataTransferItem("file", type, data));
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.items = [];
|
||||
}
|
||||
}
|
||||
|
||||
class DataTransfer {
|
||||
public items: DataTransferList = new DataTransferList();
|
||||
private _types: Record<string, string> = {};
|
||||
|
||||
get files() {
|
||||
return this.items.items
|
||||
.filter((item) => item.kind === "file")
|
||||
.map((item) => item.getAsFile()!);
|
||||
}
|
||||
|
||||
add(data: string | File, type: string = ""): void {
|
||||
this.items.add(data, type);
|
||||
}
|
||||
|
||||
setData(type: string, value: string) {
|
||||
this._types[type] = value;
|
||||
}
|
||||
|
||||
getData(type: string) {
|
||||
return this._types[type] || "";
|
||||
}
|
||||
}
|
||||
|
||||
export const testPolyfills = {
|
||||
ClipboardEvent,
|
||||
DataTransfer,
|
||||
DataTransferItem,
|
||||
};
|
||||
@@ -468,16 +468,16 @@ export class UI {
|
||||
static async editText<
|
||||
T extends ExcalidrawTextElement | ExcalidrawTextContainer,
|
||||
>(element: T, text: string) {
|
||||
const textEditorSelector = ".excalidraw-textEditorContainer > textarea";
|
||||
const openedEditor =
|
||||
document.querySelector<HTMLTextAreaElement>(textEditorSelector);
|
||||
const openedEditor = document.querySelector<HTMLTextAreaElement>(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
);
|
||||
|
||||
if (!openedEditor) {
|
||||
mouse.select(element);
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
}
|
||||
|
||||
const editor = await getTextEditor(textEditorSelector);
|
||||
const editor = await getTextEditor();
|
||||
if (!editor) {
|
||||
throw new Error("Can't find wysiwyg text editor in the dom");
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
// drag line from midpoint
|
||||
drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]);
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(13);
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(14);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(6);
|
||||
|
||||
expect(line.points.length).toEqual(3);
|
||||
@@ -416,7 +416,7 @@ describe("Test Linear Elements", () => {
|
||||
lastSegmentMidpoint[1] + delta,
|
||||
]);
|
||||
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(19);
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(21);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(9);
|
||||
|
||||
expect(line.points.length).toEqual(5);
|
||||
@@ -519,7 +519,7 @@ describe("Test Linear Elements", () => {
|
||||
// delete 3rd point
|
||||
deletePoint(points[2]);
|
||||
expect(line.points.length).toEqual(3);
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(20);
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(21);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(9);
|
||||
|
||||
const newMidPoints = LinearElementEditor.getEditorMidPoints(
|
||||
@@ -566,7 +566,7 @@ describe("Test Linear Elements", () => {
|
||||
lastSegmentMidpoint[0] + delta,
|
||||
lastSegmentMidpoint[1] + delta,
|
||||
]);
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(19);
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(21);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(9);
|
||||
expect(line.points.length).toEqual(5);
|
||||
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
|
||||
export const getTextEditor = async (selector: string, waitForEditor = true) => {
|
||||
const query = () => document.querySelector(selector) as HTMLTextAreaElement;
|
||||
export const getTextEditor = async (waitForEditor = true) => {
|
||||
const query = () =>
|
||||
document.querySelector(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
) as HTMLTextAreaElement;
|
||||
if (waitForEditor) {
|
||||
await waitFor(() => expect(query()).not.toBe(null));
|
||||
return query();
|
||||
}
|
||||
return query();
|
||||
};
|
||||
|
||||
export const updateTextEditor = (
|
||||
editor: HTMLTextAreaElement,
|
||||
value: string,
|
||||
) => {
|
||||
fireEvent.change(editor, { target: { value } });
|
||||
editor.dispatchEvent(new Event("input"));
|
||||
};
|
||||
|
||||
@@ -208,6 +208,26 @@ export const assertSelectedElements = (
|
||||
expect(selectedElementIds).toEqual(expect.arrayContaining(ids));
|
||||
};
|
||||
|
||||
export const createPasteEvent = <T extends "text/plain" | "text/html">(
|
||||
items: Record<T, string>,
|
||||
files?: File[],
|
||||
) => {
|
||||
return Object.assign(
|
||||
new Event("paste", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
}),
|
||||
{
|
||||
clipboardData: {
|
||||
getData: (type: string) =>
|
||||
(items as Record<string, string>)[type] || "",
|
||||
files: files || [],
|
||||
},
|
||||
},
|
||||
) as any as ClipboardEvent;
|
||||
};
|
||||
|
||||
export const toggleMenu = (container: HTMLElement) => {
|
||||
// open menu
|
||||
fireEvent.click(container.querySelector(".dropdown-menu-button")!);
|
||||
|
||||
@@ -241,7 +241,7 @@ export type AppState = {
|
||||
openMenu: "canvas" | "shape" | null;
|
||||
openPopup: "canvasBackground" | "elementBackground" | "elementStroke" | null;
|
||||
openSidebar: { name: SidebarName; tab?: SidebarTabName } | null;
|
||||
openDialog: "imageExport" | "help" | "jsonExport" | "mermaid" | null;
|
||||
openDialog: "imageExport" | "help" | "jsonExport" | null;
|
||||
/**
|
||||
* Reflects user preference for whether the default sidebar should be docked.
|
||||
*
|
||||
@@ -537,12 +537,8 @@ export type AppClassProperties = {
|
||||
onInsertElements: App["onInsertElements"];
|
||||
onExportImage: App["onExportImage"];
|
||||
lastViewportPosition: App["lastViewportPosition"];
|
||||
scrollToContent: App["scrollToContent"];
|
||||
addFiles: App["addFiles"];
|
||||
addElementsFromPasteOrLibrary: App["addElementsFromPasteOrLibrary"];
|
||||
togglePenMode: App["togglePenMode"];
|
||||
setActiveTool: App["setActiveTool"];
|
||||
setOpenDialog: App["setOpenDialog"];
|
||||
};
|
||||
|
||||
export type PointerDownState = Readonly<{
|
||||
|
||||
14
src/utils.ts
14
src/utils.ts
@@ -917,17 +917,3 @@ export const isRenderThrottlingEnabled = (() => {
|
||||
return false;
|
||||
};
|
||||
})();
|
||||
|
||||
/** Checks if value is inside given collection. Useful for type-safety. */
|
||||
export const isMemberOf = <T extends string>(
|
||||
/** Set/Map/Array/Object */
|
||||
collection: Set<T> | readonly T[] | Record<T, any> | Map<T, any>,
|
||||
/** value to look for */
|
||||
value: string,
|
||||
): value is T => {
|
||||
return collection instanceof Set || collection instanceof Map
|
||||
? collection.has(value as T)
|
||||
: "includes" in collection
|
||||
? collection.includes(value as T)
|
||||
: collection.hasOwnProperty(value);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ export default defineConfig({
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
coverage: {
|
||||
reporter: ["text", "json-summary", "json", "html"],
|
||||
reporter: ["text", "json-summary", "json"],
|
||||
lines: 70,
|
||||
branches: 70,
|
||||
functions: 68,
|
||||
|
||||
730
yarn.lock
730
yarn.lock
@@ -1321,11 +1321,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz#6110f918d273fe2af8ea1c4398a88774bb9fc12f"
|
||||
integrity sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==
|
||||
|
||||
"@braintree/sanitize-url@^6.0.2":
|
||||
version "6.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783"
|
||||
integrity sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==
|
||||
|
||||
"@esbuild/android-arm64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd"
|
||||
@@ -1583,20 +1578,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@excalidraw/laser-pointer/-/laser-pointer-1.2.0.tgz#cd34ea7d24b11743c726488cc1fcb28c161cacba"
|
||||
integrity sha512-WjFFwLk9ahmKRKku7U0jqYpeM3fe9ZS1K43pfwPREHk4/FYU3iKDKVeS8m4tEAASnRlBt3hhLCBQLBF2uvgOnw==
|
||||
|
||||
"@excalidraw/markdown-to-text@0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@excalidraw/markdown-to-text/-/markdown-to-text-0.1.2.tgz#1703705e7da608cf478f17bfe96fb295f55a23eb"
|
||||
integrity sha512-1nDXBNAojfi3oSFwJswKREkFm5wrSjqay81QlyRv2pkITG/XYB5v+oChENVBQLcxQwX4IUATWvXM5BcaNhPiIg==
|
||||
|
||||
"@excalidraw/mermaid-to-excalidraw@0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@excalidraw/mermaid-to-excalidraw/-/mermaid-to-excalidraw-0.1.2.tgz#be7b412536fc00b7986ccdccba8e7c33592aa004"
|
||||
integrity sha512-LFk+cLGhXlvRTaf0f6ClCFIZFRsbZPb1ke2cytr5/JlnOefnXQQHgWITafskjcIO2c34KXFGO0HjgYPNFLUknw==
|
||||
dependencies:
|
||||
"@excalidraw/markdown-to-text" "0.1.2"
|
||||
mermaid "10.2.3"
|
||||
nanoid "4.0.2"
|
||||
|
||||
"@excalidraw/prettier-config@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@excalidraw/prettier-config/-/prettier-config-1.0.2.tgz#b7c061c99cee2f78b9ca470ea1fbd602683bba65"
|
||||
@@ -2575,13 +2556,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.0.tgz#23509ebc1fa32f1b4d50d6a66c4032d5b8eaabdc"
|
||||
integrity sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==
|
||||
|
||||
"@types/debug@^4.0.0":
|
||||
version "4.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.8.tgz#cef723a5d0a90990313faec2d1e22aee5eecb317"
|
||||
integrity sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==
|
||||
dependencies:
|
||||
"@types/ms" "*"
|
||||
|
||||
"@types/estree@0.0.39":
|
||||
version "0.0.39"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||
@@ -2654,18 +2628,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
|
||||
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
|
||||
|
||||
"@types/mdast@^3.0.0":
|
||||
version "3.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.12.tgz#beeb511b977c875a5b0cc92eab6fcac2f0895514"
|
||||
integrity sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg==
|
||||
dependencies:
|
||||
"@types/unist" "^2"
|
||||
|
||||
"@types/ms@*":
|
||||
version "0.7.31"
|
||||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
|
||||
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
|
||||
|
||||
"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0":
|
||||
version "18.15.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f"
|
||||
@@ -2776,11 +2738,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.3.tgz#a136f83b0758698df454e328759dbd3d44555311"
|
||||
integrity sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==
|
||||
|
||||
"@types/unist@^2", "@types/unist@^2.0.0":
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.7.tgz#5b06ad6894b236a1d2bd6b2f07850ca5c59cf4d6"
|
||||
integrity sha512-cputDpIbFgLUaGQn6Vqg3/YsJwxUwHLO13v3i5ouxT4lat0khip9AEWxtERujXV9wxIB1EyF97BSJFt6vpdI8g==
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "21.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
|
||||
@@ -3474,11 +3431,6 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
character-entities@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22"
|
||||
integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
|
||||
|
||||
check-error@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
|
||||
@@ -3582,11 +3534,6 @@ combined-stream@^1.0.8:
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@7:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
||||
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
||||
|
||||
commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
@@ -3654,20 +3601,6 @@ corser@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87"
|
||||
integrity sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==
|
||||
|
||||
cose-base@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cose-base/-/cose-base-1.0.3.tgz#650334b41b869578a543358b80cda7e0abe0a60a"
|
||||
integrity sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==
|
||||
dependencies:
|
||||
layout-base "^1.0.0"
|
||||
|
||||
cose-base@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/cose-base/-/cose-base-2.2.0.tgz#1c395c35b6e10bb83f9769ca8b817d614add5c01"
|
||||
integrity sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==
|
||||
dependencies:
|
||||
layout-base "^2.0.0"
|
||||
|
||||
cosmiconfig@^7.0.0, cosmiconfig@^7.0.1:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
|
||||
@@ -3736,280 +3669,6 @@ csstype@^3.0.2:
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
|
||||
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
|
||||
|
||||
cytoscape-cose-bilkent@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz#762fa121df9930ffeb51a495d87917c570ac209b"
|
||||
integrity sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==
|
||||
dependencies:
|
||||
cose-base "^1.0.0"
|
||||
|
||||
cytoscape-fcose@^2.1.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz#e4d6f6490df4fab58ae9cea9e5c3ab8d7472f471"
|
||||
integrity sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==
|
||||
dependencies:
|
||||
cose-base "^2.2.0"
|
||||
|
||||
cytoscape@^3.23.0:
|
||||
version "3.26.0"
|
||||
resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.26.0.tgz#b4c6961445fd51e1fd3cca83c3ffe924d9a8abc9"
|
||||
integrity sha512-IV+crL+KBcrCnVVUCZW+zRRRFUZQcrtdOPXki+o4CFUWLdAEYvuZLcBSJC9EBK++suamERKzeY7roq2hdovV3w==
|
||||
dependencies:
|
||||
heap "^0.2.6"
|
||||
lodash "^4.17.21"
|
||||
|
||||
"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5"
|
||||
integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
|
||||
dependencies:
|
||||
internmap "1 - 2"
|
||||
|
||||
d3-axis@3:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322"
|
||||
integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==
|
||||
|
||||
d3-brush@3:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c"
|
||||
integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 3"
|
||||
d3-drag "2 - 3"
|
||||
d3-interpolate "1 - 3"
|
||||
d3-selection "3"
|
||||
d3-transition "3"
|
||||
|
||||
d3-chord@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966"
|
||||
integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==
|
||||
dependencies:
|
||||
d3-path "1 - 3"
|
||||
|
||||
"d3-color@1 - 3", d3-color@3:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
|
||||
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
|
||||
|
||||
d3-contour@4:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc"
|
||||
integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==
|
||||
dependencies:
|
||||
d3-array "^3.2.0"
|
||||
|
||||
d3-delaunay@6:
|
||||
version "6.0.4"
|
||||
resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b"
|
||||
integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==
|
||||
dependencies:
|
||||
delaunator "5"
|
||||
|
||||
"d3-dispatch@1 - 3", d3-dispatch@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
|
||||
integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
|
||||
|
||||
"d3-drag@2 - 3", d3-drag@3:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba"
|
||||
integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 3"
|
||||
d3-selection "3"
|
||||
|
||||
"d3-dsv@1 - 3", d3-dsv@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73"
|
||||
integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==
|
||||
dependencies:
|
||||
commander "7"
|
||||
iconv-lite "0.6"
|
||||
rw "1"
|
||||
|
||||
"d3-ease@1 - 3", d3-ease@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
|
||||
integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
|
||||
|
||||
d3-fetch@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22"
|
||||
integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==
|
||||
dependencies:
|
||||
d3-dsv "1 - 3"
|
||||
|
||||
d3-force@3:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4"
|
||||
integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 3"
|
||||
d3-quadtree "1 - 3"
|
||||
d3-timer "1 - 3"
|
||||
|
||||
"d3-format@1 - 3", d3-format@3:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
|
||||
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
|
||||
|
||||
d3-geo@3:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.0.tgz#74fd54e1f4cebd5185ac2039217a98d39b0a4c0e"
|
||||
integrity sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==
|
||||
dependencies:
|
||||
d3-array "2.5.0 - 3"
|
||||
|
||||
d3-hierarchy@3:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6"
|
||||
integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==
|
||||
|
||||
"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
|
||||
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
|
||||
"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526"
|
||||
integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
|
||||
|
||||
d3-polygon@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398"
|
||||
integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==
|
||||
|
||||
"d3-quadtree@1 - 3", d3-quadtree@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f"
|
||||
integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==
|
||||
|
||||
d3-random@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4"
|
||||
integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==
|
||||
|
||||
d3-scale-chromatic@3:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a"
|
||||
integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
d3-interpolate "1 - 3"
|
||||
|
||||
d3-scale@4:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
|
||||
integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
|
||||
dependencies:
|
||||
d3-array "2.10.0 - 3"
|
||||
d3-format "1 - 3"
|
||||
d3-interpolate "1.2.0 - 3"
|
||||
d3-time "2.1.1 - 3"
|
||||
d3-time-format "2 - 4"
|
||||
|
||||
"d3-selection@2 - 3", d3-selection@3:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
|
||||
integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
|
||||
|
||||
d3-shape@3:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
|
||||
integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
|
||||
dependencies:
|
||||
d3-path "^3.1.0"
|
||||
|
||||
"d3-time-format@2 - 4", d3-time-format@4:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
|
||||
integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
|
||||
dependencies:
|
||||
d3-time "1 - 3"
|
||||
|
||||
"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7"
|
||||
integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
|
||||
dependencies:
|
||||
d3-array "2 - 3"
|
||||
|
||||
"d3-timer@1 - 3", d3-timer@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
|
||||
integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
|
||||
|
||||
"d3-transition@2 - 3", d3-transition@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f"
|
||||
integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
d3-dispatch "1 - 3"
|
||||
d3-ease "1 - 3"
|
||||
d3-interpolate "1 - 3"
|
||||
d3-timer "1 - 3"
|
||||
|
||||
d3-zoom@3:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3"
|
||||
integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 3"
|
||||
d3-drag "2 - 3"
|
||||
d3-interpolate "1 - 3"
|
||||
d3-selection "2 - 3"
|
||||
d3-transition "2 - 3"
|
||||
|
||||
d3@^7.4.0, d3@^7.8.2:
|
||||
version "7.8.5"
|
||||
resolved "https://registry.yarnpkg.com/d3/-/d3-7.8.5.tgz#fde4b760d4486cdb6f0cc8e2cbff318af844635c"
|
||||
integrity sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==
|
||||
dependencies:
|
||||
d3-array "3"
|
||||
d3-axis "3"
|
||||
d3-brush "3"
|
||||
d3-chord "3"
|
||||
d3-color "3"
|
||||
d3-contour "4"
|
||||
d3-delaunay "6"
|
||||
d3-dispatch "3"
|
||||
d3-drag "3"
|
||||
d3-dsv "3"
|
||||
d3-ease "3"
|
||||
d3-fetch "3"
|
||||
d3-force "3"
|
||||
d3-format "3"
|
||||
d3-geo "3"
|
||||
d3-hierarchy "3"
|
||||
d3-interpolate "3"
|
||||
d3-path "3"
|
||||
d3-polygon "3"
|
||||
d3-quadtree "3"
|
||||
d3-random "3"
|
||||
d3-scale "4"
|
||||
d3-scale-chromatic "3"
|
||||
d3-selection "3"
|
||||
d3-shape "3"
|
||||
d3-time "3"
|
||||
d3-time-format "4"
|
||||
d3-timer "3"
|
||||
d3-transition "3"
|
||||
d3-zoom "3"
|
||||
|
||||
dagre-d3-es@7.0.10:
|
||||
version "7.0.10"
|
||||
resolved "https://registry.yarnpkg.com/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz#19800d4be674379a3cd8c86a8216a2ac6827cadc"
|
||||
integrity sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==
|
||||
dependencies:
|
||||
d3 "^7.8.2"
|
||||
lodash-es "^4.17.21"
|
||||
|
||||
damerau-levenshtein@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
|
||||
@@ -4024,12 +3683,7 @@ data-urls@^4.0.0:
|
||||
whatwg-mimetype "^3.0.0"
|
||||
whatwg-url "^12.0.0"
|
||||
|
||||
dayjs@^1.11.7:
|
||||
version "1.11.9"
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a"
|
||||
integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==
|
||||
|
||||
debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3, debug@^4.3.4:
|
||||
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
@@ -4055,13 +3709,6 @@ decimal.js@^10.4.3:
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
|
||||
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
|
||||
|
||||
decode-named-character-reference@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e"
|
||||
integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==
|
||||
dependencies:
|
||||
character-entities "^2.0.0"
|
||||
|
||||
decode-uri-component@^0.2.0:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
|
||||
@@ -4122,23 +3769,11 @@ define-properties@^1.1.3, define-properties@^1.1.4:
|
||||
has-property-descriptors "^1.0.0"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
delaunator@5:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b"
|
||||
integrity sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==
|
||||
dependencies:
|
||||
robust-predicates "^3.0.0"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||
|
||||
dequal@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
|
||||
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
|
||||
|
||||
detect-node-es@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
|
||||
@@ -4154,11 +3789,6 @@ diff-sequences@^29.4.3:
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2"
|
||||
integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==
|
||||
|
||||
diff@^5.0.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40"
|
||||
integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
|
||||
|
||||
dir-glob@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
|
||||
@@ -4204,11 +3834,6 @@ domexception@^4.0.0:
|
||||
dependencies:
|
||||
webidl-conversions "^7.0.0"
|
||||
|
||||
dompurify@3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.3.tgz#4b115d15a091ddc96f232bcef668550a2f6f1430"
|
||||
integrity sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ==
|
||||
|
||||
dotenv@16.0.1:
|
||||
version "16.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d"
|
||||
@@ -4231,11 +3856,6 @@ electron-to-chromium@^1.4.284:
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.359.tgz#5c4d13cb08032469fcd6bd36457915caa211356b"
|
||||
integrity sha512-OoVcngKCIuNXtZnsYoqlCvr0Cf3NIPzDIgwUfI9bdTFjXCrr79lI0kwQstLPZ7WhCezLlGksZk/BFAzoXC7GDw==
|
||||
|
||||
elkjs@^0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/elkjs/-/elkjs-0.8.2.tgz#c37763c5a3e24e042e318455e0147c912a7c248e"
|
||||
integrity sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
@@ -5132,11 +4752,6 @@ he@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
heap@^0.2.6:
|
||||
version "0.2.7"
|
||||
resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc"
|
||||
integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==
|
||||
|
||||
html-encoding-sniffer@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
|
||||
@@ -5216,7 +4831,7 @@ i18next-browser-languagedetector@6.1.4:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.6"
|
||||
|
||||
iconv-lite@0.6, iconv-lite@0.6.3:
|
||||
iconv-lite@0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
|
||||
@@ -5312,11 +4927,6 @@ internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5:
|
||||
has "^1.0.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
"internmap@1 - 2":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
|
||||
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
||||
|
||||
invariant@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
@@ -5789,16 +5399,6 @@ jsonpointer@^5.0.0:
|
||||
array-includes "^3.1.5"
|
||||
object.assign "^4.1.3"
|
||||
|
||||
khroma@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/khroma/-/khroma-2.0.0.tgz#7577de98aed9f36c7a474c4d453d94c0d6c6588b"
|
||||
integrity sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==
|
||||
|
||||
kleur@^4.0.3:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
|
||||
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
|
||||
|
||||
language-subtag-registry@~0.3.2:
|
||||
version "0.3.22"
|
||||
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d"
|
||||
@@ -5811,16 +5411,6 @@ language-tags@=1.0.5:
|
||||
dependencies:
|
||||
language-subtag-registry "~0.3.2"
|
||||
|
||||
layout-base@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/layout-base/-/layout-base-1.0.2.tgz#1291e296883c322a9dd4c5dd82063721b53e26e2"
|
||||
integrity sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==
|
||||
|
||||
layout-base@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/layout-base/-/layout-base-2.0.1.tgz#d0337913586c90f9c2c075292069f5c2da5dd285"
|
||||
integrity sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==
|
||||
|
||||
leven@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
|
||||
@@ -5897,11 +5487,6 @@ localforage@^1.8.1:
|
||||
dependencies:
|
||||
lie "3.1.1"
|
||||
|
||||
lodash-es@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||
|
||||
lodash.camelcase@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
@@ -6023,31 +5608,6 @@ make-dir@^4.0.0:
|
||||
dependencies:
|
||||
semver "^7.5.3"
|
||||
|
||||
mdast-util-from-markdown@^1.3.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz#9421a5a247f10d31d2faed2a30df5ec89ceafcf0"
|
||||
integrity sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==
|
||||
dependencies:
|
||||
"@types/mdast" "^3.0.0"
|
||||
"@types/unist" "^2.0.0"
|
||||
decode-named-character-reference "^1.0.0"
|
||||
mdast-util-to-string "^3.1.0"
|
||||
micromark "^3.0.0"
|
||||
micromark-util-decode-numeric-character-reference "^1.0.0"
|
||||
micromark-util-decode-string "^1.0.0"
|
||||
micromark-util-normalize-identifier "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
unist-util-stringify-position "^3.0.0"
|
||||
uvu "^0.5.0"
|
||||
|
||||
mdast-util-to-string@^3.1.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz#66f7bb6324756741c5f47a53557f0cbf16b6f789"
|
||||
integrity sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==
|
||||
dependencies:
|
||||
"@types/mdast" "^3.0.0"
|
||||
|
||||
merge-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||
@@ -6058,223 +5618,6 @@ merge2@^1.3.0, merge2@^1.4.1:
|
||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
mermaid@10.2.3:
|
||||
version "10.2.3"
|
||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-10.2.3.tgz#789d3b582c5da8c69aa4a7c0e2b826562c8c8b12"
|
||||
integrity sha512-cMVE5s9PlQvOwfORkyVpr5beMsLdInrycAosdr+tpZ0WFjG4RJ/bUHST7aTgHNJbujHkdBRAm+N50P3puQOfPw==
|
||||
dependencies:
|
||||
"@braintree/sanitize-url" "^6.0.2"
|
||||
cytoscape "^3.23.0"
|
||||
cytoscape-cose-bilkent "^4.1.0"
|
||||
cytoscape-fcose "^2.1.0"
|
||||
d3 "^7.4.0"
|
||||
dagre-d3-es "7.0.10"
|
||||
dayjs "^1.11.7"
|
||||
dompurify "3.0.3"
|
||||
elkjs "^0.8.2"
|
||||
khroma "^2.0.0"
|
||||
lodash-es "^4.17.21"
|
||||
mdast-util-from-markdown "^1.3.0"
|
||||
non-layered-tidy-tree-layout "^2.0.2"
|
||||
stylis "^4.1.3"
|
||||
ts-dedent "^2.2.0"
|
||||
uuid "^9.0.0"
|
||||
web-worker "^1.2.0"
|
||||
|
||||
micromark-core-commonmark@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz#1386628df59946b2d39fb2edfd10f3e8e0a75bb8"
|
||||
integrity sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==
|
||||
dependencies:
|
||||
decode-named-character-reference "^1.0.0"
|
||||
micromark-factory-destination "^1.0.0"
|
||||
micromark-factory-label "^1.0.0"
|
||||
micromark-factory-space "^1.0.0"
|
||||
micromark-factory-title "^1.0.0"
|
||||
micromark-factory-whitespace "^1.0.0"
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-chunked "^1.0.0"
|
||||
micromark-util-classify-character "^1.0.0"
|
||||
micromark-util-html-tag-name "^1.0.0"
|
||||
micromark-util-normalize-identifier "^1.0.0"
|
||||
micromark-util-resolve-all "^1.0.0"
|
||||
micromark-util-subtokenize "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.1"
|
||||
uvu "^0.5.0"
|
||||
|
||||
micromark-factory-destination@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz#eb815957d83e6d44479b3df640f010edad667b9f"
|
||||
integrity sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==
|
||||
dependencies:
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-factory-label@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz#cc95d5478269085cfa2a7282b3de26eb2e2dec68"
|
||||
integrity sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==
|
||||
dependencies:
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
uvu "^0.5.0"
|
||||
|
||||
micromark-factory-space@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz#c8f40b0640a0150751d3345ed885a080b0d15faf"
|
||||
integrity sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==
|
||||
dependencies:
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-factory-title@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz#dd0fe951d7a0ac71bdc5ee13e5d1465ad7f50ea1"
|
||||
integrity sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==
|
||||
dependencies:
|
||||
micromark-factory-space "^1.0.0"
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-factory-whitespace@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz#798fb7489f4c8abafa7ca77eed6b5745853c9705"
|
||||
integrity sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==
|
||||
dependencies:
|
||||
micromark-factory-space "^1.0.0"
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-util-character@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz#4fedaa3646db249bc58caeb000eb3549a8ca5dcc"
|
||||
integrity sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==
|
||||
dependencies:
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-util-chunked@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz#37a24d33333c8c69a74ba12a14651fd9ea8a368b"
|
||||
integrity sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==
|
||||
dependencies:
|
||||
micromark-util-symbol "^1.0.0"
|
||||
|
||||
micromark-util-classify-character@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz#6a7f8c8838e8a120c8e3c4f2ae97a2bff9190e9d"
|
||||
integrity sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==
|
||||
dependencies:
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-util-combine-extensions@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz#192e2b3d6567660a85f735e54d8ea6e3952dbe84"
|
||||
integrity sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==
|
||||
dependencies:
|
||||
micromark-util-chunked "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-util-decode-numeric-character-reference@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz#b1e6e17009b1f20bc652a521309c5f22c85eb1c6"
|
||||
integrity sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==
|
||||
dependencies:
|
||||
micromark-util-symbol "^1.0.0"
|
||||
|
||||
micromark-util-decode-string@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz#dc12b078cba7a3ff690d0203f95b5d5537f2809c"
|
||||
integrity sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==
|
||||
dependencies:
|
||||
decode-named-character-reference "^1.0.0"
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-decode-numeric-character-reference "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
|
||||
micromark-util-encode@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz#92e4f565fd4ccb19e0dcae1afab9a173bbeb19a5"
|
||||
integrity sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==
|
||||
|
||||
micromark-util-html-tag-name@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz#48fd7a25826f29d2f71479d3b4e83e94829b3588"
|
||||
integrity sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==
|
||||
|
||||
micromark-util-normalize-identifier@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz#7a73f824eb9f10d442b4d7f120fecb9b38ebf8b7"
|
||||
integrity sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==
|
||||
dependencies:
|
||||
micromark-util-symbol "^1.0.0"
|
||||
|
||||
micromark-util-resolve-all@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz#4652a591ee8c8fa06714c9b54cd6c8e693671188"
|
||||
integrity sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==
|
||||
dependencies:
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-util-sanitize-uri@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz#613f738e4400c6eedbc53590c67b197e30d7f90d"
|
||||
integrity sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==
|
||||
dependencies:
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-encode "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
|
||||
micromark-util-subtokenize@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz#941c74f93a93eaf687b9054aeb94642b0e92edb1"
|
||||
integrity sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==
|
||||
dependencies:
|
||||
micromark-util-chunked "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
uvu "^0.5.0"
|
||||
|
||||
micromark-util-symbol@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz#813cd17837bdb912d069a12ebe3a44b6f7063142"
|
||||
integrity sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==
|
||||
|
||||
micromark-util-types@^1.0.0, micromark-util-types@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz#e6676a8cae0bb86a2171c498167971886cb7e283"
|
||||
integrity sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==
|
||||
|
||||
micromark@^3.0.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark/-/micromark-3.2.0.tgz#1af9fef3f995ea1ea4ac9c7e2f19c48fd5c006e9"
|
||||
integrity sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==
|
||||
dependencies:
|
||||
"@types/debug" "^4.0.0"
|
||||
debug "^4.0.0"
|
||||
decode-named-character-reference "^1.0.0"
|
||||
micromark-core-commonmark "^1.0.1"
|
||||
micromark-factory-space "^1.0.0"
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-chunked "^1.0.0"
|
||||
micromark-util-combine-extensions "^1.0.0"
|
||||
micromark-util-decode-numeric-character-reference "^1.0.0"
|
||||
micromark-util-encode "^1.0.0"
|
||||
micromark-util-normalize-identifier "^1.0.0"
|
||||
micromark-util-resolve-all "^1.0.0"
|
||||
micromark-util-sanitize-uri "^1.0.0"
|
||||
micromark-util-subtokenize "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.1"
|
||||
uvu "^0.5.0"
|
||||
|
||||
micromatch@^4.0.4:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
|
||||
@@ -6353,11 +5696,6 @@ moo-color@^1.0.2:
|
||||
dependencies:
|
||||
color-name "^1.1.4"
|
||||
|
||||
mri@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
|
||||
integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
|
||||
|
||||
mrmime@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27"
|
||||
@@ -6391,11 +5729,6 @@ nanoid@3.3.3:
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
|
||||
integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
|
||||
|
||||
nanoid@4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
|
||||
integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
|
||||
|
||||
nanoid@^3.3.6:
|
||||
version "3.3.6"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
||||
@@ -6421,11 +5754,6 @@ node-releases@^2.0.8:
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f"
|
||||
integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==
|
||||
|
||||
non-layered-tidy-tree-layout@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz#57d35d13c356643fc296a55fb11ac15e74da7804"
|
||||
integrity sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==
|
||||
|
||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
@@ -7100,11 +6428,6 @@ rimraf@^3.0.2:
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
robust-predicates@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771"
|
||||
integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==
|
||||
|
||||
rollup-plugin-terser@^7.0.0:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
|
||||
@@ -7158,11 +6481,6 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
rw@1:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
|
||||
integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==
|
||||
|
||||
rxjs@^7.5.5:
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4"
|
||||
@@ -7170,13 +6488,6 @@ rxjs@^7.5.5:
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
sade@^1.7.3:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701"
|
||||
integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==
|
||||
dependencies:
|
||||
mri "^1.1.0"
|
||||
|
||||
safari-14-idb-fix@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz#450fc049b996ec7f3fd9ca2f89d32e0761583440"
|
||||
@@ -7555,11 +6866,6 @@ strip-literal@^1.0.1:
|
||||
dependencies:
|
||||
acorn "^8.8.2"
|
||||
|
||||
stylis@^4.1.3:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c"
|
||||
integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
@@ -7722,11 +7028,6 @@ tr46@^4.1.1:
|
||||
dependencies:
|
||||
punycode "^2.3.0"
|
||||
|
||||
ts-dedent@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
|
||||
integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
|
||||
|
||||
tsconfig-paths@^3.14.1:
|
||||
version "3.14.2"
|
||||
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088"
|
||||
@@ -7868,13 +7169,6 @@ unique-string@^2.0.0:
|
||||
dependencies:
|
||||
crypto-random-string "^2.0.0"
|
||||
|
||||
unist-util-stringify-position@^3.0.0:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz#03ad3348210c2d930772d64b489580c13a7db39d"
|
||||
integrity sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
|
||||
universalify@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
||||
@@ -7943,21 +7237,6 @@ use-sync-external-store@1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||
|
||||
uuid@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||
|
||||
uvu@^0.5.0:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df"
|
||||
integrity sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==
|
||||
dependencies:
|
||||
dequal "^2.0.0"
|
||||
diff "^5.0.0"
|
||||
kleur "^4.0.3"
|
||||
sade "^1.7.3"
|
||||
|
||||
v8-compile-cache@^2.0.3:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
||||
@@ -8143,11 +7422,6 @@ w3c-xmlserializer@^4.0.0:
|
||||
dependencies:
|
||||
xml-name-validator "^4.0.0"
|
||||
|
||||
web-worker@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.2.0.tgz#5d85a04a7fbc1e7db58f66595d7a3ac7c9c180da"
|
||||
integrity sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==
|
||||
|
||||
webidl-conversions@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||
|
||||
Reference in New Issue
Block a user