Newer
Older
cortex-hub / ui / client-app / src / components / DiffViewer.js
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="flex items-center space-x-1 px-4 py-2 rounded-md text-gray-700 bg-gray-100 hover:bg-gray-200 dark:text-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 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>
                <span>Download</span>
              </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;