diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/children-components/sidebar.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/sidebar.mdx index 08445a1c3..ed12f6470 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/api/children-components/sidebar.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/sidebar.mdx @@ -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 diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton.mdx index 3c1baeeaf..838896c42 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton.mdx @@ -10,13 +10,17 @@ The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/ **_Signature_** -
-  convertToExcalidrawElements(elements:{" "}
-  
-    ExcalidrawElementSkeleton
-  
-  )
-
+```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([ ``` ![image](https://github.com/excalidraw/excalidraw/assets/11256141/a8b047c8-2eed-4aea-82a2-e1e6bbddb8d4) + +### 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); +``` + +```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" + }] +} +``` diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/ref.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx similarity index 81% rename from dev-docs/docs/@excalidraw/excalidraw/api/props/ref.mdx rename to dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx index 6edcc9b9e..22e9034fd 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/api/props/ref.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx @@ -1,27 +1,26 @@ -# ref +# excalidrawAPI
-  
-    createRef
-  {" "}
-  |{" "}
-  useRef{" "}
-  |{" "}
-  
-    callbackRef
-  {" "}
-  | 
- { current: { readyPromise: - resolvablePromise - } } + (api:{" "} + + ExcalidrawAPI + + ) => void;
-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 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`. -
-  const excalidrawRef = { current:{ readyPromise:
-  
-     resolvablePromise
-  
-   } }
-
+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 ( -
- -
- ); -}; -``` +::: ## 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. -
-  (tool: 
{ type:{" "} - - SHAPES - - [number]["value"]| "eraser" } | -
{ type: "custom"; customType: string }) => void -
+```ts +( + tool: ( + | ( + | { type: Exclude } + | { + type: Extract; + 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 [``](/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, + ) => 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. diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx index 5764528b6..069b15901 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx @@ -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` | Promise | `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` | Promise | `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. +
-(activeTool:  AppState["activeTool"], pointerDownState: PointerDownState) => void
+(activeTool:{" "}
+  
+    {" "}
+    AppState["activeTool"]
+  
+  , pointerDownState: 
+    PointerDownState
+  ) => void
 
### 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.
-(data: ClipboardData, event: ClipboardEvent | null) => boolean
+  (data:{" "}
+  
+    ClipboardData
+  
+  , event: ClipboardEvent | null) => boolean
 
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()`.
-(element: ExcalidrawElement, 
- event: CustomEvent<{ nativeEvent: MouseEvent }>) => void
+  (element:{" "}
+  
+    ExcalidrawElement
+  
+  , event: CustomEvent<{ nativeEvent: MouseEvent }>) => void
 
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. \ No newline at end of file +Supplying a list of hostnames (with or without `www.`) is the preferred way to allow a specific list of domains. diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx index d7551e366..ac91f9e00 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx @@ -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).
   {
@@ -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.
\ No newline at end of file
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/utils/utils-intro.md b/dev-docs/docs/@excalidraw/excalidraw/api/utils/utils-intro.md
index 262e6f7c7..9f3d3287c 100644
--- a/dev-docs/docs/@excalidraw/excalidraw/api/utils/utils-intro.md
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/utils/utils-intro.md
@@ -129,7 +129,7 @@ if (contents.type === MIME_TYPES.excalidraw) {
 
 
 loadSceneOrLibraryFromBlob(
  - blob: Blob, + blob: Blob,
  localAppState: AppState | null,
  localElements: ExcalidrawElement[] | null,
  fileHandle?: FileSystemHandle | null
@@ -164,9 +164,9 @@ import { isLinearElement } from "@excalidraw/excalidraw"; **Signature** -```tsx +
 isLinearElement(elementType?: ExcalidrawElement): boolean
-```
+
### getNonDeletedElements @@ -195,8 +195,10 @@ import { mergeLibraryItems } from "@excalidraw/excalidraw"; **_Signature_**
-mergeLibraryItems(localItems: LibraryItems,
  - otherItems: LibraryItems) => LibraryItems +mergeLibraryItems(
  + localItems: LibraryItems,
  + otherItems: LibraryItems
+): LibraryItems
### parseLibraryTokensFromUrl @@ -331,13 +333,15 @@ const App = () => ( render(); ``` -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_** + +
+elementsOverlappingBBox(
  + elements: readonly NonDeletedExcalidrawElement[];
  + bounds: Bounds | ExcalidrawElement;
  + errorMargin?: number;
  + type: "overlap" | "contain" | "inside";
+): NonDeletedExcalidrawElement[]; +
+ +**_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_** + +
+isElementInsideBBox(
  + element: NonDeletedExcalidrawElement,
  + bounds: Bounds,
  + eitherDirection = false,
+): boolean +
+ +**_How to use_** + +```js +import { isElementInsideBBox } from "@excalidraw/excalidraw"; +``` + +### elementPartiallyOverlapsWithOrContainsBBox + +Checks if `element` is overlapping the `bounds` rectangle, or is fully inside. + +**_Signature_** + +
+elementPartiallyOverlapsWithOrContainsBBox(
  + element: NonDeletedExcalidrawElement,
  + bounds: Bounds,
+): boolean +
+ +**_How to use_** + +```js +import { elementPartiallyOverlapsWithOrContainsBBox } from "@excalidraw/excalidraw"; +``` diff --git a/dev-docs/docs/@excalidraw/excalidraw/development.mdx b/dev-docs/docs/@excalidraw/excalidraw/development.mdx index 066e0a24e..2e73d5731 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/development.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/development.mdx @@ -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. diff --git a/dev-docs/docs/@excalidraw/excalidraw/faq.mdx b/dev-docs/docs/@excalidraw/excalidraw/faq.mdx index 4274972ab..5c66a603f 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/faq.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/faq.mdx @@ -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": process.env.IS_PREACT, + }, +``` ## Need help? diff --git a/dev-docs/docs/@excalidraw/excalidraw/integration.mdx b/dev-docs/docs/@excalidraw/excalidraw/integration.mdx index 6eada99ec..aa8e002c5 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/integration.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/integration.mdx @@ -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 && }; -} -``` -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 && }; +} +``` + +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": process.env.IS_PREACT, + }, +``` +::: + ## Browser To use it in a browser directly: diff --git a/dev-docs/docusaurus.config.js b/dev-docs/docusaurus.config.js index 706101f66..6b1c0d469 100644 --- a/dev-docs/docusaurus.config.js +++ b/dev-docs/docusaurus.config.js @@ -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; diff --git a/dev-docs/package.json b/dev-docs/package.json index 4601891c9..77bd5cfcb 100644 --- a/dev-docs/package.json +++ b/dev-docs/package.json @@ -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": { diff --git a/dev-docs/sidebars.js b/dev-docs/sidebars.js index 2b4ab8d86..8510ef135 100644 --- a/dev-docs/sidebars.js +++ b/dev-docs/sidebars.js @@ -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", ], diff --git a/dev-docs/yarn.lock b/dev-docs/yarn.lock index b22621f31..f3fdde469 100644 --- a/dev-docs/yarn.lock +++ b/dev-docs/yarn.lock @@ -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" diff --git a/package.json b/package.json index 1e578619a..30a4cc026 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,9 @@ }, "dependencies": { "@braintree/sanitize-url": "6.0.2", - "@excalidraw/mermaid-to-excalidraw": "0.1.2", "@excalidraw/laser-pointer": "1.2.0", - "@excalidraw/random-username": "1.0.0", + "@excalidraw/mermaid-to-excalidraw": "0.1.2", + "@excalidraw/random-username": "1.1.0", "@radix-ui/react-popover": "1.0.3", "@radix-ui/react-tabs": "1.0.2", "@sentry/browser": "6.2.5", @@ -132,8 +132,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" } } diff --git a/src/components/Actions.tsx b/src/components/Actions.tsx index d21b7b7ce..0aabffaff 100644 --- a/src/components/Actions.tsx +++ b/src/components/Actions.tsx @@ -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"; @@ -220,10 +220,12 @@ export const ShapesSwitcher = ({ activeTool, appState, app, + UIOptions, }: { activeTool: UIAppState["activeTool"]; appState: UIAppState; app: AppClassProperties; + UIOptions: AppProps["UIOptions"]; }) => { const [isExtraToolsMenuOpen, setIsExtraToolsMenuOpen] = useState(false); @@ -234,6 +236,14 @@ export const ShapesSwitcher = ({ return ( <> {SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => { + if ( + UIOptions.tools?.[ + value as Extract + ] === false + ) { + return null; + } + const label = t(`toolBar.${value}`); const letter = key && capitalizeString(typeof key === "string" ? key : key[0]); diff --git a/src/components/App.tsx b/src/components/App.tsx index d6fd9115b..80fc50bd2 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -351,6 +351,7 @@ import { import { actionToggleHandTool, zoomToFit } from "../actions/actionCanvas"; import { jotaiStore } from "../jotai"; import { activeConfirmDialogAtom } from "./ActiveConfirmDialog"; +import { ImageSceneDataError } from "../errors"; import { getSnapLinesAtPointer, snapDraggedElements, @@ -2299,6 +2300,11 @@ class App extends React.Component { // 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); @@ -2504,7 +2510,8 @@ class App extends React.Component { ) { 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") @@ -2744,7 +2751,7 @@ class App extends React.Component { }); }; - togglePenMode = (force?: boolean) => { + togglePenMode = (force: boolean | null) => { this.setState((prevState) => { return { penMode: force ?? !prevState.penMode, @@ -3312,6 +3319,16 @@ class App extends React.Component { } }); + // We purposely widen the `tool` type so this helper can be called with + // any tool without having to type check it + private isToolSupported = (tool: T) => { + return ( + this.props.UIOptions.tools?.[ + tool as Extract + ] !== false + ); + }; + setActiveTool = ( tool: ( | ( @@ -3324,6 +3341,13 @@ class App extends React.Component { | { 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); @@ -4769,9 +4793,13 @@ class App extends React.Component { }); 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( @@ -5638,9 +5666,11 @@ class App extends React.Component { private createImageElement = ({ sceneX, sceneY, + addToFrameUnderCursor = true, }: { sceneX: number; sceneY: number; + addToFrameUnderCursor?: boolean; }) => { const [gridX, gridY] = getGridPoint( sceneX, @@ -5650,10 +5680,12 @@ class App extends React.Component { : 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", @@ -7503,6 +7535,13 @@ class App extends React.Component { 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 { @@ -7586,6 +7625,7 @@ class App extends React.Component { const imageElement = this.createImageElement({ sceneX: x, sceneY: y, + addToFrameUnderCursor: false, }); if (insertOnCanvasDirectly) { @@ -7886,7 +7926,10 @@ class App extends React.Component { ); 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 // --------------------------------------------------------------------- @@ -8014,6 +8057,17 @@ class App extends React.Component { }); } } 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 }); } }; diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index 8b909da23..b32adcf30 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -66,7 +66,7 @@ interface LayerUIProps { elements: readonly NonDeletedExcalidrawElement[]; onLockToggle: () => void; onHandToolToggle: () => void; - onPenModeToggle: () => void; + onPenModeToggle: AppClassProperties["togglePenMode"]; showExitZenModeBtn: boolean; langCode: Language["code"]; renderTopRightUI?: ExcalidrawProps["renderTopRightUI"]; @@ -258,7 +258,7 @@ const LayerUI = ({ onPenModeToggle(null)} title={t("toolBar.penMode")} penDetected={appState.penDetected} /> @@ -280,6 +280,7 @@ const LayerUI = ({ @@ -470,6 +471,7 @@ const LayerUI = ({ renderSidebars={renderSidebars} device={device} renderWelcomeScreen={renderWelcomeScreen} + UIOptions={UIOptions} /> )} {!device.editor.isMobile && ( diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index bb26fe713..91d0c518c 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -1,6 +1,7 @@ import React from "react"; import { AppClassProperties, + AppProps, AppState, Device, ExcalidrawProps, @@ -35,7 +36,7 @@ type MobileMenuProps = { elements: readonly NonDeletedExcalidrawElement[]; onLockToggle: () => void; onHandToolToggle: () => void; - onPenModeToggle: () => void; + onPenModeToggle: AppClassProperties["togglePenMode"]; renderTopRightUI?: ( isMobile: boolean, @@ -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 = ({ @@ -94,7 +98,7 @@ export const MobileMenu = ({ )} onPenModeToggle(null)} title={t("toolBar.penMode")} isMobile penDetected={appState.penDetected} diff --git a/src/constants.ts b/src/constants.ts index fca1c0d29..247349f64 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -222,6 +222,9 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = { toggleTheme: null, saveAsImage: true, }, + tools: { + image: true, + }, }; // breakpoints diff --git a/src/cursor.ts b/src/cursor.ts index c39c2efcf..0d5fd2c3c 100644 --- a/src/cursor.ts +++ b/src/cursor.ts @@ -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; } }; diff --git a/src/data/blob.ts b/src/data/blob.ts index 81ce340fe..b1b625700 100644 --- a/src/data/blob.ts +++ b/src/data/blob.ts @@ -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")); } }; diff --git a/src/errors.ts b/src/errors.ts index e0444d105..4df403496 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -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; + } +} diff --git a/src/locales/en.json b/src/locales/en.json index 846d2dbcf..3b4eba6de 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -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 Aggressively Block Fingerprinting setting enabled.", "line2": "This could result in breaking the Text Elements in your drawings.", diff --git a/src/packages/excalidraw/CHANGELOG.md b/src/packages/excalidraw/CHANGELOG.md index 9c4b90858..4896c2f42 100644 --- a/src/packages/excalidraw/CHANGELOG.md +++ b/src/packages/excalidraw/CHANGELOG.md @@ -11,18 +11,22 @@ The change should be grouped under one of the below section and must contain PR Please add the latest change on the top under the correct section. --> -## Unreleased +## 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 +35,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 @@ -59,7 +207,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) @@ -338,7 +486,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 `` children to render any of our i18n locale strings. [#6224](https://github.com/excalidraw/excalidraw/pull/6224) diff --git a/src/packages/excalidraw/example/App.tsx b/src/packages/excalidraw/example/App.tsx index 974bbb7ef..0f9056783 100644 --- a/src/packages/excalidraw/example/App.tsx +++ b/src/packages/excalidraw/example/App.tsx @@ -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("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 +