mirror of
https://github.com/excalidraw/excalidraw
synced 2025-07-25 13:58:22 +08:00
Compare commits
14 Commits
frame-grou
...
dwelle/v0.
Author | SHA1 | Date | |
---|---|---|---|
640caba739 | |||
2879c9d852 | |||
81046ccd6b | |||
207a0bcc6e | |||
f53edb7437 | |||
8d0a8ce65b | |||
a528769b68 | |||
ddb7585057 | |||
111a48ffb1 | |||
54153629c0 | |||
9c425224c7 | |||
9d1d45a8ea | |||
029c3c48ba | |||
adfd95be33 |
@ -80,7 +80,7 @@ A given tab trigger button that switches to a given sidebar tab. It must be rend
|
||||
| `className` | `string` | No | |
|
||||
| `style` | `React.CSSProperties` | No | |
|
||||
|
||||
You can use the [`ref.toggleSidebar({ name: "custom" })`](/docs/@excalidraw/excalidraw/api/props/ref#toggleSidebar) api to control the sidebar, but we export a trigger button to make UI use cases easier.
|
||||
You can use the [`ref.toggleSidebar({ name: "custom" })`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api#toggleSidebar) api to control the sidebar, but we export a trigger button to make UI use cases easier.
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -10,13 +10,17 @@ The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/
|
||||
|
||||
**_Signature_**
|
||||
|
||||
<pre>
|
||||
convertToExcalidrawElements(elements:{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133">
|
||||
ExcalidrawElementSkeleton
|
||||
</a>
|
||||
)
|
||||
</pre>
|
||||
```ts
|
||||
convertToExcalidrawElements(
|
||||
elements: ExcalidrawElementSkeleton,
|
||||
opts?: { regenerateIds: boolean }
|
||||
): ExcalidrawElement[]
|
||||
```
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `elements` | [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L137) | | The Excalidraw element Skeleton which needs to be converted to Excalidraw elements. |
|
||||
| `opts` | `{ regenerateIds: boolean }` | ` {regenerateIds: true}` | By default `id` will be regenerated for all the elements irrespective of whether you pass the `id` so if you don't want the ids to regenerated, you can set this attribute to `false`. |
|
||||
|
||||
**_How to use_**
|
||||
|
||||
@ -24,13 +28,13 @@ The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/
|
||||
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
|
||||
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/excalidraw-api#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_.
|
||||
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.
|
||||
|
||||
@ -427,3 +431,45 @@ convertToExcalidrawElements([
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Frames
|
||||
|
||||
To create a frame, you need to pass `type`, `children` (list of Excalidraw element ids). The rest of the attributes are optional.
|
||||
|
||||
```ts
|
||||
{
|
||||
type: "frame";
|
||||
children: readonly ExcalidrawElement["id"][];
|
||||
name?: string;
|
||||
} & Partial<ExcalidrawFrameElement>);
|
||||
```
|
||||
|
||||
```ts
|
||||
convertToExcalidrawElements([
|
||||
{
|
||||
"type": "rectangle",
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
"strokeWidth": 2,
|
||||
"id": "1"
|
||||
},
|
||||
{
|
||||
"type": "diamond",
|
||||
"x": 120,
|
||||
"y": 20,
|
||||
"backgroundColor": "#fff3bf",
|
||||
"strokeWidth": 2,
|
||||
"label": {
|
||||
"text": "HELLO EXCALIDRAW",
|
||||
"strokeColor": "#099268",
|
||||
"fontSize": 30
|
||||
},
|
||||
"id": "2"
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"children": ["1", "2"],
|
||||
"name": "My frame"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
@ -1,27 +1,26 @@
|
||||
# ref
|
||||
# excalidrawAPI
|
||||
|
||||
<pre>
|
||||
<a href="https://reactjs.org/docs/refs-and-the-dom.html#creating-refs">
|
||||
createRef
|
||||
</a>{" "}
|
||||
|{" "}
|
||||
<a href="https://reactjs.org/docs/hooks-reference.html#useref">useRef</a>{" "}
|
||||
|{" "}
|
||||
<a href="https://reactjs.org/docs/refs-and-the-dom.html#callback-refs">
|
||||
callbackRef
|
||||
</a>{" "}
|
||||
| <br />
|
||||
{ current: { readyPromise: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L460">
|
||||
resolvablePromise
|
||||
</a> } }
|
||||
(api:{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L616">
|
||||
ExcalidrawAPI
|
||||
</a>
|
||||
) => void;
|
||||
</pre>
|
||||
|
||||
You can pass a `ref` when you want to access some excalidraw APIs. We expose the below APIs:
|
||||
Once the callback is triggered, you will need to store the api in state to access it later.
|
||||
|
||||
```jsx showLineNumbers
|
||||
export default function App() {
|
||||
const [excalidrawAPI, setExcalidrawAPI] = useState(null);
|
||||
return <Excalidraw excalidrawAPI={{(api)=> setExcalidrawAPI(api)}} />;
|
||||
}
|
||||
```
|
||||
|
||||
You can use this prop when you want to access some [Excalidraw APIs](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L616). We expose the below APIs :point_down:
|
||||
|
||||
| API | Signature | Usage |
|
||||
| --- | --- | --- |
|
||||
| ready | `boolean` | This is set to true once Excalidraw is rendered |
|
||||
| [readyPromise](#readypromise) | `function` | This promise will be resolved with the api once excalidraw has rendered. This will be helpful when you want do some action on the host app once this promise resolves. For this to work you will have to pass ref as shown [here](#readypromise) |
|
||||
| [updateScene](#updatescene) | `function` | updates the scene with the sceneData |
|
||||
| [updateLibrary](#updatelibrary) | `function` | updates the scene with the sceneData |
|
||||
| [addFiles](#addfiles) | `function` | add files data to the appState |
|
||||
@ -39,54 +38,15 @@ You can pass a `ref` when you want to access some excalidraw APIs. We expose the
|
||||
| [setCursor](#setcursor) | `function` | This API can be used to set customise the mouse cursor on the canvas |
|
||||
| [resetCursor](#resetcursor) | `function` | This API can be used to reset to default mouse cursor on the canvas |
|
||||
| [toggleMenu](#togglemenu) | `function` | Toggles specific menus on/off |
|
||||
| [onChange](#onChange) | `function` | Subscribes to change events |
|
||||
| [onPointerDown](#onPointerDown) | `function` | Subscribes to `pointerdown` events |
|
||||
| [onPointerUp](#onPointerUp) | `function` | Subscribes to `pointerup` events |
|
||||
|
||||
## readyPromise
|
||||
:::info The `Ref` support has been removed in v0.17.0 so if you are using refs, please update the integration to use the `excalidrawAPI`.
|
||||
|
||||
<pre>
|
||||
const excalidrawRef = { current:{ readyPromise:
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L460">
|
||||
resolvablePromise
|
||||
</a>
|
||||
} }
|
||||
</pre>
|
||||
Additionally `ready` and `readyPromise` from the API have been discontinued. These APIs were found to be superfluous, and as part of the effort to streamline the APIs and maintain simplicity, they were removed in version v0.17.0.
|
||||
|
||||
Since plain object is passed as a `ref`, the `readyPromise` is resolved as soon as the component is mounted. Most of the time you will not need this unless you have a specific use case where you can't pass the `ref` in the react way and want to do some action on the host when this promise resolves.
|
||||
|
||||
```jsx showLineNumbers
|
||||
const resolvablePromise = () => {
|
||||
let resolve;
|
||||
let reject;
|
||||
const promise = new Promise((_resolve, _reject) => {
|
||||
resolve = _resolve;
|
||||
reject = _reject;
|
||||
});
|
||||
promise.resolve = resolve;
|
||||
promise.reject = reject;
|
||||
return promise;
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const excalidrawRef = useMemo(
|
||||
() => ({
|
||||
current: {
|
||||
readyPromise: resolvablePromise(),
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
excalidrawRef.current.readyPromise.then((api) => {
|
||||
console.log("loaded", api);
|
||||
});
|
||||
}, [excalidrawRef]);
|
||||
return (
|
||||
<div style={{ height: "500px" }}>
|
||||
<Excalidraw ref={excalidrawRef} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
:::
|
||||
|
||||
## updateScene
|
||||
|
||||
@ -387,14 +347,25 @@ This API can be used to get the files present in the scene. It may contain files
|
||||
|
||||
This API has the below signature. It sets the `tool` passed in param as the active tool.
|
||||
|
||||
<pre>
|
||||
(tool: <br /> { type:{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/shapes.tsx#L15">
|
||||
SHAPES
|
||||
</a>
|
||||
[number]["value"]| "eraser" } |
|
||||
<br /> { type: "custom"; customType: string }) => void
|
||||
</pre>
|
||||
```ts
|
||||
(
|
||||
tool: (
|
||||
| (
|
||||
| { type: Exclude<ToolType, "image"> }
|
||||
| {
|
||||
type: Extract<ToolType, "image">;
|
||||
insertOnCanvasDirectly?: boolean;
|
||||
}
|
||||
)
|
||||
| { type: "custom"; customType: string }
|
||||
) & { locked?: boolean },
|
||||
) => {};
|
||||
```
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `type` | [ToolType](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L91) | `selection` | The tool type which should be set as active tool. When setting `image` as active tool, the insertion onto canvas when using image tool is disabled by default, so you can enable it by setting `insertOnCanvasDirectly` to `true` |
|
||||
| `locked` | `boolean` | `false` | Indicates whether the the active tool should be locked. It behaves the same way when using the `lock` tool in the editor interface |
|
||||
|
||||
## setCursor
|
||||
|
||||
@ -421,3 +392,51 @@ This API is especially useful when you render a custom [`<Sidebar/>`](/docs/@exc
|
||||
```
|
||||
|
||||
This API can be used to reset to default mouse cursor.
|
||||
|
||||
## onChange
|
||||
|
||||
```tsx
|
||||
(
|
||||
callback: (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
files: BinaryFiles,
|
||||
) => void
|
||||
) => () => void
|
||||
```
|
||||
|
||||
Subscribes to change events, similar to [`props.onChange`](/docs/@excalidraw/excalidraw/api/props#onchange).
|
||||
|
||||
Returns an unsubscribe function.
|
||||
|
||||
## onPointerDown
|
||||
|
||||
```tsx
|
||||
(
|
||||
callback: (
|
||||
activeTool: AppState["activeTool"],
|
||||
pointerDownState: PointerDownState,
|
||||
event: React.PointerEvent<HTMLElement>,
|
||||
) => void,
|
||||
) => () => void
|
||||
```
|
||||
|
||||
Subscribes to canvas `pointerdown` events.
|
||||
|
||||
Returns an unsubscribe function.
|
||||
|
||||
## onPointerUp
|
||||
|
||||
```tsx
|
||||
(
|
||||
callback: (
|
||||
activeTool: AppState["activeTool"],
|
||||
pointerDownState: PointerDownState,
|
||||
event: PointerEvent,
|
||||
) => void,
|
||||
) => () => void
|
||||
```
|
||||
|
||||
Subscribes to canvas `pointerup` events.
|
||||
|
||||
Returns an unsubscribe function.
|
@ -1,11 +1,11 @@
|
||||
# Props
|
||||
|
||||
All `props` are *optional*.
|
||||
All `props` are _optional_.
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata) | `object` | `null` | <code>Promise<object | null></code> | `null` | The initial data with which app loads. |
|
||||
| [`ref`](/docs/@excalidraw/excalidraw/api/props/ref) | `object` | _ | `Ref` to be passed to Excalidraw |
|
||||
| [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata) | `object` | `null` | <code>Promise<object | null></code> | `null` | The initial data with which app loads. |
|
||||
| [`excalidrawAPI`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api) | `function` | _ | Callback triggered with the excalidraw api once rendered |
|
||||
| [`isCollaborating`](#iscollaborating) | `boolean` | _ | This indicates if the app is in `collaboration` mode |
|
||||
| [`onChange`](#onchange) | `function` | _ | This callback is triggered whenever the component updates due to any change. This callback will receive the excalidraw `elements` and the current `app state`. |
|
||||
| [`onPointerUpdate`](#onpointerupdate) | `function` | _ | Callback triggered when mouse pointer is updated. |
|
||||
@ -37,7 +37,7 @@ Beyond attributes that Excalidraw elements already support, you can store `custo
|
||||
|
||||
You can use this to add any extra information you need to keep track of.
|
||||
|
||||
You can add `customData` to elements when passing them as [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata), or using [`updateScene`](/docs/@excalidraw/excalidraw/api/props/ref#updatescene) / [`updateLibrary`](/docs/@excalidraw/excalidraw/api/props/ref#updatelibrary) afterwards.
|
||||
You can add `customData` to elements when passing them as [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata), or using [`updateScene`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api#updatescene) / [`updateLibrary`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api#updatelibrary) afterwards.
|
||||
|
||||
```js showLineNumbers
|
||||
{
|
||||
@ -93,8 +93,16 @@ This callback is triggered when mouse pointer is updated.
|
||||
|
||||
This prop if passed will be triggered on pointer down events and has the below signature.
|
||||
|
||||
|
||||
<pre>
|
||||
(activeTool: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L115"> AppState["activeTool"]</a>, pointerDownState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L424">PointerDownState</a>) => void
|
||||
(activeTool:{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L115">
|
||||
{" "}
|
||||
AppState["activeTool"]
|
||||
</a>
|
||||
, pointerDownState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L424">
|
||||
PointerDownState
|
||||
</a>) => void
|
||||
</pre>
|
||||
|
||||
### onScrollChange
|
||||
@ -110,7 +118,11 @@ This prop if passed will be triggered when canvas is scrolled and has the below
|
||||
This callback is triggered if passed when something is pasted into the scene. You can use this callback in case you want to do something additional when the paste event occurs.
|
||||
|
||||
<pre>
|
||||
(data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/clipboard.ts#L18">ClipboardData</a>, event: ClipboardEvent | null) => boolean
|
||||
(data:{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/clipboard.ts#L18">
|
||||
ClipboardData
|
||||
</a>
|
||||
, event: ClipboardEvent | null) => boolean
|
||||
</pre>
|
||||
|
||||
This callback must return a `boolean` value or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to a boolean value.
|
||||
@ -136,8 +148,11 @@ It is invoked with empty items when user clears the library. You can use this ca
|
||||
This prop if passed will be triggered when clicked on `link`. To handle the redirect yourself (such as when using your own router for internal links), you must call `event.preventDefault()`.
|
||||
|
||||
<pre>
|
||||
(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement</a>,
|
||||
event: CustomEvent<{ nativeEvent: MouseEvent }>) => void
|
||||
(element:{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">
|
||||
ExcalidrawElement
|
||||
</a>
|
||||
, event: CustomEvent<{ nativeEvent: MouseEvent }>) => void
|
||||
</pre>
|
||||
|
||||
Example:
|
||||
@ -180,30 +195,30 @@ import { defaultLang, languages } from "@excalidraw/excalidraw";
|
||||
|
||||
### viewModeEnabled
|
||||
|
||||
This prop indicates whether the app is in `view mode`. When supplied, the value takes precedence over *intialData.appState.viewModeEnabled*, the `view mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
||||
This prop indicates whether the app is in `view mode`. When supplied, the value takes precedence over _intialData.appState.viewModeEnabled_, the `view mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
||||
|
||||
### zenModeEnabled
|
||||
|
||||
This prop indicates whether the app is in `zen mode`. When supplied, the value takes precedence over *intialData.appState.zenModeEnabled*, the `zen mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
||||
This prop indicates whether the app is in `zen mode`. When supplied, the value takes precedence over _intialData.appState.zenModeEnabled_, the `zen mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
||||
|
||||
### gridModeEnabled
|
||||
|
||||
This prop indicates whether the shows the grid. When supplied, the value takes precedence over *intialData.appState.gridModeEnabled*, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
||||
This prop indicates whether the shows the grid. When supplied, the value takes precedence over _intialData.appState.gridModeEnabled_, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
||||
|
||||
### libraryReturnUrl
|
||||
|
||||
If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com).
|
||||
Defaults to *window.location.origin + window.location.pathname*. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
|
||||
Defaults to _window.location.origin + window.location.pathname_. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
|
||||
|
||||
### theme
|
||||
|
||||
This prop controls Excalidraw's theme. When supplied, the value takes precedence over *intialData.appState.theme*, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app unless *UIOptions.canvasActions.toggleTheme* is set to `true`, in which case the `theme` prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the `theme` prop when you detect a change to `appState.theme` from the [onChange](#onchange) callback).
|
||||
This prop controls Excalidraw's theme. When supplied, the value takes precedence over _intialData.appState.theme_, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app unless _UIOptions.canvasActions.toggleTheme_ is set to `true`, in which case the `theme` prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the `theme` prop when you detect a change to `appState.theme` from the [onChange](#onchange) callback).
|
||||
|
||||
You can use [`THEME`](/docs/@excalidraw/excalidraw/api/utils#theme) to specify the theme.
|
||||
|
||||
### name
|
||||
|
||||
This prop sets the `name` of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over *intialData.appState.name*, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
|
||||
This prop sets the `name` of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over _intialData.appState.name_, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
|
||||
|
||||
|
||||
### detectScroll
|
||||
@ -236,4 +251,4 @@ validateEmbeddable?: boolean | string[] | RegExp | RegExp[] | ((link: string) =>
|
||||
|
||||
This is an optional property. By default we support a handful of well-known sites. You may allow additional sites or disallow the default ones by supplying a custom validator. If you pass `true`, all URLs will be allowed. You can also supply a list of hostnames, RegExp (or list of RegExp objects), or a function. If the function returns `undefined`, the built-in validator will be used.
|
||||
|
||||
Supplying a list of hostnames (with or without `www.`) is the preferred way to allow a specific list of domains.
|
||||
Supplying a list of hostnames (with or without `www.`) is the preferred way to allow a specific list of domains.
|
||||
|
@ -1,6 +1,6 @@
|
||||
# UIOptions
|
||||
|
||||
This prop can be used to customise UI of Excalidraw. Currently we support customising [`canvasActions`](#canvasactions), [`dockedSidebarBreakpoint`](#dockedsidebarbreakpoint) and [`welcomeScreen`](#welcmescreen).
|
||||
This prop can be used to customise UI of Excalidraw. Currently we support customising [`canvasActions`](#canvasactions), [`dockedSidebarBreakpoint`](#dockedsidebarbreakpoint) [`welcomeScreen`](#welcmescreen) and [`tools`](#tools).
|
||||
|
||||
<pre>
|
||||
{
|
||||
@ -70,3 +70,12 @@ function App() {
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## tools
|
||||
|
||||
This `prop ` controls the visibility of the tools in the editor.
|
||||
Currently you can control the visibility of `image` tool via this prop.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| image | boolean | true | Decides whether `image` tool should be visible.
|
@ -129,7 +129,7 @@ if (contents.type === MIME_TYPES.excalidraw) {
|
||||
|
||||
<pre>
|
||||
loadSceneOrLibraryFromBlob(<br/>
|
||||
blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,
|
||||
blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,<br/>
|
||||
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> | null,<br/>
|
||||
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>
|
||||
fileHandle?: FileSystemHandle | null<br/>
|
||||
@ -164,9 +164,9 @@ import { isLinearElement } from "@excalidraw/excalidraw";
|
||||
|
||||
**Signature**
|
||||
|
||||
```tsx
|
||||
<pre>
|
||||
isLinearElement(elementType?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L80">ExcalidrawElement</a>): boolean
|
||||
```
|
||||
</pre>
|
||||
|
||||
### getNonDeletedElements
|
||||
|
||||
@ -195,8 +195,10 @@ import { mergeLibraryItems } from "@excalidraw/excalidraw";
|
||||
**_Signature_**
|
||||
|
||||
<pre>
|
||||
mergeLibraryItems(localItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L250">LibraryItems</a>,<br/>
|
||||
otherItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">LibraryItems</a>) => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L250">LibraryItems</a>
|
||||
mergeLibraryItems(<br/>
|
||||
localItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L250">LibraryItems</a>,<br/>
|
||||
otherItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">LibraryItems</a><br/>
|
||||
): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L250">LibraryItems</a>
|
||||
</pre>
|
||||
|
||||
### parseLibraryTokensFromUrl
|
||||
@ -331,13 +333,15 @@ const App = () => (
|
||||
render(<App />);
|
||||
```
|
||||
|
||||
The `device` has the following `attributes`
|
||||
The `device` has the following `attributes`, some grouped into `viewport` and `editor` objects, per context.
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `isMobile` | `boolean` | Set to `true` when the device is `mobile` |
|
||||
| `isTouchScreen` | `boolean` | Set to `true` for `touch` devices |
|
||||
| `canDeviceFitSidebar` | `boolean` | Implies whether there is enough space to fit the `sidebar` |
|
||||
| `viewport.isMobile` | `boolean` | Set to `true` when viewport is in `mobile` breakpoint |
|
||||
| `viewport.isLandscape` | `boolean` | Set to `true` when the viewport is in `landscape` mode |
|
||||
| `editor.canFitSidebar` | `boolean` | Set to `true` if there's enough space to fit the `sidebar` |
|
||||
| `editor.isMobile` | `boolean` | Set to `true` when editor container is in `mobile` breakpoint |
|
||||
| `isTouchScreen` | `boolean` | Set to `true` for `touch` when touch event detected |
|
||||
|
||||
### i18n
|
||||
|
||||
@ -382,3 +386,94 @@ function App() {
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### getCommonBounds
|
||||
|
||||
This util can be used to get the common bounds of the passed elements.
|
||||
|
||||
**_Signature_**
|
||||
|
||||
```ts
|
||||
getCommonBounds(
|
||||
elements: readonly ExcalidrawElement[]
|
||||
): readonly [
|
||||
minX: number,
|
||||
minY: number,
|
||||
maxX: number,
|
||||
maxY: number,
|
||||
]
|
||||
```
|
||||
|
||||
**_How to use_**
|
||||
|
||||
```js
|
||||
import { getCommonBounds } from "@excalidraw/excalidraw";
|
||||
```
|
||||
|
||||
### elementsOverlappingBBox
|
||||
|
||||
To filter `elements` that are inside, overlap, or contain the `bounds` rectangle.
|
||||
|
||||
The bounds check is approximate and does not precisely follow the element's shape. You can also supply `errorMargin` which effectively makes the `bounds` larger by that amount.
|
||||
|
||||
This API has 3 `type`s of operation: `overlap`, `contain`, and `inside`:
|
||||
|
||||
- `overlap` - filters elements that are overlapping or inside bounds.
|
||||
- `contain` - filters elements that are inside bounds or bounds inside elements.
|
||||
- `inside` - filters elements that are inside bounds.
|
||||
|
||||
**_Signature_**
|
||||
|
||||
<pre>
|
||||
elementsOverlappingBBox(<br/>
|
||||
elements: readonly NonDeletedExcalidrawElement[];<br/>
|
||||
bounds: <a href="https://github.com/excalidraw/excalidraw/blob/9c425224c789d083bf16e0597ce4a429b9ee008e/src/element/bounds.ts#L37-L42">Bounds</a> | ExcalidrawElement;<br/>
|
||||
errorMargin?: number;<br/>
|
||||
type: "overlap" | "contain" | "inside";<br/>
|
||||
): NonDeletedExcalidrawElement[];
|
||||
</pre>
|
||||
|
||||
**_How to use_**
|
||||
|
||||
```js
|
||||
import { elementsOverlappingBBox } from "@excalidraw/excalidraw";
|
||||
```
|
||||
|
||||
### isElementInsideBBox
|
||||
|
||||
Lower-level API than `elementsOverlappingBBox` to check if a single `element` is inside `bounds`. If `eitherDirection=true`, returns `true` if `element` is fully inside `bounds` rectangle, or vice versa. When `false`, it returns `true` only for the former case.
|
||||
|
||||
**_Signature_**
|
||||
|
||||
<pre>
|
||||
isElementInsideBBox(<br/>
|
||||
element: NonDeletedExcalidrawElement,<br/>
|
||||
bounds: <a href="https://github.com/excalidraw/excalidraw/blob/9c425224c789d083bf16e0597ce4a429b9ee008e/src/element/bounds.ts#L37-L42">Bounds</a>,<br/>
|
||||
eitherDirection = false,<br/>
|
||||
): boolean
|
||||
</pre>
|
||||
|
||||
**_How to use_**
|
||||
|
||||
```js
|
||||
import { isElementInsideBBox } from "@excalidraw/excalidraw";
|
||||
```
|
||||
|
||||
### elementPartiallyOverlapsWithOrContainsBBox
|
||||
|
||||
Checks if `element` is overlapping the `bounds` rectangle, or is fully inside.
|
||||
|
||||
**_Signature_**
|
||||
|
||||
<pre>
|
||||
elementPartiallyOverlapsWithOrContainsBBox(<br/>
|
||||
element: NonDeletedExcalidrawElement,<br/>
|
||||
bounds: <a href="https://github.com/excalidraw/excalidraw/blob/9c425224c789d083bf16e0597ce4a429b9ee008e/src/element/bounds.ts#L37-L42">Bounds</a>,<br/>
|
||||
): boolean
|
||||
</pre>
|
||||
|
||||
**_How to use_**
|
||||
|
||||
```js
|
||||
import { elementPartiallyOverlapsWithOrContainsBBox } from "@excalidraw/excalidraw";
|
||||
```
|
||||
|
@ -43,7 +43,7 @@ Once the version is released `@excalibot` will post a comment with the release v
|
||||
To release the next stable version follow the below steps:
|
||||
|
||||
```bash
|
||||
yarn prerelease version
|
||||
yarn prerelease:excalidraw
|
||||
```
|
||||
|
||||
You need to pass the `version` for which you want to create the release. This will make the changes needed before making the release like updating `package.json`, `changelog` and more.
|
||||
@ -51,7 +51,7 @@ You need to pass the `version` for which you want to create the release. This wi
|
||||
The next step is to run the `release` script:
|
||||
|
||||
```bash
|
||||
yarn release
|
||||
yarn release:excalidraw
|
||||
```
|
||||
|
||||
This will publish the package.
|
||||
|
@ -31,6 +31,17 @@ We strongly recommend turning it off. You can follow the steps below on how to d
|
||||
If disabling this setting doesn't fix the display of text elements, please consider opening an [issue](https://github.com/excalidraw/excalidraw/issues/new) on our GitHub, or message us on [Discord](https://discord.gg/UexuTaE).
|
||||
|
||||
|
||||
### ReferenceError: process is not defined
|
||||
|
||||
When using `vite` or any build tools, you will have to make sure the `process` is accessible as we are accessing `process.env.IS_PREACT` to decide whether to use `preact` build.
|
||||
|
||||
Since Vite removes env variables by default, you can update the vite config to ensure its available :point_down:
|
||||
|
||||
```
|
||||
define: {
|
||||
"process.env.IS_PREACT": JSON.stringify("true"),
|
||||
},
|
||||
```
|
||||
|
||||
## Need help?
|
||||
|
||||
|
@ -30,32 +30,17 @@ function App() {
|
||||
}
|
||||
```
|
||||
|
||||
### Rendering Excalidraw only on client
|
||||
### Next.js
|
||||
|
||||
Since _Excalidraw_ doesn't support server side rendering, you should render the component once the host is `mounted`.
|
||||
|
||||
Here are two ways on how you can render **Excalidraw** on **Next.js**.
|
||||
|
||||
1. Importing Excalidraw once **client** is rendered.
|
||||
|
||||
```jsx showLineNumbers
|
||||
import { useState, useEffect } from "react";
|
||||
export default function App() {
|
||||
const [Excalidraw, setExcalidraw] = useState(null);
|
||||
useEffect(() => {
|
||||
import("@excalidraw/excalidraw").then((comp) =>
|
||||
setExcalidraw(comp.Excalidraw),
|
||||
);
|
||||
}, []);
|
||||
return <>{Excalidraw && <Excalidraw />}</>;
|
||||
}
|
||||
```
|
||||
|
||||
Here is a working [demo](https://codesandbox.io/p/sandbox/excalidraw-with-next-5xb3d)
|
||||
1. Using **Next.js Dynamic** import [Recommended].
|
||||
|
||||
2. Using **Next.js Dynamic** import.
|
||||
|
||||
Since Excalidraw doesn't server side rendering so you can also use `dynamic import` to render by setting `ssr` to `false`. However one drawback is the `Refs` don't work with dynamic import in Next.js. We are working on overcoming this and have a better API.
|
||||
Since Excalidraw doesn't support server side rendering so you can also use `dynamic import` to render by setting `ssr` to `false`.
|
||||
|
||||
```jsx showLineNumbers
|
||||
import dynamic from "next/dynamic";
|
||||
@ -72,8 +57,47 @@ export default function App() {
|
||||
|
||||
Here is a working [demo](https://codesandbox.io/p/sandbox/excalidraw-with-next-dynamic-k8yjq2).
|
||||
|
||||
|
||||
2. Importing Excalidraw once **client** is rendered.
|
||||
|
||||
```jsx showLineNumbers
|
||||
import { useState, useEffect } from "react";
|
||||
export default function App() {
|
||||
const [Excalidraw, setExcalidraw] = useState(null);
|
||||
useEffect(() => {
|
||||
import("@excalidraw/excalidraw").then((comp) =>
|
||||
setExcalidraw(comp.Excalidraw),
|
||||
);
|
||||
}, []);
|
||||
return <>{Excalidraw && <Excalidraw />}</>;
|
||||
}
|
||||
```
|
||||
|
||||
Here is a working [demo](https://codesandbox.io/p/sandbox/excalidraw-with-next-5xb3d)
|
||||
|
||||
The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
|
||||
|
||||
### Preact
|
||||
|
||||
Since we support `umd` build ships with `react/jsx-runtime` and `react-dom/client` inlined with the package. This conflicts with `Preact` and hence the build doesn't work directly with `Preact`.
|
||||
|
||||
However we have shipped a separate build for `Preact` so if you are using `Preact` you need to set `process.env.IS_PREACT` to `true` to use the `Preact` build.
|
||||
|
||||
Once the above `env` variable is set, you will be able to use the package in `Preact` as well.
|
||||
|
||||
:::info
|
||||
|
||||
When using `vite` or any build tools, you will have to make sure the `process` is accessible as we are accessing `process.env.IS_PREACT` to decide whether to use `preact` build.
|
||||
|
||||
Since Vite removes env variables by default, you can update the vite config to ensure its available :point_down:
|
||||
|
||||
```
|
||||
define: {
|
||||
"process.env.IS_PREACT": JSON.stringify("true"),
|
||||
},
|
||||
```
|
||||
:::
|
||||
|
||||
## Browser
|
||||
|
||||
To use it in a browser directly:
|
||||
|
@ -1,6 +1,11 @@
|
||||
// @ts-check
|
||||
// Note: type annotations allow type checking and IDEs autocompletion
|
||||
|
||||
// Set the env variable to false so the excalidraw npm package doesn't throw
|
||||
// process undefined as docusaurus doesn't expose env variables by default
|
||||
|
||||
process.env.IS_PREACT = "false";
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: "Excalidraw developer docs",
|
||||
@ -139,7 +144,15 @@ const config = {
|
||||
},
|
||||
}),
|
||||
themes: ["@docusaurus/theme-live-codeblock"],
|
||||
plugins: ["docusaurus-plugin-sass"],
|
||||
plugins: [
|
||||
"docusaurus-plugin-sass",
|
||||
[
|
||||
"docusaurus2-dotenv",
|
||||
{
|
||||
systemvars: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
@ -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-eb020d0",
|
||||
"@excalidraw/excalidraw": "0.17.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"docusaurus-plugin-sass": "0.2.3",
|
||||
@ -30,6 +30,7 @@
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.0.0-rc.1",
|
||||
"@tsconfig/docusaurus": "^1.0.5",
|
||||
"docusaurus2-dotenv": "1.4.0",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"browserslist": {
|
||||
|
@ -53,7 +53,7 @@ const sidebars = {
|
||||
},
|
||||
items: [
|
||||
"@excalidraw/excalidraw/api/props/initialdata",
|
||||
"@excalidraw/excalidraw/api/props/ref",
|
||||
"@excalidraw/excalidraw/api/props/excalidraw-api",
|
||||
"@excalidraw/excalidraw/api/props/render-props",
|
||||
"@excalidraw/excalidraw/api/props/ui-options",
|
||||
],
|
||||
|
@ -1718,10 +1718,10 @@
|
||||
url-loader "^4.1.1"
|
||||
webpack "^5.73.0"
|
||||
|
||||
"@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==
|
||||
"@excalidraw/excalidraw@0.17.0":
|
||||
version "0.17.0"
|
||||
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.17.0.tgz#3c64aa8e36406ac171b008cfecbdce5bb0755725"
|
||||
integrity sha512-NzP22v5xMqxYW27ZtTHhiGFe7kE8NeBk45aoeM/mDSkXiOXPDH+PcvwzHRN/Ei+Vj/0sTPHxejn8bZyRWKGjXg==
|
||||
|
||||
"@hapi/hoek@^9.0.0":
|
||||
version "9.3.0"
|
||||
@ -3567,6 +3567,13 @@ docusaurus-plugin-sass@0.2.3:
|
||||
dependencies:
|
||||
sass-loader "^10.1.1"
|
||||
|
||||
docusaurus2-dotenv@1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/docusaurus2-dotenv/-/docusaurus2-dotenv-1.4.0.tgz#9ab900e29de9081f9f1f28f7224ff63760385641"
|
||||
integrity sha512-iWqem5fnBAyeBBtX75Fxp71uUAnwFaXzOmade8zAhN4vL3RG9m27sLSRwjJGVVgIkEo3esjGyCcTGTiCjfi+sg==
|
||||
dependencies:
|
||||
dotenv-webpack "1.7.0"
|
||||
|
||||
dom-converter@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
|
||||
@ -3652,6 +3659,25 @@ dot-prop@^5.2.0:
|
||||
dependencies:
|
||||
is-obj "^2.0.0"
|
||||
|
||||
dotenv-defaults@^1.0.2:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/dotenv-defaults/-/dotenv-defaults-1.1.1.tgz#032c024f4b5906d9990eb06d722dc74cc60ec1bd"
|
||||
integrity sha512-6fPRo9o/3MxKvmRZBD3oNFdxODdhJtIy1zcJeUSCs6HCy4tarUpd+G67UTU9tF6OWXeSPqsm4fPAB+2eY9Rt9Q==
|
||||
dependencies:
|
||||
dotenv "^6.2.0"
|
||||
|
||||
dotenv-webpack@1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv-webpack/-/dotenv-webpack-1.7.0.tgz#4384d8c57ee6f405c296278c14a9f9167856d3a1"
|
||||
integrity sha512-wwNtOBW/6gLQSkb8p43y0Wts970A3xtNiG/mpwj9MLUhtPCQG6i+/DSXXoNN7fbPCU/vQ7JjwGmgOeGZSSZnsw==
|
||||
dependencies:
|
||||
dotenv-defaults "^1.0.2"
|
||||
|
||||
dotenv@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
|
||||
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==
|
||||
|
||||
duplexer3@^0.1.4:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e"
|
||||
|
@ -128,8 +128,8 @@
|
||||
"test:coverage:watch": "vitest --coverage --watch",
|
||||
"test:ui": "yarn test --ui --coverage.enabled=true",
|
||||
"autorelease": "node scripts/autorelease.js",
|
||||
"prerelease": "node scripts/prerelease.js",
|
||||
"prerelease:excalidraw": "node scripts/prerelease.js",
|
||||
"build:preview": "yarn build && vite preview --port 5000",
|
||||
"release": "node scripts/release.js"
|
||||
"release:excalidraw": "node scripts/release.js"
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
hasStrokeWidth,
|
||||
} from "../scene";
|
||||
import { SHAPES } from "../shapes";
|
||||
import { AppClassProperties, UIAppState, Zoom } from "../types";
|
||||
import { AppClassProperties, AppProps, UIAppState, Zoom } from "../types";
|
||||
import { capitalizeString, isTransparent } from "../utils";
|
||||
import Stack from "./Stack";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
@ -218,10 +218,12 @@ export const ShapesSwitcher = ({
|
||||
activeTool,
|
||||
appState,
|
||||
app,
|
||||
UIOptions,
|
||||
}: {
|
||||
activeTool: UIAppState["activeTool"];
|
||||
appState: UIAppState;
|
||||
app: AppClassProperties;
|
||||
UIOptions: AppProps["UIOptions"];
|
||||
}) => {
|
||||
const [isExtraToolsMenuOpen, setIsExtraToolsMenuOpen] = useState(false);
|
||||
|
||||
@ -232,6 +234,14 @@ export const ShapesSwitcher = ({
|
||||
return (
|
||||
<>
|
||||
{SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => {
|
||||
if (
|
||||
UIOptions.tools?.[
|
||||
value as Extract<typeof value, keyof AppProps["UIOptions"]["tools"]>
|
||||
] === false
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const label = t(`toolBar.${value}`);
|
||||
const letter =
|
||||
key && capitalizeString(typeof key === "string" ? key : key[0]);
|
||||
|
@ -341,6 +341,7 @@ import {
|
||||
import { actionToggleHandTool, zoomToFit } from "../actions/actionCanvas";
|
||||
import { jotaiStore } from "../jotai";
|
||||
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
||||
import { ImageSceneDataError } from "../errors";
|
||||
import {
|
||||
getSnapLinesAtPointer,
|
||||
snapDraggedElements,
|
||||
@ -945,7 +946,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
title="Excalidraw Embedded Content"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen={true}
|
||||
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads"
|
||||
sandbox={`${
|
||||
embedLink?.sandbox?.allowSameOrigin
|
||||
? "allow-same-origin"
|
||||
: ""
|
||||
} allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@ -2272,6 +2277,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
// prefer spreadsheet data over image file (MS Office/Libre Office)
|
||||
if (isSupportedImageFile(file) && !data.spreadsheet) {
|
||||
if (!this.isToolSupported("image")) {
|
||||
this.setState({ errorMessage: t("errors.imageToolNotSupported") });
|
||||
return;
|
||||
}
|
||||
|
||||
const imageElement = this.createImageElement({ sceneX, sceneY });
|
||||
this.insertImageElement(imageElement, file);
|
||||
this.initializeImageDimensions(imageElement);
|
||||
@ -2477,7 +2487,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
) {
|
||||
if (
|
||||
!isPlainPaste &&
|
||||
mixedContent.some((node) => node.type === "imageUrl")
|
||||
mixedContent.some((node) => node.type === "imageUrl") &&
|
||||
this.isToolSupported("image")
|
||||
) {
|
||||
const imageURLs = mixedContent
|
||||
.filter((node) => node.type === "imageUrl")
|
||||
@ -3284,6 +3295,16 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
});
|
||||
|
||||
// We purposely widen the `tool` type so this helper can be called with
|
||||
// any tool without having to type check it
|
||||
private isToolSupported = <T extends ToolType | "custom">(tool: T) => {
|
||||
return (
|
||||
this.props.UIOptions.tools?.[
|
||||
tool as Extract<T, keyof AppProps["UIOptions"]["tools"]>
|
||||
] !== false
|
||||
);
|
||||
};
|
||||
|
||||
setActiveTool = (
|
||||
tool: (
|
||||
| (
|
||||
@ -3296,6 +3317,13 @@ class App extends React.Component<AppProps, AppState> {
|
||||
| { type: "custom"; customType: string }
|
||||
) & { locked?: boolean },
|
||||
) => {
|
||||
if (!this.isToolSupported(tool.type)) {
|
||||
console.warn(
|
||||
`"${tool.type}" tool is disabled via "UIOptions.canvasActions.tools.${tool.type}"`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const nextActiveTool = updateActiveTool(this.state, tool);
|
||||
if (nextActiveTool.type === "hand") {
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB);
|
||||
@ -4740,9 +4768,13 @@ class App extends React.Component<AppProps, AppState> {
|
||||
});
|
||||
|
||||
const { x, y } = viewportCoordsToSceneCoords(event, this.state);
|
||||
|
||||
const frame = this.getTopLayerFrameAtSceneCoords({ x, y });
|
||||
|
||||
mutateElement(pendingImageElement, {
|
||||
x,
|
||||
y,
|
||||
frameId: frame ? frame.id : null,
|
||||
});
|
||||
} else if (this.state.activeTool.type === "freedraw") {
|
||||
this.handleFreeDrawElementOnPointerDown(
|
||||
@ -5609,9 +5641,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
private createImageElement = ({
|
||||
sceneX,
|
||||
sceneY,
|
||||
addToFrameUnderCursor = true,
|
||||
}: {
|
||||
sceneX: number;
|
||||
sceneY: number;
|
||||
addToFrameUnderCursor?: boolean;
|
||||
}) => {
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
sceneX,
|
||||
@ -5621,10 +5655,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||
: this.state.gridSize,
|
||||
);
|
||||
|
||||
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
||||
x: gridX,
|
||||
y: gridY,
|
||||
});
|
||||
const topLayerFrame = addToFrameUnderCursor
|
||||
? this.getTopLayerFrameAtSceneCoords({
|
||||
x: gridX,
|
||||
y: gridY,
|
||||
})
|
||||
: null;
|
||||
|
||||
const element = newImageElement({
|
||||
type: "image",
|
||||
@ -7471,6 +7507,13 @@ class App extends React.Component<AppProps, AppState> {
|
||||
imageFile: File,
|
||||
showCursorImagePreview?: boolean,
|
||||
) => {
|
||||
// we should be handling all cases upstream, but in case we forget to handle
|
||||
// a future case, let's throw here
|
||||
if (!this.isToolSupported("image")) {
|
||||
this.setState({ errorMessage: t("errors.imageToolNotSupported") });
|
||||
return;
|
||||
}
|
||||
|
||||
this.scene.addNewElement(imageElement);
|
||||
|
||||
try {
|
||||
@ -7554,6 +7597,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const imageElement = this.createImageElement({
|
||||
sceneX: x,
|
||||
sceneY: y,
|
||||
addToFrameUnderCursor: false,
|
||||
});
|
||||
|
||||
if (insertOnCanvasDirectly) {
|
||||
@ -7854,7 +7898,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
);
|
||||
|
||||
try {
|
||||
if (isSupportedImageFile(file)) {
|
||||
// if image tool not supported, don't show an error here and let it fall
|
||||
// through so we still support importing scene data from images. If no
|
||||
// scene data encoded, we'll show an error then
|
||||
if (isSupportedImageFile(file) && this.isToolSupported("image")) {
|
||||
// first attempt to decode scene from the image if it's embedded
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@ -7982,6 +8029,17 @@ class App extends React.Component<AppProps, AppState> {
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (
|
||||
error instanceof ImageSceneDataError &&
|
||||
error.code === "IMAGE_NOT_CONTAINS_SCENE_DATA" &&
|
||||
!this.isToolSupported("image")
|
||||
) {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
errorMessage: t("errors.imageToolNotSupported"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({ isLoading: false, errorMessage: error.message });
|
||||
}
|
||||
};
|
||||
|
@ -280,6 +280,7 @@ const LayerUI = ({
|
||||
<ShapesSwitcher
|
||||
appState={appState}
|
||||
activeTool={appState.activeTool}
|
||||
UIOptions={UIOptions}
|
||||
app={app}
|
||||
/>
|
||||
</Stack.Row>
|
||||
@ -470,6 +471,7 @@ const LayerUI = ({
|
||||
renderSidebars={renderSidebars}
|
||||
device={device}
|
||||
renderWelcomeScreen={renderWelcomeScreen}
|
||||
UIOptions={UIOptions}
|
||||
/>
|
||||
)}
|
||||
{!device.editor.isMobile && (
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AppClassProperties,
|
||||
AppProps,
|
||||
AppState,
|
||||
Device,
|
||||
ExcalidrawProps,
|
||||
@ -45,6 +46,7 @@ type MobileMenuProps = {
|
||||
renderSidebars: () => JSX.Element | null;
|
||||
device: Device;
|
||||
renderWelcomeScreen: boolean;
|
||||
UIOptions: AppProps["UIOptions"];
|
||||
app: AppClassProperties;
|
||||
};
|
||||
|
||||
@ -62,6 +64,7 @@ export const MobileMenu = ({
|
||||
renderSidebars,
|
||||
device,
|
||||
renderWelcomeScreen,
|
||||
UIOptions,
|
||||
app,
|
||||
}: MobileMenuProps) => {
|
||||
const {
|
||||
@ -83,6 +86,7 @@ export const MobileMenu = ({
|
||||
<ShapesSwitcher
|
||||
appState={appState}
|
||||
activeTool={appState.activeTool}
|
||||
UIOptions={UIOptions}
|
||||
app={app}
|
||||
/>
|
||||
</Stack.Row>
|
||||
|
@ -222,6 +222,9 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
|
||||
toggleTheme: null,
|
||||
saveAsImage: true,
|
||||
},
|
||||
tools: {
|
||||
image: true,
|
||||
},
|
||||
};
|
||||
|
||||
// breakpoints
|
||||
|
@ -99,7 +99,7 @@ export const setCursorForShape = (
|
||||
interactiveCanvas.style.cursor = `url(${url}), auto`;
|
||||
} else if (!["image", "custom"].includes(appState.activeTool.type)) {
|
||||
interactiveCanvas.style.cursor = CURSOR_TYPE.CROSSHAIR;
|
||||
} else {
|
||||
} else if (appState.activeTool.type !== "image") {
|
||||
interactiveCanvas.style.cursor = CURSOR_TYPE.AUTO;
|
||||
}
|
||||
};
|
||||
|
@ -14,6 +14,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -49,6 +50,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -79,6 +81,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "ellipse-1",
|
||||
@ -132,6 +135,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "ellipse-1",
|
||||
@ -190,6 +194,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -227,6 +232,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
},
|
||||
],
|
||||
"containerId": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -271,6 +277,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
},
|
||||
],
|
||||
"containerId": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -313,6 +320,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "text-2",
|
||||
@ -368,6 +376,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id48",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -410,6 +419,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id40",
|
||||
@ -465,6 +475,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id37",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -507,6 +518,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -542,6 +554,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -577,6 +590,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id44",
|
||||
@ -632,6 +646,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id41",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -676,6 +691,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
},
|
||||
],
|
||||
"containerId": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -720,6 +736,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
},
|
||||
],
|
||||
"containerId": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -757,6 +774,7 @@ exports[`Test Transform > should not allow duplicate ids 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -787,6 +805,7 @@ exports[`Test Transform > should transform linear elements 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -832,6 +851,7 @@ exports[`Test Transform > should transform linear elements 2`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": "triangle",
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -877,6 +897,7 @@ exports[`Test Transform > should transform linear elements 3`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -922,6 +943,7 @@ exports[`Test Transform > should transform linear elements 4`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -967,6 +989,7 @@ exports[`Test Transform > should transform regular shapes 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -997,6 +1020,7 @@ exports[`Test Transform > should transform regular shapes 2`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1027,6 +1051,7 @@ exports[`Test Transform > should transform regular shapes 3`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1057,6 +1082,7 @@ exports[`Test Transform > should transform regular shapes 4`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "#c0eb75",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1087,6 +1113,7 @@ exports[`Test Transform > should transform regular shapes 5`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1117,6 +1144,7 @@ exports[`Test Transform > should transform regular shapes 6`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1149,6 +1177,7 @@ exports[`Test Transform > should transform text element 1`] = `
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -1188,6 +1217,7 @@ exports[`Test Transform > should transform text element 2`] = `
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -1230,6 +1260,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -1280,6 +1311,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -1330,6 +1362,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -1380,6 +1413,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -1427,6 +1461,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id25",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -1466,6 +1501,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id26",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -1505,6 +1541,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id27",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -1545,6 +1582,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id28",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -1588,6 +1626,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1623,6 +1662,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1658,6 +1698,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1693,6 +1734,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1728,6 +1770,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1763,6 +1806,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1795,6 +1839,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id13",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -1834,6 +1879,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id14",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -1874,6 +1920,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id15",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -1916,6 +1963,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id16",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -1956,6 +2004,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id17",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
@ -1997,6 +2046,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"baseline": 0,
|
||||
"boundElements": null,
|
||||
"containerId": "id18",
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 20,
|
||||
|
@ -3,7 +3,7 @@ import { cleanAppStateForExport } from "../appState";
|
||||
import { IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
|
||||
import { clearElementsForExport } from "../element";
|
||||
import { ExcalidrawElement, FileId } from "../element/types";
|
||||
import { CanvasError } from "../errors";
|
||||
import { CanvasError, ImageSceneDataError } from "../errors";
|
||||
import { t } from "../i18n";
|
||||
import { calculateScrollCenter } from "../scene";
|
||||
import { AppState, DataURL, LibraryItem } from "../types";
|
||||
@ -24,15 +24,12 @@ const parseFileContents = async (blob: Blob | File) => {
|
||||
).decodePngMetadata(blob);
|
||||
} catch (error: any) {
|
||||
if (error.message === "INVALID") {
|
||||
throw new DOMException(
|
||||
throw new ImageSceneDataError(
|
||||
t("alerts.imageDoesNotContainScene"),
|
||||
"EncodingError",
|
||||
"IMAGE_NOT_CONTAINS_SCENE_DATA",
|
||||
);
|
||||
} else {
|
||||
throw new DOMException(
|
||||
t("alerts.cannotRestoreFromImage"),
|
||||
"EncodingError",
|
||||
);
|
||||
throw new ImageSceneDataError(t("alerts.cannotRestoreFromImage"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -58,15 +55,12 @@ const parseFileContents = async (blob: Blob | File) => {
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.message === "INVALID") {
|
||||
throw new DOMException(
|
||||
throw new ImageSceneDataError(
|
||||
t("alerts.imageDoesNotContainScene"),
|
||||
"EncodingError",
|
||||
"IMAGE_NOT_CONTAINS_SCENE_DATA",
|
||||
);
|
||||
} else {
|
||||
throw new DOMException(
|
||||
t("alerts.cannotRestoreFromImage"),
|
||||
"EncodingError",
|
||||
);
|
||||
throw new ImageSceneDataError(t("alerts.cannotRestoreFromImage"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -131,8 +125,19 @@ export const loadSceneOrLibraryFromBlob = async (
|
||||
fileHandle?: FileSystemHandle | null,
|
||||
) => {
|
||||
const contents = await parseFileContents(blob);
|
||||
let data;
|
||||
try {
|
||||
const data = JSON.parse(contents);
|
||||
try {
|
||||
data = JSON.parse(contents);
|
||||
} catch (error: any) {
|
||||
if (isSupportedImageFile(blob)) {
|
||||
throw new ImageSceneDataError(
|
||||
t("alerts.imageDoesNotContainScene"),
|
||||
"IMAGE_NOT_CONTAINS_SCENE_DATA",
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
if (isValidExcalidrawData(data)) {
|
||||
return {
|
||||
type: MIME_TYPES.excalidraw,
|
||||
@ -162,7 +167,9 @@ export const loadSceneOrLibraryFromBlob = async (
|
||||
}
|
||||
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
||||
} catch (error: any) {
|
||||
console.error(error.message);
|
||||
if (error instanceof ImageSceneDataError) {
|
||||
throw error;
|
||||
}
|
||||
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
||||
}
|
||||
};
|
||||
|
@ -822,4 +822,22 @@ describe("Test Transform", () => {
|
||||
"Duplicate id found for rect-1",
|
||||
);
|
||||
});
|
||||
|
||||
it("should contains customData if provided", () => {
|
||||
const rawData = [
|
||||
{
|
||||
type: "rectangle",
|
||||
x: 100,
|
||||
y: 100,
|
||||
customData: { createdBy: "user01" },
|
||||
},
|
||||
];
|
||||
const convertedElements = convertToExcalidrawElements(
|
||||
rawData as ExcalidrawElementSkeleton[],
|
||||
opts,
|
||||
);
|
||||
expect(convertedElements[0].customData).toStrictEqual({
|
||||
createdBy: "user01",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -13,6 +13,7 @@ import { Point } from "../types";
|
||||
import { generateRoughOptions } from "../scene/Shape";
|
||||
import {
|
||||
isArrowElement,
|
||||
isBoundToContainer,
|
||||
isFreeDrawElement,
|
||||
isLinearElement,
|
||||
isTextElement,
|
||||
@ -22,6 +23,7 @@ import { getBoundTextElement, getContainerElement } from "./textElement";
|
||||
import { LinearElementEditor } from "./linearElementEditor";
|
||||
import { Mutable } from "../utility-types";
|
||||
import { ShapeCache } from "../scene/ShapeCache";
|
||||
import Scene from "../scene/Scene";
|
||||
|
||||
export type RectangleBox = {
|
||||
x: number;
|
||||
@ -53,16 +55,29 @@ export class ElementBounds {
|
||||
static getBounds(element: ExcalidrawElement) {
|
||||
const cachedBounds = ElementBounds.boundsCache.get(element);
|
||||
|
||||
if (cachedBounds?.version && cachedBounds.version === element.version) {
|
||||
if (
|
||||
cachedBounds?.version &&
|
||||
cachedBounds.version === element.version &&
|
||||
// we don't invalidate cache when we update containers and not labels,
|
||||
// which is causing problems down the line. Fix TBA.
|
||||
!isBoundToContainer(element)
|
||||
) {
|
||||
return cachedBounds.bounds;
|
||||
}
|
||||
|
||||
const bounds = ElementBounds.calculateBounds(element);
|
||||
|
||||
ElementBounds.boundsCache.set(element, {
|
||||
version: element.version,
|
||||
bounds,
|
||||
});
|
||||
// hack to ensure that downstream checks could retrieve element Scene
|
||||
// so as to have correctly calculated bounds
|
||||
// FIXME remove when we get rid of all the id:Scene / element:Scene mapping
|
||||
const shouldCache = Scene.getScene(element);
|
||||
|
||||
if (shouldCache) {
|
||||
ElementBounds.boundsCache.set(element, {
|
||||
version: element.version,
|
||||
bounds,
|
||||
});
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ type EmbeddedLink =
|
||||
| ({
|
||||
aspectRatio: { w: number; h: number };
|
||||
warning?: string;
|
||||
sandbox?: { allowSameOrigin?: boolean };
|
||||
} & (
|
||||
| { type: "video" | "generic"; link: string }
|
||||
| { type: "document"; srcdoc: (theme: Theme) => string }
|
||||
@ -30,20 +31,20 @@ const RE_YOUTUBE =
|
||||
/^(?:http(?:s)?:\/\/)?(?:www\.)?youtu(?:be\.com|\.be)\/(embed\/|watch\?v=|shorts\/|playlist\?list=|embed\/videoseries\?list=)?([a-zA-Z0-9_-]+)(?:\?t=|&t=|\?start=|&start=)?([a-zA-Z0-9_-]+)?[^\s]*$/;
|
||||
|
||||
const RE_VIMEO =
|
||||
/^(?:http(?:s)?:\/\/)?(?:(?:w){3}.)?(?:player\.)?vimeo\.com\/(?:video\/)?([^?\s]+)(?:\?.*)?$/;
|
||||
/^(?:http(?:s)?:\/\/)?(?:(?:w){3}\.)?(?:player\.)?vimeo\.com\/(?:video\/)?([^?\s]+)(?:\?.*)?$/;
|
||||
const RE_FIGMA = /^https:\/\/(?:www\.)?figma\.com/;
|
||||
|
||||
const RE_GH_GIST = /^https:\/\/gist\.github\.com/;
|
||||
const RE_GH_GIST_EMBED =
|
||||
/^<script[\s\S]*?\ssrc=["'](https:\/\/gist.github.com\/.*?)\.js["']/i;
|
||||
/https?:\/\/gist\.github\.com\/([\w_-]+)\/([\w_-]+)\.js["']/i;
|
||||
|
||||
// not anchored to start to allow <blockquote> twitter embeds
|
||||
const RE_TWITTER = /(?:http(?:s)?:\/\/)?(?:(?:w){3}.)?twitter.com/;
|
||||
const RE_TWITTER = /(?:https?:\/\/)?(?:(?:w){3}\.)?(?:twitter|x)\.com/;
|
||||
const RE_TWITTER_EMBED =
|
||||
/^<blockquote[\s\S]*?\shref=["'](https:\/\/twitter.com\/[^"']*)/i;
|
||||
/^<blockquote[\s\S]*?\shref=["'](https?:\/\/(?:twitter|x)\.com\/[^"']*)/i;
|
||||
|
||||
const RE_VALTOWN =
|
||||
/^https:\/\/(?:www\.)?val.town\/(v|embed)\/[a-zA-Z_$][0-9a-zA-Z_$]+\.[a-zA-Z_$][0-9a-zA-Z_$]+/;
|
||||
/^https:\/\/(?:www\.)?val\.town\/(v|embed)\/[a-zA-Z_$][0-9a-zA-Z_$]+\.[a-zA-Z_$][0-9a-zA-Z_$]+/;
|
||||
|
||||
const RE_GENERIC_EMBED =
|
||||
/^<(?:iframe|blockquote)[\s\S]*?\s(?:src|href)=["']([^"']*)["'][\s\S]*?>$/i;
|
||||
@ -143,46 +144,27 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
|
||||
}
|
||||
|
||||
if (RE_TWITTER.test(link)) {
|
||||
let ret: EmbeddedLink;
|
||||
// assume embed code
|
||||
if (/<blockquote/.test(link)) {
|
||||
const srcDoc = createSrcDoc(link);
|
||||
ret = {
|
||||
type: "document",
|
||||
srcdoc: () => srcDoc,
|
||||
aspectRatio: { w: 480, h: 480 },
|
||||
};
|
||||
// assume regular tweet url
|
||||
} else {
|
||||
ret = {
|
||||
type: "document",
|
||||
srcdoc: (theme: string) =>
|
||||
createSrcDoc(
|
||||
`<blockquote class="twitter-tweet" data-dnt="true" data-theme="${theme}"><a href="${link}"></a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`,
|
||||
),
|
||||
aspectRatio: { w: 480, h: 480 },
|
||||
};
|
||||
}
|
||||
// the embed srcdoc still supports twitter.com domain only
|
||||
link = link.replace(/\bx.com\b/, "twitter.com");
|
||||
|
||||
const ret: EmbeddedLink = {
|
||||
type: "document",
|
||||
srcdoc: (theme: string) =>
|
||||
createSrcDoc(
|
||||
`<blockquote class="twitter-tweet" data-dnt="true" data-theme="${theme}"><a href="${link}"></a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`,
|
||||
),
|
||||
aspectRatio: { w: 480, h: 480 },
|
||||
sandbox: { allowSameOrigin: true },
|
||||
};
|
||||
embeddedLinkCache.set(originalLink, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (RE_GH_GIST.test(link)) {
|
||||
let ret: EmbeddedLink;
|
||||
// assume embed code
|
||||
if (/<script>/.test(link)) {
|
||||
const srcDoc = createSrcDoc(link);
|
||||
ret = {
|
||||
type: "document",
|
||||
srcdoc: () => srcDoc,
|
||||
aspectRatio: { w: 550, h: 720 },
|
||||
};
|
||||
// assume regular url
|
||||
} else {
|
||||
ret = {
|
||||
type: "document",
|
||||
srcdoc: () =>
|
||||
createSrcDoc(`
|
||||
const ret: EmbeddedLink = {
|
||||
type: "document",
|
||||
srcdoc: () =>
|
||||
createSrcDoc(`
|
||||
<script src="${link}.js"></script>
|
||||
<style type="text/css">
|
||||
* { margin: 0px; }
|
||||
@ -190,9 +172,8 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
|
||||
.gist .gist-file { height: calc(100vh - 2px); padding: 0px; display: grid; grid-template-rows: 1fr auto; }
|
||||
</style>
|
||||
`),
|
||||
aspectRatio: { w: 550, h: 720 },
|
||||
};
|
||||
}
|
||||
aspectRatio: { w: 550, h: 720 },
|
||||
};
|
||||
embeddedLinkCache.set(link, ret);
|
||||
return ret;
|
||||
}
|
||||
@ -310,8 +291,8 @@ export const extractSrc = (htmlString: string): string => {
|
||||
}
|
||||
|
||||
const gistMatch = htmlString.match(RE_GH_GIST_EMBED);
|
||||
if (gistMatch && gistMatch.length === 2) {
|
||||
return gistMatch[1];
|
||||
if (gistMatch && gistMatch.length === 3) {
|
||||
return `https://gist.github.com/${gistMatch[1]}/${gistMatch[2]}`;
|
||||
}
|
||||
|
||||
if (RE_GIPHY.test(htmlString)) {
|
||||
|
@ -67,6 +67,7 @@ export type ElementConstructorOpts = MarkOptional<
|
||||
| "roundness"
|
||||
| "locked"
|
||||
| "opacity"
|
||||
| "customData"
|
||||
>;
|
||||
|
||||
const _newElementBase = <T extends ExcalidrawElement>(
|
||||
@ -120,6 +121,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
||||
updated: getUpdatedTimestamp(),
|
||||
link,
|
||||
locked,
|
||||
customData: rest.customData,
|
||||
};
|
||||
return element;
|
||||
};
|
||||
|
@ -16,3 +16,19 @@ export class AbortError extends DOMException {
|
||||
super(message, "AbortError");
|
||||
}
|
||||
}
|
||||
|
||||
type ImageSceneDataErrorCode =
|
||||
| "IMAGE_NOT_CONTAINS_SCENE_DATA"
|
||||
| "IMAGE_SCENE_DATA_ERROR";
|
||||
|
||||
export class ImageSceneDataError extends Error {
|
||||
public code;
|
||||
constructor(
|
||||
message = "Image Scene Data Error",
|
||||
code: ImageSceneDataErrorCode = "IMAGE_SCENE_DATA_ERROR",
|
||||
) {
|
||||
super(message);
|
||||
this.name = "EncodingError";
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
@ -209,6 +209,7 @@
|
||||
"importLibraryError": "Couldn't load library",
|
||||
"collabSaveFailed": "Couldn't save to the backend database. If problems persist, you should save your file locally to ensure you don't lose your work.",
|
||||
"collabSaveFailed_sizeExceeded": "Couldn't save to the backend database, the canvas seems to be too big. You should save the file locally to ensure you don't lose your work.",
|
||||
"imageToolNotSupported": "Images are disabled.",
|
||||
"brave_measure_text_error": {
|
||||
"line1": "Looks like you are using Brave browser with the <bold>Aggressively Block Fingerprinting</bold> setting enabled.",
|
||||
"line2": "This could result in breaking the <bold>Text Elements</bold> in your drawings.",
|
||||
|
@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"path": "dist/excalidraw.production.min.js",
|
||||
"limit": "325 kB"
|
||||
"limit": "335 kB"
|
||||
},
|
||||
{
|
||||
"path": "dist/excalidraw-assets/locales",
|
||||
|
@ -15,14 +15,84 @@ Please add the latest change on the top under the correct section.
|
||||
|
||||
### Features
|
||||
|
||||
- Add `onPointerUp` prop [#7638](https://github.com/excalidraw/excalidraw/pull/7638).
|
||||
|
||||
- Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area. [#7450](https://github.com/excalidraw/excalidraw/pull/7450)
|
||||
|
||||
### Fixes
|
||||
|
||||
- Keep customData when converting to ExcalidrawElement. [#7656](https://github.com/excalidraw/excalidraw/pull/7656)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- `ExcalidrawEmbeddableElement.validated` was removed and moved to private editor state. This should largely not affect your apps unless you were reading from this attribute. We keep validating embeddable urls internally, and the public [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateembeddable) still applies. [#7539](https://github.com/excalidraw/excalidraw/pull/7539)
|
||||
|
||||
- Create an `ESM` build for `@excalidraw/excalidraw`. The API is in progress and subject to change before stable release. There are some changes on how the package will be consumed
|
||||
|
||||
#### Bundler
|
||||
|
||||
- CSS needs to be imported so you will need to import the css along with the excalidraw component
|
||||
|
||||
```js
|
||||
import { Excalidraw } from "@excalidraw/excalidraw";
|
||||
import "@excalidraw/excalidraw/index.css";
|
||||
```
|
||||
|
||||
- The `types` path is updated
|
||||
|
||||
Instead of importing from `@excalidraw/excalidraw/types/`, you will need to import from `@excalidraw/excalidraw/dist/excalidraw` or `@excalidraw/excalidraw/dist/utils` depending on the types you are using.
|
||||
|
||||
However this we will be fixing before stable release, so in case you want to try it out you will need to update the types for now.
|
||||
|
||||
#### Browser
|
||||
|
||||
- Since its `ESM` so now script type `module` can be used to load it and css needs to be loaded as well.
|
||||
|
||||
```html
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://unpkg.com/@excalidraw/excalidraw@next/dist/browser/dev/index.css"
|
||||
/>
|
||||
<script type="module">
|
||||
import * as ExcalidrawLib from "https://unpkg.com/@excalidraw/excalidraw@next/dist/browser/dev/index.js";
|
||||
window.ExcalidrawLib = ExcalidrawLib;
|
||||
</script>
|
||||
```
|
||||
|
||||
- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336)
|
||||
|
||||
## 0.17.1 (2023-11-28)
|
||||
|
||||
### Fixes
|
||||
|
||||
- Umd build for browser since it was breaking in v0.17.0 [#7349](https://github.com/excalidraw/excalidraw/pull/7349). Also make sure that when using `Vite`, the `process.env.IS_PREACT` is set as `"true"` (string) and not a boolean.
|
||||
|
||||
```
|
||||
define: {
|
||||
"process.env.IS_PREACT": JSON.stringify("true"),
|
||||
}
|
||||
```
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336)
|
||||
|
||||
## 0.17.0 (2023-11-14)
|
||||
|
||||
### Features
|
||||
|
||||
- Added support for disabling `image` tool (also disabling image insertion in general, though keeps support for importing from `.excalidraw` files) [#6320](https://github.com/excalidraw/excalidraw/pull/6320).
|
||||
|
||||
For disabling `image` you need to set 👇
|
||||
|
||||
```
|
||||
UIOptions.tools = {
|
||||
image: false
|
||||
}
|
||||
```
|
||||
|
||||
- Support `excalidrawAPI` prop for accessing the Excalidraw API [#7251](https://github.com/excalidraw/excalidraw/pull/7251).
|
||||
|
||||
#### BREAKING CHANGE
|
||||
|
||||
- The `Ref` support has been removed in v0.17.0 so if you are using refs, please update the integration to use the [`excalidrawAPI`](http://localhost:3003/docs/@excalidraw/excalidraw/api/props/excalidraw-api)
|
||||
|
||||
- Additionally `ready` and `readyPromise` from the API have been discontinued. These APIs were found to be superfluous, and as part of the effort to streamline the APIs and maintain simplicity, they were removed in version v0.17.0.
|
||||
|
||||
- Export [`getCommonBounds`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils#getcommonbounds) helper from the package [#7247](https://github.com/excalidraw/excalidraw/pull/7247).
|
||||
|
||||
- Support frames via programmatic API [#7205](https://github.com/excalidraw/excalidraw/pull/7205).
|
||||
@ -31,12 +101,156 @@ Please add the latest change on the top under the correct section.
|
||||
|
||||
- Regenerate ids by default when using transform api and also update bindings by 0.5px to avoid possible overlapping [#7195](https://github.com/excalidraw/excalidraw/pull/7195)
|
||||
|
||||
- Add onChange, onPointerDown, onPointerUp api subscribers [#7154](https://github.com/excalidraw/excalidraw/pull/7154).
|
||||
|
||||
- Support props.locked for setActiveTool [#7153](https://github.com/excalidraw/excalidraw/pull/7153).
|
||||
|
||||
- Add `selected` prop for `MainMenu.Item` and `MainMenu.ItemCustom` components to indicate active state. [#7078](https://github.com/excalidraw/excalidraw/pull/7078)
|
||||
|
||||
#### BREAKING CHANGES
|
||||
### Fixes
|
||||
|
||||
- Double image dialog on image insertion [#7152](https://github.com/excalidraw/excalidraw/pull/7152).
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- The `Ref` support has been removed in v0.17.0 so if you are using refs, please update the integration to use the [`excalidrawAPI`](http://localhost:3003/docs/@excalidraw/excalidraw/api/props/excalidraw-api) [#7251](https://github.com/excalidraw/excalidraw/pull/7251).
|
||||
|
||||
- Additionally `ready` and `readyPromise` from the API have been discontinued. These APIs were found to be superfluous, and as part of the effort to streamline the APIs and maintain simplicity, they were removed in version v0.17.0 [#7251](https://github.com/excalidraw/excalidraw/pull/7251).
|
||||
|
||||
- [`useDevice`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils#usedevice) hook's return value was changed to differentiate between `editor` and `viewport` breakpoints. [#7243](https://github.com/excalidraw/excalidraw/pull/7243)
|
||||
|
||||
### Build
|
||||
|
||||
- Support Preact [#7255](https://github.com/excalidraw/excalidraw/pull/7255). The host needs to set `process.env.IS_PREACT` to `true`
|
||||
|
||||
When using `vite` or any build tools, you will have to make sure the `process` is accessible as we are accessing `process.env.IS_PREACT` to decide whether to use the `preact` build.
|
||||
|
||||
Since `Vite` removes env variables by default, you can update the Vite config to ensure its available :point_down:
|
||||
|
||||
```
|
||||
define: {
|
||||
"process.env.IS_PREACT": process.env.IS_PREACT,
|
||||
},
|
||||
```
|
||||
|
||||
## Excalidraw Library
|
||||
|
||||
**_This section lists the updates made to the excalidraw library and will not affect the integration._**
|
||||
|
||||
### Features
|
||||
|
||||
- Allow D&D dice app domain for embeds [#7263](https://github.com/excalidraw/excalidraw/pull/7263)
|
||||
|
||||
- Remove full screen shortcut [#7222](https://github.com/excalidraw/excalidraw/pull/7222)
|
||||
|
||||
- Make adaptive-roughness less aggressive [#7250](https://github.com/excalidraw/excalidraw/pull/7250)
|
||||
|
||||
- Render frames on export [#7210](https://github.com/excalidraw/excalidraw/pull/7210)
|
||||
|
||||
- Support mermaid flowchart and sequence diagrams to excalidraw diagrams 🥳 [#6920](https://github.com/excalidraw/excalidraw/pull/6920)
|
||||
|
||||
- Support frames via programmatic API [#7205](https://github.com/excalidraw/excalidraw/pull/7205)
|
||||
|
||||
- Make clipboard more robust and reintroduce contextmenu actions [#7198](https://github.com/excalidraw/excalidraw/pull/7198)
|
||||
|
||||
- Support giphy.com embed domain [#7192](https://github.com/excalidraw/excalidraw/pull/7192)
|
||||
|
||||
- Renderer tweaks [#6698](https://github.com/excalidraw/excalidraw/pull/6698)
|
||||
|
||||
- Closing of "Save to.." Dialog on Save To Disk [#7168](https://github.com/excalidraw/excalidraw/pull/7168)
|
||||
|
||||
- Added Copy/Paste from Google Docs [#7136](https://github.com/excalidraw/excalidraw/pull/7136)
|
||||
|
||||
- Remove bound-arrows from frames [#7157](https://github.com/excalidraw/excalidraw/pull/7157)
|
||||
|
||||
- New dark mode theme & light theme tweaks [#7104](https://github.com/excalidraw/excalidraw/pull/7104)
|
||||
|
||||
- Better laser cursor for dark mode [#7132](https://github.com/excalidraw/excalidraw/pull/7132)
|
||||
|
||||
- Laser pointer improvements [#7128](https://github.com/excalidraw/excalidraw/pull/7128)
|
||||
|
||||
- Initial Laser Pointer MVP [#6739](https://github.com/excalidraw/excalidraw/pull/6739)
|
||||
|
||||
- Export `iconFillColor()` [#6996](https://github.com/excalidraw/excalidraw/pull/6996)
|
||||
|
||||
- Element alignments - snapping [#6256](https://github.com/excalidraw/excalidraw/pull/6256)
|
||||
|
||||
### Fixes
|
||||
|
||||
- Image insertion bugs [#7278](https://github.com/excalidraw/excalidraw/pull/7278)
|
||||
|
||||
- ExportToSvg to honor frameRendering also for name not only for frame itself [#7270](https://github.com/excalidraw/excalidraw/pull/7270)
|
||||
|
||||
- Can't toggle penMode off due to missing typecheck in togglePenMode [#7273](https://github.com/excalidraw/excalidraw/pull/7273)
|
||||
|
||||
- Replace hard coded font family with const value in addFrameLabelsAsTextElements [#7269](https://github.com/excalidraw/excalidraw/pull/7269)
|
||||
|
||||
- Perf issue when ungrouping elements within frame [#7265](https://github.com/excalidraw/excalidraw/pull/7265)
|
||||
|
||||
- Fixes the shortcut collision between "toggleHandTool" and "distributeHorizontally" [#7189](https://github.com/excalidraw/excalidraw/pull/7189)
|
||||
|
||||
- Allow pointer events when editing a linear element [#7238](https://github.com/excalidraw/excalidraw/pull/7238)
|
||||
|
||||
- Make modal use viewport breakpoints [#7246](https://github.com/excalidraw/excalidraw/pull/7246)
|
||||
|
||||
- Align input `:hover`/`:focus` with spec [#7225](https://github.com/excalidraw/excalidraw/pull/7225)
|
||||
|
||||
- Dialog remounting on className updates [#7224](https://github.com/excalidraw/excalidraw/pull/7224)
|
||||
|
||||
- Don't update label position when dragging labelled arrows [#6891](https://github.com/excalidraw/excalidraw/pull/6891)
|
||||
|
||||
- Frame add/remove/z-index ordering changes [#7194](https://github.com/excalidraw/excalidraw/pull/7194)
|
||||
|
||||
- Element relative position when dragging multiple elements on grid [#7107](https://github.com/excalidraw/excalidraw/pull/7107)
|
||||
|
||||
- Freedraw non-solid bg hitbox not working [#7193](https://github.com/excalidraw/excalidraw/pull/7193)
|
||||
|
||||
- Actions panel ux improvement [#6850](https://github.com/excalidraw/excalidraw/pull/6850)
|
||||
|
||||
- Better fill rendering with latest RoughJS [#7031](https://github.com/excalidraw/excalidraw/pull/7031)
|
||||
|
||||
- Fix for Strange Symbol Appearing on Canvas after Deleting Grouped Graphics (Issue #7116) [#7170](https://github.com/excalidraw/excalidraw/pull/7170)
|
||||
|
||||
- Attempt to fix flake in wysiwyg tests [#7173](https://github.com/excalidraw/excalidraw/pull/7173)
|
||||
|
||||
- Ensure `ClipboardItem` created in the same tick to fix safari [#7066](https://github.com/excalidraw/excalidraw/pull/7066)
|
||||
|
||||
- Wysiwyg left in undefined state on reload [#7123](https://github.com/excalidraw/excalidraw/pull/7123)
|
||||
|
||||
- Ensure relative z-index of elements added to frame is retained [#7134](https://github.com/excalidraw/excalidraw/pull/7134)
|
||||
|
||||
- Memoize static canvas on `props.renderConfig` [#7131](https://github.com/excalidraw/excalidraw/pull/7131)
|
||||
|
||||
- Regression from #6739 preventing redirect link in view mode [#7120](https://github.com/excalidraw/excalidraw/pull/7120)
|
||||
|
||||
- Update links to excalidraw-app [#7072](https://github.com/excalidraw/excalidraw/pull/7072)
|
||||
|
||||
- Ensure we do not stop laser update prematurely [#7100](https://github.com/excalidraw/excalidraw/pull/7100)
|
||||
|
||||
- Remove invisible elements safely [#7083](https://github.com/excalidraw/excalidraw/pull/7083)
|
||||
|
||||
- Icon size in manifest [#7073](https://github.com/excalidraw/excalidraw/pull/7073)
|
||||
|
||||
- Elements being dropped/duplicated when added to frame [#7057](https://github.com/excalidraw/excalidraw/pull/7057)
|
||||
|
||||
- Frame name not editable on dbl-click [#7037](https://github.com/excalidraw/excalidraw/pull/7037)
|
||||
|
||||
- Polyfill `Element.replaceChildren` [#7034](https://github.com/excalidraw/excalidraw/pull/7034)
|
||||
|
||||
### Refactor
|
||||
|
||||
- DRY out tool typing [#7086](https://github.com/excalidraw/excalidraw/pull/7086)
|
||||
|
||||
- Refactor event globals to differentiate from `lastPointerUp` [#7084](https://github.com/excalidraw/excalidraw/pull/7084)
|
||||
|
||||
- DRY out and simplify setting active tool from toolbar [#7079](https://github.com/excalidraw/excalidraw/pull/7079)
|
||||
|
||||
### Performance
|
||||
|
||||
- Improve element in frame check [#7124](https://github.com/excalidraw/excalidraw/pull/7124)
|
||||
|
||||
---
|
||||
|
||||
## 0.16.1 (2023-09-21)
|
||||
|
||||
## Excalidraw Library
|
||||
@ -58,7 +272,7 @@ Please add the latest change on the top under the correct section.
|
||||
- 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).
|
||||
- Add support for `opts.fitToViewport` and `opts.viewportZoomFactor` in the [`ExcalidrawAPI.scrollToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/excalidraw-api#scrolltocontent) API. [#6581](https://github.com/excalidraw/excalidraw/pull/6581).
|
||||
- Properly sanitize element `link` urls. [#6728](https://github.com/excalidraw/excalidraw/pull/6728).
|
||||
- Sidebar component now supports tabs — for more detailed description of new behavior and breaking changes, see the linked PR. [#6213](https://github.com/excalidraw/excalidraw/pull/6213)
|
||||
- Exposed `DefaultSidebar` component to allow modifying the default sidebar, such as adding custom tabs to it. [#6213](https://github.com/excalidraw/excalidraw/pull/6213)
|
||||
@ -337,7 +551,7 @@ Please add the latest change on the top under the correct section.
|
||||
|
||||
### Features
|
||||
|
||||
- [`ExcalidrawAPI.scrollToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#scrolltocontent) has new opts object allowing you to fit viewport to content, and animate the scrolling. [#6319](https://github.com/excalidraw/excalidraw/pull/6319)
|
||||
- [`ExcalidrawAPI.scrollToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/excalidraw-api#scrolltocontent) has new opts object allowing you to fit viewport to content, and animate the scrolling. [#6319](https://github.com/excalidraw/excalidraw/pull/6319)
|
||||
|
||||
- Expose `useI18n()` hook return an object containing `t()` i18n helper and current `langCode`. You can use this in components you render as `<Excalidraw>` children to render any of our i18n locale strings. [#6224](https://github.com/excalidraw/excalidraw/pull/6224)
|
||||
|
||||
|
@ -98,6 +98,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
|
||||
const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
|
||||
const [exportEmbedScene, setExportEmbedScene] = useState(false);
|
||||
const [theme, setTheme] = useState<Theme>("light");
|
||||
const [disableImageTool, setDisableImageTool] = useState(false);
|
||||
const [isCollaborating, setIsCollaborating] = useState(false);
|
||||
const [commentIcons, setCommentIcons] = useState<{ [id: string]: Comment }>(
|
||||
{},
|
||||
@ -606,6 +607,16 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
|
||||
/>
|
||||
Switch to Dark Theme
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={disableImageTool === true}
|
||||
onChange={() => {
|
||||
setDisableImageTool(!disableImageTool);
|
||||
}}
|
||||
/>
|
||||
Disable Image Tool
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -686,6 +697,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
|
||||
canvasActions: {
|
||||
loadScene: false,
|
||||
},
|
||||
tools: { image: !disableImageTool },
|
||||
}}
|
||||
renderTopRightUI={renderTopRightUI}
|
||||
onLinkOpen={onLinkOpen}
|
||||
|
@ -56,6 +56,9 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
||||
...DEFAULT_UI_OPTIONS.canvasActions,
|
||||
...canvasActions,
|
||||
},
|
||||
tools: {
|
||||
image: props.UIOptions?.tools?.image ?? true,
|
||||
},
|
||||
};
|
||||
|
||||
if (canvasActions?.export) {
|
||||
|
@ -1,4 +1,10 @@
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
if (process.env.IS_PREACT === "true") {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
module.exports = require("./dist/excalidraw-with-preact.production.min.js");
|
||||
} else {
|
||||
module.exports = require("./dist/excalidraw-with-preact.development.js");
|
||||
}
|
||||
} else if (process.env.NODE_ENV === "production") {
|
||||
module.exports = require("./dist/excalidraw.production.min.js");
|
||||
} else {
|
||||
module.exports = require("./dist/excalidraw.development.js");
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@excalidraw/excalidraw",
|
||||
"version": "0.16.1",
|
||||
"version": "0.17.4",
|
||||
"main": "main.js",
|
||||
"types": "types/packages/excalidraw/index.d.ts",
|
||||
"files": [
|
||||
@ -78,7 +78,7 @@
|
||||
"homepage": "https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw",
|
||||
"scripts": {
|
||||
"gen:types": "tsc --project ../../../tsconfig-types.json",
|
||||
"build:umd": "rm -rf dist && cross-env NODE_ENV=production webpack --config webpack.prod.config.js && cross-env NODE_ENV=development webpack --config webpack.dev.config.js && yarn gen:types",
|
||||
"build:umd": "rm -rf dist && cross-env NODE_ENV=production webpack --config webpack.prod.config.js && cross-env NODE_ENV=development webpack --config webpack.dev.config.js && NODE_ENV=development webpack --config webpack.preact.config.js && NODE_ENV=production webpack --config webpack.preact.config.js && yarn gen:types",
|
||||
"build:umd:withAnalyzer": "cross-env NODE_ENV=production ANALYZER=true webpack --config webpack.prod.config.js",
|
||||
"pack": "yarn build:umd && yarn pack",
|
||||
"start": "webpack serve --config webpack.dev-server.config.js",
|
||||
|
32
src/packages/excalidraw/webpack.preact.config.js
Normal file
32
src/packages/excalidraw/webpack.preact.config.js
Normal file
@ -0,0 +1,32 @@
|
||||
const prodConfig = require("./webpack.prod.config");
|
||||
const devConfig = require("./webpack.dev.config");
|
||||
|
||||
const isProd = process.env.NODE_ENV === "production";
|
||||
|
||||
const config = isProd ? prodConfig : devConfig;
|
||||
const outputFile = isProd
|
||||
? "excalidraw-with-preact.production.min"
|
||||
: "excalidraw-with-preact.development";
|
||||
|
||||
const preactWebpackConfig = {
|
||||
...config,
|
||||
entry: {
|
||||
[outputFile]: "./entry.js",
|
||||
},
|
||||
externals: {
|
||||
...config.externals,
|
||||
"react-dom/client": {
|
||||
root: "ReactDOMClient",
|
||||
commonjs2: "react-dom/client",
|
||||
commonjs: "react-dom/client",
|
||||
amd: "react-dom/client",
|
||||
},
|
||||
"react/jsx-runtime": {
|
||||
root: "ReactJSXRuntime",
|
||||
commonjs2: "react/jsx-runtime",
|
||||
commonjs: "react/jsx-runtime",
|
||||
amd: "react/jsx-runtime",
|
||||
},
|
||||
},
|
||||
};
|
||||
module.exports = preactWebpackConfig;
|
@ -385,6 +385,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -419,6 +420,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -580,6 +582,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -639,6 +642,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -780,6 +784,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -812,6 +817,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -871,6 +877,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -914,6 +921,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -943,6 +951,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -986,6 +995,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1015,6 +1025,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1156,6 +1167,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1188,6 +1200,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1247,6 +1260,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1290,6 +1304,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1319,6 +1334,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1362,6 +1378,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1391,6 +1408,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1534,6 +1552,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1593,6 +1612,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1732,6 +1752,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1791,6 +1812,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1832,6 +1854,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -1973,6 +1996,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2005,6 +2029,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2064,6 +2089,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2107,6 +2133,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2136,6 +2163,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2282,6 +2310,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -2316,6 +2345,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -2377,6 +2407,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2420,6 +2451,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2449,6 +2481,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2495,6 +2528,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -2526,6 +2560,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -2671,6 +2706,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2703,6 +2739,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2762,6 +2799,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2805,6 +2843,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2834,6 +2873,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2877,6 +2917,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2906,6 +2947,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2949,6 +2991,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -2978,6 +3021,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3021,6 +3065,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3050,6 +3095,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3093,6 +3139,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3122,6 +3169,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3165,6 +3213,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3194,6 +3243,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3237,6 +3287,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3266,6 +3317,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3309,6 +3361,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3338,6 +3391,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3479,6 +3533,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3511,6 +3566,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3570,6 +3626,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3613,6 +3670,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3642,6 +3700,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3685,6 +3744,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3714,6 +3774,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3855,6 +3916,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3887,6 +3949,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3946,6 +4009,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -3989,6 +4053,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -4018,6 +4083,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -4061,6 +4127,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -4090,6 +4157,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -4234,6 +4302,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -4266,6 +4335,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -4325,6 +4395,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -4368,6 +4439,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -4397,6 +4469,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -4443,6 +4516,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -4474,6 +4548,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -4520,6 +4595,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -4549,6 +4625,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -4966,6 +5043,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -4998,6 +5076,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -5057,6 +5136,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -5100,6 +5180,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -5129,6 +5210,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -5548,6 +5630,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -5582,6 +5665,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -5643,6 +5727,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -5686,6 +5771,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -5715,6 +5801,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -5761,6 +5848,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -5792,6 +5880,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -6838,6 +6927,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -6870,6 +6960,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -6902,6 +6993,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -6961,6 +7053,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] hi
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
|
@ -7,6 +7,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -56,6 +57,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -90,6 +92,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -122,6 +125,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -171,6 +175,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
|
@ -5,6 +5,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -37,6 +38,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 2`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -69,6 +71,7 @@ exports[`move element > rectangle 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -106,6 +109,7 @@ exports[`move element > rectangles with binding arrow 1`] = `
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -143,6 +147,7 @@ exports[`move element > rectangles with binding arrow 2`] = `
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -175,6 +180,7 @@ exports[`move element > rectangles with binding arrow 3`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": {
|
||||
"elementId": "id1",
|
||||
|
@ -5,6 +5,7 @@ exports[`multi point mode in linear elements > arrow 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -59,6 +60,7 @@ exports[`multi point mode in linear elements > line 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ exports[`select single element on the scene > arrow 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -52,6 +53,7 @@ exports[`select single element on the scene > arrow escape 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -99,6 +101,7 @@ exports[`select single element on the scene > diamond 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -131,6 +134,7 @@ exports[`select single element on the scene > ellipse 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -163,6 +167,7 @@ exports[`select single element on the scene > rectangle 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
|
@ -5,6 +5,7 @@ exports[`restoreElements > should restore arrow element correctly 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -52,6 +53,7 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
||||
"angle": 0,
|
||||
"backgroundColor": "blue",
|
||||
"boundElements": [],
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -88,6 +90,7 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
||||
"angle": 0,
|
||||
"backgroundColor": "blue",
|
||||
"boundElements": [],
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -124,6 +127,7 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
||||
"angle": 0,
|
||||
"backgroundColor": "blue",
|
||||
"boundElements": [],
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
"groupIds": [
|
||||
@ -160,6 +164,7 @@ exports[`restoreElements > should restore freedraw element correctly 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
@ -196,6 +201,7 @@ exports[`restoreElements > should restore line and draw elements correctly 1`] =
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -243,6 +249,7 @@ exports[`restoreElements > should restore line and draw elements correctly 2`] =
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
@ -292,6 +299,7 @@ exports[`restoreElements > should restore text element correctly passing value f
|
||||
"baseline": 0,
|
||||
"boundElements": [],
|
||||
"containerId": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 14,
|
||||
@ -333,6 +341,7 @@ exports[`restoreElements > should restore text element correctly with unknown fo
|
||||
"baseline": 0,
|
||||
"boundElements": [],
|
||||
"containerId": null,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
"fontSize": 10,
|
||||
|
@ -471,7 +471,7 @@ export type ExportOpts = {
|
||||
// truthiness value will determine whether the action is rendered or not
|
||||
// (see manager renderAction). We also override canvasAction values in
|
||||
// excalidraw package index.tsx.
|
||||
type CanvasActions = Partial<{
|
||||
export type CanvasActions = Partial<{
|
||||
changeViewBackgroundColor: boolean;
|
||||
clearCanvas: boolean;
|
||||
export: false | ExportOpts;
|
||||
@ -481,9 +481,12 @@ type CanvasActions = Partial<{
|
||||
saveAsImage: boolean;
|
||||
}>;
|
||||
|
||||
type UIOptions = Partial<{
|
||||
export type UIOptions = Partial<{
|
||||
dockedSidebarBreakpoint: number;
|
||||
canvasActions: CanvasActions;
|
||||
tools: {
|
||||
image: boolean;
|
||||
};
|
||||
/** @deprecated does nothing. Will be removed in 0.15 */
|
||||
welcomeScreen?: boolean;
|
||||
}>;
|
||||
|
Reference in New Issue
Block a user