Merge branch '8-export-drawing' into pre-release
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { PenTool, Trash2, FolderInput, ArrowRight, Check, Clock, Copy } from 'lucide-react';
|
||||
import { PenTool, Trash2, FolderInput, ArrowRight, Check, Clock, Copy, Download } from 'lucide-react';
|
||||
import type { Drawing, Collection } from '../types';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import clsx from 'clsx';
|
||||
import { exportToSvg } from "@excalidraw/excalidraw";
|
||||
import { exportDrawingToFile } from '../utils/exportUtils';
|
||||
|
||||
import * as api from '../api';
|
||||
|
||||
@@ -325,6 +326,16 @@ export const DrawingCard: React.FC<DrawingCardProps> = ({
|
||||
<Copy size={14} /> Duplicate
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
exportDrawingToFile(drawing);
|
||||
setContextMenu(null);
|
||||
}}
|
||||
className="w-full px-3 py-2 text-sm text-left text-slate-600 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800 hover:text-neutral-900 dark:hover:text-white flex items-center gap-2"
|
||||
>
|
||||
<Download size={14} /> Export
|
||||
</button>
|
||||
|
||||
<div className="border-t border-slate-50 dark:border-slate-700 my-1"></div>
|
||||
|
||||
<button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useState, useRef } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { ArrowLeft, Download } from 'lucide-react';
|
||||
import { Excalidraw, exportToSvg } from '@excalidraw/excalidraw';
|
||||
import '@excalidraw/excalidraw/index.css';
|
||||
import debounce from 'lodash/debounce';
|
||||
@@ -9,6 +9,7 @@ import { Toaster, toast } from 'sonner';
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
import { getUserIdentity } from '../utils/identity';
|
||||
import { reconcileElements } from '../utils/sync';
|
||||
import { exportFromEditor } from '../utils/exportUtils';
|
||||
import type { UserIdentity } from '../utils/identity';
|
||||
import * as api from '../api';
|
||||
import { useTheme } from '../context/ThemeContext';
|
||||
@@ -682,6 +683,25 @@ export const Editor: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Download Button */}
|
||||
<button
|
||||
onClick={() => {
|
||||
if (excalidrawAPI.current) {
|
||||
const elements = excalidrawAPI.current.getSceneElementsIncludingDeleted();
|
||||
const appState = excalidrawAPI.current.getAppState();
|
||||
const files = excalidrawAPI.current.getFiles() || {};
|
||||
exportFromEditor(drawingName, elements, appState, files);
|
||||
toast.success('Drawing exported');
|
||||
}
|
||||
}}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-neutral-800 rounded-lg text-gray-600 dark:text-gray-300 transition-colors"
|
||||
title="Export drawing"
|
||||
>
|
||||
<Download size={20} />
|
||||
</button>
|
||||
|
||||
<div className="h-6 w-px bg-gray-300 dark:bg-gray-700" />
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="relative group">
|
||||
<div
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import type { Drawing } from "../types";
|
||||
|
||||
export interface ExportData {
|
||||
type: "excalidraw";
|
||||
version: 2;
|
||||
source: string;
|
||||
elements: any[];
|
||||
appState: any;
|
||||
files: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a drawing to a .excalidraw file and trigger download
|
||||
*/
|
||||
export const exportDrawingToFile = (
|
||||
drawing: Drawing,
|
||||
filename?: string
|
||||
): void => {
|
||||
const exportData: ExportData = {
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
source: window.location.origin,
|
||||
elements: drawing.elements || [],
|
||||
appState: {
|
||||
gridSize: drawing.appState?.gridSize ?? null,
|
||||
viewBackgroundColor: drawing.appState?.viewBackgroundColor ?? "#ffffff",
|
||||
},
|
||||
files: drawing.files || {},
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], {
|
||||
type: "application/json",
|
||||
});
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename || `${drawing.name}.excalidraw`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
/**
|
||||
* Export drawing from Editor with current state
|
||||
*/
|
||||
export const exportFromEditor = (
|
||||
name: string,
|
||||
elements: readonly any[],
|
||||
appState: any,
|
||||
files: Record<string, any>
|
||||
): void => {
|
||||
const exportData: ExportData = {
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
source: window.location.origin,
|
||||
elements: Array.from(elements),
|
||||
appState: {
|
||||
gridSize: appState?.gridSize ?? null,
|
||||
viewBackgroundColor: appState?.viewBackgroundColor ?? "#ffffff",
|
||||
},
|
||||
files: files || {},
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], {
|
||||
type: "application/json",
|
||||
});
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `${name}.excalidraw`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
Reference in New Issue
Block a user