瀏覽代碼

support export canvas to clipboard (#232)

David Luzar 5 年之前
父節點
當前提交
deee57314d
共有 7 個文件被更改,包括 89 次插入11 次删除
  1. 37 3
      src/components/panels/PanelExport.tsx
  2. 16 0
      src/components/panels/panelExport.scss
  3. 7 0
      src/global.d.ts
  4. 6 5
      src/index.tsx
  5. 20 2
      src/scene/data.ts
  6. 1 1
      src/scene/index.ts
  7. 2 0
      src/scene/types.ts

+ 37 - 3
src/components/panels/PanelExport.tsx

@@ -1,17 +1,35 @@
 import React from "react";
 import { EditableText } from "../EditableText";
 import { Panel } from "../Panel";
+import { ExportType } from "../../scene/types";
+
+import "./panelExport.scss";
 
 interface PanelExportProps {
   projectName: string;
   onProjectNameChange: (name: string) => void;
-  onExportAsPNG: React.MouseEventHandler;
+  onExportCanvas: (type: ExportType) => void;
   exportBackground: boolean;
   onExportBackgroundChange: (val: boolean) => void;
   onSaveScene: 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> = ({
   projectName,
   exportBackground,
@@ -19,7 +37,7 @@ export const PanelExport: React.FC<PanelExportProps> = ({
   onExportBackgroundChange,
   onSaveScene,
   onLoadScene,
-  onExportAsPNG
+  onExportCanvas
 }) => {
   return (
     <Panel title="Export">
@@ -32,7 +50,23 @@ export const PanelExport: React.FC<PanelExportProps> = ({
           />
         )}
         <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>
           <input
             type="checkbox"

+ 16 - 0
src/components/panels/panelExport.scss

@@ -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 - 0
src/global.d.ts

@@ -0,0 +1,7 @@
+interface Window {
+  ClipboardItem: any;
+}
+
+interface Clipboard extends EventTarget {
+  write(data: any[]): Promise<void>;
+}

+ 6 - 5
src/index.tsx

@@ -23,7 +23,7 @@ import {
   getSelectedAttribute,
   loadFromJSON,
   saveAsJSON,
-  exportAsPNG,
+  exportCanvas,
   restoreFromLocalStorage,
   saveToLocalStorage,
   hasBackground,
@@ -37,6 +37,7 @@ import {
 import { renderScene } from "./renderer";
 import { AppState } from "./types";
 import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types";
+import { ExportType } from "./scene/types";
 
 import { getDateTime, isInputLike, measureText } from "./utils";
 import { KEYS, META_KEY, isArrowKey } from "./keys";
@@ -170,9 +171,9 @@ export class App extends React.Component<{}, AppState> {
     if (event.key === KEYS.ESCAPE) {
       elements = clearSelection(elements);
       this.forceUpdate();
-      this.setState({ elementType: 'selection' });
+      this.setState({ elementType: "selection" });
       if (window.document.activeElement instanceof HTMLElement) {
-        window.document.activeElement.blur()
+        window.document.activeElement.blur();
       }
       event.preventDefault();
       return;
@@ -614,8 +615,8 @@ export class App extends React.Component<{}, AppState> {
           <PanelExport
             projectName={this.state.name}
             onProjectNameChange={this.updateProjectName}
-            onExportAsPNG={() =>
-              exportAsPNG(elements, this.canvas!, this.state)
+            onExportCanvas={(type: ExportType) =>
+              exportCanvas(type, elements, this.canvas!, this.state)
             }
             exportBackground={this.state.exportBackground}
             onExportBackgroundChange={val =>

+ 20 - 2
src/scene/data.ts

@@ -6,6 +6,7 @@ import { getElementAbsoluteCoords } from "../element";
 
 import { renderScene } from "../renderer";
 import { AppState } from "../types";
+import { ExportType } from "./types";
 import nanoid from "nanoid";
 
 const LOCAL_STORAGE_KEY = "excalidraw";
@@ -76,7 +77,8 @@ export function loadFromJSON() {
   });
 }
 
-export function exportAsPNG(
+export function exportCanvas(
+  type: ExportType,
   elements: readonly ExcalidrawElement[],
   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
   if (tempCanvas !== canvas) tempCanvas.remove();

+ 1 - 1
src/scene/index.ts

@@ -8,7 +8,7 @@ export {
   getSelectedAttribute
 } from "./selection";
 export {
-  exportAsPNG,
+  exportCanvas,
   loadFromJSON,
   saveAsJSON,
   restoreFromLocalStorage,

+ 2 - 0
src/scene/types.ts

@@ -15,3 +15,5 @@ export type SceneScroll = {
 export interface Scene {
   elements: ExcalidrawTextElement[];
 }
+
+export type ExportType = "png" | "clipboard";