import React, { useState, useEffect } from "react"; import { createTwoFilesPatch } from 'diff'; import { parse, html } from 'diff2html'; import 'diff2html/bundles/css/diff2html.min.css'; import './DiffViewer.css'; const DiffViewer = ({ oldContent, newContent, filePath, onClose }) => { const [diffHtml, setDiffHtml] = useState(""); const [isDropdownOpen, setIsDropdownOpen] = useState(false); useEffect(() => { // Generate the unified diff format const generatedDiff = createTwoFilesPatch( filePath, filePath, oldContent, newContent, 'old version', 'new version' ); // Parse the unified diff to an internal data structure const diffJson = parse(generatedDiff); // Use diff2html to generate the side-by-side HTML const configuration = { drawFileList: true, matching: 'lines', outputFormat: 'side-by-side' // Crucial for side-by-side view }; const htmlOutput = html(diffJson, configuration); setDiffHtml(htmlOutput); }, [oldContent, newContent, filePath]); if (!diffHtml) return null; const handleDownload = (fileType) => { let content, filename; switch (fileType) { case 'diff': content = createTwoFilesPatch( filePath, filePath, oldContent, newContent, 'old version', 'new version' ); filename = `${filePath.split('/').pop()}_changes.diff`; break; case 'old': content = oldContent; filename = `${filePath.split('/').pop()}_old.txt`; break; case 'new': content = newContent; filename = `${filePath.split('/').pop()}_new.txt`; break; default: return; } const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); setIsDropdownOpen(false); // Close the dropdown after download }; return ( <div className="fixed inset-0 bg-gray-900 bg-opacity-75 z-50 flex items-center justify-center p-4"> <div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-10xl w-full max-h-[90vh] flex flex-col overflow-hidden"> <div className="flex justify-between items-center p-4 border-b border-gray-200 dark:border-gray-700"> <h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Changes in {filePath}</h2> <div className="flex items-center space-x-2"> <div className="relative"> <button onClick={() => setIsDropdownOpen(!isDropdownOpen)} className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors duration-200" > <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" viewBox="0 0 20 20" fill="currentColor"> <path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" /> </svg> </button> {isDropdownOpen && ( <div className="absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white dark:bg-gray-700 ring-1 ring-black ring-opacity-5 focus:outline-none z-10"> <div className="py-1"> <button onClick={() => handleDownload('diff')} className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-600 w-full text-left" > Download Diff </button> <button onClick={() => handleDownload('old')} className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-600 w-full text-left" > Download Old File </button> <button onClick={() => handleDownload('new')} className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-600 w-full text-left" > Download New File </button> </div> </div> )} </div> <button onClick={onClose} className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors duration-200" > <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> </svg> </button> </div> </div> <div className="flex-grow p-4 overflow-y-auto" dangerouslySetInnerHTML={{ __html: diffHtml }} /> </div> </div> ); }; export default DiffViewer;