Merge remote-tracking branch 'origin/release' into danieljgeiger-mathjax-maint-stage
This commit is contained in:
commit
62f5475c4a
@ -0,0 +1,429 @@
|
||||
# Creating Elements programmatically
|
||||
|
||||
We support a simplified API to make it easier to generate Excalidraw elements programmatically. This API is in beta and subject to change before stable. You can check the [PR](https://github.com/excalidraw/excalidraw/pull/6546) for more details.
|
||||
|
||||
For this purpose we introduced a new type [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133). This is the simplified version of [`ExcalidrawElement`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L134) type with the minimum possible attributes so that creating elements programmatically is much easier (especially for cases like binding arrows or creating text containers).
|
||||
|
||||
The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133) can be converted to fully qualified Excalidraw elements by using [`convertToExcalidrawElements`](/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements).
|
||||
|
||||
## convertToExcalidrawElements
|
||||
|
||||
**_Signature_**
|
||||
|
||||
<pre>
|
||||
convertToExcalidrawElements(elements:{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133">
|
||||
ExcalidrawElementSkeleton
|
||||
</a>
|
||||
)
|
||||
</pre>
|
||||
|
||||
**_How to use_**
|
||||
|
||||
```js
|
||||
import { convertToExcalidrawElements } from "@excalidraw/excalidraw";
|
||||
```
|
||||
|
||||
This function converts the Excalidraw Element Skeleton to excalidraw elements which could be then rendered on the canvas. Hence calling this function is necessary before passing it to APIs like [`initialData`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/initialdata), [`updateScene`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#updatescene) if you are using the Skeleton API
|
||||
|
||||
## Supported Features
|
||||
|
||||
### Rectangle, Ellipse, and Diamond
|
||||
|
||||
To create these shapes you need to pass its `type` and `x` and `y` coordinates for position. The rest of the attributes are optional_.
|
||||
|
||||
For the Skeleton API to work, `convertToExcalidrawElements` needs to be called before passing it to Excalidraw Component via initialData, updateScene or any such API.
|
||||
|
||||
```jsx live
|
||||
function App() {
|
||||
const elements = convertToExcalidrawElements([
|
||||
{
|
||||
type: "rectangle",
|
||||
x: 100,
|
||||
y: 250,
|
||||
},
|
||||
{
|
||||
type: "ellipse",
|
||||
x: 250,
|
||||
y: 250,
|
||||
},
|
||||
{
|
||||
type: "diamond",
|
||||
x: 380,
|
||||
y: 250,
|
||||
},
|
||||
]);
|
||||
return (
|
||||
<div style={{ height: "500px" }}>
|
||||
<Excalidraw
|
||||
initialData={{
|
||||
elements,
|
||||
appState: { zenModeEnabled: true, viewBackgroundColor: "#a5d8ff" },
|
||||
scrollToContent: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You can pass additional [`properties`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L27) as well to decorate the shapes.
|
||||
|
||||
:::info
|
||||
|
||||
You can copy the below test examples and replace the elements in the live editor above to test it out.
|
||||
|
||||
:::
|
||||
|
||||
```js
|
||||
convertToExcalidrawElements([
|
||||
{
|
||||
type: "rectangle",
|
||||
x: 50,
|
||||
y: 250,
|
||||
width: 200,
|
||||
height: 100,
|
||||
backgroundColor: "#c0eb75",
|
||||
strokeWidth: 2,
|
||||
},
|
||||
{
|
||||
type: "ellipse",
|
||||
x: 300,
|
||||
y: 250,
|
||||
width: 200,
|
||||
height: 100,
|
||||
backgroundColor: "#ffc9c9",
|
||||
strokeStyle: "dotted",
|
||||
fillStyle: "solid",
|
||||
strokeWidth: 2,
|
||||
},
|
||||
{
|
||||
type: "diamond",
|
||||
x: 550,
|
||||
y: 250,
|
||||
width: 200,
|
||||
height: 100,
|
||||
backgroundColor: "#a5d8ff",
|
||||
strokeColor: "#1971c2",
|
||||
strokeStyle: "dashed",
|
||||
fillStyle: "cross-hatch",
|
||||
strokeWidth: 2,
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Text Element
|
||||
|
||||
The `type`, `x`, `y` and `text` properties are required to create a text element, rest of the attributes are optional
|
||||
|
||||
```js
|
||||
convertToExcalidrawElements([
|
||||
{
|
||||
type: "text",
|
||||
x: 100,
|
||||
y: 100,
|
||||
text: "HELLO WORLD!",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
x: 100,
|
||||
y: 150,
|
||||
text: "STYLED HELLO WORLD!",
|
||||
fontSize: 20,
|
||||
strokeColor: "#5f3dc4",
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Lines and Arrows
|
||||
|
||||
The `type`, `x`, and `y` properties are required, rest of the attributes are optional
|
||||
|
||||
```js
|
||||
convertToExcalidrawElements([
|
||||
{
|
||||
type: "arrow",
|
||||
x: 100,
|
||||
y: 20,
|
||||
},
|
||||
{
|
||||
type: "line",
|
||||
x: 100,
|
||||
y: 60,
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### With Addtional properties
|
||||
|
||||
```js
|
||||
convertToExcalidrawElements([
|
||||
{
|
||||
type: "arrow",
|
||||
x: 450,
|
||||
y: 20,
|
||||
startArrowhead: "dot",
|
||||
endArrowhead: "triangle",
|
||||
strokeColor: "#1971c2",
|
||||
strokeWidth: 2,
|
||||
},
|
||||
{
|
||||
type: "line",
|
||||
x: 450,
|
||||
y: 60,
|
||||
strokeColor: "#2f9e44",
|
||||
strokeWidth: 2,
|
||||
strokeStyle: "dotted",
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Text Containers
|
||||
|
||||
In addition to `type`, `x` and `y` properties, [`label`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L124C7-L130C59) property is required for text containers. The `text` property in `label` is required, rest of the attributes are optional.
|
||||
|
||||
If you don't provide the dimensions of container, we calculate it based of the label dimensions.
|
||||
|
||||
```js
|
||||
convertToExcalidrawElements([
|
||||
{
|
||||
type: "rectangle",
|
||||
x: 300,
|
||||
y: 290,
|
||||
label: {
|
||||
text: "RECTANGLE TEXT CONTAINER",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "ellipse",
|
||||
x: 500,
|
||||
y: 100,
|
||||
label: {
|
||||
text: "ELLIPSE\n TEXT CONTAINER",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "diamond",
|
||||
x: 100,
|
||||
y: 100,
|
||||
label: {
|
||||
text: "DIAMOND\nTEXT CONTAINER",
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### With Additional properties
|
||||
|
||||
```js
|
||||
convertToExcalidrawElements([
|
||||
{
|
||||
type: "diamond",
|
||||
x: -120,
|
||||
y: 100,
|
||||
width: 270,
|
||||
backgroundColor: "#fff3bf",
|
||||
strokeWidth: 2,
|
||||
label: {
|
||||
text: "STYLED DIAMOND TEXT CONTAINER",
|
||||
strokeColor: "#099268",
|
||||
fontSize: 20,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "rectangle",
|
||||
x: 180,
|
||||
y: 150,
|
||||
width: 200,
|
||||
strokeColor: "#c2255c",
|
||||
label: {
|
||||
text: "TOP LEFT ALIGNED RECTANGLE TEXT CONTAINER",
|
||||
textAlign: "left",
|
||||
verticalAlign: "top",
|
||||
fontSize: 20,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "ellipse",
|
||||
x: 400,
|
||||
y: 130,
|
||||
strokeColor: "#f08c00",
|
||||
backgroundColor: "#ffec99",
|
||||
width: 200,
|
||||
label: {
|
||||
text: "STYLED ELLIPSE TEXT CONTAINER",
|
||||
strokeColor: "#c2255c",
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Labelled Arrows
|
||||
|
||||
Similar to Text Containers, you can create labelled arrows as well.
|
||||
|
||||
```js
|
||||
convertToExcalidrawElements([
|
||||
{
|
||||
type: "arrow",
|
||||
x: 100,
|
||||
y: 100,
|
||||
label: {
|
||||
text: "LABELED ARROW",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "arrow",
|
||||
x: 100,
|
||||
y: 200,
|
||||
label: {
|
||||
text: "STYLED LABELED ARROW",
|
||||
strokeColor: "#099268",
|
||||
fontSize: 20,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "arrow",
|
||||
x: 100,
|
||||
y: 300,
|
||||
strokeColor: "#1098ad",
|
||||
strokeWidth: 2,
|
||||
label: {
|
||||
text: "ANOTHER STYLED LABELLED ARROW",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "arrow",
|
||||
x: 100,
|
||||
y: 400,
|
||||
strokeColor: "#1098ad",
|
||||
strokeWidth: 2,
|
||||
label: {
|
||||
text: "ANOTHER STYLED LABELLED ARROW",
|
||||
strokeColor: "#099268",
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Arrow bindings
|
||||
|
||||
To bind arrow to a shape you need to specify its [`start`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L86) and [`end`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L54) properties. You need to pass either `type` or `id` property in `start` and `end` properties, rest of the attributes are optional
|
||||
|
||||
```js
|
||||
convertToExcalidrawElements([
|
||||
{
|
||||
type: "arrow",
|
||||
x: 255,
|
||||
y: 239,
|
||||
label: {
|
||||
text: "HELLO WORLD!!",
|
||||
},
|
||||
start: {
|
||||
type: "rectangle",
|
||||
},
|
||||
end: {
|
||||
type: "ellipse",
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
When position for `start` and `end ` properties are not specified, we compute it according to arrow position.
|
||||
|
||||

|
||||
|
||||
```js
|
||||
convertToExcalidrawElements([
|
||||
{
|
||||
type: "arrow",
|
||||
x: 255,
|
||||
y: 239,
|
||||
label: {
|
||||
text: "HELLO WORLD!!",
|
||||
},
|
||||
start: {
|
||||
type: "text",
|
||||
text: "HEYYYYY",
|
||||
},
|
||||
end: {
|
||||
type: "text",
|
||||
text: "WHATS UP ?",
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### When passing `id`
|
||||
|
||||
Useful when you want to bind multiple arrows to one diagram / use some existing diagram
|
||||
|
||||
```js
|
||||
convertToExcalidrawElements([
|
||||
{
|
||||
type: "ellipse",
|
||||
id: "ellipse-1",
|
||||
strokeColor: "#66a80f",
|
||||
x: 390,
|
||||
y: 356,
|
||||
width: 150,
|
||||
height: 150,
|
||||
backgroundColor: "#d8f5a2",
|
||||
},
|
||||
{
|
||||
type: "diamond",
|
||||
id: "diamond-1",
|
||||
strokeColor: "#9c36b5",
|
||||
width: 100,
|
||||
x: -30,
|
||||
y: 380,
|
||||
},
|
||||
{
|
||||
type: "arrow",
|
||||
x: 100,
|
||||
y: 440,
|
||||
width: 295,
|
||||
height: 35,
|
||||
strokeColor: "#1864ab",
|
||||
start: {
|
||||
type: "rectangle",
|
||||
width: 150,
|
||||
height: 150,
|
||||
},
|
||||
end: {
|
||||
id: "ellipse-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "arrow",
|
||||
x: 60,
|
||||
y: 420,
|
||||
width: 330,
|
||||
strokeColor: "#e67700",
|
||||
start: {
|
||||
id: "diamond-1",
|
||||
},
|
||||
end: {
|
||||
id: "ellipse-1",
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||

|
@ -18,7 +18,7 @@
|
||||
"@docusaurus/core": "2.2.0",
|
||||
"@docusaurus/preset-classic": "2.2.0",
|
||||
"@docusaurus/theme-live-codeblock": "2.2.0",
|
||||
"@excalidraw/excalidraw": "0.15.2-6546-3398d86",
|
||||
"@excalidraw/excalidraw": "0.15.2-eb020d0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"docusaurus-plugin-sass": "0.2.3",
|
||||
|
@ -81,12 +81,8 @@ const sidebars = {
|
||||
"@excalidraw/excalidraw/api/utils/restore",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Constants",
|
||||
link: { type: "doc", id: "@excalidraw/excalidraw/api/constants" },
|
||||
items: [],
|
||||
},
|
||||
"@excalidraw/excalidraw/api/constants",
|
||||
"@excalidraw/excalidraw/api/excalidraw-element-skeleton",
|
||||
],
|
||||
},
|
||||
"@excalidraw/excalidraw/faq",
|
||||
|
@ -1631,10 +1631,10 @@
|
||||
url-loader "^4.1.1"
|
||||
webpack "^5.73.0"
|
||||
|
||||
"@excalidraw/excalidraw@0.15.2-6546-3398d86":
|
||||
version "0.15.2-6546-3398d86"
|
||||
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.15.2-6546-3398d86.tgz#e74d5ad944b8b414924d27ee91469a32b4f08dbf"
|
||||
integrity sha512-Tzq6qighJUytXRA8iMzQ8onoGclo9CuvPSw7DMvPxME8nxAxn5CeK/gsxIs3zwooj9CC6XF42BSrx0+n+fPxaQ==
|
||||
"@excalidraw/excalidraw@0.15.2-eb020d0":
|
||||
version "0.15.2-eb020d0"
|
||||
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.15.2-eb020d0.tgz#25bd61e6f23da7c084fb16a3e0fe0dd9ad8e6533"
|
||||
integrity sha512-TKGLzpOVqFQcwK1GFKTDXgg1s2U6tc5KE3qXuv87osbzOtftQn3x4+VH61vwdj11l00nEN80SMdXUC43T9uJqQ==
|
||||
|
||||
"@hapi/hoek@^9.0.0":
|
||||
version "9.3.0"
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { debounce, getVersion, nFormatter } from "../utils";
|
||||
import { debounce, getVersion, nFormatter } from "../src/utils";
|
||||
import {
|
||||
getElementsStorageSize,
|
||||
getTotalStorageSize,
|
||||
} from "./data/localStorage";
|
||||
import { DEFAULT_VERSION } from "../constants";
|
||||
import { t } from "../i18n";
|
||||
import { copyTextToSystemClipboard } from "../clipboard";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { UIAppState } from "../types";
|
||||
import { DEFAULT_VERSION } from "../src/constants";
|
||||
import { t } from "../src/i18n";
|
||||
import { copyTextToSystemClipboard } from "../src/clipboard";
|
||||
import { NonDeletedExcalidrawElement } from "../src/element/types";
|
||||
import { UIAppState } from "../src/types";
|
||||
|
||||
type StorageSizes = { scene: number; total: number };
|
||||
|
@ -1,23 +1,23 @@
|
||||
import throttle from "lodash.throttle";
|
||||
import { PureComponent } from "react";
|
||||
import { ExcalidrawImperativeAPI } from "../../types";
|
||||
import { ErrorDialog } from "../../components/ErrorDialog";
|
||||
import { APP_NAME, ENV, EVENT } from "../../constants";
|
||||
import { ImportedDataState } from "../../data/types";
|
||||
import { ExcalidrawImperativeAPI } from "../../src/types";
|
||||
import { ErrorDialog } from "../../src/components/ErrorDialog";
|
||||
import { APP_NAME, ENV, EVENT } from "../../src/constants";
|
||||
import { ImportedDataState } from "../../src/data/types";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
InitializedExcalidrawImageElement,
|
||||
} from "../../element/types";
|
||||
} from "../../src/element/types";
|
||||
import {
|
||||
getSceneVersion,
|
||||
restoreElements,
|
||||
} from "../../packages/excalidraw/index";
|
||||
import { Collaborator, Gesture } from "../../types";
|
||||
} from "../../src/packages/excalidraw/index";
|
||||
import { Collaborator, Gesture } from "../../src/types";
|
||||
import {
|
||||
preventUnload,
|
||||
resolvablePromise,
|
||||
withBatchedUpdates,
|
||||
} from "../../utils";
|
||||
} from "../../src/utils";
|
||||
import {
|
||||
CURSOR_SYNC_TIMEOUT,
|
||||
FILE_UPLOAD_MAX_BYTES,
|
||||
@ -48,25 +48,25 @@ import {
|
||||
} from "../data/localStorage";
|
||||
import Portal from "./Portal";
|
||||
import RoomDialog from "./RoomDialog";
|
||||
import { t } from "../../i18n";
|
||||
import { UserIdleState } from "../../types";
|
||||
import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../constants";
|
||||
import { t } from "../../src/i18n";
|
||||
import { UserIdleState } from "../../src/types";
|
||||
import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../src/constants";
|
||||
import {
|
||||
encodeFilesForUpload,
|
||||
FileManager,
|
||||
updateStaleImageStatuses,
|
||||
} from "../data/FileManager";
|
||||
import { AbortError } from "../../errors";
|
||||
import { AbortError } from "../../src/errors";
|
||||
import {
|
||||
isImageElement,
|
||||
isInitializedImageElement,
|
||||
} from "../../element/typeChecks";
|
||||
import { newElementWith } from "../../element/mutateElement";
|
||||
} from "../../src/element/typeChecks";
|
||||
import { newElementWith } from "../../src/element/mutateElement";
|
||||
import {
|
||||
ReconciledElements,
|
||||
reconcileElements as _reconcileElements,
|
||||
} from "./reconciliation";
|
||||
import { decryptData } from "../../data/encryption";
|
||||
import { decryptData } from "../../src/data/encryption";
|
||||
import { resetBrowserStateVersions } from "../data/tabSync";
|
||||
import { LocalData } from "../data/LocalData";
|
||||
import { atom, useAtom } from "jotai";
|
@ -6,19 +6,19 @@ import {
|
||||
|
||||
import { TCollabClass } from "./Collab";
|
||||
|
||||
import { ExcalidrawElement } from "../../element/types";
|
||||
import { ExcalidrawElement } from "../../src/element/types";
|
||||
import {
|
||||
WS_EVENTS,
|
||||
FILE_UPLOAD_TIMEOUT,
|
||||
WS_SCENE_EVENT_TYPES,
|
||||
} from "../app_constants";
|
||||
import { UserIdleState } from "../../types";
|
||||
import { trackEvent } from "../../analytics";
|
||||
import { UserIdleState } from "../../src/types";
|
||||
import { trackEvent } from "../../src/analytics";
|
||||
import throttle from "lodash.throttle";
|
||||
import { newElementWith } from "../../element/mutateElement";
|
||||
import { newElementWith } from "../../src/element/mutateElement";
|
||||
import { BroadcastedExcalidrawElement } from "./reconciliation";
|
||||
import { encryptData } from "../../data/encryption";
|
||||
import { PRECEDING_ELEMENT_KEY } from "../../constants";
|
||||
import { encryptData } from "../../src/data/encryption";
|
||||
import { PRECEDING_ELEMENT_KEY } from "../../src/constants";
|
||||
|
||||
class Portal {
|
||||
collab: TCollabClass;
|
@ -1,4 +1,4 @@
|
||||
@import "../../css/variables.module";
|
||||
@import "../../src/css/variables.module";
|
||||
|
||||
.excalidraw {
|
||||
.RoomDialog {
|
@ -1,13 +1,13 @@
|
||||
import { useRef, useState } from "react";
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
|
||||
import { copyTextToSystemClipboard } from "../../clipboard";
|
||||
import { trackEvent } from "../../analytics";
|
||||
import { getFrame } from "../../utils";
|
||||
import { useI18n } from "../../i18n";
|
||||
import { KEYS } from "../../keys";
|
||||
import { copyTextToSystemClipboard } from "../../src/clipboard";
|
||||
import { trackEvent } from "../../src/analytics";
|
||||
import { getFrame } from "../../src/utils";
|
||||
import { useI18n } from "../../src/i18n";
|
||||
import { KEYS } from "../../src/keys";
|
||||
|
||||
import { Dialog } from "../../components/Dialog";
|
||||
import { Dialog } from "../../src/components/Dialog";
|
||||
import {
|
||||
copyIcon,
|
||||
playerPlayIcon,
|
||||
@ -16,11 +16,11 @@ import {
|
||||
shareIOS,
|
||||
shareWindows,
|
||||
tablerCheckIcon,
|
||||
} from "../../components/icons";
|
||||
import { TextField } from "../../components/TextField";
|
||||
import { FilledButton } from "../../components/FilledButton";
|
||||
} from "../../src/components/icons";
|
||||
import { TextField } from "../../src/components/TextField";
|
||||
import { FilledButton } from "../../src/components/FilledButton";
|
||||
|
||||
import { ReactComponent as CollabImage } from "../../assets/lock.svg";
|
||||
import { ReactComponent as CollabImage } from "../../src/assets/lock.svg";
|
||||
import "./RoomDialog.scss";
|
||||
|
||||
const getShareIcon = () => {
|
@ -1,7 +1,7 @@
|
||||
import { PRECEDING_ELEMENT_KEY } from "../../constants";
|
||||
import { ExcalidrawElement } from "../../element/types";
|
||||
import { AppState } from "../../types";
|
||||
import { arrayToMapWithIndex } from "../../utils";
|
||||
import { PRECEDING_ELEMENT_KEY } from "../../src/constants";
|
||||
import { ExcalidrawElement } from "../../src/element/types";
|
||||
import { AppState } from "../../src/types";
|
||||
import { arrayToMapWithIndex } from "../../src/utils";
|
||||
|
||||
export type ReconciledElements = readonly ExcalidrawElement[] & {
|
||||
_brand: "reconciledElements";
|
@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { Footer } from "../../packages/excalidraw/index";
|
||||
import { Footer } from "../../src/packages/excalidraw/index";
|
||||
import { EncryptedIcon } from "./EncryptedIcon";
|
||||
import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
|
||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||
|
||||
export const AppFooter = React.memo(() => {
|
||||
return (
|
||||
@ -13,8 +14,11 @@ export const AppFooter = React.memo(() => {
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{isExcalidrawPlusSignedUser ? (
|
||||
<ExcalidrawPlusAppLink />
|
||||
) : (
|
||||
<EncryptedIcon />
|
||||
)}
|
||||
</div>
|
||||
</Footer>
|
||||
);
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { PlusPromoIcon } from "../../components/icons";
|
||||
import { MainMenu } from "../../packages/excalidraw/index";
|
||||
import { PlusPromoIcon } from "../../src/components/icons";
|
||||
import { MainMenu } from "../../src/packages/excalidraw/index";
|
||||
import { LanguageList } from "./LanguageList";
|
||||
|
||||
export const AppMainMenu: React.FC<{
|
@ -1,8 +1,9 @@
|
||||
import React from "react";
|
||||
import { PlusPromoIcon } from "../../components/icons";
|
||||
import { useI18n } from "../../i18n";
|
||||
import { WelcomeScreen } from "../../packages/excalidraw/index";
|
||||
import { PlusPromoIcon } from "../../src/components/icons";
|
||||
import { useI18n } from "../../src/i18n";
|
||||
import { WelcomeScreen } from "../../src/packages/excalidraw/index";
|
||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||
import { POINTER_EVENTS } from "../../src/constants";
|
||||
|
||||
export const AppWelcomeScreen: React.FC<{
|
||||
setCollabDialogShown: (toggle: boolean) => any;
|
||||
@ -18,7 +19,7 @@ export const AppWelcomeScreen: React.FC<{
|
||||
if (bit === "Excalidraw+") {
|
||||
return (
|
||||
<a
|
||||
style={{ pointerEvents: "all" }}
|
||||
style={{ pointerEvents: POINTER_EVENTS.inheritFromUI }}
|
||||
href={`${
|
||||
import.meta.env.VITE_APP_PLUS_APP
|
||||
}?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenSignedInUser`}
|
@ -1,6 +1,6 @@
|
||||
import { shield } from "../../components/icons";
|
||||
import { Tooltip } from "../../components/Tooltip";
|
||||
import { useI18n } from "../../i18n";
|
||||
import { shield } from "../../src/components/icons";
|
||||
import { Tooltip } from "../../src/components/Tooltip";
|
||||
import { useI18n } from "../../src/i18n";
|
||||
|
||||
export const EncryptedIcon = () => {
|
||||
const { t } = useI18n();
|
@ -1,20 +1,20 @@
|
||||
import React from "react";
|
||||
import { Card } from "../../components/Card";
|
||||
import { ToolButton } from "../../components/ToolButton";
|
||||
import { serializeAsJSON } from "../../data/json";
|
||||
import { Card } from "../../src/components/Card";
|
||||
import { ToolButton } from "../../src/components/ToolButton";
|
||||
import { serializeAsJSON } from "../../src/data/json";
|
||||
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
|
||||
import { FileId, NonDeletedExcalidrawElement } from "../../element/types";
|
||||
import { AppState, BinaryFileData, BinaryFiles } from "../../types";
|
||||
import { FileId, NonDeletedExcalidrawElement } from "../../src/element/types";
|
||||
import { AppState, BinaryFileData, BinaryFiles } from "../../src/types";
|
||||
import { nanoid } from "nanoid";
|
||||
import { useI18n } from "../../i18n";
|
||||
import { encryptData, generateEncryptionKey } from "../../data/encryption";
|
||||
import { isInitializedImageElement } from "../../element/typeChecks";
|
||||
import { useI18n } from "../../src/i18n";
|
||||
import { encryptData, generateEncryptionKey } from "../../src/data/encryption";
|
||||
import { isInitializedImageElement } from "../../src/element/typeChecks";
|
||||
import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
|
||||
import { encodeFilesForUpload } from "../data/FileManager";
|
||||
import { MIME_TYPES } from "../../constants";
|
||||
import { trackEvent } from "../../analytics";
|
||||
import { getFrame } from "../../utils";
|
||||
import { ExcalidrawLogo } from "../../components/ExcalidrawLogo";
|
||||
import { MIME_TYPES } from "../../src/constants";
|
||||
import { trackEvent } from "../../src/analytics";
|
||||
import { getFrame } from "../../src/utils";
|
||||
import { ExcalidrawLogo } from "../../src/components/ExcalidrawLogo";
|
||||
|
||||
export const exportToExcalidrawPlus = async (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
@ -1,7 +1,7 @@
|
||||
import oc from "open-color";
|
||||
import React from "react";
|
||||
import { THEME } from "../../constants";
|
||||
import { Theme } from "../../element/types";
|
||||
import { THEME } from "../../src/constants";
|
||||
import { Theme } from "../../src/element/types";
|
||||
|
||||
// https://github.com/tholman/github-corners
|
||||
export const GitHubCorner = React.memo(
|
@ -1,8 +1,8 @@
|
||||
import { useSetAtom } from "jotai";
|
||||
import React from "react";
|
||||
import { appLangCodeAtom } from "..";
|
||||
import { useI18n } from "../../i18n";
|
||||
import { languages } from "../../i18n";
|
||||
import { useI18n } from "../../src/i18n";
|
||||
import { languages } from "../../src/i18n";
|
||||
|
||||
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
|
||||
const { t, langCode } = useI18n();
|
@ -1,19 +1,19 @@
|
||||
import { compressData } from "../../data/encode";
|
||||
import { newElementWith } from "../../element/mutateElement";
|
||||
import { isInitializedImageElement } from "../../element/typeChecks";
|
||||
import { compressData } from "../../src/data/encode";
|
||||
import { newElementWith } from "../../src/element/mutateElement";
|
||||
import { isInitializedImageElement } from "../../src/element/typeChecks";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawImageElement,
|
||||
FileId,
|
||||
InitializedExcalidrawImageElement,
|
||||
} from "../../element/types";
|
||||
import { t } from "../../i18n";
|
||||
} from "../../src/element/types";
|
||||
import { t } from "../../src/i18n";
|
||||
import {
|
||||
BinaryFileData,
|
||||
BinaryFileMetadata,
|
||||
ExcalidrawImperativeAPI,
|
||||
BinaryFiles,
|
||||
} from "../../types";
|
||||
} from "../../src/types";
|
||||
|
||||
export class FileManager {
|
||||
/** files being fetched */
|
@ -11,11 +11,11 @@
|
||||
*/
|
||||
|
||||
import { createStore, entries, del, getMany, set, setMany } from "idb-keyval";
|
||||
import { clearAppStateForLocalStorage } from "../../appState";
|
||||
import { clearElementsForLocalStorage } from "../../element";
|
||||
import { ExcalidrawElement, FileId } from "../../element/types";
|
||||
import { AppState, BinaryFileData, BinaryFiles } from "../../types";
|
||||
import { debounce } from "../../utils";
|
||||
import { clearAppStateForLocalStorage } from "../../src/appState";
|
||||
import { clearElementsForLocalStorage } from "../../src/element";
|
||||
import { ExcalidrawElement, FileId } from "../../src/element/types";
|
||||
import { AppState, BinaryFileData, BinaryFiles } from "../../src/types";
|
||||
import { debounce } from "../../src/utils";
|
||||
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
|
||||
import { FileManager } from "./FileManager";
|
||||
import { Locker } from "./Locker";
|
@ -1,20 +1,20 @@
|
||||
import { ExcalidrawElement, FileId } from "../../element/types";
|
||||
import { getSceneVersion } from "../../element";
|
||||
import { ExcalidrawElement, FileId } from "../../src/element/types";
|
||||
import { getSceneVersion } from "../../src/element";
|
||||
import Portal from "../collab/Portal";
|
||||
import { restoreElements } from "../../data/restore";
|
||||
import { restoreElements } from "../../src/data/restore";
|
||||
import {
|
||||
AppState,
|
||||
BinaryFileData,
|
||||
BinaryFileMetadata,
|
||||
DataURL,
|
||||
} from "../../types";
|
||||
} from "../../src/types";
|
||||
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
|
||||
import { decompressData } from "../../data/encode";
|
||||
import { encryptData, decryptData } from "../../data/encryption";
|
||||
import { MIME_TYPES } from "../../constants";
|
||||
import { decompressData } from "../../src/data/encode";
|
||||
import { encryptData, decryptData } from "../../src/data/encryption";
|
||||
import { MIME_TYPES } from "../../src/constants";
|
||||
import { reconcileElements } from "../collab/reconciliation";
|
||||
import { getSyncableElements, SyncableExcalidrawElement } from ".";
|
||||
import { ResolutionType } from "../../utility-types";
|
||||
import { ResolutionType } from "../../src/utility-types";
|
||||
|
||||
// private
|
||||
// -----------------------------------------------------------------------------
|
@ -1,23 +1,23 @@
|
||||
import { compressData, decompressData } from "../../data/encode";
|
||||
import { compressData, decompressData } from "../../src/data/encode";
|
||||
import {
|
||||
decryptData,
|
||||
generateEncryptionKey,
|
||||
IV_LENGTH_BYTES,
|
||||
} from "../../data/encryption";
|
||||
import { serializeAsJSON } from "../../data/json";
|
||||
import { restore } from "../../data/restore";
|
||||
import { ImportedDataState } from "../../data/types";
|
||||
import { isInvisiblySmallElement } from "../../element/sizeHelpers";
|
||||
import { isInitializedImageElement } from "../../element/typeChecks";
|
||||
import { ExcalidrawElement, FileId } from "../../element/types";
|
||||
import { t } from "../../i18n";
|
||||
} from "../../src/data/encryption";
|
||||
import { serializeAsJSON } from "../../src/data/json";
|
||||
import { restore } from "../../src/data/restore";
|
||||
import { ImportedDataState } from "../../src/data/types";
|
||||
import { isInvisiblySmallElement } from "../../src/element/sizeHelpers";
|
||||
import { isInitializedImageElement } from "../../src/element/typeChecks";
|
||||
import { ExcalidrawElement, FileId } from "../../src/element/types";
|
||||
import { t } from "../../src/i18n";
|
||||
import {
|
||||
AppState,
|
||||
BinaryFileData,
|
||||
BinaryFiles,
|
||||
UserIdleState,
|
||||
} from "../../types";
|
||||
import { bytesToHexString } from "../../utils";
|
||||
} from "../../src/types";
|
||||
import { bytesToHexString } from "../../src/utils";
|
||||
import {
|
||||
DELETED_ELEMENT_TIMEOUT,
|
||||
FILE_UPLOAD_MAX_BYTES,
|
@ -1,12 +1,12 @@
|
||||
import { ExcalidrawElement } from "../../element/types";
|
||||
import { AppState } from "../../types";
|
||||
import { ExcalidrawElement } from "../../src/element/types";
|
||||
import { AppState } from "../../src/types";
|
||||
import {
|
||||
clearAppStateForLocalStorage,
|
||||
getDefaultAppState,
|
||||
} from "../../appState";
|
||||
import { clearElementsForLocalStorage } from "../../element";
|
||||
} from "../../src/appState";
|
||||
import { clearElementsForLocalStorage } from "../../src/element";
|
||||
import { STORAGE_KEYS } from "../app_constants";
|
||||
import { ImportedDataState } from "../../data/types";
|
||||
import { ImportedDataState } from "../../src/data/types";
|
||||
|
||||
export const saveUsernameToLocalStorage = (username: string) => {
|
||||
try {
|
@ -77,13 +77,14 @@
|
||||
align-items: center;
|
||||
border: 1px solid var(--color-primary);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: var(--space-factor);
|
||||
border-radius: var(--border-radius-lg);
|
||||
background-color: var(--island-bg-color);
|
||||
color: var(--color-primary) !important;
|
||||
text-decoration: none !important;
|
||||
|
||||
font-size: 0.75rem;
|
||||
box-sizing: border-box;
|
||||
height: var(--default-button-size);
|
||||
height: var(--lg-button-size);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary);
|
@ -1,32 +1,32 @@
|
||||
import polyfill from "../polyfill";
|
||||
import polyfill from "../src/polyfill";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { trackEvent } from "../analytics";
|
||||
import { getDefaultAppState } from "../appState";
|
||||
import { ErrorDialog } from "../components/ErrorDialog";
|
||||
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
||||
import { useSubtypes } from "../element/subtypes/use";
|
||||
import { trackEvent } from "../src/analytics";
|
||||
import { getDefaultAppState } from "../src/appState";
|
||||
import { ErrorDialog } from "../src/components/ErrorDialog";
|
||||
import { TopErrorBoundary } from "../src/components/TopErrorBoundary";
|
||||
import { useSubtypes } from "../src/element/subtypes/use";
|
||||
import {
|
||||
APP_NAME,
|
||||
EVENT,
|
||||
THEME,
|
||||
TITLE_TIMEOUT,
|
||||
VERSION_TIMEOUT,
|
||||
} from "../constants";
|
||||
import { loadFromBlob } from "../data/blob";
|
||||
} from "../src/constants";
|
||||
import { loadFromBlob } from "../src/data/blob";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
FileId,
|
||||
NonDeletedExcalidrawElement,
|
||||
Theme,
|
||||
} from "../element/types";
|
||||
import { useCallbackRefState } from "../hooks/useCallbackRefState";
|
||||
import { t } from "../i18n";
|
||||
} from "../src/element/types";
|
||||
import { useCallbackRefState } from "../src/hooks/useCallbackRefState";
|
||||
import { t } from "../src/i18n";
|
||||
import {
|
||||
Excalidraw,
|
||||
defaultLang,
|
||||
LiveCollaborationTrigger,
|
||||
} from "../packages/excalidraw/index";
|
||||
} from "../src/packages/excalidraw/index";
|
||||
import {
|
||||
AppState,
|
||||
LibraryItems,
|
||||
@ -34,7 +34,7 @@ import {
|
||||
BinaryFiles,
|
||||
ExcalidrawInitialDataState,
|
||||
UIAppState,
|
||||
} from "../types";
|
||||
} from "../src/types";
|
||||
import {
|
||||
debounce,
|
||||
getVersion,
|
||||
@ -44,7 +44,7 @@ import {
|
||||
ResolvablePromise,
|
||||
resolvablePromise,
|
||||
isRunningInIframe,
|
||||
} from "../utils";
|
||||
} from "../src/utils";
|
||||
import {
|
||||
FIREBASE_STORAGE_PREFIXES,
|
||||
STORAGE_KEYS,
|
||||
@ -69,33 +69,40 @@ import {
|
||||
importUsernameFromLocalStorage,
|
||||
} from "./data/localStorage";
|
||||
import CustomStats from "./CustomStats";
|
||||
import { restore, restoreAppState, RestoredDataState } from "../data/restore";
|
||||
import {
|
||||
restore,
|
||||
restoreAppState,
|
||||
RestoredDataState,
|
||||
} from "../src/data/restore";
|
||||
import {
|
||||
ExportToExcalidrawPlus,
|
||||
exportToExcalidrawPlus,
|
||||
} from "./components/ExportToExcalidrawPlus";
|
||||
import { updateStaleImageStatuses } from "./data/FileManager";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { isInitializedImageElement } from "../element/typeChecks";
|
||||
import { newElementWith } from "../src/element/mutateElement";
|
||||
import { isInitializedImageElement } from "../src/element/typeChecks";
|
||||
import { loadFilesFromFirebase } from "./data/firebase";
|
||||
import { LocalData } from "./data/LocalData";
|
||||
import { isBrowserStorageStateNewer } from "./data/tabSync";
|
||||
import clsx from "clsx";
|
||||
import { reconcileElements } from "./collab/reconciliation";
|
||||
import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library";
|
||||
import {
|
||||
parseLibraryTokensFromUrl,
|
||||
useHandleLibrary,
|
||||
} from "../src/data/library";
|
||||
import { AppMainMenu } from "./components/AppMainMenu";
|
||||
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
|
||||
import { AppFooter } from "./components/AppFooter";
|
||||
import { atom, Provider, useAtom, useAtomValue } from "jotai";
|
||||
import { useAtomWithInitialValue } from "../jotai";
|
||||
import { useAtomWithInitialValue } from "../src/jotai";
|
||||
import { appJotaiStore } from "./app-jotai";
|
||||
|
||||
import "./index.scss";
|
||||
import { ResolutionType } from "../utility-types";
|
||||
import { ShareableLinkDialog } from "../components/ShareableLinkDialog";
|
||||
import { openConfirmModal } from "../components/OverwriteConfirm/OverwriteConfirmState";
|
||||
import { OverwriteConfirmDialog } from "../components/OverwriteConfirm/OverwriteConfirm";
|
||||
import Trans from "../components/Trans";
|
||||
import { ResolutionType } from "../src/utility-types";
|
||||
import { ShareableLinkDialog } from "../src/components/ShareableLinkDialog";
|
||||
import { openConfirmModal } from "../src/components/OverwriteConfirm/OverwriteConfirmState";
|
||||
import { OverwriteConfirmDialog } from "../src/components/OverwriteConfirm/OverwriteConfirm";
|
||||
import Trans from "../src/components/Trans";
|
||||
|
||||
polyfill();
|
||||
|
29
excalidraw-app/tests/LanguageList.test.tsx
Normal file
29
excalidraw-app/tests/LanguageList.test.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { defaultLang } from "../../src/i18n";
|
||||
import { UI } from "../../src/tests/helpers/ui";
|
||||
import { screen, fireEvent, waitFor, render } from "../../src/tests/test-utils";
|
||||
|
||||
import ExcalidrawApp from "../../excalidraw-app";
|
||||
|
||||
describe("Test LanguageList", () => {
|
||||
it("rerenders UI on language change", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
|
||||
// select rectangle tool to show properties menu
|
||||
UI.clickTool("rectangle");
|
||||
// english lang should display `thin` label
|
||||
expect(screen.queryByTitle(/thin/i)).not.toBeNull();
|
||||
fireEvent.click(document.querySelector(".dropdown-menu-button")!);
|
||||
|
||||
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
|
||||
target: { value: "de-DE" },
|
||||
});
|
||||
// switching to german, `thin` label should no longer exist
|
||||
await waitFor(() => expect(screen.queryByTitle(/thin/i)).toBeNull());
|
||||
// reset language
|
||||
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
|
||||
target: { value: defaultLang.code },
|
||||
});
|
||||
// switching back to English
|
||||
await waitFor(() => expect(screen.queryByTitle(/thin/i)).not.toBeNull());
|
||||
});
|
||||
});
|
@ -1,11 +1,11 @@
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import ExcalidrawApp from "../../excalidraw-app";
|
||||
import {
|
||||
mockBoundingClientRect,
|
||||
render,
|
||||
restoreOriginalGetBoundingClientRect,
|
||||
} from "./test-utils";
|
||||
} from "../../src/tests/test-utils";
|
||||
|
||||
import { UI } from "./helpers/ui";
|
||||
import { UI } from "../../src/tests/helpers/ui";
|
||||
|
||||
describe("Test MobileMenu", () => {
|
||||
const { h } = window;
|
257
excalidraw-app/tests/__snapshots__/MobileMenu.test.tsx.snap
Normal file
257
excalidraw-app/tests/__snapshots__/MobileMenu.test.tsx.snap
Normal file
File diff suppressed because one or more lines are too long
@ -1,8 +1,8 @@
|
||||
import { vi } from "vitest";
|
||||
import { render, updateSceneData, waitFor } from "./test-utils";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { API } from "./helpers/api";
|
||||
import { createUndoAction } from "../actions/actionHistory";
|
||||
import { render, updateSceneData, waitFor } from "../../src/tests/test-utils";
|
||||
import ExcalidrawApp from "../../excalidraw-app";
|
||||
import { API } from "../../src/tests/helpers/api";
|
||||
import { createUndoAction } from "../../src/actions/actionHistory";
|
||||
const { h } = window;
|
||||
|
||||
Object.defineProperty(window, "crypto", {
|
||||
@ -16,7 +16,7 @@ Object.defineProperty(window, "crypto", {
|
||||
},
|
||||
});
|
||||
|
||||
vi.mock("../excalidraw-app/data/index.ts", async (importActual) => {
|
||||
vi.mock("../../excalidraw-app/data/index.ts", async (importActual) => {
|
||||
const module = (await importActual()) as any;
|
||||
return {
|
||||
__esmodule: true,
|
||||
@ -27,7 +27,7 @@ vi.mock("../excalidraw-app/data/index.ts", async (importActual) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../excalidraw-app/data/firebase.ts", () => {
|
||||
vi.mock("../../excalidraw-app/data/firebase.ts", () => {
|
||||
const loadFromFirebase = async () => null;
|
||||
const saveToFirebase = () => {};
|
||||
const isSavedToFirebase = () => true;
|
@ -1,13 +1,13 @@
|
||||
import { expect } from "chai";
|
||||
import { PRECEDING_ELEMENT_KEY } from "../constants";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { PRECEDING_ELEMENT_KEY } from "../../src/constants";
|
||||
import { ExcalidrawElement } from "../../src/element/types";
|
||||
import {
|
||||
BroadcastedExcalidrawElement,
|
||||
ReconciledElements,
|
||||
reconcileElements,
|
||||
} from "../excalidraw-app/collab/reconciliation";
|
||||
import { randomInteger } from "../random";
|
||||
import { AppState } from "../types";
|
||||
} from "../../excalidraw-app/collab/reconciliation";
|
||||
import { randomInteger } from "../../src/random";
|
||||
import { AppState } from "../../src/types";
|
||||
|
||||
type Id = string;
|
||||
type ElementLike = {
|
@ -85,6 +85,7 @@ import {
|
||||
VERTICAL_ALIGN,
|
||||
YOUTUBE_STATES,
|
||||
ZOOM_STEP,
|
||||
POINTER_EVENTS,
|
||||
} from "../constants";
|
||||
import { exportCanvas, loadFromBlob } from "../data";
|
||||
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
||||
@ -886,7 +887,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||
width: isVisible ? `${el.width}px` : 0,
|
||||
height: isVisible ? `${el.height}px` : 0,
|
||||
transform: isVisible ? `rotate(${el.angle}rad)` : "none",
|
||||
pointerEvents: isActive ? "auto" : "none",
|
||||
pointerEvents: isActive
|
||||
? POINTER_EVENTS.enabled
|
||||
: POINTER_EVENTS.disabled,
|
||||
}}
|
||||
>
|
||||
{isHovered && (
|
||||
@ -1110,9 +1113,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
cursor: CURSOR_TYPE.MOVE,
|
||||
// disable all interaction (e.g. cursor change) when in view
|
||||
// mode
|
||||
pointerEvents: this.state.viewModeEnabled ? "none" : "all",
|
||||
pointerEvents: this.state.viewModeEnabled
|
||||
? POINTER_EVENTS.disabled
|
||||
: POINTER_EVENTS.inheritFromUI,
|
||||
}}
|
||||
onPointerDown={(event) => this.handleCanvasPointerDown(event)}
|
||||
onWheel={(event) => this.handleWheel(event)}
|
||||
@ -1154,6 +1157,16 @@ class App extends React.Component<AppProps, AppState> {
|
||||
"excalidraw--view-mode": this.state.viewModeEnabled,
|
||||
"excalidraw--mobile": this.device.isMobile,
|
||||
})}
|
||||
style={{
|
||||
["--ui-pointerEvents" as any]:
|
||||
this.state.selectionElement ||
|
||||
this.state.draggingElement ||
|
||||
this.state.resizingElement ||
|
||||
(this.state.editingElement &&
|
||||
!isTextElement(this.state.editingElement))
|
||||
? POINTER_EVENTS.disabled
|
||||
: POINTER_EVENTS.enabled,
|
||||
}}
|
||||
ref={this.excalidrawContainerRef}
|
||||
onDrop={this.handleAppOnDrop}
|
||||
tabIndex={0}
|
||||
@ -1346,7 +1359,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
private openEyeDropper = ({ type }: { type: "stroke" | "background" }) => {
|
||||
jotaiStore.set(activeEyeDropperAtom, {
|
||||
swapPreviewOnAlt: true,
|
||||
previewType: type === "stroke" ? "strokeColor" : "backgroundColor",
|
||||
colorPickerType:
|
||||
type === "stroke" ? "elementStroke" : "elementBackground",
|
||||
onSelect: (color, event) => {
|
||||
const shouldUpdateStrokeColor =
|
||||
(type === "background" && event.altKey) ||
|
||||
@ -1357,12 +1371,14 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.state.activeTool.type !== "selection"
|
||||
) {
|
||||
if (shouldUpdateStrokeColor) {
|
||||
this.setState({
|
||||
currentItemStrokeColor: color,
|
||||
this.syncActionResult({
|
||||
appState: { ...this.state, currentItemStrokeColor: color },
|
||||
commitToHistory: true,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
currentItemBackgroundColor: color,
|
||||
this.syncActionResult({
|
||||
appState: { ...this.state, currentItemBackgroundColor: color },
|
||||
commitToHistory: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@ -3975,7 +3991,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
scenePointerX,
|
||||
scenePointerY,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const [lastCommittedX, lastCommittedY] =
|
||||
@ -4792,7 +4808,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
origin,
|
||||
withCmdOrCtrl: event[KEYS.CTRL_OR_CMD],
|
||||
originInGrid: tupleToCoors(
|
||||
getGridPoint(origin.x, origin.y, this.state.gridSize),
|
||||
getGridPoint(
|
||||
origin.x,
|
||||
origin.y,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
),
|
||||
),
|
||||
scrollbars: isOverScrollBars(
|
||||
currentScrollBars,
|
||||
@ -5316,7 +5336,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
sceneY: number;
|
||||
link: string;
|
||||
}) => {
|
||||
const [gridX, gridY] = getGridPoint(sceneX, sceneY, this.state.gridSize);
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
sceneX,
|
||||
sceneY,
|
||||
this.lastPointerDown?.[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const embedLink = getEmbedLink(link);
|
||||
|
||||
@ -5362,7 +5386,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
sceneX: number;
|
||||
sceneY: number;
|
||||
}) => {
|
||||
const [gridX, gridY] = getGridPoint(sceneX, sceneY, this.state.gridSize);
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
sceneX,
|
||||
sceneY,
|
||||
this.lastPointerDown?.[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
||||
x: gridX,
|
||||
@ -5446,7 +5474,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
pointerDownState.origin.x,
|
||||
pointerDownState.origin.y,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
||||
@ -5540,7 +5568,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
pointerDownState.origin.x,
|
||||
pointerDownState.origin.y,
|
||||
this.state.gridSize,
|
||||
this.lastPointerDown?.[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
||||
@ -5599,7 +5627,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
pointerDownState.origin.x,
|
||||
pointerDownState.origin.y,
|
||||
this.state.gridSize,
|
||||
this.lastPointerDown?.[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const frame = newFrameElement({
|
||||
@ -5682,7 +5710,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
pointerCoords.x,
|
||||
pointerCoords.y,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
// for arrows/lines, don't start dragging until a given threshold
|
||||
@ -5728,6 +5756,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.state.selectedLinearElement,
|
||||
pointerCoords,
|
||||
this.state,
|
||||
!event[KEYS.CTRL_OR_CMD],
|
||||
);
|
||||
if (!ret) {
|
||||
return;
|
||||
@ -5853,7 +5882,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const [dragX, dragY] = getGridPoint(
|
||||
pointerCoords.x - pointerDownState.drag.offset.x,
|
||||
pointerCoords.y - pointerDownState.drag.offset.y,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const [dragDistanceX, dragDistanceY] = [
|
||||
@ -5920,7 +5949,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const [originDragX, originDragY] = getGridPoint(
|
||||
pointerDownState.origin.x - pointerDownState.drag.offset.x,
|
||||
pointerDownState.origin.y - pointerDownState.drag.offset.y,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
mutateElement(duplicatedElement, {
|
||||
x: duplicatedElement.x + (originDragX - dragX),
|
||||
@ -7713,7 +7742,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
pointerCoords.x,
|
||||
pointerCoords.y,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const image =
|
||||
@ -7782,7 +7811,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const [resizeX, resizeY] = getGridPoint(
|
||||
pointerCoords.x - pointerDownState.resize.offset.x,
|
||||
pointerCoords.y - pointerDownState.resize.offset.y,
|
||||
this.state.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
||||
);
|
||||
|
||||
const frameElementsOffsetsMap = new Map<
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { getColor } from "./ColorPicker";
|
||||
import { useAtom } from "jotai";
|
||||
import { activeColorPickerSectionAtom } from "./colorPickerUtils";
|
||||
import {
|
||||
ColorPickerType,
|
||||
activeColorPickerSectionAtom,
|
||||
} from "./colorPickerUtils";
|
||||
import { eyeDropperIcon } from "../icons";
|
||||
import { jotaiScope } from "../../jotai";
|
||||
import { KEYS } from "../../keys";
|
||||
@ -15,14 +18,14 @@ interface ColorInputProps {
|
||||
color: string;
|
||||
onChange: (color: string) => void;
|
||||
label: string;
|
||||
eyeDropperType: "strokeColor" | "backgroundColor";
|
||||
colorPickerType: ColorPickerType;
|
||||
}
|
||||
|
||||
export const ColorInput = ({
|
||||
color,
|
||||
onChange,
|
||||
label,
|
||||
eyeDropperType,
|
||||
colorPickerType,
|
||||
}: ColorInputProps) => {
|
||||
const device = useDevice();
|
||||
const [innerValue, setInnerValue] = useState(color);
|
||||
@ -116,7 +119,7 @@ export const ColorInput = ({
|
||||
: {
|
||||
keepOpenOnAlt: false,
|
||||
onSelect: (color) => onChange(color),
|
||||
previewType: eyeDropperType,
|
||||
colorPickerType,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -82,14 +82,7 @@ const ColorPickerPopupContent = ({
|
||||
const { container } = useExcalidrawContainer();
|
||||
const { isMobile, isLandscape } = useDevice();
|
||||
|
||||
const eyeDropperType =
|
||||
type === "canvasBackground"
|
||||
? undefined
|
||||
: type === "elementBackground"
|
||||
? "backgroundColor"
|
||||
: "strokeColor";
|
||||
|
||||
const colorInputJSX = eyeDropperType && (
|
||||
const colorInputJSX = (
|
||||
<div>
|
||||
<PickerHeading>{t("colorPicker.hexCode")}</PickerHeading>
|
||||
<ColorInput
|
||||
@ -98,7 +91,7 @@ const ColorPickerPopupContent = ({
|
||||
onChange={(color) => {
|
||||
onChange(color);
|
||||
}}
|
||||
eyeDropperType={eyeDropperType}
|
||||
colorPickerType={type}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -160,7 +153,7 @@ const ColorPickerPopupContent = ({
|
||||
"0px 7px 14px rgba(0, 0, 0, 0.05), 0px 0px 3.12708px rgba(0, 0, 0, 0.0798), 0px 0px 0.931014px rgba(0, 0, 0, 0.1702)",
|
||||
}}
|
||||
>
|
||||
{palette && eyeDropperType ? (
|
||||
{palette ? (
|
||||
<Picker
|
||||
palette={palette}
|
||||
color={color}
|
||||
@ -173,7 +166,7 @@ const ColorPickerPopupContent = ({
|
||||
state = state || {
|
||||
keepOpenOnAlt: true,
|
||||
onSelect: onChange,
|
||||
previewType: eyeDropperType,
|
||||
colorPickerType: type,
|
||||
};
|
||||
state.keepOpenOnAlt = true;
|
||||
return state;
|
||||
@ -184,7 +177,7 @@ const ColorPickerPopupContent = ({
|
||||
: {
|
||||
keepOpenOnAlt: false,
|
||||
onSelect: onChange,
|
||||
previewType: eyeDropperType,
|
||||
colorPickerType: type,
|
||||
};
|
||||
});
|
||||
}}
|
||||
|
@ -1,35 +1,47 @@
|
||||
import { atom } from "jotai";
|
||||
import { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { rgbToHex } from "../colors";
|
||||
import { EVENT } from "../constants";
|
||||
import { useUIAppState } from "../context/ui-appState";
|
||||
import { mutateElement } from "../element/mutateElement";
|
||||
import { useCreatePortalContainer } from "../hooks/useCreatePortalContainer";
|
||||
import { useOutsideClick } from "../hooks/useOutsideClick";
|
||||
import { KEYS } from "../keys";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import Scene from "../scene/Scene";
|
||||
import { ShapeCache } from "../scene/ShapeCache";
|
||||
import { useApp, useExcalidrawContainer, useExcalidrawElements } from "./App";
|
||||
import { useStable } from "../hooks/useStable";
|
||||
|
||||
import "./EyeDropper.scss";
|
||||
import { ColorPickerType } from "./ColorPicker/colorPickerUtils";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
|
||||
type EyeDropperProperties = {
|
||||
export type EyeDropperProperties = {
|
||||
keepOpenOnAlt: boolean;
|
||||
swapPreviewOnAlt?: boolean;
|
||||
/** called when user picks color (on pointerup) */
|
||||
onSelect: (color: string, event: PointerEvent) => void;
|
||||
previewType: "strokeColor" | "backgroundColor";
|
||||
/**
|
||||
* property of selected elements to update live when alt-dragging.
|
||||
* Supply `null` if not applicable (e.g. updating the canvas bg instead of
|
||||
* elements)
|
||||
**/
|
||||
colorPickerType: ColorPickerType;
|
||||
};
|
||||
|
||||
export const activeEyeDropperAtom = atom<null | EyeDropperProperties>(null);
|
||||
|
||||
export const EyeDropper: React.FC<{
|
||||
onCancel: () => void;
|
||||
onSelect: Required<EyeDropperProperties>["onSelect"];
|
||||
swapPreviewOnAlt?: EyeDropperProperties["swapPreviewOnAlt"];
|
||||
previewType: EyeDropperProperties["previewType"];
|
||||
}> = ({ onCancel, onSelect, swapPreviewOnAlt, previewType }) => {
|
||||
onSelect: EyeDropperProperties["onSelect"];
|
||||
/** called when color changes, on pointerdown for preview */
|
||||
onChange: (
|
||||
type: ColorPickerType,
|
||||
color: string,
|
||||
selectedElements: ExcalidrawElement[],
|
||||
event: { altKey: boolean },
|
||||
) => void;
|
||||
colorPickerType: EyeDropperProperties["colorPickerType"];
|
||||
}> = ({ onCancel, onChange, onSelect, colorPickerType }) => {
|
||||
const eyeDropperContainer = useCreatePortalContainer({
|
||||
className: "excalidraw-eye-dropper-backdrop",
|
||||
parentSelector: ".excalidraw-eye-dropper-container",
|
||||
@ -40,9 +52,13 @@ export const EyeDropper: React.FC<{
|
||||
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
|
||||
const metaStuffRef = useRef({ selectedElements, app });
|
||||
metaStuffRef.current.selectedElements = selectedElements;
|
||||
metaStuffRef.current.app = app;
|
||||
const stableProps = useStable({
|
||||
app,
|
||||
onCancel,
|
||||
onChange,
|
||||
onSelect,
|
||||
selectedElements,
|
||||
});
|
||||
|
||||
const { container: excalidrawContainer } = useExcalidrawContainer();
|
||||
|
||||
@ -90,28 +106,28 @@ export const EyeDropper: React.FC<{
|
||||
const currentColor = getCurrentColor({ clientX, clientY });
|
||||
|
||||
if (isHoldingPointerDown) {
|
||||
for (const element of metaStuffRef.current.selectedElements) {
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
[altKey && swapPreviewOnAlt
|
||||
? previewType === "strokeColor"
|
||||
? "backgroundColor"
|
||||
: "strokeColor"
|
||||
: previewType]: currentColor,
|
||||
},
|
||||
false,
|
||||
stableProps.onChange(
|
||||
colorPickerType,
|
||||
currentColor,
|
||||
stableProps.selectedElements,
|
||||
{ altKey },
|
||||
);
|
||||
ShapeCache.delete(element);
|
||||
}
|
||||
Scene.getScene(
|
||||
metaStuffRef.current.selectedElements[0],
|
||||
)?.informMutation();
|
||||
}
|
||||
|
||||
colorPreviewDiv.style.background = currentColor;
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
stableProps.onCancel();
|
||||
};
|
||||
|
||||
const onSelect: Required<EyeDropperProperties>["onSelect"] = (
|
||||
color,
|
||||
event,
|
||||
) => {
|
||||
stableProps.onSelect(color, event);
|
||||
};
|
||||
|
||||
const pointerDownListener = (event: PointerEvent) => {
|
||||
isHoldingPointerDown = true;
|
||||
// NOTE we can't event.preventDefault() as that would stop
|
||||
@ -148,8 +164,8 @@ export const EyeDropper: React.FC<{
|
||||
|
||||
// init color preview else it would show only after the first mouse move
|
||||
mouseMoveListener({
|
||||
clientX: metaStuffRef.current.app.lastViewportPosition.x,
|
||||
clientY: metaStuffRef.current.app.lastViewportPosition.y,
|
||||
clientX: stableProps.app.lastViewportPosition.x,
|
||||
clientY: stableProps.app.lastViewportPosition.y,
|
||||
altKey: false,
|
||||
});
|
||||
|
||||
@ -179,12 +195,10 @@ export const EyeDropper: React.FC<{
|
||||
window.removeEventListener(EVENT.BLUR, onCancel);
|
||||
};
|
||||
}, [
|
||||
stableProps,
|
||||
app.canvas,
|
||||
eyeDropperContainer,
|
||||
onCancel,
|
||||
onSelect,
|
||||
swapPreviewOnAlt,
|
||||
previewType,
|
||||
colorPickerType,
|
||||
excalidrawContainer,
|
||||
appState.offsetLeft,
|
||||
appState.offsetTop,
|
||||
|
@ -7,7 +7,7 @@
|
||||
}
|
||||
|
||||
.FixedSideContainer > * {
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
|
||||
.FixedSideContainer_side_top {
|
||||
|
@ -83,15 +83,20 @@ const getHints = ({ appState, isMobile, device, app }: HintViewerProps) => {
|
||||
if (activeTool.type === "selection") {
|
||||
if (
|
||||
appState.draggingElement?.type === "selection" &&
|
||||
!selectedElements.length &&
|
||||
!appState.editingElement &&
|
||||
!appState.editingLinearElement
|
||||
) {
|
||||
return t("hints.deepBoxSelect");
|
||||
}
|
||||
|
||||
if (appState.gridSize && appState.draggingElement) {
|
||||
return t("hints.disableSnapping");
|
||||
}
|
||||
|
||||
if (!selectedElements.length && !isMobile) {
|
||||
return t("hints.canvasPanning");
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedElements.length === 1) {
|
||||
if (isLinearElement(selectedElements[0])) {
|
||||
@ -102,10 +107,14 @@ const getHints = ({ appState, isMobile, device, app }: HintViewerProps) => {
|
||||
}
|
||||
return t("hints.lineEditor_info");
|
||||
}
|
||||
if (isTextBindableContainer(selectedElements[0])) {
|
||||
if (
|
||||
!appState.draggingElement &&
|
||||
isTextBindableContainer(selectedElements[0])
|
||||
) {
|
||||
return t("hints.bindTextToElement");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
@ -56,34 +56,52 @@
|
||||
}
|
||||
|
||||
.disable-zen-mode {
|
||||
height: 30px;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
bottom: 0;
|
||||
[dir="ltr"] & {
|
||||
right: 15px;
|
||||
right: 1rem;
|
||||
}
|
||||
[dir="rtl"] & {
|
||||
left: 15px;
|
||||
left: 1rem;
|
||||
}
|
||||
font-size: 10px;
|
||||
padding: 10px;
|
||||
font-weight: 500;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: visibility 0s linear 0s, opacity 0.5s;
|
||||
|
||||
font-family: var(--ui-font);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
|
||||
border-radius: var(--border-radius-lg);
|
||||
border: 1px solid var(--default-border-color);
|
||||
background-color: var(--island-bg-color);
|
||||
color: var(--text-primary-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-hover-bg);
|
||||
}
|
||||
&:active {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
&--visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: visibility 0s linear 300ms, opacity 0.5s;
|
||||
transition-delay: 0.8s;
|
||||
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
}
|
||||
|
||||
.layer-ui__wrapper__footer-left,
|
||||
.layer-ui__wrapper__footer-right,
|
||||
.disable-zen-mode--visible {
|
||||
pointer-events: all;
|
||||
.footer-center,
|
||||
.layer-ui__wrapper__footer-right {
|
||||
& > * {
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
}
|
||||
|
||||
.layer-ui__wrapper__footer-right {
|
||||
|
@ -2,7 +2,7 @@ import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { CLASSES, DEFAULT_SIDEBAR, LIBRARY_SIDEBAR_WIDTH } from "../constants";
|
||||
import { isTextElement, showSelectedShapeActions } from "../element";
|
||||
import { showSelectedShapeActions } from "../element";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { Language, t } from "../i18n";
|
||||
import { calculateScrollCenter } from "../scene";
|
||||
@ -52,6 +52,9 @@ import { EyeDropper, activeEyeDropperAtom } from "./EyeDropper";
|
||||
|
||||
import "./LayerUI.scss";
|
||||
import "./Toolbar.scss";
|
||||
import { mutateElement } from "../element/mutateElement";
|
||||
import { ShapeCache } from "../scene/ShapeCache";
|
||||
import Scene from "../scene/Scene";
|
||||
|
||||
interface LayerUIProps {
|
||||
actionManager: ActionManager;
|
||||
@ -368,11 +371,44 @@ const LayerUI = ({
|
||||
)}
|
||||
{eyeDropperState && !device.isMobile && (
|
||||
<EyeDropper
|
||||
swapPreviewOnAlt={eyeDropperState.swapPreviewOnAlt}
|
||||
previewType={eyeDropperState.previewType}
|
||||
colorPickerType={eyeDropperState.colorPickerType}
|
||||
onCancel={() => {
|
||||
setEyeDropperState(null);
|
||||
}}
|
||||
onChange={(colorPickerType, color, selectedElements, { altKey }) => {
|
||||
if (
|
||||
colorPickerType !== "elementBackground" &&
|
||||
colorPickerType !== "elementStroke"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedElements.length) {
|
||||
for (const element of selectedElements) {
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
[altKey && eyeDropperState.swapPreviewOnAlt
|
||||
? colorPickerType === "elementBackground"
|
||||
? "strokeColor"
|
||||
: "backgroundColor"
|
||||
: colorPickerType === "elementBackground"
|
||||
? "backgroundColor"
|
||||
: "strokeColor"]: color,
|
||||
},
|
||||
false,
|
||||
);
|
||||
ShapeCache.delete(element);
|
||||
}
|
||||
Scene.getScene(selectedElements[0])?.informMutation();
|
||||
} else if (colorPickerType === "elementBackground") {
|
||||
setAppState({
|
||||
currentItemBackgroundColor: color,
|
||||
});
|
||||
} else {
|
||||
setAppState({ currentItemStrokeColor: color });
|
||||
}
|
||||
}}
|
||||
onSelect={(color, event) => {
|
||||
setEyeDropperState((state) => {
|
||||
return state?.keepOpenOnAlt && event.altKey ? state : null;
|
||||
@ -427,13 +463,7 @@ const LayerUI = ({
|
||||
{!device.isMobile && (
|
||||
<>
|
||||
<div
|
||||
className={clsx("layer-ui__wrapper", {
|
||||
"disable-pointerEvents":
|
||||
appState.draggingElement ||
|
||||
appState.resizingElement ||
|
||||
(appState.editingElement &&
|
||||
!isTextElement(appState.editingElement)),
|
||||
})}
|
||||
className="layer-ui__wrapper"
|
||||
style={
|
||||
appState.openSidebar &&
|
||||
isSidebarDocked &&
|
||||
|
@ -17,6 +17,8 @@
|
||||
background-color: var(--sidebar-bg-color);
|
||||
box-shadow: var(--sidebar-shadow);
|
||||
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
|
||||
:root[dir="rtl"] & {
|
||||
left: 0;
|
||||
right: auto;
|
||||
|
@ -7,7 +7,7 @@
|
||||
right: 12px;
|
||||
font-size: 12px;
|
||||
z-index: 10;
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
|
||||
h3 {
|
||||
margin: 0 24px 8px 0;
|
||||
|
@ -26,7 +26,7 @@
|
||||
}
|
||||
|
||||
.UserList > * {
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
|
||||
.UserList_mobile {
|
||||
|
@ -73,7 +73,7 @@ const Footer = ({
|
||||
<FooterCenterTunnel.Out />
|
||||
<div
|
||||
className={clsx("layer-ui__wrapper__footer-right zen-mode-transition", {
|
||||
"transition-right disable-pointerEvents": appState.zenModeEnabled,
|
||||
"transition-right": appState.zenModeEnabled,
|
||||
})}
|
||||
>
|
||||
<div style={{ position: "relative" }}>
|
||||
|
@ -1,10 +1,11 @@
|
||||
.footer-center {
|
||||
pointer-events: none;
|
||||
& > * {
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
margin-inline-end: 0.6rem;
|
||||
}
|
||||
|
@ -161,7 +161,7 @@
|
||||
.welcome-screen-menu-item {
|
||||
box-sizing: border-box;
|
||||
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
|
||||
color: var(--color-gray-50);
|
||||
font-size: 0.875rem;
|
||||
@ -202,7 +202,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:active) .welcome-screen-menu-item:hover {
|
||||
.welcome-screen-menu-item:hover {
|
||||
text-decoration: none;
|
||||
background: var(--color-gray-10);
|
||||
|
||||
@ -246,7 +246,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:active) .welcome-screen-menu-item:hover {
|
||||
.welcome-screen-menu-item:hover {
|
||||
background: var(--color-gray-85);
|
||||
|
||||
.welcome-screen-menu-item__shortcut {
|
||||
|
@ -41,6 +41,14 @@ export const POINTER_BUTTON = {
|
||||
TOUCH: -1,
|
||||
} as const;
|
||||
|
||||
export const POINTER_EVENTS = {
|
||||
enabled: "all",
|
||||
disabled: "none",
|
||||
// asserted as any so it can be freely assigned to React Element
|
||||
// "pointerEnvets" CSS prop
|
||||
inheritFromUI: "var(--ui-pointerEvents)" as any,
|
||||
} as const;
|
||||
|
||||
export enum EVENT {
|
||||
COPY = "copy",
|
||||
PASTE = "paste",
|
||||
|
@ -253,7 +253,7 @@
|
||||
max-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
pointer-events: initial;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
|
||||
.panelColumn {
|
||||
padding: 8px 8px 0 8px;
|
||||
@ -301,7 +301,7 @@
|
||||
pointer-events: none !important;
|
||||
|
||||
& > * {
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,16 +312,16 @@
|
||||
cursor: default;
|
||||
pointer-events: none !important;
|
||||
|
||||
& > * {
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-gap: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.layer-ui__wrapper:not(.disable-pointerEvents) .App-menu_top > * {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.App-menu_top > *:first-child {
|
||||
justify-self: flex-start;
|
||||
}
|
||||
@ -425,17 +425,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.disable-zen-mode {
|
||||
border-radius: var(--border-radius-lg);
|
||||
background-color: var(--color-gray-20);
|
||||
border: 1px solid var(--color-gray-30);
|
||||
padding: 10px 20px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-gray-30);
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-back-to-content {
|
||||
border-radius: var(--border-radius-lg);
|
||||
background-color: var(--island-bg-color);
|
||||
@ -447,7 +436,7 @@
|
||||
left: 50%;
|
||||
bottom: 30px;
|
||||
transform: translateX(-50%);
|
||||
pointer-events: all;
|
||||
pointer-events: var(--ui-pointerEvents);
|
||||
font-family: inherit;
|
||||
|
||||
&:hover {
|
||||
|
@ -42,7 +42,7 @@ import {
|
||||
} from "./binding";
|
||||
import { tupleToCoors } from "../utils";
|
||||
import { isBindingElement } from "./typeChecks";
|
||||
import { shouldRotateWithDiscreteAngle } from "../keys";
|
||||
import { KEYS, shouldRotateWithDiscreteAngle } from "../keys";
|
||||
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
||||
import { DRAGGING_THRESHOLD } from "../constants";
|
||||
import { Mutable } from "../utility-types";
|
||||
@ -221,7 +221,7 @@ export class LinearElementEditor {
|
||||
element,
|
||||
referencePoint,
|
||||
[scenePointerX, scenePointerY],
|
||||
appState.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
);
|
||||
|
||||
LinearElementEditor.movePoints(element, [
|
||||
@ -238,7 +238,7 @@ export class LinearElementEditor {
|
||||
element,
|
||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||
appState.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
);
|
||||
|
||||
const deltaX = newDraggingPointPosition[0] - draggingPoint[0];
|
||||
@ -254,7 +254,7 @@ export class LinearElementEditor {
|
||||
element,
|
||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||
appState.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
)
|
||||
: ([
|
||||
element.points[pointIndex][0] + deltaX,
|
||||
@ -647,7 +647,7 @@ export class LinearElementEditor {
|
||||
element,
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
appState.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
),
|
||||
],
|
||||
});
|
||||
@ -798,7 +798,7 @@ export class LinearElementEditor {
|
||||
element,
|
||||
lastCommittedPoint,
|
||||
[scenePointerX, scenePointerY],
|
||||
appState.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
);
|
||||
|
||||
newPoint = [
|
||||
@ -810,7 +810,7 @@ export class LinearElementEditor {
|
||||
element,
|
||||
scenePointerX - appState.editingLinearElement.pointerOffset.x,
|
||||
scenePointerY - appState.editingLinearElement.pointerOffset.y,
|
||||
appState.gridSize,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1176,6 +1176,7 @@ export class LinearElementEditor {
|
||||
linearElementEditor: LinearElementEditor,
|
||||
pointerCoords: PointerCoords,
|
||||
appState: AppState,
|
||||
snapToGrid: boolean,
|
||||
) {
|
||||
const element = LinearElementEditor.getElement(
|
||||
linearElementEditor.elementId,
|
||||
@ -1196,7 +1197,7 @@ export class LinearElementEditor {
|
||||
element,
|
||||
pointerCoords.x,
|
||||
pointerCoords.y,
|
||||
appState.gridSize,
|
||||
snapToGrid ? appState.gridSize : null,
|
||||
);
|
||||
const points = [
|
||||
...element.points.slice(0, segmentMidpoint.index!),
|
||||
|
@ -203,7 +203,6 @@ describe("duplicating multiple elements", () => {
|
||||
);
|
||||
|
||||
clonedArrows.forEach((arrow) => {
|
||||
// console.log(arrow);
|
||||
expect(
|
||||
clonedRectangle.boundElements!.find((e) => e.id === arrow.id),
|
||||
).toEqual(
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { render } from "../../../../tests/test-utils";
|
||||
import { API } from "../../../../tests/helpers/api";
|
||||
import ExcalidrawApp from "../../../../excalidraw-app";
|
||||
import { Excalidraw } from "../../../../packages/excalidraw/index";
|
||||
|
||||
import { measureTextElement } from "../../../textElement";
|
||||
import { ensureSubtypesLoaded } from "../../";
|
||||
|
||||
describe("mathjax", () => {
|
||||
it("text-only measurements match", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
await ensureSubtypesLoaded(["math"]);
|
||||
const text = "A quick brown fox jumps over the lazy dog.";
|
||||
const elements = [
|
||||
@ -19,7 +19,7 @@ describe("mathjax", () => {
|
||||
expect(metrics1).toStrictEqual(metrics2);
|
||||
});
|
||||
it("minimum height remains", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
await ensureSubtypesLoaded(["math"]);
|
||||
const elements = [
|
||||
API.createElement({ type: "text", id: "A", text: "a" }),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { GlobalTestState, render, screen } from "../tests/test-utils";
|
||||
import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
|
||||
import { CODES, KEYS } from "../keys";
|
||||
@ -41,7 +41,7 @@ describe("textWysiwyg", () => {
|
||||
describe("start text editing", () => {
|
||||
const { h } = window;
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
h.elements = [];
|
||||
});
|
||||
|
||||
@ -243,7 +243,7 @@ describe("textWysiwyg", () => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
//@ts-ignore
|
||||
h.app.refreshDeviceState(h.app.excalidrawContainerRef.current!);
|
||||
|
||||
@ -477,7 +477,7 @@ describe("textWysiwyg", () => {
|
||||
const { h } = window;
|
||||
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
h.elements = [];
|
||||
|
||||
rectangle = UI.createElement("rectangle", {
|
||||
@ -1511,7 +1511,7 @@ describe("textWysiwyg", () => {
|
||||
});
|
||||
|
||||
it("should bump the version of labelled arrow when label updated", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
const arrow = UI.createElement("arrow", {
|
||||
width: 300,
|
||||
height: 0,
|
||||
|
7
src/hooks/useStable.ts
Normal file
7
src/hooks/useStable.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { useRef } from "react";
|
||||
|
||||
export const useStable = <T extends Record<string, any>>(value: T) => {
|
||||
const ref = useRef<T>(value);
|
||||
Object.assign(ref.current, value);
|
||||
return ref.current;
|
||||
};
|
@ -1,9 +1,9 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import ExcalidrawApp from "./excalidraw-app";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { registerSW } from "virtual:pwa-register";
|
||||
|
||||
import "./excalidraw-app/sentry";
|
||||
import "../excalidraw-app/sentry";
|
||||
window.__EXCALIDRAW_SHA__ = import.meta.env.VITE_APP_GIT_SHA;
|
||||
const rootElement = document.getElementById("root")!;
|
||||
const root = createRoot(rootElement);
|
||||
|
@ -264,7 +264,8 @@
|
||||
"bindTextToElement": "Press enter to add text",
|
||||
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging",
|
||||
"eraserRevert": "Hold Alt to revert the elements marked for deletion",
|
||||
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page."
|
||||
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page.",
|
||||
"disableSnapping": "Hold CtrlOrCmd to disable snapping"
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "Cannot show preview",
|
||||
|
@ -11,29 +11,11 @@ The change should be grouped under one of the below section and must contain PR
|
||||
Please add the latest change on the top under the correct section.
|
||||
-->
|
||||
|
||||
## Unreleased
|
||||
|
||||
### renderEmbeddable
|
||||
|
||||
```tsx
|
||||
(element: NonDeletedExcalidrawElement, radius: number, appState: UIAppState) => JSX.Element | null;`
|
||||
```
|
||||
|
||||
The renderEmbeddable function allows you to customize the rendering of a JSX component instead of using the default `<iframe>`. By setting props.renderEmbeddable, you can provide a custom implementation for rendering the element.
|
||||
|
||||
#### Parameters:
|
||||
|
||||
- element (NonDeletedExcalidrawElement): The element to be rendered.
|
||||
- radius (number): The calculated border radius in pixels.
|
||||
- appState (UIAppState): The current state of the UI.
|
||||
|
||||
#### Return value:
|
||||
|
||||
JSX.Element | null: The JSX component representing the custom rendering, or null if the default `<iframe>` should be rendered.
|
||||
|
||||
### Features
|
||||
## 0.16.0 (2023-09-19)
|
||||
|
||||
- Add a `subtype` attribute to `ExcalidrawElement` to allow self-contained extensions of any `ExcalidrawElement` type. Implement MathJax support on stem.excalidraw.com as a `math` subtype of `ExcalidrawTextElement`. Both standard Latex input and simplified AsciiMath input are supported. [#6037](https://github.com/excalidraw/excalidraw/pull/6037).
|
||||
- Support creating containers, linear elements, text containers, labelled arrows and arrow bindings programatically [#6546](https://github.com/excalidraw/excalidraw/pull/6546)
|
||||
- Introducing Web-Embeds (alias iframe element)[#6691](https://github.com/excalidraw/excalidraw/pull/6691)
|
||||
- Added [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateEmbeddable) to customize embeddable src url validation. [#6691](https://github.com/excalidraw/excalidraw/pull/6691)
|
||||
- Add support for `opts.fitToViewport` and `opts.viewportZoomFactor` in the [`ExcalidrawAPI.scrollToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#scrolltocontent) API. [#6581](https://github.com/excalidraw/excalidraw/pull/6581).
|
||||
- Properly sanitize element `link` urls. [#6728](https://github.com/excalidraw/excalidraw/pull/6728).
|
||||
@ -49,6 +31,235 @@ JSX.Element | null: The JSX component representing the custom rendering, or null
|
||||
- `props.onClose` replaced with `props.onStateChange`.
|
||||
- `restore()`/`restoreAppState()` now retains `appState.openSidebar` regardless of docked state.
|
||||
|
||||
## Excalidraw Library
|
||||
|
||||
**_This section lists the updates made to the excalidraw library and will not affect the integration._**
|
||||
|
||||
### Features
|
||||
|
||||
- allow `avif`, `jfif`, `webp`, `bmp`, `ico` image types [#6500](https://github.com/excalidraw/excalidraw/pull/6500)
|
||||
- Zen-mode/go-to-plus button style tweaks [#7006](https://github.com/excalidraw/excalidraw/pull/7006)
|
||||
|
||||
- Holding down CMD/CTRL will disable snap to grid when grid is active [#6983](https://github.com/excalidraw/excalidraw/pull/6983)
|
||||
|
||||
- Update logo [#6979](https://github.com/excalidraw/excalidraw/pull/6979)
|
||||
|
||||
- Export `changeProperty()` and `getFormValue()`. [#6957](https://github.com/excalidraw/excalidraw/pull/6957)
|
||||
|
||||
- Partition main canvas vertically [#6759](https://github.com/excalidraw/excalidraw/pull/6759)
|
||||
|
||||
- Support creating containers, linear elements, text containers, labelled arrows and arrow bindings programatically [#6546](https://github.com/excalidraw/excalidraw/pull/6546)
|
||||
|
||||
- Add support for simplePDF in Web-Embeds [#6810](https://github.com/excalidraw/excalidraw/pull/6810)
|
||||
|
||||
- Add support for val.town embeds [#6821](https://github.com/excalidraw/excalidraw/pull/6821)
|
||||
|
||||
- Render bold lines in grid [#6779](https://github.com/excalidraw/excalidraw/pull/6779)
|
||||
|
||||
- Adds support for stackblitz.com embeds [#6813](https://github.com/excalidraw/excalidraw/pull/6813)
|
||||
|
||||
- Cache most of element selection [#6747](https://github.com/excalidraw/excalidraw/pull/6747)
|
||||
|
||||
- Support customizing what parts of frames are rendered [#6752](https://github.com/excalidraw/excalidraw/pull/6752)
|
||||
|
||||
- Make `appState.selectedElementIds` more stable [#6745](https://github.com/excalidraw/excalidraw/pull/6745)
|
||||
|
||||
- Overwrite confirmation dialogs [#6658](https://github.com/excalidraw/excalidraw/pull/6658)
|
||||
|
||||
- Simple analitycs [#6683](https://github.com/excalidraw/excalidraw/pull/6683)
|
||||
|
||||
- Introduce frames [#6123](https://github.com/excalidraw/excalidraw/pull/6123)
|
||||
|
||||
- Add canvas-roundrect-polyfill package [#6675](https://github.com/excalidraw/excalidraw/pull/6675)
|
||||
|
||||
- Polyfill `CanvasRenderingContext2D.roundRect` [#6673](https://github.com/excalidraw/excalidraw/pull/6673)
|
||||
|
||||
- Disable collab feature when running in iframe [#6646](https://github.com/excalidraw/excalidraw/pull/6646)
|
||||
|
||||
- Assign random user name when not set [#6663](https://github.com/excalidraw/excalidraw/pull/6663)
|
||||
|
||||
- Redesigned collab cursors [#6659](https://github.com/excalidraw/excalidraw/pull/6659)
|
||||
|
||||
- Eye dropper [#6615](https://github.com/excalidraw/excalidraw/pull/6615)
|
||||
|
||||
- Redesign of Live Collaboration dialog [#6635](https://github.com/excalidraw/excalidraw/pull/6635)
|
||||
|
||||
- Recover scrolled position after Library re-opening [#6624](https://github.com/excalidraw/excalidraw/pull/6624)
|
||||
|
||||
- Clearing library cache [#6621](https://github.com/excalidraw/excalidraw/pull/6621)
|
||||
|
||||
- Update design of ImageExportDialog [#6614](https://github.com/excalidraw/excalidraw/pull/6614)
|
||||
|
||||
- Add flipping for multiple elements [#5578](https://github.com/excalidraw/excalidraw/pull/5578)
|
||||
|
||||
- Color picker redesign [#6216](https://github.com/excalidraw/excalidraw/pull/6216)
|
||||
|
||||
- Add "unlock all elements" to canvas contextMenu [#5894](https://github.com/excalidraw/excalidraw/pull/5894)
|
||||
|
||||
- Library sidebar design tweaks [#6582](https://github.com/excalidraw/excalidraw/pull/6582)
|
||||
|
||||
- Add Trans component for interpolating JSX in translations [#6534](https://github.com/excalidraw/excalidraw/pull/6534)
|
||||
|
||||
- Testing simple analytics and fathom analytics for better privacy of the users [#6529](https://github.com/excalidraw/excalidraw/pull/6529)
|
||||
|
||||
- Retain `seed` on shift-paste [#6509](https://github.com/excalidraw/excalidraw/pull/6509)
|
||||
|
||||
- Allow `avif`, `jfif`, `webp`, `bmp`, `ico` image types (#6500
|
||||
|
||||
### Fixes
|
||||
|
||||
- Improperly disabling UI pointer-events on canvas interaction [#7005](https://github.com/excalidraw/excalidraw/pull/7005)
|
||||
|
||||
- Several eyeDropper fixes [#7002](https://github.com/excalidraw/excalidraw/pull/7002)
|
||||
|
||||
- IsBindableElement to affirm frames [#6900](https://github.com/excalidraw/excalidraw/pull/6900)
|
||||
|
||||
- Use `device.isMobile` for sidebar trigger label breakpoint [#6994](https://github.com/excalidraw/excalidraw/pull/6994)
|
||||
|
||||
- Export to plus url [#6980](https://github.com/excalidraw/excalidraw/pull/6980)
|
||||
|
||||
- Z-index inconsistencies during addition / deletion in frames [#6914](https://github.com/excalidraw/excalidraw/pull/6914)
|
||||
|
||||
- Update size-limit so react is not installed as dependency [#6964](https://github.com/excalidraw/excalidraw/pull/6964)
|
||||
|
||||
- Stale labeled arrow bounds cache after editing the label [#6893](https://github.com/excalidraw/excalidraw/pull/6893)
|
||||
|
||||
- Canvas flickering due to resetting canvas on skipped frames [#6960](https://github.com/excalidraw/excalidraw/pull/6960)
|
||||
|
||||
- Grid jittery after partition PR [#6935](https://github.com/excalidraw/excalidraw/pull/6935)
|
||||
|
||||
- Regression in indexing when adding elements to frame [#6904](https://github.com/excalidraw/excalidraw/pull/6904)
|
||||
|
||||
- Stabilize `selectedElementIds` when box selecting [#6912](https://github.com/excalidraw/excalidraw/pull/6912)
|
||||
|
||||
- Resetting deleted elements on duplication [#6906](https://github.com/excalidraw/excalidraw/pull/6906)
|
||||
|
||||
- Make canvas compos memoize appState on props they declare [#6897](https://github.com/excalidraw/excalidraw/pull/6897)
|
||||
|
||||
- Scope `--color-selection` retrieval to given instance [#6886](https://github.com/excalidraw/excalidraw/pull/6886)
|
||||
|
||||
- Webpack config exclude statement to system agnostic [#6857](https://github.com/excalidraw/excalidraw/pull/6857)
|
||||
|
||||
- Remove `embeddable` from generic elements [#6853](https://github.com/excalidraw/excalidraw/pull/6853)
|
||||
|
||||
- Resizing arrow labels [#6789](https://github.com/excalidraw/excalidraw/pull/6789)
|
||||
|
||||
- Eye-dropper not working with app offset correctly on non-1 dPR [#6835](https://github.com/excalidraw/excalidraw/pull/6835)
|
||||
|
||||
- Add self destroying service-worker.js to migrate everyone from CRA to Vite [#6833](https://github.com/excalidraw/excalidraw/pull/6833)
|
||||
|
||||
- Forgotten REACT_APP env variables [#6834](https://github.com/excalidraw/excalidraw/pull/6834)
|
||||
|
||||
- Refresh sw when browser refreshed [#6824](https://github.com/excalidraw/excalidraw/pull/6824)
|
||||
|
||||
- Adding to selection via shift box-select [#6815](https://github.com/excalidraw/excalidraw/pull/6815)
|
||||
|
||||
- Prevent binding focus NaN value [#6803](https://github.com/excalidraw/excalidraw/pull/6803)
|
||||
|
||||
- Use pull request in semantic workflow for better security [#6799](https://github.com/excalidraw/excalidraw/pull/6799)
|
||||
|
||||
- Don't show `canvasBackground` label when `UIOptions.canvasActions.changeViewBackgroundColor` is false [#6781](https://github.com/excalidraw/excalidraw/pull/6781)
|
||||
|
||||
- Use subdirectory for @excalidraw/excalidraw size limit [#6787](https://github.com/excalidraw/excalidraw/pull/6787)
|
||||
|
||||
- Use actual dock state to not close docked library on insert [#6766](https://github.com/excalidraw/excalidraw/pull/6766)
|
||||
|
||||
- UI disappears when pressing the eyedropper shortcut on mobile [#6725](https://github.com/excalidraw/excalidraw/pull/6725)
|
||||
|
||||
- Elements in non-existing frame getting removed [#6708](https://github.com/excalidraw/excalidraw/pull/6708)
|
||||
|
||||
- Scrollbars renders but disable [#6706](https://github.com/excalidraw/excalidraw/pull/6706)
|
||||
|
||||
- Typo in chart.ts [#6696](https://github.com/excalidraw/excalidraw/pull/6696)
|
||||
|
||||
- Do not bind text to container using text tool when it has text already [#6694](https://github.com/excalidraw/excalidraw/pull/6694)
|
||||
|
||||
- Don't allow binding text to images [#6693](https://github.com/excalidraw/excalidraw/pull/6693)
|
||||
|
||||
- Updated link for documentation page under help section [#6654](https://github.com/excalidraw/excalidraw/pull/6654)
|
||||
|
||||
- Collab username style fixes [#6668](https://github.com/excalidraw/excalidraw/pull/6668)
|
||||
|
||||
- Bound arrows not updated when rotating multiple elements [#6662](https://github.com/excalidraw/excalidraw/pull/6662)
|
||||
|
||||
- Delete setCursor when resize [#6660](https://github.com/excalidraw/excalidraw/pull/6660)
|
||||
|
||||
- Creating text while color picker open [#6651](https://github.com/excalidraw/excalidraw/pull/6651)
|
||||
|
||||
- Cleanup textWysiwyg and getAdjustedDimensions [#6520](https://github.com/excalidraw/excalidraw/pull/6520)
|
||||
|
||||
- Eye dropper not accounting for offsets [#6640](https://github.com/excalidraw/excalidraw/pull/6640)
|
||||
|
||||
- Color picker input closing problem [#6599](https://github.com/excalidraw/excalidraw/pull/6599)
|
||||
|
||||
- Export dialog shortcut toggles console on firefox [#6620](https://github.com/excalidraw/excalidraw/pull/6620)
|
||||
|
||||
- Add react v17 `useTransition` polyfill [#6618](https://github.com/excalidraw/excalidraw/pull/6618)
|
||||
|
||||
- Library dropdown visibility issue for mobile [#6613](https://github.com/excalidraw/excalidraw/pull/6613)
|
||||
|
||||
- `withInternalFallback` leaking state in multi-instance scenarios [#6602](https://github.com/excalidraw/excalidraw/pull/6602)
|
||||
|
||||
- Language list containing duplicate `en` lang [#6583](https://github.com/excalidraw/excalidraw/pull/6583)
|
||||
|
||||
- Garbled text displayed on avatars [#6575](https://github.com/excalidraw/excalidraw/pull/6575)
|
||||
|
||||
- Assign the original text to text editor only during init [#6580](https://github.com/excalidraw/excalidraw/pull/6580)
|
||||
|
||||
- I18n: Apply Trans component to publish library dialogue [#6564](https://github.com/excalidraw/excalidraw/pull/6564)
|
||||
|
||||
- Fix brave error i18n string and remove unused [#6561](https://github.com/excalidraw/excalidraw/pull/6561)
|
||||
|
||||
- Revert add version tags to Docker build [#6540](https://github.com/excalidraw/excalidraw/pull/6540)
|
||||
|
||||
- Don't refresh dimensions for text containers on font load [#6523](https://github.com/excalidraw/excalidraw/pull/6523)
|
||||
|
||||
- Cleanup getMaxContainerHeight and getMaxContainerWidth [#6519](https://github.com/excalidraw/excalidraw/pull/6519)
|
||||
|
||||
- Cleanup redrawTextBoundingBox [#6518](https://github.com/excalidraw/excalidraw/pull/6518)
|
||||
|
||||
- Text jumps when editing on Android Chrome [#6503](https://github.com/excalidraw/excalidraw/pull/6503)
|
||||
|
||||
### Styles
|
||||
|
||||
- Removes extra spaces [#6558](https://github.com/excalidraw/excalidraw/pull/6558)
|
||||
|
||||
- Fix font family inconsistencies [#6501](https://github.com/excalidraw/excalidraw/pull/6501)
|
||||
|
||||
### Refactor
|
||||
|
||||
- Factor out shape generation from `renderElement.ts` pt 2 [#6878](https://github.com/excalidraw/excalidraw/pull/6878)
|
||||
|
||||
- Add typeScript support to enforce valid translation keys [#6776](https://github.com/excalidraw/excalidraw/pull/6776)
|
||||
|
||||
- Simplify `ImageExportDialog` [#6578](https://github.com/excalidraw/excalidraw/pull/6578)
|
||||
|
||||
### Performance
|
||||
|
||||
- Limiting the suggested binding to fix performance issue [#6877](https://github.com/excalidraw/excalidraw/pull/6877)
|
||||
|
||||
- Memoize rendering of library [#6622](https://github.com/excalidraw/excalidraw/pull/6622)
|
||||
|
||||
- Improve rendering performance for Library [#6587](https://github.com/excalidraw/excalidraw/pull/6587)
|
||||
|
||||
- Use `UIAppState` where possible to reduce UI rerenders [#6560](https://github.com/excalidraw/excalidraw/pull/6560)
|
||||
|
||||
### Build
|
||||
|
||||
- Increase limit for bundle by 1kb [#6880](https://github.com/excalidraw/excalidraw/pull/6880)
|
||||
|
||||
- Update to node 18 in docker [#6822](https://github.com/excalidraw/excalidraw/pull/6822)
|
||||
|
||||
- Migrate to Vite 🚀 [#6818](https://github.com/excalidraw/excalidraw/pull/6818)
|
||||
|
||||
- Migrate to Vite 🚀 [#6713](https://github.com/excalidraw/excalidraw/pull/6713)
|
||||
|
||||
- Increase limit to 290 kB for prod bundle [#6809](https://github.com/excalidraw/excalidraw/pull/6809)
|
||||
|
||||
- Add version tags to Docker build [#6508](https://github.com/excalidraw/excalidraw/pull/6508)
|
||||
|
||||
---
|
||||
|
||||
## 0.15.2 (2023-04-20)
|
||||
|
||||
### Docs
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@excalidraw/excalidraw",
|
||||
"version": "0.15.2",
|
||||
"version": "0.16.0",
|
||||
"main": "main.js",
|
||||
"types": "types/packages/excalidraw/index.d.ts",
|
||||
"files": [
|
||||
|
@ -3,7 +3,7 @@ import * as Renderer from "../renderer/renderScene";
|
||||
import { reseed } from "../random";
|
||||
import { render, queryByTestId } from "../tests/test-utils";
|
||||
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { vi } from "vitest";
|
||||
|
||||
const renderStaticScene = vi.spyOn(Renderer, "renderStaticScene");
|
||||
@ -35,7 +35,7 @@ describe("Test <App/>", () => {
|
||||
};
|
||||
};
|
||||
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
expect(
|
||||
queryByTestId(
|
||||
document.querySelector(".excalidraw-modal-container")!,
|
50
src/tests/__snapshots__/App.test.tsx.snap
Normal file
50
src/tests/__snapshots__/App.test.tsx.snap
Normal file
@ -0,0 +1,50 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Test <App/> > should show error modal when using brave and measureText API is not working 1`] = `
|
||||
<div
|
||||
data-testid="brave-measure-text-error"
|
||||
>
|
||||
<p>
|
||||
Looks like you are using Brave browser with the
|
||||
<span
|
||||
style="font-weight: 600;"
|
||||
>
|
||||
Aggressively Block Fingerprinting
|
||||
</span>
|
||||
setting enabled.
|
||||
</p>
|
||||
<p>
|
||||
This could result in breaking the
|
||||
<span
|
||||
style="font-weight: 600;"
|
||||
>
|
||||
Text Elements
|
||||
</span>
|
||||
in your drawings.
|
||||
</p>
|
||||
<p>
|
||||
We strongly recommend disabling this setting. You can follow
|
||||
<a
|
||||
href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser"
|
||||
>
|
||||
these steps
|
||||
</a>
|
||||
on how to do so.
|
||||
</p>
|
||||
<p>
|
||||
If disabling this setting doesn't fix the display of text elements, please open an
|
||||
<a
|
||||
href="https://github.com/excalidraw/excalidraw/issues/new"
|
||||
>
|
||||
issue
|
||||
</a>
|
||||
on our GitHub, or write us on
|
||||
<a
|
||||
href="https://discord.gg/UexuTaE"
|
||||
>
|
||||
Discord
|
||||
.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
@ -13089,126 +13089,6 @@ exports[`regression tests > pinch-to-zoom works > [end of test] number of elemen
|
||||
|
||||
exports[`regression tests > pinch-to-zoom works > [end of test] number of renders 1`] = `7`;
|
||||
|
||||
exports[`regression tests > rerenders UI on language change > [end of test] appState 1`] = `
|
||||
{
|
||||
"activeEmbeddable": null,
|
||||
"activeTool": {
|
||||
"customType": null,
|
||||
"lastActiveTool": null,
|
||||
"locked": false,
|
||||
"type": "rectangle",
|
||||
},
|
||||
"collaborators": Map {},
|
||||
"contextMenu": null,
|
||||
"currentChartType": "bar",
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemEndArrowhead": "arrow",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemRoundness": "round",
|
||||
"currentItemStartArrowhead": null,
|
||||
"currentItemStrokeColor": "#1e1e1e",
|
||||
"currentItemStrokeStyle": "solid",
|
||||
"currentItemStrokeWidth": 1,
|
||||
"currentItemTextAlign": "left",
|
||||
"cursorButton": "up",
|
||||
"defaultSidebarDockedPreference": false,
|
||||
"draggingElement": null,
|
||||
"editingElement": null,
|
||||
"editingFrame": null,
|
||||
"editingGroupId": null,
|
||||
"editingLinearElement": null,
|
||||
"elementsToHighlight": null,
|
||||
"errorMessage": null,
|
||||
"exportBackground": true,
|
||||
"exportEmbedScene": false,
|
||||
"exportScale": 1,
|
||||
"exportWithDarkMode": false,
|
||||
"fileHandle": null,
|
||||
"frameRendering": {
|
||||
"clip": true,
|
||||
"enabled": true,
|
||||
"name": true,
|
||||
"outline": true,
|
||||
},
|
||||
"frameToHighlight": null,
|
||||
"gridSize": null,
|
||||
"height": 768,
|
||||
"isBindingEnabled": true,
|
||||
"isLoading": false,
|
||||
"isResizing": false,
|
||||
"isRotating": false,
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openDialog": null,
|
||||
"openMenu": "canvas",
|
||||
"openPopup": null,
|
||||
"openSidebar": null,
|
||||
"pasteDialog": {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElementId": null,
|
||||
"previousSelectedElementIds": {},
|
||||
"resizingElement": null,
|
||||
"scrollX": 0,
|
||||
"scrollY": 0,
|
||||
"scrolledOutside": false,
|
||||
"selectedElementIds": {},
|
||||
"selectedElementsAreBeingDragged": false,
|
||||
"selectedGroupIds": {},
|
||||
"selectedLinearElement": null,
|
||||
"selectionElement": null,
|
||||
"shouldCacheIgnoreZoom": false,
|
||||
"showHyperlinkPopup": false,
|
||||
"showStats": false,
|
||||
"showWelcomeScreen": true,
|
||||
"startBoundElement": null,
|
||||
"suggestedBindings": [],
|
||||
"theme": "light",
|
||||
"toast": null,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"viewModeEnabled": false,
|
||||
"width": 1024,
|
||||
"zenModeEnabled": false,
|
||||
"zoom": {
|
||||
"value": 1,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`regression tests > rerenders UI on language change > [end of test] history 1`] = `
|
||||
{
|
||||
"recording": false,
|
||||
"redoStack": [],
|
||||
"stateHistory": [
|
||||
{
|
||||
"appState": {
|
||||
"editingGroupId": null,
|
||||
"editingLinearElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"selectedElementIds": {},
|
||||
"selectedGroupIds": {},
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
},
|
||||
"elements": [],
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`regression tests > rerenders UI on language change > [end of test] number of elements 1`] = `0`;
|
||||
|
||||
exports[`regression tests > rerenders UI on language change > [end of test] number of renders 1`] = `5`;
|
||||
|
||||
exports[`regression tests > shift click on selected element should deselect it on pointer up > [end of test] appState 1`] = `
|
||||
{
|
||||
"activeEmbeddable": null,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { CODES } from "../keys";
|
||||
import { API } from "../tests/helpers/api";
|
||||
import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
|
||||
@ -9,7 +9,7 @@ import {
|
||||
screen,
|
||||
togglePopover,
|
||||
} from "../tests/test-utils";
|
||||
import { copiedStyles } from "./actionStyles";
|
||||
import { copiedStyles } from "../actions/actionStyles";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
@ -17,7 +17,7 @@ const mouse = new Pointer("mouse");
|
||||
|
||||
describe("actionStyles", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
@ -1,6 +1,6 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import { render } from "./test-utils";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../../src/packages/excalidraw/index";
|
||||
import { defaultLang, setLanguage } from "../i18n";
|
||||
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
import { API } from "./helpers/api";
|
||||
@ -60,7 +60,7 @@ describe("aligning", () => {
|
||||
mouse.reset();
|
||||
|
||||
await setLanguage(defaultLang);
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
});
|
||||
|
||||
it("aligns two objects correctly to the top", () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { queryByTestId, render, waitFor } from "./test-utils";
|
||||
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { API } from "./helpers/api";
|
||||
import { getDefaultAppState } from "../appState";
|
||||
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
|
||||
@ -14,14 +14,17 @@ describe("appState", () => {
|
||||
const defaultAppState = getDefaultAppState();
|
||||
const exportBackground = !defaultAppState.exportBackground;
|
||||
|
||||
await render(<ExcalidrawApp />, {
|
||||
localStorageData: {
|
||||
await render(
|
||||
<Excalidraw
|
||||
initialData={{
|
||||
appState: {
|
||||
exportBackground,
|
||||
viewBackgroundColor: "#F00",
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>,
|
||||
{},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(h.state.exportBackground).toBe(exportBackground);
|
||||
@ -53,13 +56,15 @@ describe("appState", () => {
|
||||
});
|
||||
|
||||
it("changing fontSize with text tool selected (no element created yet)", async () => {
|
||||
const { container } = await render(<ExcalidrawApp />, {
|
||||
localStorageData: {
|
||||
const { container } = await render(
|
||||
<Excalidraw
|
||||
initialData={{
|
||||
appState: {
|
||||
currentItemFontSize: 30,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
UI.clickTool("text");
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { fireEvent, render } from "./test-utils";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../../src/packages/excalidraw/index";
|
||||
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
import { getTransformHandles } from "../element/transformHandles";
|
||||
import { API } from "./helpers/api";
|
||||
@ -12,7 +12,7 @@ const mouse = new Pointer("mouse");
|
||||
|
||||
describe("element binding", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
});
|
||||
|
||||
it("should create valid binding if duplicate start/end points", async () => {
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
createPasteEvent,
|
||||
} from "./test-utils";
|
||||
import { Pointer, Keyboard } from "./helpers/ui";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { KEYS } from "../keys";
|
||||
import {
|
||||
getDefaultLineHeight,
|
||||
@ -79,8 +79,13 @@ beforeEach(async () => {
|
||||
|
||||
mouse.reset();
|
||||
|
||||
await render(<ExcalidrawApp />);
|
||||
h.app.setAppState({ zoom: { value: 1 as NormalizedZoomValue } });
|
||||
await render(
|
||||
<Excalidraw
|
||||
autoFocus={true}
|
||||
handleKeyboardGlobally={true}
|
||||
initialData={{ appState: { zoom: { value: 1 as NormalizedZoomValue } } }}
|
||||
/>,
|
||||
);
|
||||
setClipboardText("");
|
||||
Object.assign(document, {
|
||||
elementFromPoint: () => GlobalTestState.canvas,
|
||||
@ -91,7 +96,6 @@ describe("general paste behavior", () => {
|
||||
it("should randomize seed on paste", async () => {
|
||||
const rectangle = API.createElement({ type: "rectangle" });
|
||||
const clipboardJSON = (await copyToClipboard([rectangle], null))!;
|
||||
|
||||
pasteWithCtrlCmdV(clipboardJSON);
|
||||
|
||||
await waitFor(() => {
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
waitFor,
|
||||
togglePopover,
|
||||
} from "./test-utils";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import * as Renderer from "../renderer/renderScene";
|
||||
import { reseed } from "../random";
|
||||
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
@ -20,7 +20,6 @@ import { ShortcutName } from "../actions/shortcuts";
|
||||
import { copiedStyles } from "../actions/actionStyles";
|
||||
import { API } from "./helpers/api";
|
||||
import { setDateTimeForTests } from "../utils";
|
||||
import { LibraryItem } from "../types";
|
||||
import { vi } from "vitest";
|
||||
|
||||
const checkpoint = (name: string) => {
|
||||
@ -56,7 +55,7 @@ describe("contextMenu element", () => {
|
||||
reseed(7);
|
||||
setDateTimeForTests("201933152653");
|
||||
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
@ -394,11 +393,9 @@ describe("contextMenu element", () => {
|
||||
const contextMenu = UI.queryContextMenu();
|
||||
fireEvent.click(queryByText(contextMenu!, "Add to library")!);
|
||||
|
||||
await waitFor(() => {
|
||||
const library = localStorage.getItem("excalidraw-library");
|
||||
expect(library).not.toBeNull();
|
||||
const addedElement = JSON.parse(library!)[0] as LibraryItem;
|
||||
expect(addedElement.elements[0]).toEqual(h.elements[0]);
|
||||
await waitFor(async () => {
|
||||
const libraryItems = await h.app.library.getLatestLibrary();
|
||||
expect(libraryItems[0].elements[0]).toEqual(h.elements[0]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { ExcalidrawElement } from "../element/types";
|
||||
import { getShortcutKey } from "../utils";
|
||||
import { API } from "./helpers/api";
|
||||
import { render } from "./test-utils";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import {
|
||||
CustomShortcutName,
|
||||
getShortcutFromShortcutName,
|
||||
@ -27,7 +27,7 @@ describe("regression tests", () => {
|
||||
});
|
||||
|
||||
it("should apply universal action predicates", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
// Create the test elements
|
||||
const el1 = API.createElement({ type: "rectangle", id: "A", y: 0 });
|
||||
const el2 = API.createElement({ type: "rectangle", id: "B", y: 30 });
|
||||
|
@ -1,5 +1,5 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import * as Renderer from "../renderer/renderScene";
|
||||
import { KEYS } from "../keys";
|
||||
import {
|
||||
@ -30,7 +30,7 @@ const { h } = window;
|
||||
describe("Test dragCreate", () => {
|
||||
describe("add element to the scene when pointer dragging long enough", () => {
|
||||
it("rectangle", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("rectangle");
|
||||
fireEvent.click(tool);
|
||||
@ -62,7 +62,7 @@ describe("Test dragCreate", () => {
|
||||
});
|
||||
|
||||
it("ellipse", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("ellipse");
|
||||
fireEvent.click(tool);
|
||||
@ -95,7 +95,7 @@ describe("Test dragCreate", () => {
|
||||
});
|
||||
|
||||
it("diamond", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("diamond");
|
||||
fireEvent.click(tool);
|
||||
@ -127,7 +127,7 @@ describe("Test dragCreate", () => {
|
||||
});
|
||||
|
||||
it("arrow", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("arrow");
|
||||
fireEvent.click(tool);
|
||||
@ -163,7 +163,7 @@ describe("Test dragCreate", () => {
|
||||
});
|
||||
|
||||
it("line", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("line");
|
||||
fireEvent.click(tool);
|
||||
@ -207,7 +207,7 @@ describe("Test dragCreate", () => {
|
||||
});
|
||||
|
||||
it("rectangle", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("rectangle");
|
||||
fireEvent.click(tool);
|
||||
@ -227,7 +227,7 @@ describe("Test dragCreate", () => {
|
||||
});
|
||||
|
||||
it("ellipse", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("ellipse");
|
||||
fireEvent.click(tool);
|
||||
@ -247,7 +247,7 @@ describe("Test dragCreate", () => {
|
||||
});
|
||||
|
||||
it("diamond", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("diamond");
|
||||
fireEvent.click(tool);
|
||||
@ -267,7 +267,9 @@ describe("Test dragCreate", () => {
|
||||
});
|
||||
|
||||
it("arrow", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(
|
||||
<Excalidraw handleKeyboardGlobally={true} />,
|
||||
);
|
||||
// select tool
|
||||
const tool = getByToolName("arrow");
|
||||
fireEvent.click(tool);
|
||||
@ -292,7 +294,9 @@ describe("Test dragCreate", () => {
|
||||
});
|
||||
|
||||
it("line", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(
|
||||
<Excalidraw handleKeyboardGlobally={true} />,
|
||||
);
|
||||
// select tool
|
||||
const tool = getByToolName("line");
|
||||
fireEvent.click(tool);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { render } from "../tests/test-utils";
|
||||
import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
|
||||
import { KEYS } from "../keys";
|
||||
@ -15,7 +15,7 @@ const h = window.h;
|
||||
|
||||
describe("element locking", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
h.elements = [];
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { render, waitFor } from "./test-utils";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { API } from "./helpers/api";
|
||||
import {
|
||||
encodePngMetadata,
|
||||
@ -42,7 +42,7 @@ Object.defineProperty(window, "TextDecoder", {
|
||||
|
||||
describe("export", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
});
|
||||
|
||||
it("export embedded png and reimport", async () => {
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { render } from "./test-utils";
|
||||
import { API } from "./helpers/api";
|
||||
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { vi } from "vitest";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
describe("fitToContent", () => {
|
||||
it("should zoom to fit the selected element", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
|
||||
h.state.width = 10;
|
||||
h.state.height = 10;
|
||||
@ -30,7 +30,7 @@ describe("fitToContent", () => {
|
||||
});
|
||||
|
||||
it("should zoom to fit multiple elements", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
|
||||
const topLeft = API.createElement({
|
||||
width: 20,
|
||||
@ -61,7 +61,7 @@ describe("fitToContent", () => {
|
||||
});
|
||||
|
||||
it("should scroll the viewport to the selected element", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
|
||||
h.state.width = 10;
|
||||
h.state.height = 10;
|
||||
@ -106,7 +106,7 @@ describe("fitToContent animated", () => {
|
||||
});
|
||||
|
||||
it("should ease scroll the viewport to the selected element", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
|
||||
h.state.width = 10;
|
||||
h.state.height = 10;
|
||||
@ -142,7 +142,7 @@ describe("fitToContent animated", () => {
|
||||
});
|
||||
|
||||
it("should animate the scroll but not the zoom", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
|
||||
h.state.width = 50;
|
||||
h.state.height = 50;
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
FileId,
|
||||
} from "../element/types";
|
||||
import { newLinearElement } from "../element";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { mutateElement } from "../element/mutateElement";
|
||||
import { NormalizedZoomValue } from "../types";
|
||||
import { ROUNDNESS } from "../constants";
|
||||
@ -52,7 +52,7 @@ beforeEach(async () => {
|
||||
Object.assign(document, {
|
||||
elementFromPoint: () => GlobalTestState.canvas,
|
||||
});
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw autoFocus={true} handleKeyboardGlobally={true} />);
|
||||
h.setState({
|
||||
zoom: {
|
||||
value: 1 as NormalizedZoomValue,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { assertSelectedElements, render } from "./test-utils";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { Keyboard, Pointer, UI } from "./helpers/ui";
|
||||
import { API } from "./helpers/api";
|
||||
import { getDefaultAppState } from "../appState";
|
||||
@ -13,14 +13,16 @@ const mouse = new Pointer("mouse");
|
||||
|
||||
describe("history", () => {
|
||||
it("initializing scene should end up with single history entry", async () => {
|
||||
await render(<ExcalidrawApp />, {
|
||||
localStorageData: {
|
||||
await render(
|
||||
<Excalidraw
|
||||
initialData={{
|
||||
elements: [API.createElement({ type: "rectangle", id: "A" })],
|
||||
appState: {
|
||||
zenModeEnabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(h.state.zenModeEnabled).toBe(true));
|
||||
await waitFor(() =>
|
||||
@ -60,14 +62,16 @@ describe("history", () => {
|
||||
});
|
||||
|
||||
it("scene import via drag&drop should create new history entry", async () => {
|
||||
await render(<ExcalidrawApp />, {
|
||||
localStorageData: {
|
||||
await render(
|
||||
<Excalidraw
|
||||
initialData={{
|
||||
elements: [API.createElement({ type: "rectangle", id: "A" })],
|
||||
appState: {
|
||||
viewBackgroundColor: "#FFF",
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(h.state.viewBackgroundColor).toBe("#FFF"));
|
||||
await waitFor(() =>
|
||||
@ -113,7 +117,7 @@ describe("history", () => {
|
||||
});
|
||||
|
||||
it("undo/redo works properly with groups", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
const rect1 = API.createElement({ type: "rectangle", groupIds: ["A"] });
|
||||
const rect2 = API.createElement({ type: "rectangle", groupIds: ["A"] });
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { vi } from "vitest";
|
||||
import { fireEvent, render, waitFor } from "./test-utils";
|
||||
import { queryByTestId } from "@testing-library/react";
|
||||
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { API } from "./helpers/api";
|
||||
import { MIME_TYPES } from "../constants";
|
||||
import { LibraryItem, LibraryItems } from "../types";
|
||||
@ -42,7 +42,7 @@ vi.mock("../data/filesystem.ts", async (importOriginal) => {
|
||||
|
||||
describe("library", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
h.app.library.resetLibrary();
|
||||
});
|
||||
|
||||
@ -189,7 +189,7 @@ describe("library", () => {
|
||||
|
||||
describe("library menu", () => {
|
||||
it("should load library from file picker", async () => {
|
||||
const { container } = await render(<ExcalidrawApp />);
|
||||
const { container } = await render(<Excalidraw />);
|
||||
|
||||
const latestLibrary = await h.app.library.getLatestLibrary();
|
||||
expect(latestLibrary.length).toBe(0);
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
ExcalidrawTextElementWithContainer,
|
||||
FontString,
|
||||
} from "../element/types";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { centerPoint } from "../math";
|
||||
import { reseed } from "../random";
|
||||
import * as Renderer from "../renderer/renderScene";
|
||||
@ -43,7 +43,7 @@ describe("Test Linear Elements", () => {
|
||||
renderInteractiveScene.mockClear();
|
||||
renderStaticScene.mockClear();
|
||||
reseed(7);
|
||||
const comp = await render(<ExcalidrawApp />);
|
||||
const comp = await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
h.state.width = 1000;
|
||||
h.state.height = 1000;
|
||||
container = comp.container;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { render, fireEvent } from "./test-utils";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import * as Renderer from "../renderer/renderScene";
|
||||
import { reseed } from "../random";
|
||||
import { bindOrUnbindLinearElement } from "../element/binding";
|
||||
@ -31,7 +31,7 @@ const { h } = window;
|
||||
|
||||
describe("move element", () => {
|
||||
it("rectangle", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
const canvas = container.querySelector("canvas.interactive")!;
|
||||
|
||||
{
|
||||
@ -67,7 +67,7 @@ describe("move element", () => {
|
||||
});
|
||||
|
||||
it("rectangles with binding arrow", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
|
||||
// create elements
|
||||
const rectA = UI.createElement("rectangle", { size: 100 });
|
||||
@ -119,7 +119,7 @@ describe("move element", () => {
|
||||
|
||||
describe("duplicate element on move when ALT is clicked", () => {
|
||||
it("rectangle", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
const canvas = container.querySelector("canvas.interactive")!;
|
||||
|
||||
{
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
mockBoundingClientRect,
|
||||
restoreOriginalGetBoundingClientRect,
|
||||
} from "./test-utils";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import * as Renderer from "../renderer/renderScene";
|
||||
import { KEYS } from "../keys";
|
||||
import { ExcalidrawLinearElement } from "../element/types";
|
||||
@ -29,7 +29,7 @@ const { h } = window;
|
||||
|
||||
describe("remove shape in non linear elements", () => {
|
||||
beforeAll(() => {
|
||||
mockBoundingClientRect();
|
||||
mockBoundingClientRect({ width: 1000, height: 1000 });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@ -37,12 +37,13 @@ describe("remove shape in non linear elements", () => {
|
||||
});
|
||||
|
||||
it("rectangle", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("rectangle");
|
||||
fireEvent.click(tool);
|
||||
|
||||
const canvas = container.querySelector("canvas.interactive")!;
|
||||
|
||||
fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
|
||||
fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 });
|
||||
|
||||
@ -52,7 +53,7 @@ describe("remove shape in non linear elements", () => {
|
||||
});
|
||||
|
||||
it("ellipse", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("ellipse");
|
||||
fireEvent.click(tool);
|
||||
@ -67,7 +68,7 @@ describe("remove shape in non linear elements", () => {
|
||||
});
|
||||
|
||||
it("diamond", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("diamond");
|
||||
fireEvent.click(tool);
|
||||
@ -84,7 +85,7 @@ describe("remove shape in non linear elements", () => {
|
||||
|
||||
describe("multi point mode in linear elements", () => {
|
||||
it("arrow", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("arrow");
|
||||
fireEvent.click(tool);
|
||||
@ -109,8 +110,8 @@ describe("multi point mode in linear elements", () => {
|
||||
key: KEYS.ENTER,
|
||||
});
|
||||
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(10);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(11);
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(9);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(10);
|
||||
expect(h.elements.length).toEqual(1);
|
||||
|
||||
const element = h.elements[0] as ExcalidrawLinearElement;
|
||||
@ -128,7 +129,7 @@ describe("multi point mode in linear elements", () => {
|
||||
});
|
||||
|
||||
it("line", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("line");
|
||||
fireEvent.click(tool);
|
||||
@ -153,8 +154,8 @@ describe("multi point mode in linear elements", () => {
|
||||
key: KEYS.ENTER,
|
||||
});
|
||||
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(10);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(11);
|
||||
expect(renderInteractiveScene).toHaveBeenCalledTimes(9);
|
||||
expect(renderStaticScene).toHaveBeenCalledTimes(10);
|
||||
expect(h.elements.length).toEqual(1);
|
||||
|
||||
const element = h.elements[0] as ExcalidrawLinearElement;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { CODES, KEYS } from "../keys";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { reseed } from "../random";
|
||||
import * as Renderer from "../renderer/renderScene";
|
||||
import { setDateTimeForTests } from "../utils";
|
||||
@ -13,9 +13,7 @@ import {
|
||||
render,
|
||||
screen,
|
||||
togglePopover,
|
||||
waitFor,
|
||||
} from "./test-utils";
|
||||
import { defaultLang } from "../i18n";
|
||||
import { FONT_FAMILY } from "../constants";
|
||||
import { vi } from "vitest";
|
||||
|
||||
@ -56,7 +54,7 @@ beforeEach(async () => {
|
||||
finger1.reset();
|
||||
finger2.reset();
|
||||
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
h.setState({ height: 768, width: 1024 });
|
||||
});
|
||||
|
||||
@ -443,26 +441,6 @@ describe("regression tests", () => {
|
||||
expect(h.state.zoom.value).toBe(1);
|
||||
});
|
||||
|
||||
it("rerenders UI on language change", async () => {
|
||||
// select rectangle tool to show properties menu
|
||||
UI.clickTool("rectangle");
|
||||
// english lang should display `thin` label
|
||||
expect(screen.queryByTitle(/thin/i)).not.toBeNull();
|
||||
fireEvent.click(document.querySelector(".dropdown-menu-button")!);
|
||||
|
||||
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
|
||||
target: { value: "de-DE" },
|
||||
});
|
||||
// switching to german, `thin` label should no longer exist
|
||||
await waitFor(() => expect(screen.queryByTitle(/thin/i)).toBeNull());
|
||||
// reset language
|
||||
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
|
||||
target: { value: defaultLang.code },
|
||||
});
|
||||
// switching back to English
|
||||
await waitFor(() => expect(screen.queryByTitle(/thin/i)).not.toBeNull());
|
||||
});
|
||||
|
||||
it("make a group and duplicate it", () => {
|
||||
UI.clickTool("rectangle");
|
||||
mouse.down(10, 10);
|
||||
|
@ -6,7 +6,7 @@ import { reseed } from "../random";
|
||||
import { UI, Keyboard } from "./helpers/ui";
|
||||
import { resize } from "./utils";
|
||||
import { ExcalidrawTextElement } from "../element/types";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { API } from "./helpers/api";
|
||||
import { KEYS } from "../keys";
|
||||
import { vi } from "vitest";
|
||||
@ -126,7 +126,7 @@ describe("resize rectangle ellipses and diamond elements", () => {
|
||||
|
||||
describe("Test text element", () => {
|
||||
it("should update font size via keyboard", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
|
||||
const textElement = API.createElement({
|
||||
type: "text",
|
||||
|
@ -8,7 +8,6 @@ import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { API } from "./helpers/api";
|
||||
import { Keyboard } from "./helpers/ui";
|
||||
import { KEYS } from "../keys";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
@ -56,7 +55,7 @@ describe("appState", () => {
|
||||
|
||||
it("moving by page up/down/left/right", async () => {
|
||||
mockBoundingClientRect();
|
||||
await render(<ExcalidrawApp />, {});
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />, {});
|
||||
|
||||
const scrollTest = () => {
|
||||
const initialScrollY = h.state.scrollY;
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
restoreOriginalGetBoundingClientRect,
|
||||
assertSelectedElements,
|
||||
} from "./test-utils";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import * as Renderer from "../renderer/renderScene";
|
||||
import { KEYS } from "../keys";
|
||||
import { reseed } from "../random";
|
||||
@ -34,7 +34,7 @@ const mouse = new Pointer("mouse");
|
||||
|
||||
describe("box-selection", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
});
|
||||
|
||||
it("should allow adding to selection via box-select when holding shift", async () => {
|
||||
@ -102,7 +102,7 @@ describe("box-selection", () => {
|
||||
|
||||
describe("inner box-selection", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
});
|
||||
it("selecting elements visually nested inside another", async () => {
|
||||
const rect1 = API.createElement({
|
||||
@ -218,7 +218,7 @@ describe("inner box-selection", () => {
|
||||
|
||||
describe("selection element", () => {
|
||||
it("create selection element on pointer down", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("selection");
|
||||
fireEvent.click(tool);
|
||||
@ -239,7 +239,7 @@ describe("selection element", () => {
|
||||
});
|
||||
|
||||
it("resize selection element on pointer move", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("selection");
|
||||
fireEvent.click(tool);
|
||||
@ -261,7 +261,7 @@ describe("selection element", () => {
|
||||
});
|
||||
|
||||
it("remove selection element on pointer up", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(<Excalidraw />);
|
||||
// select tool
|
||||
const tool = getByToolName("selection");
|
||||
fireEvent.click(tool);
|
||||
@ -287,7 +287,9 @@ describe("select single element on the scene", () => {
|
||||
});
|
||||
|
||||
it("rectangle", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(
|
||||
<Excalidraw handleKeyboardGlobally={true} />,
|
||||
);
|
||||
const canvas = container.querySelector("canvas.interactive")!;
|
||||
{
|
||||
// create element
|
||||
@ -317,7 +319,9 @@ describe("select single element on the scene", () => {
|
||||
});
|
||||
|
||||
it("diamond", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(
|
||||
<Excalidraw handleKeyboardGlobally={true} />,
|
||||
);
|
||||
const canvas = container.querySelector("canvas.interactive")!;
|
||||
{
|
||||
// create element
|
||||
@ -347,7 +351,9 @@ describe("select single element on the scene", () => {
|
||||
});
|
||||
|
||||
it("ellipse", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(
|
||||
<Excalidraw handleKeyboardGlobally={true} />,
|
||||
);
|
||||
const canvas = container.querySelector("canvas.interactive")!;
|
||||
{
|
||||
// create element
|
||||
@ -377,7 +383,9 @@ describe("select single element on the scene", () => {
|
||||
});
|
||||
|
||||
it("arrow", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(
|
||||
<Excalidraw handleKeyboardGlobally={true} />,
|
||||
);
|
||||
const canvas = container.querySelector("canvas.interactive")!;
|
||||
{
|
||||
// create element
|
||||
@ -419,7 +427,9 @@ describe("select single element on the scene", () => {
|
||||
});
|
||||
|
||||
it("arrow escape", async () => {
|
||||
const { getByToolName, container } = await render(<ExcalidrawApp />);
|
||||
const { getByToolName, container } = await render(
|
||||
<Excalidraw handleKeyboardGlobally={true} />,
|
||||
);
|
||||
const canvas = container.querySelector("canvas.interactive")!;
|
||||
{
|
||||
// create element
|
||||
@ -464,7 +474,7 @@ describe("select single element on the scene", () => {
|
||||
|
||||
describe("tool locking & selection", () => {
|
||||
it("should not select newly created element while tool is locked", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
|
||||
UI.clickTool("lock");
|
||||
expect(h.state.activeTool.locked).toBe(true);
|
||||
@ -480,7 +490,7 @@ describe("tool locking & selection", () => {
|
||||
|
||||
describe("selectedElementIds stability", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
});
|
||||
|
||||
it("box-selection should be stable when not changing selection", () => {
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
|
||||
import { render } from "./test-utils";
|
||||
import { API } from "./helpers/api";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
@ -252,7 +252,7 @@ const { h } = window;
|
||||
|
||||
describe("subtype registration", () => {
|
||||
it("should check for invalid subtype or parents", async () => {
|
||||
await render(<ExcalidrawApp />, {});
|
||||
await render(<Excalidraw />, {});
|
||||
// Define invalid subtype records
|
||||
const null1 = {} as SubtypeRecord;
|
||||
const null2 = { subtype: "" } as SubtypeRecord;
|
||||
@ -368,7 +368,7 @@ describe("subtypes", () => {
|
||||
API.createElement({ type: "diamond", id: "D", subtype: test1.subtype }),
|
||||
API.createElement({ type: "ellipse", id: "E", subtype: test1.subtype }),
|
||||
];
|
||||
await render(<ExcalidrawApp />, { localStorageData: { elements } });
|
||||
await render(<Excalidraw />, { localStorageData: { elements } });
|
||||
elements.forEach((el) => expect(el.subtype).toBe(test1.subtype));
|
||||
});
|
||||
it("should enforce prop value restrictions", async () => {
|
||||
@ -381,7 +381,7 @@ describe("subtypes", () => {
|
||||
}),
|
||||
API.createElement({ type: "line", id: "B", roughness: 1 }),
|
||||
];
|
||||
await render(<ExcalidrawApp />, { localStorageData: { elements } });
|
||||
await render(<Excalidraw />, { localStorageData: { elements } });
|
||||
elements.forEach((el) => {
|
||||
if (el.subtype === test1.subtype) {
|
||||
expect(el.roughness).toBe(0);
|
||||
@ -440,7 +440,7 @@ describe("subtypes", () => {
|
||||
fontSize: FONTSIZE,
|
||||
}),
|
||||
];
|
||||
await render(<ExcalidrawApp />, { localStorageData: { elements } });
|
||||
await render(<Excalidraw />, { localStorageData: { elements } });
|
||||
const mockMeasureText = (text: string, font: FontString) => {
|
||||
if (text === testString) {
|
||||
let multiplier = 1;
|
||||
@ -608,7 +608,7 @@ describe("subtype actions", () => {
|
||||
API.createElement({ type: "line", id: "C", subtype: test3.subtype }),
|
||||
API.createElement({ type: "text", id: "D", subtype: test3.subtype }),
|
||||
];
|
||||
await render(<ExcalidrawApp />, { localStorageData: { elements } });
|
||||
await render(<Excalidraw />, { localStorageData: { elements } });
|
||||
});
|
||||
it("should apply to elements with their subtype", async () => {
|
||||
h.setState({ selectedElementIds: { A: true } });
|
||||
@ -672,7 +672,8 @@ describe("subtype loading", () => {
|
||||
text: testString,
|
||||
}),
|
||||
];
|
||||
await render(<ExcalidrawApp />, { localStorageData: { elements } });
|
||||
await render(<Excalidraw />, { localStorageData: { elements } });
|
||||
h.elements = elements;
|
||||
});
|
||||
it("should redraw text bounding boxes", async () => {
|
||||
h.setState({ selectedElementIds: { A: true } });
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
|
||||
import * as toolQueries from "./queries/toolQueries";
|
||||
import { ImportedDataState } from "../data/types";
|
||||
import { STORAGE_KEYS } from "../excalidraw-app/app_constants";
|
||||
import { STORAGE_KEYS } from "../../excalidraw-app/app_constants";
|
||||
|
||||
import { SceneData } from "../types";
|
||||
import { getSelectedElements } from "../scene/selection";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { render, GlobalTestState } from "./test-utils";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { KEYS } from "../keys";
|
||||
import { Keyboard, Pointer, UI } from "./helpers/ui";
|
||||
import { CURSOR_TYPE } from "../constants";
|
||||
@ -12,7 +12,7 @@ const pointerTypes = [mouse, touch, pen];
|
||||
|
||||
describe("view mode", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
});
|
||||
|
||||
it("after switching to view mode – cursor type should be pointer", async () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import { render } from "./test-utils";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { Excalidraw } from "../packages/excalidraw/index";
|
||||
import { reseed } from "../random";
|
||||
import {
|
||||
actionSendBackward,
|
||||
@ -121,7 +121,6 @@ const assertZindex = ({
|
||||
operations: [Actions, string[]][];
|
||||
}) => {
|
||||
const selectedElementIds = populateElements(elements, appState);
|
||||
|
||||
operations.forEach(([action, expected]) => {
|
||||
h.app.actionManager.executeAction(action);
|
||||
expect(h.elements.map((element) => element.id)).toEqual(expected);
|
||||
@ -131,7 +130,7 @@ const assertZindex = ({
|
||||
|
||||
describe("z-index manipulation", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
await render(<Excalidraw />);
|
||||
});
|
||||
|
||||
it("send back", () => {
|
||||
|
@ -96,9 +96,9 @@ const getTargetIndexAccountingForBinding = (
|
||||
if (direction === "left") {
|
||||
return elements.indexOf(nextElement);
|
||||
}
|
||||
|
||||
const boundTextElement =
|
||||
Scene.getScene(nextElement)!.getElement(boundElementId);
|
||||
|
||||
if (boundTextElement) {
|
||||
return elements.indexOf(boundTextElement);
|
||||
}
|
||||
|
@ -16,6 +16,6 @@
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "excalidraw-app"],
|
||||
"exclude": ["src/packages/excalidraw/types"]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user