support export canvas to clipboard (#232)
This commit is contained in:
parent
1541428ab1
commit
deee57314d
@ -1,17 +1,35 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { EditableText } from "../EditableText";
|
import { EditableText } from "../EditableText";
|
||||||
import { Panel } from "../Panel";
|
import { Panel } from "../Panel";
|
||||||
|
import { ExportType } from "../../scene/types";
|
||||||
|
|
||||||
|
import "./panelExport.scss";
|
||||||
|
|
||||||
interface PanelExportProps {
|
interface PanelExportProps {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
onProjectNameChange: (name: string) => void;
|
onProjectNameChange: (name: string) => void;
|
||||||
onExportAsPNG: React.MouseEventHandler;
|
onExportCanvas: (type: ExportType) => void;
|
||||||
exportBackground: boolean;
|
exportBackground: boolean;
|
||||||
onExportBackgroundChange: (val: boolean) => void;
|
onExportBackgroundChange: (val: boolean) => void;
|
||||||
onSaveScene: React.MouseEventHandler;
|
onSaveScene: React.MouseEventHandler;
|
||||||
onLoadScene: React.MouseEventHandler;
|
onLoadScene: React.MouseEventHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fa-clipboard
|
||||||
|
const ClipboardIcon = () => (
|
||||||
|
<svg viewBox="0 0 384 512">
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M384 112v352c0 26.51-21.49 48-48 48H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h80c0-35.29 28.71-64 64-64s64 28.71 64 64h80c26.51 0 48 21.49 48 48zM192 40c-13.255 0-24 10.745-24 24s10.745 24 24 24 24-10.745 24-24-10.745-24-24-24m96 114v-20a6 6 0 0 0-6-6H102a6 6 0 0 0-6 6v20a6 6 0 0 0 6 6h180a6 6 0 0 0 6-6z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const probablySupportsClipboard =
|
||||||
|
"toBlob" in HTMLCanvasElement.prototype &&
|
||||||
|
"write" in navigator.clipboard &&
|
||||||
|
"ClipboardItem" in window;
|
||||||
|
|
||||||
export const PanelExport: React.FC<PanelExportProps> = ({
|
export const PanelExport: React.FC<PanelExportProps> = ({
|
||||||
projectName,
|
projectName,
|
||||||
exportBackground,
|
exportBackground,
|
||||||
@ -19,7 +37,7 @@ export const PanelExport: React.FC<PanelExportProps> = ({
|
|||||||
onExportBackgroundChange,
|
onExportBackgroundChange,
|
||||||
onSaveScene,
|
onSaveScene,
|
||||||
onLoadScene,
|
onLoadScene,
|
||||||
onExportAsPNG
|
onExportCanvas
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Panel title="Export">
|
<Panel title="Export">
|
||||||
@ -32,7 +50,23 @@ export const PanelExport: React.FC<PanelExportProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<h5>Image</h5>
|
<h5>Image</h5>
|
||||||
<button onClick={onExportAsPNG}>Export to png</button>
|
<div className="panelExport-imageButtons">
|
||||||
|
<button
|
||||||
|
className="panelExport-exportToPngButton"
|
||||||
|
onClick={() => onExportCanvas("png")}
|
||||||
|
>
|
||||||
|
Export to PNG
|
||||||
|
</button>
|
||||||
|
{probablySupportsClipboard && (
|
||||||
|
<button
|
||||||
|
className="panelExport-exportToClipboardButton"
|
||||||
|
onClick={() => onExportCanvas("clipboard")}
|
||||||
|
title="Copy to clipboard (experimental)"
|
||||||
|
>
|
||||||
|
<ClipboardIcon />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
16
src/components/panels/panelExport.scss
Normal file
16
src/components/panels/panelExport.scss
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.panelExport-imageButtons {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelExport-exportToPngButton {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelExport-exportToClipboardButton {
|
||||||
|
margin-left: 10px;
|
||||||
|
padding: 0 15px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 15px;
|
||||||
|
}
|
||||||
|
}
|
7
src/global.d.ts
vendored
Normal file
7
src/global.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
interface Window {
|
||||||
|
ClipboardItem: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Clipboard extends EventTarget {
|
||||||
|
write(data: any[]): Promise<void>;
|
||||||
|
}
|
@ -23,7 +23,7 @@ import {
|
|||||||
getSelectedAttribute,
|
getSelectedAttribute,
|
||||||
loadFromJSON,
|
loadFromJSON,
|
||||||
saveAsJSON,
|
saveAsJSON,
|
||||||
exportAsPNG,
|
exportCanvas,
|
||||||
restoreFromLocalStorage,
|
restoreFromLocalStorage,
|
||||||
saveToLocalStorage,
|
saveToLocalStorage,
|
||||||
hasBackground,
|
hasBackground,
|
||||||
@ -37,6 +37,7 @@ import {
|
|||||||
import { renderScene } from "./renderer";
|
import { renderScene } from "./renderer";
|
||||||
import { AppState } from "./types";
|
import { AppState } from "./types";
|
||||||
import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types";
|
import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types";
|
||||||
|
import { ExportType } from "./scene/types";
|
||||||
|
|
||||||
import { getDateTime, isInputLike, measureText } from "./utils";
|
import { getDateTime, isInputLike, measureText } from "./utils";
|
||||||
import { KEYS, META_KEY, isArrowKey } from "./keys";
|
import { KEYS, META_KEY, isArrowKey } from "./keys";
|
||||||
@ -170,9 +171,9 @@ export class App extends React.Component<{}, AppState> {
|
|||||||
if (event.key === KEYS.ESCAPE) {
|
if (event.key === KEYS.ESCAPE) {
|
||||||
elements = clearSelection(elements);
|
elements = clearSelection(elements);
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
this.setState({ elementType: 'selection' });
|
this.setState({ elementType: "selection" });
|
||||||
if (window.document.activeElement instanceof HTMLElement) {
|
if (window.document.activeElement instanceof HTMLElement) {
|
||||||
window.document.activeElement.blur()
|
window.document.activeElement.blur();
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
@ -614,8 +615,8 @@ export class App extends React.Component<{}, AppState> {
|
|||||||
<PanelExport
|
<PanelExport
|
||||||
projectName={this.state.name}
|
projectName={this.state.name}
|
||||||
onProjectNameChange={this.updateProjectName}
|
onProjectNameChange={this.updateProjectName}
|
||||||
onExportAsPNG={() =>
|
onExportCanvas={(type: ExportType) =>
|
||||||
exportAsPNG(elements, this.canvas!, this.state)
|
exportCanvas(type, elements, this.canvas!, this.state)
|
||||||
}
|
}
|
||||||
exportBackground={this.state.exportBackground}
|
exportBackground={this.state.exportBackground}
|
||||||
onExportBackgroundChange={val =>
|
onExportBackgroundChange={val =>
|
||||||
|
@ -6,6 +6,7 @@ import { getElementAbsoluteCoords } from "../element";
|
|||||||
|
|
||||||
import { renderScene } from "../renderer";
|
import { renderScene } from "../renderer";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
|
import { ExportType } from "./types";
|
||||||
import nanoid from "nanoid";
|
import nanoid from "nanoid";
|
||||||
|
|
||||||
const LOCAL_STORAGE_KEY = "excalidraw";
|
const LOCAL_STORAGE_KEY = "excalidraw";
|
||||||
@ -76,7 +77,8 @@ export function loadFromJSON() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function exportAsPNG(
|
export function exportCanvas(
|
||||||
|
type: ExportType,
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
{
|
{
|
||||||
@ -136,7 +138,23 @@ export function exportAsPNG(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
saveFile(`${name}.png`, tempCanvas.toDataURL("image/png"));
|
if (type === "png") {
|
||||||
|
saveFile(`${name}.png`, tempCanvas.toDataURL("image/png"));
|
||||||
|
} else if (type === "clipboard") {
|
||||||
|
try {
|
||||||
|
tempCanvas.toBlob(async function(blob) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.write([
|
||||||
|
new window.ClipboardItem({ "image/png": blob })
|
||||||
|
]);
|
||||||
|
} catch (err) {
|
||||||
|
window.alert("Couldn't copy to clipboard. Try using Chrome browser.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
window.alert("Couldn't copy to clipboard. Try using Chrome browser.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// clean up the DOM
|
// clean up the DOM
|
||||||
if (tempCanvas !== canvas) tempCanvas.remove();
|
if (tempCanvas !== canvas) tempCanvas.remove();
|
||||||
|
@ -8,7 +8,7 @@ export {
|
|||||||
getSelectedAttribute
|
getSelectedAttribute
|
||||||
} from "./selection";
|
} from "./selection";
|
||||||
export {
|
export {
|
||||||
exportAsPNG,
|
exportCanvas,
|
||||||
loadFromJSON,
|
loadFromJSON,
|
||||||
saveAsJSON,
|
saveAsJSON,
|
||||||
restoreFromLocalStorage,
|
restoreFromLocalStorage,
|
||||||
|
@ -15,3 +15,5 @@ export type SceneScroll = {
|
|||||||
export interface Scene {
|
export interface Scene {
|
||||||
elements: ExcalidrawTextElement[];
|
elements: ExcalidrawTextElement[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ExportType = "png" | "clipboard";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user