Excalidraw export (#2246)
This commit is contained in:
parent
58861e87e5
commit
a7da8901d8
33
.github/workflows/build-packages.yml
vendored
Normal file
33
.github/workflows/build-packages.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
name: Build packages
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Setup Node.js 12.x
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 12.x
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm ci --prefix src/packages/excalidraw
|
||||||
|
npm ci --prefix src/packages/utils
|
||||||
|
|
||||||
|
- name: Build @excalidraw/excalidraw
|
||||||
|
run: |
|
||||||
|
npm run pack --prefix src/packages/excalidraw
|
||||||
|
|
||||||
|
- name: Build @excalidraw/utils
|
||||||
|
run: |
|
||||||
|
npm run pack --prefix src/packages/utils
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -15,3 +15,4 @@ yarn.lock
|
|||||||
.idea
|
.idea
|
||||||
dist/
|
dist/
|
||||||
.eslintcache
|
.eslintcache
|
||||||
|
*.tgz
|
||||||
|
@ -2,7 +2,7 @@ import React, { useState, useLayoutEffect, useEffect } from "react";
|
|||||||
|
|
||||||
import { LoadingMessage } from "../components/LoadingMessage";
|
import { LoadingMessage } from "../components/LoadingMessage";
|
||||||
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
||||||
import Excalidraw from "../excalidraw-embed/index";
|
import Excalidraw from "../packages/excalidraw/index";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
importFromLocalStorage,
|
importFromLocalStorage,
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import React, { useEffect, forwardRef } from "react";
|
import React, { useEffect, forwardRef } from "react";
|
||||||
|
|
||||||
import { InitializeApp } from "../components/InitializeApp";
|
import { InitializeApp } from "../../components/InitializeApp";
|
||||||
import App, { ExcalidrawImperativeAPI } from "../components/App";
|
import App, { ExcalidrawImperativeAPI } from "../../components/App";
|
||||||
|
|
||||||
import "../css/app.scss";
|
import "../../css/app.scss";
|
||||||
import "../css/styles.scss";
|
import "../../css/styles.scss";
|
||||||
|
|
||||||
import { ExcalidrawProps } from "../types";
|
import { ExcalidrawProps } from "../../types";
|
||||||
import { IsMobileProvider } from "../is-mobile";
|
import { IsMobileProvider } from "../../is-mobile";
|
||||||
|
|
||||||
const Excalidraw = (props: ExcalidrawProps) => {
|
const Excalidraw = (props: ExcalidrawProps) => {
|
||||||
const {
|
const {
|
@ -9,7 +9,7 @@ module.exports = {
|
|||||||
mode: "production",
|
mode: "production",
|
||||||
entry: {
|
entry: {
|
||||||
"excalidraw.min": "./index.tsx",
|
"excalidraw.min": "./index.tsx",
|
||||||
"fonts.min": "../../public/fonts.css",
|
"fonts.min": "../../../public/fonts.css",
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, "dist"),
|
path: path.resolve(__dirname, "dist"),
|
||||||
@ -40,7 +40,7 @@ module.exports = {
|
|||||||
loader: "ts-loader",
|
loader: "ts-loader",
|
||||||
options: {
|
options: {
|
||||||
transpileOnly: true,
|
transpileOnly: true,
|
||||||
configFile: path.resolve(__dirname, "tsconfig.prod.json"),
|
configFile: path.resolve(__dirname, "../tsconfig.prod.json"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
89
src/packages/utils.ts
Normal file
89
src/packages/utils.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import {
|
||||||
|
exportToCanvas as _exportToCanvas,
|
||||||
|
exportToSvg as _exportToSvg,
|
||||||
|
} from "../scene/export";
|
||||||
|
import { getDefaultAppState } from "../appState";
|
||||||
|
import { AppState } from "../types";
|
||||||
|
import { ExcalidrawElement } from "../element/types";
|
||||||
|
import { getNonDeletedElements } from "../element";
|
||||||
|
|
||||||
|
type ExportOpts = {
|
||||||
|
elements: readonly ExcalidrawElement[];
|
||||||
|
appState?: Omit<AppState, "offsetTop" | "offsetLeft">;
|
||||||
|
getDimensions: (
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
) => { width: number; height: number; scale: number };
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportToCanvas = ({
|
||||||
|
elements,
|
||||||
|
appState = getDefaultAppState(),
|
||||||
|
getDimensions = (width, height) => ({ width, height, scale: 1 }),
|
||||||
|
}: ExportOpts) => {
|
||||||
|
return _exportToCanvas(
|
||||||
|
getNonDeletedElements(elements),
|
||||||
|
{ ...appState, offsetTop: 0, offsetLeft: 0 },
|
||||||
|
{
|
||||||
|
exportBackground: appState.exportBackground ?? true,
|
||||||
|
viewBackgroundColor: appState.viewBackgroundColor ?? "#FFF",
|
||||||
|
shouldAddWatermark: appState.shouldAddWatermark ?? false,
|
||||||
|
},
|
||||||
|
(width: number, height: number) => {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const ret = getDimensions(width, height);
|
||||||
|
|
||||||
|
canvas.width = ret.width;
|
||||||
|
canvas.height = ret.height;
|
||||||
|
|
||||||
|
return canvas;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportToBlob = (
|
||||||
|
opts: ExportOpts & {
|
||||||
|
mimeType?: string;
|
||||||
|
quality?: number;
|
||||||
|
},
|
||||||
|
): Promise<Blob | null> => {
|
||||||
|
const canvas = exportToCanvas(opts);
|
||||||
|
|
||||||
|
let { mimeType = "image/png", quality } = opts;
|
||||||
|
|
||||||
|
if (mimeType === "image/png" && typeof quality === "number") {
|
||||||
|
console.warn(`"quality" will be ignored for "image/png" mimeType`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeType === "image/jpg") {
|
||||||
|
mimeType = "image/jpeg";
|
||||||
|
}
|
||||||
|
|
||||||
|
quality = quality ? quality : /image\/jpe?g/.test(mimeType) ? 0.92 : 0.8;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
canvas.toBlob(
|
||||||
|
(blob: Blob | null) => {
|
||||||
|
resolve(blob);
|
||||||
|
},
|
||||||
|
mimeType,
|
||||||
|
quality,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportToSvg = ({
|
||||||
|
elements,
|
||||||
|
appState = getDefaultAppState(),
|
||||||
|
exportPadding,
|
||||||
|
metadata,
|
||||||
|
}: ExportOpts & {
|
||||||
|
exportPadding?: number;
|
||||||
|
metadata?: string;
|
||||||
|
}): SVGSVGElement => {
|
||||||
|
return _exportToSvg(getNonDeletedElements(elements), {
|
||||||
|
...appState,
|
||||||
|
exportPadding,
|
||||||
|
metadata,
|
||||||
|
});
|
||||||
|
};
|
9
src/packages/utils/CHANGELOG.md
Normal file
9
src/packages/utils/CHANGELOG.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
First release of `@excalidraw/utils` to provide utilities functions.
|
||||||
|
|
||||||
|
- Added `exportToBlob` and `exportToSvg` to export an Excalidraw diagram definition, respectively,
|
||||||
|
to a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) and
|
||||||
|
to a [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement) ([#2246](https://github.com/excalidraw/excalidraw/pull/2246))
|
92
src/packages/utils/README.md
Normal file
92
src/packages/utils/README.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# @excalidraw/utils
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
npm i @excalidraw/utils
|
||||||
|
|
||||||
|
If you prefer Yarn over npm, use this command to install the Excalidraw utils package:
|
||||||
|
|
||||||
|
yarn add @excalidraw/utils
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### `exportToBlob` (async)
|
||||||
|
|
||||||
|
Export an Excalidraw diagram to a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob).
|
||||||
|
|
||||||
|
### `exportToSvg`
|
||||||
|
|
||||||
|
Export an Excalidraw diagram to a [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Excalidraw utils is published as a UMD (Universal Module Definition).
|
||||||
|
If you are using a Web bundler (for instance, Webpack), you can import it as an ES6 module:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { exportToSvg, exportToBlob } from "@excalidraw/utils";
|
||||||
|
```
|
||||||
|
|
||||||
|
To use it in a browser directly:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://unpkg.com/@excalidraw/utils@0.1.0/dist/excalidraw.min.js"></script>
|
||||||
|
<script>
|
||||||
|
// ExcalidrawUtils is a global variable defined by excalidraw.min.js
|
||||||
|
const { exportToSvg, exportToBlob } = ExcalidrawUtils;
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's the `exportToBlob` and `exportToSvg` functions in action:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const excalidrawDiagram = {
|
||||||
|
type: "excalidraw",
|
||||||
|
version: 2,
|
||||||
|
source: "https://excalidraw.com",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
id: "vWrqOAfkind2qcm7LDAGZ",
|
||||||
|
type: "ellipse",
|
||||||
|
x: 414,
|
||||||
|
y: 237,
|
||||||
|
width: 214,
|
||||||
|
height: 214,
|
||||||
|
angle: 0,
|
||||||
|
strokeColor: "#000000",
|
||||||
|
backgroundColor: "#15aabf",
|
||||||
|
fillStyle: "hachure",
|
||||||
|
strokeWidth: 1,
|
||||||
|
strokeStyle: "solid",
|
||||||
|
roughness: 1,
|
||||||
|
opacity: 100,
|
||||||
|
groupIds: [],
|
||||||
|
strokeSharpness: "sharp",
|
||||||
|
seed: 1041657908,
|
||||||
|
version: 120,
|
||||||
|
versionNonce: 1188004276,
|
||||||
|
isDeleted: false,
|
||||||
|
boundElementIds: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
appState: {
|
||||||
|
viewBackgroundColor: "#ffffff",
|
||||||
|
gridSize: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export the Excalidraw diagram as SVG string
|
||||||
|
const svg = exportToSvg(excalidrawDiagram);
|
||||||
|
console.log(svg.outerHTML);
|
||||||
|
|
||||||
|
// Export the Excalidraw diagram as PNG Blob URL
|
||||||
|
(async () => {
|
||||||
|
const blob = await exportToBlob({
|
||||||
|
...excalidrawDiagram,
|
||||||
|
mimeType: "image/png",
|
||||||
|
});
|
||||||
|
|
||||||
|
const urlCreator = window.URL || window.webkitURL;
|
||||||
|
console.log(urlCreator.createObjectURL(blob));
|
||||||
|
})();
|
||||||
|
```
|
1
src/packages/utils/index.js
Normal file
1
src/packages/utils/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { exportToBlob, exportToSvg } from "../utils.ts";
|
5920
src/packages/utils/package-lock.json
generated
Normal file
5920
src/packages/utils/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
55
src/packages/utils/package.json
Normal file
55
src/packages/utils/package.json
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "@excalidraw/utils",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "dist/excalidraw-utils.min.js",
|
||||||
|
"files": [
|
||||||
|
"dist/*"
|
||||||
|
],
|
||||||
|
"description": "Excalidraw utilities functions",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"excalidraw",
|
||||||
|
"excalidraw-utils"
|
||||||
|
],
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not ie <= 11",
|
||||||
|
"not op_mini all",
|
||||||
|
"not safari < 12",
|
||||||
|
"not kaios <= 2.5",
|
||||||
|
"not edge < 79",
|
||||||
|
"not chrome < 70",
|
||||||
|
"not and_uc < 13",
|
||||||
|
"not samsung < 10"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "7.9.0",
|
||||||
|
"@babel/plugin-transform-arrow-functions": "7.8.3",
|
||||||
|
"@babel/plugin-transform-async-to-generator": "7.8.3",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.12.1",
|
||||||
|
"@babel/plugin-transform-typescript": "7.9.4",
|
||||||
|
"@babel/preset-env": "7.9.5",
|
||||||
|
"@babel/preset-typescript": "7.9.0",
|
||||||
|
"babel-loader": "8.1.0",
|
||||||
|
"babel-plugin-transform-class-properties": "6.24.1",
|
||||||
|
"cross-env": "7.0.2",
|
||||||
|
"file-loader": "6.0.0",
|
||||||
|
"ts-loader": "7.0.0",
|
||||||
|
"webpack": "4.42.0",
|
||||||
|
"webpack-cli": "3.3.11"
|
||||||
|
},
|
||||||
|
"bugs": "https://github.com/excalidraw/excalidraw/issues",
|
||||||
|
"repository": "https://github.com/excalidraw/excalidraw",
|
||||||
|
"scripts": {
|
||||||
|
"build:umd": "cross-env NODE_ENV=production webpack --config webpack.prod.config.js",
|
||||||
|
"pack": "npm run build:umd && npm pack"
|
||||||
|
}
|
||||||
|
}
|
40
src/packages/utils/webpack.prod.config.js
Normal file
40
src/packages/utils/webpack.prod.config.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
const webpack = require("webpack");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: "production",
|
||||||
|
resolve: {
|
||||||
|
extensions: [".tsx", ".ts", ".js"],
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
runtimeChunk: false,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, "dist"),
|
||||||
|
filename: "excalidraw-utils.min.js",
|
||||||
|
library: "ExcalidrawUtils",
|
||||||
|
libraryTarget: "umd",
|
||||||
|
},
|
||||||
|
entry: "./index.js",
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(ts|tsx|js)$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: "ts-loader",
|
||||||
|
options: {
|
||||||
|
transpileOnly: true,
|
||||||
|
configFile: path.resolve(__dirname, "../tsconfig.prod.json"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.optimize.LimitChunkCountPlugin({
|
||||||
|
maxChunks: 1,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
@ -128,7 +128,7 @@ export const exportToSvg = (
|
|||||||
</defs>
|
</defs>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// render backgroiund rect
|
// render background rect
|
||||||
if (exportBackground && viewBackgroundColor) {
|
if (exportBackground && viewBackgroundColor) {
|
||||||
const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
|
const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
|
||||||
rect.setAttribute("x", "0");
|
rect.setAttribute("x", "0");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user