All files / src/components ExportOptions.jsx

70.37% Statements 38/54
50% Branches 2/4
57.14% Functions 8/14
73.07% Lines 38/52

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227                                22x 22x             22x 7x 7x 7x                 22x 7x 7x   7x 7x 7x 7x 7x             22x 3x                                                               3x 3x           22x 2x 2x                             2x 2x           22x 2x               2x 2x           22x 1x 1x 1x 1x                                   22x 2x                                 2x 2x 2x             22x                                                                                            
/**
 * @fileoverview Export Options Component
 * @description Provides options to export simulation data and screenshots
 * @module components/ExportOptions
 */
 
import { useRef } from 'react';
import { useSimulation } from '../context/SimulationContext';
import './ExportOptions.css';
 
/**
 * Export Options Component
 * Allows exporting simulation data in various formats
 * @returns {JSX.Element} Export options panel
 */
function ExportOptions() {
  const simulation = useSimulation();
  const downloadLinkRef = useRef(null);
 
  /**
   * Generate filename with timestamp
   * @param {string} extension - File extension
   * @returns {string} Filename
   */
  const generateFilename = (extension) => {
    const date = new Date();
    const timestamp = date.toISOString().slice(0, 19).replace(/[:-]/g, '');
    return `md_simulation_${timestamp}.${extension}`;
  };
 
  /**
   * Download data as a file
   * @param {string} content - File content
   * @param {string} filename - Download filename
   * @param {string} mimeType - MIME type
   */
  const downloadFile = (content, filename, mimeType) => {
    const blob = new Blob([content], { type: mimeType });
    const url = URL.createObjectURL(blob);
    
    Eif (downloadLinkRef.current) {
      downloadLinkRef.current.href = url;
      downloadLinkRef.current.download = filename;
      downloadLinkRef.current.click();
      URL.revokeObjectURL(url);
    }
  };
 
  /**
   * Export simulation state as JSON
   */
  const exportJSON = () => {
    const data = {
      exportedAt: new Date().toISOString(),
      simulation: {
        time: simulation.time,
        temperature: simulation.temperature,
        energy: simulation.energy,
        settings: {
          dt: simulation.dt,
          scale: simulation.scale,
          size: simulation.size,
          enablePhysics: simulation.enablePhysics,
          showBonds: simulation.showBonds,
        },
      },
      atoms: simulation.atoms.map(atom => ({
        id: atom.id,
        type: atom.type,
        symbol: atom.symbol,
        name: atom.name,
        position: atom.pos,
        velocity: atom.vel,
        mass: atom.mass,
        radius: atom.radius,
      })),
      bonds: simulation.bonds.map(bond => ({
        atom1: bond.atom1Id,
        atom2: bond.atom2Id,
        order: bond.order,
        distance: bond.distance,
      })),
    };
 
    const json = JSON.stringify(data, null, 2);
    downloadFile(json, generateFilename('json'), 'application/json');
  };
 
  /**
   * Export atom data as CSV
   */
  const exportCSV = () => {
    const headers = ['ID', 'Type', 'Symbol', 'Name', 'X', 'Y', 'Z', 'VX', 'VY', 'VZ', 'Mass', 'Radius'];
    const rows = simulation.atoms.map(atom => [
      atom.id,
      atom.type,
      atom.symbol,
      atom.name,
      atom.pos.x.toFixed(4),
      atom.pos.y.toFixed(4),
      atom.pos.z.toFixed(4),
      atom.vel.x.toFixed(4),
      atom.vel.y.toFixed(4),
      atom.vel.z.toFixed(4),
      atom.mass.toFixed(4),
      atom.radius.toFixed(4),
    ]);
 
    const csv = [headers.join(','), ...rows.map(row => row.join(','))].join('\n');
    downloadFile(csv, generateFilename('csv'), 'text/csv');
  };
 
  /**
   * Export data in XYZ format (common molecular format)
   */
  const exportXYZ = () => {
    const lines = [
      simulation.atoms.length.toString(),
      `Molecular Dynamics Export - Time: ${simulation.time}, Temp: ${simulation.temperature.toFixed(2)}K`,
      ...simulation.atoms.map(atom => 
        `${atom.symbol}  ${atom.pos.x.toFixed(6)}  ${atom.pos.y.toFixed(6)}  ${atom.pos.z.toFixed(6)}`
      ),
    ];
 
    const xyz = lines.join('\n');
    downloadFile(xyz, generateFilename('xyz'), 'chemical/x-xyz');
  };
 
  /**
   * Capture and export canvas screenshot
   */
  const exportScreenshot = () => {
    const canvas = document.querySelector('canvas');
    Eif (!canvas) {
      alert('No canvas found to capture');
      return;
    }
 
    try {
      const dataUrl = canvas.toDataURL('image/png');
      const link = document.createElement('a');
      link.href = dataUrl;
      link.download = generateFilename('png');
      link.click();
    } catch (error) {
      console.error('Failed to capture screenshot:', error);
      alert('Failed to capture screenshot. Canvas may be tainted.');
    }
  };
 
  /**
   * Copy current state summary to clipboard
   */
  const copyToClipboard = async () => {
    const summary = [
      `=== Molecular Dynamics Simulation Summary ===`,
      `Time: ${simulation.time}`,
      `Temperature: ${simulation.temperature.toFixed(2)} K`,
      ``,
      `Energy:`,
      `  Kinetic: ${simulation.energy.kinetic.toFixed(4)}`,
      `  Potential: ${simulation.energy.potential.toFixed(4)}`,
      `  Total: ${simulation.energy.total.toFixed(4)}`,
      ``,
      `Atoms: ${simulation.atoms.length}`,
      `Bonds: ${simulation.bonds.length}`,
      ``,
      `Atom Summary:`,
      ...simulation.atoms.map(a => `  ${a.id}: ${a.symbol} (${a.name}) at (${a.pos.x.toFixed(2)}, ${a.pos.y.toFixed(2)}, ${a.pos.z.toFixed(2)})`),
    ].join('\n');
 
    try {
      await navigator.clipboard.writeText(summary);
      alert('Summary copied to clipboard!');
    } catch (error) {
      console.error('Failed to copy:', error);
      alert('Failed to copy to clipboard');
    }
  };
 
  return (
    <div className="export-options-panel">
      <h3 className="export-title">
        <span className="export-icon">📤</span>
        Export Data
      </h3>
 
      <div className="export-grid">
        <button className="export-btn" onClick={exportJSON} title="Export full simulation state">
          <span className="btn-icon">{ }</span>
          <span className="btn-label">JSON</span>
          <span className="btn-hint">Full state</span>
        </button>
 
        <button className="export-btn" onClick={exportCSV} title="Export atom data as spreadsheet">
          <span className="btn-icon">📊</span>
          <span className="btn-label">CSV</span>
          <span className="btn-hint">Atom data</span>
        </button>
 
        <button className="export-btn" onClick={exportXYZ} title="Export in molecular XYZ format">
          <span className="btn-icon">🧬</span>
          <span className="btn-label">XYZ</span>
          <span className="btn-hint">Molecular</span>
        </button>
 
        <button className="export-btn" onClick={exportScreenshot} title="Save canvas as image">
          <span className="btn-icon">📸</span>
          <span className="btn-label">Screenshot</span>
          <span className="btn-hint">PNG image</span>
        </button>
      </div>
 
      <button className="copy-btn" onClick={copyToClipboard}>
        📋 Copy Summary to Clipboard
      </button>
 
      {/* Hidden download link */}
      <a ref={downloadLinkRef} style={{ display: 'none' }} aria-hidden="true">
        Download
      </a>
    </div>
  );
}
 
export default ExportOptions;