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>
This commit is contained in:
Aakansha Doshi 2022-04-29 18:58:44 +05:30 committed by GitHub
parent aee1e2451e
commit 6a0f800716
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 111 additions and 18 deletions

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,

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;

View File

@ -17,6 +17,7 @@ Please add the latest change on the top under the correct section.
#### Features
- 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).

View File

@ -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 |

View File

@ -10,8 +10,13 @@ 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,
} = window.ExcalidrawLib;
const resolvablePromise = () => {
let resolve;
let reject;
@ -141,6 +146,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 +189,7 @@ export default function App() {
>
Update Library
</button>
<label>
<input
type="checkbox"
@ -213,6 +228,17 @@ export default function App() {
/>
Switch to Dark Theme
</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

View File

@ -197,6 +197,7 @@ export {
loadLibraryFromBlob,
loadFromBlob,
getFreeDrawSvgPath,
exportToClipboard,
} from "../../packages/utils";
export { isLinearElement } from "../../element/typeChecks";

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,33 @@ 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";