Skip to main content
Glama

Office MCP Server

by walkingzzzy
ExcelDiffViewer.tsx11.6 kB
import React, { useState, useEffect } from 'react'; import { Change } from '../types'; import './ExcelDiffViewer.css'; interface ExcelCell { address: string; value: any; formula?: string; format?: any; } interface ExcelDiff { original: ExcelCell[][]; modified: ExcelCell[][]; changes: Change[]; } interface ExcelDiffViewerProps { diff: ExcelDiff; onAcceptChange: (changeId: string) => void; onRejectChange: (changeId: string) => void; onAcceptAll: () => void; onRejectAll: () => void; showFormulas?: boolean; highlightMode?: 'side-by-side' | 'unified'; } export const ExcelDiffViewer: React.FC<ExcelDiffViewerProps> = ({ diff, onAcceptChange, onRejectChange, onAcceptAll, onRejectAll, showFormulas = false, highlightMode = 'side-by-side' }) => { const [selectedChange, setSelectedChange] = useState<string | null>(null); const [filterType, setFilterType] = useState<string>('all'); const filteredChanges = diff.changes.filter(change => { if (filterType === 'all') return true; return change.type === filterType; }); const getCellChange = (rowIndex: number, colIndex: number): Change | null => { return diff.changes.find(change => { if (!change.position) return false; const row = Math.floor(change.position.start / 100); const col = change.position.start % 100; return row === rowIndex && col === colIndex; }) || null; }; const getCellValue = (cell: ExcelCell): string => { if (showFormulas && cell.formula) { return cell.formula; } return cell.value?.toString() || ''; }; const getCellClass = (change: Change | null): string => { if (!change) return ''; return `cell-${change.type} ${selectedChange === change.id ? 'selected' : ''}`; }; const renderCell = (cell: ExcelCell, rowIndex: number, colIndex: number, isOriginal: boolean) => { const change = getCellChange(rowIndex, colIndex); const cellClass = getCellClass(change); return ( <td key={`${isOriginal ? 'orig' : 'mod'}-${rowIndex}-${colIndex}`} className={`excel-cell ${cellClass}`} onClick={() => change && setSelectedChange(change.id)} title={change?.description} > <div className="cell-content"> {getCellValue(cell)} </div> {change && ( <div className="cell-actions"> <button className="accept-btn" onClick={(e) => { e.stopPropagation(); onAcceptChange(change.id); }} title="接受更改" > ✓ </button> <button className="reject-btn" onClick={(e) => { e.stopPropagation(); onRejectChange(change.id); }} title="拒绝更改" > ✗ </button> </div> )} </td> ); }; const renderTable = (data: ExcelCell[][], title: string, isOriginal: boolean) => ( <div className="excel-table-container"> <h3>{title}</h3> <div className="table-wrapper"> <table className="excel-table"> <thead> <tr> <th></th> {data[0]?.map((_, colIndex) => ( <th key={colIndex}> {String.fromCharCode(65 + colIndex)} </th> ))} </tr> </thead> <tbody> {data.map((row, rowIndex) => ( <tr key={rowIndex}> <th className="row-header">{rowIndex + 1}</th> {row.map((cell, colIndex) => renderCell(cell, rowIndex, colIndex, isOriginal) )} </tr> ))} </tbody> </table> </div> </div> ); const renderUnifiedView = () => { const maxRows = Math.max(diff.original.length, diff.modified.length); const maxCols = Math.max( diff.original[0]?.length || 0, diff.modified[0]?.length || 0 ); return ( <div className="unified-view"> <div className="table-wrapper"> <table className="excel-table unified"> <thead> <tr> <th></th> {Array.from({ length: maxCols }, (_, colIndex) => ( <th key={colIndex}> {String.fromCharCode(65 + colIndex)} </th> ))} </tr> </thead> <tbody> {Array.from({ length: maxRows }, (_, rowIndex) => ( <tr key={rowIndex}> <th className="row-header">{rowIndex + 1}</th> {Array.from({ length: maxCols }, (_, colIndex) => { const originalCell = diff.original[rowIndex]?.[colIndex]; const modifiedCell = diff.modified[rowIndex]?.[colIndex]; const change = getCellChange(rowIndex, colIndex); if (!change) { return ( <td key={colIndex} className="excel-cell"> {getCellValue(modifiedCell || originalCell || { address: '', value: '' })} </td> ); } return ( <td key={colIndex} className={`excel-cell ${getCellClass(change)}`} onClick={() => setSelectedChange(change.id)} > <div className="unified-cell"> {change.type === 'delete' && originalCell && ( <div className="deleted-content"> - {getCellValue(originalCell)} </div> )} {(change.type === 'insert' || change.type === 'modify') && modifiedCell && ( <div className="added-content"> + {getCellValue(modifiedCell)} </div> )} </div> <div className="cell-actions"> <button className="accept-btn" onClick={(e) => { e.stopPropagation(); onAcceptChange(change.id); }} > ✓ </button> <button className="reject-btn" onClick={(e) => { e.stopPropagation(); onRejectChange(change.id); }} > ✗ </button> </div> </td> ); })} </tr> ))} </tbody> </table> </div> </div> ); }; return ( <div className="excel-diff-viewer"> <div className="diff-header"> <div className="diff-controls"> <div className="view-controls"> <label> <input type="radio" name="highlightMode" value="side-by-side" checked={highlightMode === 'side-by-side'} onChange={(e) => e.target.checked && (highlightMode = 'side-by-side')} /> 并排对比 </label> <label> <input type="radio" name="highlightMode" value="unified" checked={highlightMode === 'unified'} onChange={(e) => e.target.checked && (highlightMode = 'unified')} /> 统一视图 </label> </div> <div className="filter-controls"> <select value={filterType} onChange={(e) => setFilterType(e.target.value)} > <option value="all">所有更改</option> <option value="insert">新增</option> <option value="delete">删除</option> <option value="modify">修改</option> </select> </div> <div className="display-controls"> <label> <input type="checkbox" checked={showFormulas} onChange={(e) => (showFormulas = e.target.checked)} /> 显示公式 </label> </div> <div className="action-controls"> <button className="accept-all-btn" onClick={onAcceptAll}> 接受全部 </button> <button className="reject-all-btn" onClick={onRejectAll}> 拒绝全部 </button> </div> </div> <div className="diff-stats"> <span className="stat insert"> 新增: {diff.changes.filter(c => c.type === 'insert').length} </span> <span className="stat delete"> 删除: {diff.changes.filter(c => c.type === 'delete').length} </span> <span className="stat modify"> 修改: {diff.changes.filter(c => c.type === 'modify').length} </span> </div> </div> <div className="diff-content"> {highlightMode === 'side-by-side' ? ( <div className="side-by-side-view"> {renderTable(diff.original, '原始版本', true)} {renderTable(diff.modified, '修改版本', false)} </div> ) : ( renderUnifiedView() )} </div> {selectedChange && ( <div className="change-details"> {(() => { const change = diff.changes.find(c => c.id === selectedChange); if (!change) return null; return ( <div className="change-info"> <h4>更改详情</h4> <div className="change-meta"> <span className={`change-type ${change.type}`}> {change.type === 'insert' ? '新增' : change.type === 'delete' ? '删除' : change.type === 'modify' ? '修改' : '格式'} </span> <span className="change-time"> {new Date(change.timestamp).toLocaleString()} </span> </div> {change.description && ( <div className="change-description"> {change.description} </div> )} <div className="change-actions"> <button className="accept-btn" onClick={() => onAcceptChange(change.id)} > 接受更改 </button> <button className="reject-btn" onClick={() => onRejectChange(change.id)} > 拒绝更改 </button> </div> </div> ); })()} </div> )} </div> ); }; export default ExcelDiffViewer;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/walkingzzzy/office-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server