Compare commits

..

23 Commits

Author SHA1 Message Date
ad1992 fc2cf8e708 fix: allow selection on pointer down to unlock eleemnt 2022-05-03 18:47:47 +05:30
Aakansha Doshi aae8e2fa5d feat: export MIME_TYPES supported by excalidraw (#5135)
* feat: export MIME_TYPES supported by excalidraw

* Update src/packages/excalidraw/CHANGELOG.md
2022-05-02 15:47:03 +05:30
Aakansha Doshi 9e6d5fdbcb feat: support src collaborators (#5114)
* feat: support avatarURLfor collaborators

* fix

* better avatars :)

* use position fixed for tooltips so it renders correctly when offsets updated

* update docs

* Update src/excalidraw-app/collab/CollabWrapper.tsx

* rename avatarUrl to src
2022-05-02 15:15:24 +05:30
dependabot[bot] 22b2e10ddb chore(deps-dev): bump @babel/plugin-transform-runtime (#5125)
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.16.8 to 7.17.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.17.10/packages/babel-plugin-transform-runtime)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-runtime"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-02 14:47:37 +05:30
David Luzar d53ac2a61e fix: library init/import race conditions (#5101) 2022-04-29 16:45:02 +02:00
Aakansha Doshi 6a0f800716 feat: export exportToClipboard util from package (#5103)
* feat: export copyToClipboard from package

* use promise constructor for better browser supprt

* add type to exportToClipboard

* update docs

* use json instead of text and use selected element in actionCopy

* pass `files` in example `exportToClipboard`

* fix bad access

Co-authored-by: dwelle <luzar.david@gmail.com>
2022-04-29 18:58:44 +05:30
Johannes aee1e2451e fix: remove opacity scroll wheel interaction (#5111) 2022-04-28 19:10:08 +02:00
Aakansha Doshi da94eb1284 fix: use excalidraw asset path in fonts when exporting to svg (#5065)
* fix: use excalidraw asset path in fonts when exporting

* fix

* fix

* introduce env variables and determine asset path correctly

* fix snaps

* use env vars to determine pkg name and version

* update docs

* quotes
2022-04-28 20:19:41 +05:30
Achille Lacoin ea51251fe6 fix: propagate keydown events from excalidraw-wysiwyg inputs (#5099)
* fix: allow propagation for keydown events originating from `excalidraw-wysiwyg`

* revert check on the handleKeyDown function
2022-04-27 20:28:41 +02:00
Aakansha Doshi 399ce1e01a fix: don't bind text to container if double clicked else instead of center (#5105) 2022-04-27 17:04:21 +05:30
dependabot[bot] 7df8302ba2 chore(deps-dev): bump babel-loader in /src/packages/utils (#5088)
Bumps [babel-loader](https://github.com/babel/babel-loader) from 8.2.3 to 8.2.5.
- [Release notes](https://github.com/babel/babel-loader/releases)
- [Changelog](https://github.com/babel/babel-loader/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel-loader/compare/v8.2.3...v8.2.5)

---
updated-dependencies:
- dependency-name: babel-loader
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-27 10:03:17 +00:00
dependabot[bot] af8c59b5bb chore(deps): bump minimist in /src/packages/excalidraw (#5039)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-27 15:27:56 +05:30
dependabot[bot] cf0f00285b chore(deps): bump minimist from 1.2.5 to 1.2.6 in /src/packages/utils (#5040)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-27 15:27:43 +05:30
dependabot[bot] b5c67a384c chore(deps): bump minimist from 1.2.5 to 1.2.6 (#5042)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-27 15:27:20 +05:30
Achille Lacoin af93cedc08 EXCALIDRAW_EXPORT_SOURCE_PATH -> EXCALIDRAW_EXPORT_SOURCE (#5102) 2022-04-27 10:49:02 +02:00
Achille Lacoin b6a6f2d465 feat: Expose window.EXCALIDRAW_EXPORT_SOURCE which host can use to overwrite the source field in exports (#5095)
* Expose `window.EXCALIDRAW_EXPORT_SOURCE` which host can use to overwrite the source field in exports

* Update src/packages/excalidraw/CHANGELOG.md

Co-authored-by: David Luzar <luzar.david@gmail.com>

* address review comments

* Update src/packages/excalidraw/README_NEXT.md

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>

* Update src/packages/excalidraw/README_NEXT.md

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>

* EXCALIDRAW_EXPORT_SOURCE -> EXCALIDRAW_EXPORT_SOURCE_PATH

Co-authored-by: David Luzar <luzar.david@gmail.com>
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2022-04-26 13:28:39 +02:00
dependabot[bot] 6bcbf8b50a chore(deps): bump node-forge in /src/packages/excalidraw (#5071)
Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.2.1 to 1.3.1.
- [Release notes](https://github.com/digitalbazaar/forge/releases)
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/v1.2.1...v1.3.1)

---
updated-dependencies:
- dependency-name: node-forge
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-26 07:40:12 +00:00
dependabot[bot] 666516d7e9 chore(deps): bump async from 2.6.3 to 2.6.4 in /src/packages/excalidraw (#5069)
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-26 07:32:29 +00:00
dependabot[bot] b941c5b661 chore(deps-dev): bump webpack in /src/packages/excalidraw (#5027)
Bumps [webpack](https://github.com/webpack/webpack) from 5.65.0 to 5.72.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.65.0...v5.72.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-26 13:01:35 +05:30
dependabot[bot] 8f8c85c64e chore(deps): bump async from 2.6.3 to 2.6.4 (#5070)
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-26 12:59:37 +05:30
dependabot[bot] 116b0c48da chore(deps): bump nanoid from 3.1.32 to 3.3.3 (#5073)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.32 to 3.3.3.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.32...3.3.3)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-26 12:59:11 +05:30
dependabot[bot] aa2971e8c5 chore(deps-dev): bump autoprefixer in /src/packages/excalidraw (#5087)
Bumps [autoprefixer](https://github.com/postcss/autoprefixer) from 10.4.2 to 10.4.5.
- [Release notes](https://github.com/postcss/autoprefixer/releases)
- [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/autoprefixer/compare/10.4.2...10.4.5)

---
updated-dependencies:
- dependency-name: autoprefixer
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-26 12:58:53 +05:30
dependabot[bot] 5656ac1e3e chore(deps): bump browser-fs-access from 0.24.1 to 0.29.1 (#5090)
Bumps [browser-fs-access](https://github.com/GoogleChromeLabs/browser-fs-access) from 0.24.1 to 0.29.1.
- [Release notes](https://github.com/GoogleChromeLabs/browser-fs-access/releases)
- [Commits](https://github.com/GoogleChromeLabs/browser-fs-access/commits)

---
updated-dependencies:
- dependency-name: browser-fs-access
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-26 12:58:10 +05:30
37 changed files with 603 additions and 330 deletions
+2 -2
View File
@@ -29,7 +29,7 @@
"@types/react": "17.0.39",
"@types/react-dom": "17.0.11",
"@types/socket.io-client": "1.4.36",
"browser-fs-access": "0.24.1",
"browser-fs-access": "0.29.1",
"clsx": "1.1.1",
"fake-indexeddb": "3.1.7",
"firebase": "8.3.3",
@@ -38,7 +38,7 @@
"image-blob-reduce": "3.0.1",
"jotai": "1.6.4",
"lodash.throttle": "4.1.1",
"nanoid": "3.1.32",
"nanoid": "3.3.3",
"open-color": "1.9.1",
"pako": "1.0.11",
"perfect-freehand": "1.0.16",
+2 -2
View File
@@ -25,9 +25,9 @@ export const actionAddToLibrary = register({
}
return app.library
.loadLibrary()
.getLatestLibrary()
.then((items) => {
return app.library.saveLibrary([
return app.library.setLibrary([
{
id: randomId(),
status: "unpublished",
+3 -1
View File
@@ -15,7 +15,9 @@ export const actionCopy = register({
name: "copy",
trackEvent: { category: "element" },
perform: (elements, appState, _, app) => {
copyToClipboard(getNonDeletedElements(elements), appState, app.files);
const selectedElements = getSelectedElements(elements, appState, true);
copyToClipboard(selectedElements, appState, app.files);
return {
commitToHistory: false,
+4 -5
View File
@@ -1,4 +1,4 @@
import { getClientColors, getClientInitials } from "../clients";
import { getClientColors } from "../clients";
import { Avatar } from "../components/Avatar";
import { centerScrollOn } from "../scene/scroll";
import { Collaborator } from "../types";
@@ -43,16 +43,15 @@ export const actionGoToCollaborator = register({
}
const { background, stroke } = getClientColors(clientId, appState);
const shortName = getClientInitials(collaborator.username);
return (
<Avatar
color={background}
border={stroke}
onClick={() => updateData(collaborator.pointer)}
>
{shortName}
</Avatar>
name={collaborator.username || ""}
src={collaborator.src}
/>
);
},
});
-14
View File
@@ -503,20 +503,6 @@ export const actionChangeOpacity = register({
max="100"
step="10"
onChange={(event) => updateData(+event.target.value)}
onWheel={(event) => {
event.stopPropagation();
const target = event.target as HTMLInputElement;
const STEP = 10;
const MAX = 100;
const MIN = 0;
const value = +target.value;
if (event.deltaY < 0 && value < MAX) {
updateData(value + STEP);
} else if (event.deltaY > 0 && value > MIN) {
updateData(value - STEP);
}
}}
value={
getFormValue(
elements,
+11 -11
View File
@@ -2,7 +2,6 @@ import {
ExcalidrawElement,
NonDeletedExcalidrawElement,
} from "./element/types";
import { getSelectedElements } from "./scene";
import { AppState, BinaryFiles } from "./types";
import { SVG_EXPORT_TAG } from "./scene/export";
import { tryParseSpreadsheet, Spreadsheet, VALID_SPREADSHEET } from "./charts";
@@ -12,7 +11,7 @@ import { isPromiseLike } from "./utils";
type ElementsClipboard = {
type: typeof EXPORT_DATA_TYPES.excalidrawClipboard;
elements: ExcalidrawElement[];
elements: readonly NonDeletedExcalidrawElement[];
files: BinaryFiles | undefined;
};
@@ -57,19 +56,20 @@ const clipboardContainsElements = (
export const copyToClipboard = async (
elements: readonly NonDeletedExcalidrawElement[],
appState: AppState,
files: BinaryFiles,
files: BinaryFiles | null,
) => {
// select binded text elements when copying
const selectedElements = getSelectedElements(elements, appState, true);
const contents: ElementsClipboard = {
type: EXPORT_DATA_TYPES.excalidrawClipboard,
elements: selectedElements,
files: selectedElements.reduce((acc, element) => {
if (isInitializedImageElement(element) && files[element.fileId]) {
acc[element.fileId] = files[element.fileId];
}
return acc;
}, {} as BinaryFiles),
elements,
files: files
? elements.reduce((acc, element) => {
if (isInitializedImageElement(element) && files[element.fileId]) {
acc[element.fileId] = files[element.fileId];
}
return acc;
}, {} as BinaryFiles)
: undefined,
};
const json = JSON.stringify(contents);
CLIPBOARD = json;
+56 -36
View File
@@ -257,6 +257,7 @@ import {
isPointHittingLinkIcon,
isLocalLink,
} from "../element/Hyperlink";
import { AbortError } from "../errors";
const defaultDeviceTypeContext: DeviceType = {
isMobile: false,
@@ -703,21 +704,35 @@ class App extends React.Component<AppProps, AppState> {
window.history.replaceState({}, APP_NAME, `?${query.toString()}`);
}
const defaultStatus = "published";
this.setState({ isLibraryOpen: true });
try {
const request = await fetch(decodeURIComponent(url));
const blob = await request.blob();
const defaultStatus = "published";
const libraryItems = await loadLibraryFromBlob(blob, defaultStatus);
if (
token === this.id ||
window.confirm(
t("alerts.confirmAddLibrary", {
numShapes: libraryItems.length,
}),
)
) {
await this.library.importLibrary(libraryItems, defaultStatus);
}
await this.library.importLibrary(
new Promise<LibraryItems>(async (resolve, reject) => {
try {
const request = await fetch(decodeURIComponent(url));
const blob = await request.blob();
const libraryItems = await loadLibraryFromBlob(blob, defaultStatus);
if (
token === this.id ||
window.confirm(
t("alerts.confirmAddLibrary", {
numShapes: libraryItems.length,
}),
)
) {
resolve(libraryItems);
} else {
reject(new AbortError());
}
} catch (error: any) {
reject(error);
}
}),
);
} catch (error: any) {
console.error(error);
this.setState({ errorMessage: t("errors.importLibraryError") });
@@ -1674,6 +1689,11 @@ class App extends React.Component<AppProps, AppState> {
collaborators?: SceneData["collaborators"];
commitToHistory?: SceneData["commitToHistory"];
libraryItems?:
| ((
currentLibraryItems: LibraryItems,
) =>
| Required<SceneData>["libraryItems"]
| Promise<Required<SceneData>["libraryItems"]>)
| Required<SceneData>["libraryItems"]
| Promise<Required<SceneData>["libraryItems"]>;
}) => {
@@ -1694,20 +1714,20 @@ class App extends React.Component<AppProps, AppState> {
}
if (sceneData.libraryItems) {
this.library.saveLibrary(
new Promise<LibraryItems>(async (resolve, reject) => {
this.library.setLibrary((currentLibraryItems) => {
const nextItems =
typeof sceneData.libraryItems === "function"
? sceneData.libraryItems(currentLibraryItems)
: sceneData.libraryItems;
return new Promise<LibraryItems>(async (resolve, reject) => {
try {
resolve(
restoreLibraryItems(
await sceneData.libraryItems,
"unpublished",
),
);
} catch {
reject(new Error(t("errors.importLibraryError")));
resolve(restoreLibraryItems(await nextItems, "unpublished"));
} catch (error: any) {
reject(error);
}
}),
);
});
});
}
},
);
@@ -2144,14 +2164,13 @@ class App extends React.Component<AppProps, AppState> {
of all hit elements */
preferSelected?: boolean;
includeBoundTextElement?: boolean;
includeLockedElements?: boolean;
},
): NonDeleted<ExcalidrawElement> | null {
const allHitElements = this.getElementsAtPosition(
x,
y,
opts?.includeBoundTextElement,
opts?.includeLockedElements,
true,
);
if (allHitElements.length > 1) {
if (opts?.preferSelected) {
@@ -2239,8 +2258,7 @@ class App extends React.Component<AppProps, AppState> {
if (isTextElement(selectedElements[0])) {
existingTextElement = selectedElements[0];
} else if (isTextBindableContainer(selectedElements[0], false)) {
container = selectedElements[0];
existingTextElement = getBoundTextElement(container);
existingTextElement = getBoundTextElement(selectedElements[0]);
}
}
@@ -5281,11 +5299,14 @@ class App extends React.Component<AppProps, AppState> {
file?.type === MIME_TYPES.excalidrawlib ||
file?.name?.endsWith(".excalidrawlib")
) {
this.library
.importLibrary(file)
.catch((error) =>
this.setState({ isLoading: false, errorMessage: error.message }),
);
this.setState({ isLibraryOpen: true });
this.library.importLibrary(file).catch((error) => {
console.error(error);
this.setState({
isLoading: false,
errorMessage: t("errors.importLibraryError"),
});
});
// default: assume an Excalidraw file regardless of extension/MimeType
} else if (file) {
this.setState({ isLoading: true });
@@ -5339,7 +5360,6 @@ class App extends React.Component<AppProps, AppState> {
const { x, y } = viewportCoordsToSceneCoords(event, this.state);
const element = this.getElementAtPosition(x, y, {
preferSelected: true,
includeLockedElements: true,
});
const type = element ? "element" : "canvas";
+6
View File
@@ -12,5 +12,11 @@
cursor: pointer;
font-size: 0.8rem;
font-weight: 500;
&-img {
width: 100%;
height: 100%;
border-radius: 100%;
}
}
}
+18 -10
View File
@@ -1,20 +1,28 @@
import "./Avatar.scss";
import React from "react";
import { getClientInitials } from "../clients";
type AvatarProps = {
children: string;
onClick: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
color: string;
border: string;
name: string;
src?: string;
};
export const Avatar = ({ children, color, border, onClick }: AvatarProps) => (
<div
className="Avatar"
style={{ background: color, border: `1px solid ${border}` }}
onClick={onClick}
>
{children}
</div>
);
export const Avatar = ({ color, border, onClick, name, src }: AvatarProps) => {
const shortName = getClientInitials(name);
const style = src
? undefined
: { background: color, border: `1px solid ${border}` };
return (
<div className="Avatar" style={style} onClick={onClick}>
{src ? (
<img className="Avatar-img" src={src} alt={shortName} />
) : (
shortName
)}
</div>
);
};
+4
View File
@@ -13,6 +13,10 @@
width: 100%;
margin: 2px 0;
.Spinner {
margin-right: 1rem;
}
button {
// 2px from the left to account for focus border of left-most button
margin: 0 2px;
+9 -5
View File
@@ -139,7 +139,7 @@ export const LibraryMenu = ({
const nextItems = libraryItems.filter(
(item) => !selectedItems.includes(item.id),
);
library.saveLibrary(nextItems).catch(() => {
library.setLibrary(nextItems).catch(() => {
setAppState({ errorMessage: t("alerts.errorRemovingFromLibrary") });
});
setSelectedItems([]);
@@ -170,7 +170,7 @@ export const LibraryMenu = ({
...libraryItems,
];
onAddToLibrary();
library.saveLibrary(nextItems).catch(() => {
library.setLibrary(nextItems).catch(() => {
setAppState({ errorMessage: t("alerts.errorAddingToLibrary") });
});
},
@@ -220,7 +220,7 @@ export const LibraryMenu = ({
libItem.status = "published";
}
});
library.saveLibrary(nextLibItems);
library.setLibrary(nextLibItems);
},
[setShowPublishLibraryDialog, setPublishLibSuccess, selectedItems, library],
);
@@ -229,7 +229,10 @@ export const LibraryMenu = ({
LibraryItem["id"] | null
>(null);
if (libraryItemsData.status === "loading") {
if (
libraryItemsData.status === "loading" &&
!libraryItemsData.isInitialized
) {
return (
<LibraryMenuWrapper ref={ref}>
<div className="layer-ui__library-message">
@@ -255,7 +258,7 @@ export const LibraryMenu = ({
}
onError={(error) => window.alert(error)}
updateItemsInStorage={() =>
library.saveLibrary(libraryItemsData.libraryItems)
library.setLibrary(libraryItemsData.libraryItems)
}
onRemove={(id: string) =>
setSelectedItems(selectedItems.filter((_id) => _id !== id))
@@ -264,6 +267,7 @@ export const LibraryMenu = ({
)}
{publishLibSuccess && renderPublishSuccess()}
<LibraryMenuItems
isLoading={libraryItemsData.status === "loading"}
libraryItems={libraryItemsData.libraryItems}
onRemoveFromLibrary={() =>
removeFromLibrary(libraryItemsData.libraryItems)
+20 -12
View File
@@ -22,8 +22,10 @@ import { Tooltip } from "./Tooltip";
import "./LibraryMenuItems.scss";
import { VERSIONS } from "../constants";
import Spinner from "./Spinner";
const LibraryMenuItems = ({
isLoading,
libraryItems,
onRemoveFromLibrary,
onAddToLibrary,
@@ -40,6 +42,7 @@ const LibraryMenuItems = ({
onPublish,
resetLibrary,
}: {
isLoading: boolean;
libraryItems: LibraryItems;
pendingElements: LibraryItem["elements"];
onRemoveFromLibrary: () => void;
@@ -108,7 +111,8 @@ const LibraryMenuItems = ({
importLibraryFromJSON(library)
.catch(muteFSAbortError)
.catch((error) => {
setAppState({ errorMessage: error.message });
console.error(error);
setAppState({ errorMessage: t("errors.importLibraryError") });
});
}}
className="library-actions--load"
@@ -125,7 +129,7 @@ const LibraryMenuItems = ({
onClick={async () => {
const libraryItems = itemsSelected
? items
: await library.loadLibrary();
: await library.getLatestLibrary();
saveLibraryAsJSON(libraryItems)
.catch(muteFSAbortError)
.catch((error) => {
@@ -284,16 +288,20 @@ const LibraryMenuItems = ({
{showRemoveLibAlert && renderRemoveLibAlert()}
<div className="layer-ui__library-header" key="library-header">
{renderLibraryActions()}
<a
href={`${process.env.REACT_APP_LIBRARY_URL}?target=${
window.name || "_blank"
}&referrer=${referrer}&useHash=true&token=${id}&theme=${theme}&version=${
VERSIONS.excalidrawLibrary
}`}
target="_excalidraw_libraries"
>
{t("labels.libraries")}
</a>
{isLoading ? (
<Spinner />
) : (
<a
href={`${process.env.REACT_APP_LIBRARY_URL}?target=${
window.name || "_blank"
}&referrer=${referrer}&useHash=true&token=${id}&theme=${theme}&version=${
VERSIONS.excalidrawLibrary
}`}
target="_excalidraw_libraries"
>
{t("labels.libraries")}
</a>
)}
</div>
<Stack.Col
className="library-menu-items-container__items"
+1 -1
View File
@@ -2,7 +2,7 @@
// container in body where the actual tooltip is appended to
.excalidraw-tooltip {
position: absolute;
position: fixed;
z-index: 1000;
padding: 8px;
+2 -1
View File
@@ -108,7 +108,8 @@ export const EXPORT_DATA_TYPES = {
excalidrawLibrary: "excalidrawlib",
} as const;
export const EXPORT_SOURCE = window.location.origin;
export const EXPORT_SOURCE =
window.EXCALIDRAW_EXPORT_SOURCE || window.location.origin;
// time in milliseconds
export const IMAGE_RENDER_TIMEOUT = 500;
+135 -77
View File
@@ -5,13 +5,12 @@ import type App from "../components/App";
import { ImportedDataState } from "./types";
import { atom } from "jotai";
import { jotaiStore } from "../jotai";
import { isPromiseLike } from "../utils";
import { t } from "../i18n";
export const libraryItemsAtom = atom<
| { status: "loading"; libraryItems: null; promise: Promise<LibraryItems> }
| { status: "loaded"; libraryItems: LibraryItems }
>({ status: "loaded", libraryItems: [] });
export const libraryItemsAtom = atom<{
status: "loading" | "loaded";
isInitialized: boolean;
libraryItems: LibraryItems;
}>({ status: "loaded", isInitialized: true, libraryItems: [] });
const cloneLibraryItems = (libraryItems: LibraryItems): LibraryItems =>
JSON.parse(JSON.stringify(libraryItems));
@@ -40,12 +39,28 @@ const isUniqueItem = (
});
};
/** Merges otherItems into localItems. Unique items in otherItems array are
sorted first. */
export const mergeLibraryItems = (
localItems: LibraryItems,
otherItems: LibraryItems,
): LibraryItems => {
const newItems = [];
for (const item of otherItems) {
if (isUniqueItem(localItems, item)) {
newItems.push(item);
}
}
return [...newItems, ...localItems];
};
class Library {
/** cache for currently active promise when initializing/updating libaries
asynchronously */
private libraryItemsPromise: Promise<LibraryItems> | null = null;
/** last resolved libraryItems */
/** latest libraryItems */
private lastLibraryItems: LibraryItems = [];
/** indicates whether library is initialized with library items (has gone
* though at least one update) */
private isInitialized = false;
private app: App;
@@ -53,95 +68,138 @@ class Library {
this.app = app;
}
resetLibrary = async () => {
this.saveLibrary([]);
private updateQueue: Promise<LibraryItems>[] = [];
private getLastUpdateTask = (): Promise<LibraryItems> | undefined => {
return this.updateQueue[this.updateQueue.length - 1];
};
/** imports library (currently merges, removing duplicates) */
async importLibrary(
private notifyListeners = () => {
if (this.updateQueue.length > 0) {
jotaiStore.set(libraryItemsAtom, {
status: "loading",
libraryItems: this.lastLibraryItems,
isInitialized: this.isInitialized,
});
} else {
this.isInitialized = true;
jotaiStore.set(libraryItemsAtom, {
status: "loaded",
libraryItems: this.lastLibraryItems,
isInitialized: this.isInitialized,
});
try {
this.app.props.onLibraryChange?.(
cloneLibraryItems(this.lastLibraryItems),
);
} catch (error) {
console.error(error);
}
}
};
resetLibrary = () => {
return this.setLibrary([]);
};
/**
* imports library (from blob or libraryItems), merging with current library
* (attempting to remove duplicates)
*/
importLibrary(
library:
| Blob
| Required<ImportedDataState>["libraryItems"]
| Promise<Required<ImportedDataState>["libraryItems"]>,
defaultStatus: LibraryItem["status"] = "unpublished",
) {
return this.saveLibrary(
new Promise<LibraryItems>(async (resolve, reject) => {
try {
let libraryItems: LibraryItems;
if (library instanceof Blob) {
libraryItems = await loadLibraryFromBlob(library, defaultStatus);
} else {
libraryItems = restoreLibraryItems(await library, defaultStatus);
}
const existingLibraryItems = this.lastLibraryItems;
const filteredItems = [];
for (const item of libraryItems) {
if (isUniqueItem(existingLibraryItems, item)) {
filteredItems.push(item);
): Promise<LibraryItems> {
return this.setLibrary(
() =>
new Promise<LibraryItems>(async (resolve, reject) => {
try {
let libraryItems: LibraryItems;
if (library instanceof Blob) {
libraryItems = await loadLibraryFromBlob(library, defaultStatus);
} else {
libraryItems = restoreLibraryItems(await library, defaultStatus);
}
}
resolve([...filteredItems, ...existingLibraryItems]);
} catch (error) {
reject(new Error(t("errors.importLibraryError")));
}
}),
resolve(mergeLibraryItems(this.lastLibraryItems, libraryItems));
} catch (error) {
reject(error);
}
}),
);
}
loadLibrary = (): Promise<LibraryItems> => {
/**
* @returns latest cloned libraryItems. Awaits all in-progress updates first.
*/
getLatestLibrary = (): Promise<LibraryItems> => {
return new Promise(async (resolve) => {
try {
resolve(
cloneLibraryItems(
await (this.libraryItemsPromise || this.lastLibraryItems),
),
);
const libraryItems = await (this.getLastUpdateTask() ||
this.lastLibraryItems);
if (this.updateQueue.length > 0) {
resolve(this.getLatestLibrary());
} else {
resolve(cloneLibraryItems(libraryItems));
}
} catch (error) {
return resolve(this.lastLibraryItems);
}
});
};
saveLibrary = async (items: LibraryItems | Promise<LibraryItems>) => {
const prevLibraryItems = this.lastLibraryItems;
try {
let nextLibraryItems;
if (isPromiseLike(items)) {
const promise = items.then((items) => cloneLibraryItems(items));
this.libraryItemsPromise = promise;
jotaiStore.set(libraryItemsAtom, {
status: "loading",
promise,
libraryItems: null,
});
nextLibraryItems = await promise;
} else {
nextLibraryItems = cloneLibraryItems(items);
setLibrary = (
/**
* LibraryItems that will replace current items. Can be a function which
* will be invoked after all previous tasks are resolved
* (this is the prefered way to update the library to avoid race conditions,
* but you'll want to manually merge the library items in the callback
* - which is what we're doing in Library.importLibrary()).
*
* If supplied promise is rejected with AbortError, we swallow it and
* do not update the library.
*/
libraryItems:
| LibraryItems
| Promise<LibraryItems>
| ((
latestLibraryItems: LibraryItems,
) => LibraryItems | Promise<LibraryItems>),
): Promise<LibraryItems> => {
const task = new Promise<LibraryItems>(async (resolve, reject) => {
try {
await this.getLastUpdateTask();
if (typeof libraryItems === "function") {
libraryItems = libraryItems(this.lastLibraryItems);
}
this.lastLibraryItems = cloneLibraryItems(await libraryItems);
resolve(this.lastLibraryItems);
} catch (error: any) {
reject(error);
}
this.lastLibraryItems = nextLibraryItems;
this.libraryItemsPromise = null;
jotaiStore.set(libraryItemsAtom, {
status: "loaded",
libraryItems: nextLibraryItems,
})
.catch((error) => {
if (error.name === "AbortError") {
console.warn("Library update aborted by user");
return this.lastLibraryItems;
}
throw error;
})
.finally(() => {
this.updateQueue = this.updateQueue.filter((_task) => _task !== task);
this.notifyListeners();
});
await this.app.props.onLibraryChange?.(
cloneLibraryItems(nextLibraryItems),
);
} catch (error: any) {
this.lastLibraryItems = prevLibraryItems;
this.libraryItemsPromise = null;
jotaiStore.set(libraryItemsAtom, {
status: "loaded",
libraryItems: prevLibraryItems,
});
throw error;
}
this.updateQueue.push(task);
this.notifyListeners();
return task;
};
}
+23
View File
@@ -544,6 +544,29 @@ describe("textWysiwyg", () => {
expect((h.elements[1] as ExcalidrawTextElement).containerId).toBe(null);
});
it("should'nt bind text to container when not double clicked on center", async () => {
expect(h.elements.length).toBe(1);
expect(h.elements[0].id).toBe(rectangle.id);
// clicking somewhere on top left
mouse.doubleClickAt(rectangle.x + 20, rectangle.y + 20);
expect(h.elements.length).toBe(2);
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.type).toBe("text");
expect(text.containerId).toBe(null);
mouse.down();
const editor = document.querySelector(
".excalidraw-textEditorContainer > textarea",
) as HTMLTextAreaElement;
fireEvent.change(editor, { target: { value: "Hello World!" } });
await new Promise((r) => setTimeout(r, 0));
editor.blur();
expect(rectangle.boundElements).toBe(null);
});
it("should update font family correctly on undo/redo by selecting bounded text when font family was updated", async () => {
expect(h.elements.length).toBe(1);
-2
View File
@@ -316,8 +316,6 @@ export const textWysiwyg = ({
}
editable.onkeydown = (event) => {
event.stopPropagation();
if (!event.shiftKey && actionZoomIn.keyTest(event)) {
event.preventDefault();
app.actionManager.executeAction(actionZoomIn);
+1
View File
@@ -13,6 +13,7 @@ interface Window {
ClipboardItem: any;
__EXCALIDRAW_SHA__: string | undefined;
EXCALIDRAW_ASSET_PATH: string | undefined;
EXCALIDRAW_EXPORT_SOURCE: string;
gtag: Function;
}
+7
View File
@@ -17,11 +17,18 @@ Please add the latest change on the top under the correct section.
#### Features
- Export [`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L92) supported by Excalidraw [#5135](https://github.com/excalidraw/excalidraw/pull/5135).
- Support [`src`](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L50) for collaborators. Now onwards host can pass `src` to render the customized avatar for collaborators [#5114](https://github.com/excalidraw/excalidraw/pull/5114).
- Support `libraryItems` argument in `initialData.libraryItems` and `updateScene({ libraryItems })` to be a Promise resolving to `LibraryItems`, and support functional update of `libraryItems` in [`updateScene({ libraryItems })`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#updateScene). [#5101](https://github.com/excalidraw/excalidraw/pull/5101).
- Expose util [`mergeLibraryItems`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#mergeLibraryItems) [#5101](https://github.com/excalidraw/excalidraw/pull/5101).
- Expose util [`exportToClipboard`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exportToClipboard) which allows to copy the scene contents to clipboard as `svg`, `png` or `json` [#5103](https://github.com/excalidraw/excalidraw/pull/5103).
- Expose `window.EXCALIDRAW_EXPORT_SOURCE` which you can use to overwrite the `source` field in exported data [#5095](https://github.com/excalidraw/excalidraw/pull/5095).
- The `exportToBlob` utility now supports the `exportEmbedScene` option when generating a png image [#5047](https://github.com/excalidraw/excalidraw/pull/5047).
- Exported [`restoreLibraryItems`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreLibraryItems) API [#4995](https://github.com/excalidraw/excalidraw/pull/4995).
#### Fixes
- Use `window.EXCALIDRAW_ASSET_PATH` for fonts when exporting to svg [#5065](https://github.com/excalidraw/excalidraw/pull/5065).
- Library menu now properly rerenders if open when library is updated using `updateScene({ libraryItems })` [#4995](https://github.com/excalidraw/excalidraw/pull/4995).
#### Refactor
+61 -5
View File
@@ -436,7 +436,7 @@ This helps to load Excalidraw with `initialData`. It must be an object or a [pro
| `elements` | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78) | The elements with which Excalidraw should be mounted. |
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L42) | The App state with which Excalidraw should be mounted. |
| `scrollToContent` | boolean | This attribute implies whether to scroll to the nearest element to center once Excalidraw is mounted. By default, it will not scroll the nearest element to the center. Make sure you pass `initialData.appState.scrollX` and `initialData.appState.scrollY` when `scrollToContent` is false so that scroll positions are retained |
| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L151) | This library items with which Excalidraw should be mounted. |
| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200) &#124; Promise<[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)> | This library items with which Excalidraw should be mounted. |
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) | The files added to the scene. |
```json
@@ -512,9 +512,9 @@ You can use this function to update the scene with the sceneData. It accepts the
| --- | --- | --- |
| `elements` | [`ImportedDataState["elements"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L17) | The `elements` to be updated in the scene |
| `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L18) | The `appState` to be updated in the scene. |
| `collaborators` | <pre>Map<string, <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L29">Collaborator></a></pre> | The list of collaborators to be updated in the scene. |
| `collaborators` | <pre>Map<string, <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L35">Collaborator></a></pre> | The list of collaborators to be updated in the scene. |
| `commitToHistory` | `boolean` | Implies if the `history (undo/redo)` should be recorded. Defaults to `false`. |
| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L258) | The `libraryItems` to be update in the scene. |
| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200) &#124; Promise<[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)> &#124; ((currentItems: [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)>) => [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200) &#124; Promise<[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)>) | The `libraryItems` to be update in the scene. |
### `addFiles`
@@ -857,7 +857,7 @@ This function returns the canvas with the exported elements, appState and dimens
<pre>
exportToBlob(
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L10">ExportOpts</a> & {
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L14">ExportOpts</a> & {
mimeType?: string,
quality?: number;
})
@@ -900,6 +900,34 @@ exportToSvg({
This function returns a promise which resolves to svg of the exported drawing.
#### `exportToClipboard`
**_Signature_**
<pre>
exportToClipboard(
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L14">ExportOpts</a> & {
mimeType?: string,
quality?: number;
type: 'png' | 'svg' |'json'
})
</pre>
| Name | Type | Default | Description |
| --- | --- | --- | --- | --- | --- |
| opts | | | This param is same as the params passed to `exportToCanvas`. You can refer to [`exportToCanvas`](#exportToCanvas). |
| mimeType | string | "image/png" | Indicates the image format, this will be used when exporting as `png`. |
| quality | number | 0.92 | A value between 0 and 1 indicating the [image quality](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#parameters). Applies only to `image/jpeg`/`image/webp` MIME types. This will be used when exporting as `png`. |
| type | 'png' | 'svg' | 'json' | | This determines the format to which the scene data should be exported. |
**How to use**
```js
import { exportToClipboard } from "@excalidraw/excalidraw-next";
```
Copies the scene data in the specified format (determined by `type`) to clipboard.
##### Additional attributes of appState for `export\*` APIs
| Name | Type | Default | Description |
@@ -924,17 +952,21 @@ serializeAsJSON({
Takes the scene elements and state and returns a JSON string. Deleted `elements`as well as most properties from `AppState` are removed from the resulting JSON. (see [`serializeAsJSON()`](https://github.com/excalidraw/excalidraw/blob/master/src/data/json.ts#L16) source for details).
If you want to overwrite the source field in the JSON string, you can set `window.EXCALIDRAW_EXPORT_SOURCE` to the desired value.
#### `serializeLibraryAsJSON`
**_Signature_**
<pre>
serializeLibraryAsJSON({
libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L191">LibraryItems[]</a>,
libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">LibraryItems[]</a>,
</pre>
Takes the library items and returns a JSON string.
If you want to overwrite the source field in the JSON string, you can set `window.EXCALIDRAW_EXPORT_SOURCE` to the desired value.
#### `getSceneVersion`
**How to use**
@@ -1040,6 +1072,20 @@ getNonDeletedElements(elements: <a href="https://github.com/excalidraw/excalidra
This function returns an array of deleted elements.
#### `mergeLibraryItems`
```js
import { mergeLibraryItems } from "@excalidraw/excalidraw-next";
```
**_Signature_**
<pre>
mergeLibraryItems(localItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">LibraryItems</a>, 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#L200">LibraryItems</a>
</pre>
This function merges two `LibraryItems` arrays, where unique items from `otherItems` are sorted first in the returned array.
### Exported constants
#### `FONT_FAMILY`
@@ -1077,6 +1123,16 @@ import { THEME } from "@excalidraw/excalidraw-next";
Defaults to `THEME.LIGHT` unless passed in `initialData.appState.theme`
### `MIME_TYPES`
**How to use **
```js
import { MIME_TYPES } from "@excalidraw/excalidraw-next";
```
[`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L92) contains all the mime types supported by `Excalidraw`.
## Need help?
Check out the existing [Q&A](https://github.com/excalidraw/excalidraw/discussions?discussions_q=label%3Apackage%3Aexcalidraw). If you have any queries or need help, ask us [here](https://github.com/excalidraw/excalidraw/discussions?discussions_q=label%3Apackage%3Aexcalidraw).
+6 -2
View File
@@ -1,14 +1,18 @@
const dotenv = require("dotenv");
const { readFileSync } = require("fs");
const pkg = require("./package.json");
const parseEnvVariables = (filepath) => {
return Object.entries(dotenv.parse(readFileSync(filepath))).reduce(
const envVars = Object.entries(dotenv.parse(readFileSync(filepath))).reduce(
(env, [key, value]) => {
env[key] = JSON.stringify(value);
return env;
},
{},
);
envVars.PKG_NAME = JSON.stringify(pkg.name);
envVars.PKG_VERSION = JSON.stringify(pkg.version);
envVars.IS_EXCALIDRAW_NPM_PACKAGE = JSON.stringify(true);
return envVars;
};
module.exports = { parseEnvVariables };
+65 -4
View File
@@ -5,13 +5,19 @@ import Sidebar from "./sidebar/Sidebar";
import "./App.scss";
import initialData from "./initialData";
import { MIME_TYPES } from "../../../constants";
// This is so that we use the bundled excalidraw.development.js file instead
// of the actual source code
const { exportToCanvas, exportToSvg, exportToBlob, Excalidraw } =
window.ExcalidrawLib;
const {
exportToCanvas,
exportToSvg,
exportToBlob,
exportToClipboard,
Excalidraw,
MIME_TYPES,
} = window.ExcalidrawLib;
const resolvablePromise = () => {
let resolve;
let reject;
@@ -26,7 +32,10 @@ const resolvablePromise = () => {
const renderTopRightUI = () => {
return (
<button onClick={() => alert("This is dummy top right UI")}>
<button
onClick={() => alert("This is dummy top right UI")}
style={{ height: "2.5rem" }}
>
{" "}
Click me{" "}
</button>
@@ -53,6 +62,7 @@ export default function App() {
const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
const [exportEmbedScene, setExportEmbedScene] = useState(false);
const [theme, setTheme] = useState("light");
const [isCollaborating, setIsCollaborating] = useState(false);
const initialStatePromiseRef = useRef({ promise: null });
if (!initialStatePromiseRef.current.promise) {
@@ -141,6 +151,15 @@ export default function App() {
}
}, []);
const onCopy = async (type) => {
await exportToClipboard({
elements: excalidrawRef.current.getSceneElements(),
appState: excalidrawRef.current.getAppState(),
files: excalidrawRef.current.getFiles(),
type,
});
window.alert(`Copied to clipboard as ${type} sucessfully`);
};
return (
<div className="App">
<h1> Excalidraw Example</h1>
@@ -175,6 +194,7 @@ export default function App() {
>
Update Library
</button>
<label>
<input
type="checkbox"
@@ -213,6 +233,47 @@ export default function App() {
/>
Switch to Dark Theme
</label>
<label>
<input
type="checkbox"
checked={isCollaborating}
onChange={() => {
if (!isCollaborating) {
const collaborators = new Map();
collaborators.set("id1", {
username: "Doremon",
src: "doremon.png",
});
collaborators.set("id2", {
username: "Excalibot",
src: "https://avatars.githubusercontent.com/excalibot",
});
collaborators.set("id3", {
username: "Pika",
src: "pika.jpeg",
});
excalidrawRef.current.updateScene({ collaborators });
} else {
excalidrawRef.current.updateScene({
collaborators: new Map(),
});
}
setIsCollaborating(!isCollaborating);
}}
/>
Show collaborators
</label>
<div>
<button onClick={onCopy.bind(null, "png")}>
Copy to Clipboard as PNG
</button>
<button onClick={onCopy.bind(null, "svg")}>
Copy to Clipboard as SVG
</button>
<button onClick={onCopy.bind(null, "json")}>
Copy to Clipboard as JSON
</button>
</div>
</div>
<div className="excalidraw-wrapper">
<Excalidraw
Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

+3 -2
View File
@@ -197,11 +197,12 @@ export {
loadLibraryFromBlob,
loadFromBlob,
getFreeDrawSvgPath,
exportToClipboard,
mergeLibraryItems,
} from "../../packages/utils";
export { isLinearElement } from "../../element/typeChecks";
export { debounce } from "../../utils";
export { FONT_FAMILY, THEME } from "../../constants";
export { FONT_FAMILY, THEME, MIME_TYPES } from "../../constants";
export {
mutateElement,
+3 -3
View File
@@ -47,12 +47,12 @@
"@babel/core": "7.17.0",
"@babel/plugin-transform-arrow-functions": "7.16.7",
"@babel/plugin-transform-async-to-generator": "7.16.0",
"@babel/plugin-transform-runtime": "7.16.8",
"@babel/plugin-transform-runtime": "7.17.10",
"@babel/plugin-transform-typescript": "7.16.1",
"@babel/preset-env": "7.16.7",
"@babel/preset-react": "7.16.7",
"@babel/preset-typescript": "7.16.7",
"autoprefixer": "10.4.2",
"autoprefixer": "10.4.5",
"babel-loader": "8.2.3",
"babel-plugin-transform-class-properties": "6.24.1",
"cross-env": "7.0.3",
@@ -63,7 +63,7 @@
"terser-webpack-plugin": "5.3.1",
"ts-loader": "9.2.8",
"typescript": "4.5.4",
"webpack": "5.65.0",
"webpack": "5.72.0",
"webpack-bundle-analyzer": "4.5.0",
"webpack-cli": "4.9.2",
"webpack-dev-server": "4.7.4",
+1 -2
View File
@@ -1,9 +1,8 @@
import { ENV } from "../../constants";
import pkg from "./package.json";
if (process.env.NODE_ENV !== ENV.TEST) {
/* eslint-disable */
/* global __webpack_public_path__:writable */
__webpack_public_path__ =
window.EXCALIDRAW_ASSET_PATH ||
`https://unpkg.com/${pkg.name}@${pkg.version}/dist/`;
`https://unpkg.com/${process.env.PKG_NAME}@${process.env.PKG_VERSION}/dist/`;
}
+73 -85
View File
@@ -862,10 +862,10 @@
dependencies:
"@babel/helper-plugin-utils" "^7.16.7"
"@babel/plugin-transform-runtime@7.16.8":
version "7.16.8"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.8.tgz#3339368701103edae708f0fba9e4bfb70a3e5872"
integrity sha512-6Kg2XHPFnIarNweZxmzbgYnnWsXxkx9WQUVk2sksBRL80lBC1RAQV3wQagWxdCHiYHqPN+oenwNIuttlYgIbQQ==
"@babel/plugin-transform-runtime@7.17.10":
version "7.17.10"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.10.tgz#b89d821c55d61b5e3d3c3d1d636d8d5a81040ae1"
integrity sha512-6jrMilUAJhktTr56kACL8LnWC5hx3Lf27BS0R0DSyW/OoJfb/iTHeE96V3b1dgKG3FSFdd/0culnYWMkjcKCig==
dependencies:
"@babel/helper-module-imports" "^7.16.7"
"@babel/helper-plugin-utils" "^7.16.7"
@@ -1174,10 +1174,10 @@
dependencies:
"@types/node" "*"
"@types/eslint-scope@^3.7.0":
version "3.7.0"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86"
integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==
"@types/eslint-scope@^3.7.3":
version "3.7.3"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224"
integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==
dependencies:
"@types/eslint" "*"
"@types/estree" "*"
@@ -1190,10 +1190,10 @@
"@types/estree" "*"
"@types/json-schema" "*"
"@types/estree@*", "@types/estree@^0.0.50":
version "0.0.50"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==
"@types/estree@*", "@types/estree@^0.0.51":
version "0.0.51"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18":
version "4.17.27"
@@ -1571,20 +1571,20 @@ array-union@^2.1.0:
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
async@^2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
version "2.6.4"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
dependencies:
lodash "^4.17.14"
autoprefixer@10.4.2:
version "10.4.2"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.2.tgz#25e1df09a31a9fba5c40b578936b90d35c9d4d3b"
integrity sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==
autoprefixer@10.4.5:
version "10.4.5"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.5.tgz#662193c744094b53d3637f39be477e07bd904998"
integrity sha512-Fvd8yCoA7lNX/OUllvS+aS1I7WRBclGXsepbvT8ZaPgrH24rgXpZzF0/6Hh3ZEkwg+0AES/Osd196VZmYoEFtw==
dependencies:
browserslist "^4.19.1"
caniuse-lite "^1.0.30001297"
fraction.js "^4.1.2"
browserslist "^4.20.2"
caniuse-lite "^1.0.30001332"
fraction.js "^4.2.0"
normalize-range "^0.1.2"
picocolors "^1.0.0"
postcss-value-parser "^4.2.0"
@@ -1800,15 +1800,15 @@ braces@^3.0.1, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
browserslist@^4.14.5, browserslist@^4.17.5, browserslist@^4.17.6, browserslist@^4.19.1:
version "4.19.1"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3"
integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==
browserslist@^4.14.5, browserslist@^4.17.5, browserslist@^4.17.6, browserslist@^4.19.1, browserslist@^4.20.2:
version "4.20.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88"
integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==
dependencies:
caniuse-lite "^1.0.30001286"
electron-to-chromium "^1.4.17"
caniuse-lite "^1.0.30001317"
electron-to-chromium "^1.4.84"
escalade "^3.1.1"
node-releases "^2.0.1"
node-releases "^2.0.2"
picocolors "^1.0.0"
buffer-from@^1.0.0:
@@ -1844,10 +1844,10 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297:
version "1.0.30001298"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001298.tgz#0e690039f62e91c3ea581673d716890512e7ec52"
integrity sha512-AcKqikjMLlvghZL/vfTHorlQsLDhGRalYf1+GmWCf5SCMziSGjRYQW/JEksj14NaYHIR6KIhrFAy0HV5C25UzQ==
caniuse-lite@^1.0.30001317, caniuse-lite@^1.0.30001332:
version "1.0.30001332"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001332.tgz#39476d3aa8d83ea76359c70302eafdd4a1d727dd"
integrity sha512-10T30NYOEQtN6C11YGg411yebhvpnC6Z102+B95eAsN0oB6KUs01ivE8u+G6FMIRtIrVlYXhL+LUwQ3/hXwDWw==
chalk@^1.1.3:
version "1.1.3"
@@ -2211,10 +2211,10 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.4.17:
version "1.4.38"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.38.tgz#10ea58d73d36b13e78d5024f3b74a352d3958d01"
integrity sha512-WhHt3sZazKj0KK/UpgsbGQnUUoFeAHVishzHFExMxagpZgjiGYSC9S0ZlbhCfSH2L2i+2A1yyqOIliTctMx7KQ==
electron-to-chromium@^1.4.84:
version "1.4.118"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.118.tgz#2d917c71712dac9652cc01af46c7d0bd51552974"
integrity sha512-maZIKjnYDvF7Fs35nvVcyr44UcKNwybr93Oba2n3HkKDFAtk0svERkLN/HyczJDS3Fo4wU9th9fUQd09ZLtj1w==
emojis-list@^3.0.0:
version "3.0.0"
@@ -2226,10 +2226,10 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
enhanced-resolve@^5.0.0, enhanced-resolve@^5.8.3:
version "5.8.3"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0"
integrity sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==
enhanced-resolve@^5.0.0, enhanced-resolve@^5.9.2:
version "5.9.2"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9"
integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
@@ -2449,10 +2449,10 @@ forwarded@0.2.0:
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
fraction.js@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.2.tgz#13e420a92422b6cf244dff8690ed89401029fbe8"
integrity sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA==
fraction.js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
fresh@0.5.2:
version "0.5.2"
@@ -2544,10 +2544,10 @@ globby@^11.0.1:
merge2 "^1.3.0"
slash "^3.0.0"
graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6:
version "4.2.8"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
gzip-size@^6.0.0:
version "6.0.0"
@@ -3044,24 +3044,12 @@ micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4:
braces "^3.0.1"
picomatch "^2.2.3"
mime-db@1.45.0:
version "1.45.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
mime-db@1.51.0, "mime-db@>= 1.43.0 < 2":
version "1.51.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c"
integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==
mime-types@^2.1.27:
version "2.1.28"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd"
integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==
dependencies:
mime-db "1.45.0"
mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24:
mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24:
version "2.1.34"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24"
integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==
@@ -3103,9 +3091,9 @@ minimatch@^3.0.4:
brace-expansion "^1.1.7"
minimist@^1.2.0, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
mkdirp@^0.5.5:
version "0.5.5"
@@ -3158,14 +3146,14 @@ neo-async@^2.6.2:
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
node-forge@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c"
integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==
version "1.3.1"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
node-releases@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
node-releases@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.3.tgz#225ee7488e4a5e636da8da52854844f9d716ca96"
integrity sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
@@ -4180,18 +4168,18 @@ webpack-merge@5.8.0, webpack-merge@^5.7.3:
clone-deep "^4.0.1"
wildcard "^2.0.0"
webpack-sources@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.2.tgz#d88e3741833efec57c4c789b6010db9977545260"
integrity sha512-cp5qdmHnu5T8wRg2G3vZZHoJPN14aqQ89SyQ11NpGH5zEMDCclt49rzo+MaRazk7/UeILhAI+/sEtcM+7Fr0nw==
webpack-sources@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@5.65.0:
version "5.65.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.65.0.tgz#ed2891d9145ba1f0d318e4ea4f89c3fa18e6f9be"
integrity sha512-Q5or2o6EKs7+oKmJo7LaqZaMOlDWQse9Tm5l1WAfU/ujLGN5Pb0SqGeVkN/4bpPmEqEP5RnVhiqsOtWtUVwGRw==
webpack@5.72.0:
version "5.72.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.72.0.tgz#f8bc40d9c6bb489a4b7a8a685101d6022b8b6e28"
integrity sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w==
dependencies:
"@types/eslint-scope" "^3.7.0"
"@types/estree" "^0.0.50"
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
"@webassemblyjs/ast" "1.11.1"
"@webassemblyjs/wasm-edit" "1.11.1"
"@webassemblyjs/wasm-parser" "1.11.1"
@@ -4199,12 +4187,12 @@ webpack@5.65.0:
acorn-import-assertions "^1.7.6"
browserslist "^4.14.5"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.8.3"
enhanced-resolve "^5.9.2"
es-module-lexer "^0.9.0"
eslint-scope "5.1.1"
events "^3.2.0"
glob-to-regexp "^0.4.1"
graceful-fs "^4.2.4"
graceful-fs "^4.2.9"
json-parse-better-errors "^1.0.2"
loader-runner "^4.2.0"
mime-types "^2.1.27"
@@ -4213,7 +4201,7 @@ webpack@5.65.0:
tapable "^2.1.1"
terser-webpack-plugin "^5.1.3"
watchpack "^2.3.1"
webpack-sources "^3.2.2"
webpack-sources "^3.2.3"
websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
version "0.7.4"
+39 -3
View File
@@ -10,6 +10,11 @@ import { restore } from "../data/restore";
import { MIME_TYPES } from "../constants";
import { encodePngMetadata } from "../data/image";
import { serializeAsJSON } from "../data/json";
import {
copyBlobToClipboardAsPng,
copyTextToSystemClipboard,
copyToClipboard,
} from "../clipboard";
type ExportOpts = {
elements: readonly NonDeleted<ExcalidrawElement>[];
@@ -81,7 +86,7 @@ export const exportToBlob = async (
mimeType?: string;
quality?: number;
},
): Promise<Blob | null> => {
): Promise<Blob> => {
let { mimeType = MIME_TYPES.png, quality } = opts;
if (mimeType === MIME_TYPES.png && typeof quality === "number") {
@@ -107,9 +112,12 @@ export const exportToBlob = async (
quality = quality ? quality : /image\/jpe?g/.test(mimeType) ? 0.92 : 0.8;
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
canvas.toBlob(
async (blob: Blob | null) => {
async (blob) => {
if (!blob) {
return reject(new Error("couldn't export to blob"));
}
if (
blob &&
mimeType === MIME_TYPES.png &&
@@ -156,6 +164,34 @@ export const exportToSvg = async ({
);
};
export const exportToClipboard = async (
opts: ExportOpts & {
mimeType?: string;
quality?: number;
type: "png" | "svg" | "json";
},
) => {
if (opts.type === "svg") {
const svg = await exportToSvg(opts);
await copyTextToSystemClipboard(svg.outerHTML);
} else if (opts.type === "png") {
await copyBlobToClipboardAsPng(exportToBlob(opts));
} else if (opts.type === "json") {
const appState = {
offsetTop: 0,
offsetLeft: 0,
width: 0,
height: 0,
...getDefaultAppState(),
...opts.appState,
};
await copyToClipboard(opts.elements, appState, opts.files);
} else {
throw new Error("Invalid export type");
}
};
export { serializeAsJSON, serializeLibraryAsJSON } from "../data/json";
export { loadFromBlob, loadLibraryFromBlob } from "../data/blob";
export { getFreeDrawSvgPath } from "../renderer/renderElement";
export { mergeLibraryItems } from "../data/library";
+1 -1
View File
@@ -41,7 +41,7 @@
"@babel/plugin-transform-typescript": "7.16.1",
"@babel/preset-env": "7.16.7",
"@babel/preset-typescript": "7.16.7",
"babel-loader": "8.2.3",
"babel-loader": "8.2.5",
"babel-plugin-transform-class-properties": "6.24.1",
"cross-env": "7.0.3",
"css-loader": "6.7.1",
+9 -25
View File
@@ -1320,13 +1320,13 @@ babel-helper-get-function-arity@^6.24.1:
babel-runtime "^6.22.0"
babel-types "^6.24.1"
babel-loader@8.2.3:
version "8.2.3"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.3.tgz#8986b40f1a64cacfcb4b8429320085ef68b1342d"
integrity sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==
babel-loader@8.2.5:
version "8.2.5"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e"
integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==
dependencies:
find-cache-dir "^3.3.1"
loader-utils "^1.4.0"
loader-utils "^2.0.0"
make-dir "^3.1.0"
schema-utils "^2.6.5"
@@ -2002,13 +2002,6 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
dependencies:
minimist "^1.2.0"
json5@^2.1.2:
version "2.2.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
@@ -2031,15 +2024,6 @@ loader-runner@^4.2.0:
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384"
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
loader-utils@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
json5 "^1.0.1"
loader-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
@@ -2122,10 +2106,10 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
minimist@^1.2.0, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
minimist@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
ms@2.0.0:
version "2.0.0"
+15 -3
View File
@@ -115,6 +115,19 @@ export const exportToSvg = async (
svgRoot.setAttribute("filter", THEME_FILTER);
}
let assetPath = "https://excalidraw.com/";
// Asset path needs to be determined only when using package
if (process.env.IS_EXCALIDRAW_NPM_PACKAGE) {
assetPath =
window.EXCALIDRAW_ASSET_PATH ||
`https://unpkg.com/${process.env.PKG_NAME}@${process.env.PKG_VERSION}`;
if (assetPath?.startsWith("/")) {
assetPath = assetPath.replace("/", `${window.location.origin}/`);
}
assetPath = `${assetPath}/dist/excalidraw-assets/`;
}
svgRoot.innerHTML = `
${SVG_EXPORT_TAG}
${metadata}
@@ -122,16 +135,15 @@ export const exportToSvg = async (
<style>
@font-face {
font-family: "Virgil";
src: url("https://excalidraw.com/Virgil.woff2");
src: url("${assetPath}Virgil.woff2");
}
@font-face {
font-family: "Cascadia";
src: url("https://excalidraw.com/Cascadia.woff2");
src: url("${assetPath}Cascadia.woff2");
}
</style>
</defs>
`;
// render background rect
if (appState.exportBackground && viewBackgroundColor) {
const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
+2 -2
View File
@@ -14,12 +14,12 @@ describe("library", () => {
});
it("import library via drag&drop", async () => {
expect(await h.app.library.loadLibrary()).toEqual([]);
expect(await h.app.library.getLatestLibrary()).toEqual([]);
await API.drop(
await API.loadFile("./fixtures/fixture_library.excalidrawlib"),
);
await waitFor(async () => {
expect(await h.app.library.loadLibrary()).toEqual([
expect(await h.app.library.getLatestLibrary()).toEqual([
{
status: "unpublished",
elements: [expect.objectContaining({ id: "A" })],
+1
View File
@@ -7,6 +7,7 @@ import {
} from "../fixtures/elementFixture";
describe("exportToSvg", () => {
window.EXCALIDRAW_ASSET_PATH = "/";
const ELEMENT_HEIGHT = 100;
const ELEMENT_WIDTH = 100;
const ELEMENTS = [
+3
View File
@@ -45,6 +45,9 @@ export type Collaborator = {
background: string;
stroke: string;
};
// The url of the collaborator's avatar, defaults to username intials
// if not present
src?: string;
};
export type DataURL = string & { _brand: "DataURL" };
+3
View File
@@ -575,6 +575,9 @@ export const arrayToMap = <T extends { id: string } | string>(
export const isTestEnv = () =>
typeof process !== "undefined" && process.env?.NODE_ENV === "test";
export const isProdEnv = () =>
typeof process !== "undefined" && process.env?.NODE_ENV === "production";
export const wrapEvent = <T extends Event>(name: EVENT, nativeEvent: T) => {
return new CustomEvent(name, {
detail: {
+14 -14
View File
@@ -2971,9 +2971,9 @@ async-limiter@~1.0.0:
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
async@^2.6.2:
version "2.6.3"
resolved "https://registry.npmjs.org/async/-/async-2.6.3.tgz"
integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
version "2.6.4"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
dependencies:
lodash "^4.17.14"
@@ -3394,10 +3394,10 @@ brorand@^1.0.1, brorand@^1.1.0:
resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz"
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
browser-fs-access@0.24.1:
version "0.24.1"
resolved "https://registry.yarnpkg.com/browser-fs-access/-/browser-fs-access-0.24.1.tgz#29b915fdcd2ef0972ebabc2b7685c6893d288a72"
integrity sha512-fRcwhfNej0h2Jy+Uodxjbc5PQvNkZyG9fXu3S6Mcv0kigKath5sL54GXfMtOay/A/ULMa956eQZ9lMWVPiLtMA==
browser-fs-access@0.29.1:
version "0.29.1"
resolved "https://registry.yarnpkg.com/browser-fs-access/-/browser-fs-access-0.29.1.tgz#8a9794c73cf86b9aec74201829999c597128379c"
integrity sha512-LSvVX5e21LRrXqVMhqtAwj5xPgDb+fXAIH80NsnCQ9xuZPs2xWsOREi24RKgZa1XOiQRbcmVrv87+ulOKsgjxw==
browser-process-hrtime@^1.0.0:
version "1.0.0"
@@ -8369,9 +8369,9 @@ minimatch@3.0.4, minimatch@^3.0.4:
brace-expansion "^1.1.7"
minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minipass-collect@^1.0.2:
version "1.0.2"
@@ -8510,10 +8510,10 @@ nan@^2.12.1:
resolved "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
nanoid@3.1.32, nanoid@^3.1.20:
version "3.1.32"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.32.tgz#8f96069e6239cc0a9ae8c0d3b41a3b4933a88c0a"
integrity sha512-F8mf7R3iT9bvThBoW4tGXhXFHCctyCiUUPrWF8WaTqa3h96d9QybkSeba43XVOOE3oiLfkVDe4bT8MeGmkrTxw==
nanoid@3.3.3, nanoid@^3.1.20:
version "3.3.3"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
nanomatch@^1.2.9:
version "1.2.13"