Skip to content

Commit 59f6156

Browse files
committed
start of frontend application
1 parent a7cac51 commit 59f6156

File tree

14 files changed

+3351
-92
lines changed

14 files changed

+3351
-92
lines changed

frontend/postcss.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
}

frontend/src/App.tsx

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,39 @@
1-
import { useState } from 'react'
2-
import reactLogo from './assets/react.svg'
3-
import viteLogo from '/vite.svg'
4-
import './App.css'
1+
import React from 'react';
2+
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
3+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4+
import { Layout } from './components/Layout';
5+
import { HomePage } from './pages/HomePage';
6+
import { ComparePage } from './pages/ComparePage';
7+
import { AnalyzePage } from './pages/AnalyzePage';
8+
import { SettingsPage } from './pages/SettingsPage';
59

6-
function App() {
7-
const [count, setCount] = useState(0)
10+
// Create a client
11+
const queryClient = new QueryClient({
12+
defaultOptions: {
13+
queries: {
14+
staleTime: 1000 * 60 * 5, // 5 minutes
15+
retry: 1,
16+
},
17+
},
18+
});
819

20+
function App() {
921
return (
10-
<>
11-
<div>
12-
<a href="https://vite.dev" target="_blank">
13-
<img src={viteLogo} className="logo" alt="Vite logo" />
14-
</a>
15-
<a href="https://react.dev" target="_blank">
16-
<img src={reactLogo} className="logo react" alt="React logo" />
17-
</a>
18-
</div>
19-
<h1>Vite + React</h1>
20-
<div className="card">
21-
<button onClick={() => setCount((count) => count + 1)}>
22-
count is {count}
23-
</button>
24-
<p>
25-
Edit <code>src/App.tsx</code> and save to test HMR
26-
</p>
27-
</div>
28-
<p className="read-the-docs">
29-
Click on the Vite and React logos to learn more
30-
</p>
31-
</>
32-
)
22+
<QueryClientProvider client={queryClient}>
23+
<Router>
24+
<div className="min-h-screen bg-gray-50">
25+
<Layout>
26+
<Routes>
27+
<Route path="/" element={<HomePage />} />
28+
<Route path="/compare" element={<ComparePage />} />
29+
<Route path="/analyze" element={<AnalyzePage />} />
30+
<Route path="/settings" element={<SettingsPage />} />
31+
</Routes>
32+
</Layout>
33+
</div>
34+
</Router>
35+
</QueryClientProvider>
36+
);
3337
}
3438

35-
export default App
39+
export default App;
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import React, { useRef, useEffect } from 'react';
2+
import Editor, { Monaco } from '@monaco-editor/react';
3+
import { clsx } from 'clsx';
4+
5+
export interface CodeChange {
6+
type: 'added' | 'removed' | 'modified';
7+
startLine: number;
8+
endLine: number;
9+
content?: string;
10+
}
11+
12+
interface CodeEditorProps {
13+
value: string;
14+
language: string;
15+
theme?: 'light' | 'dark';
16+
readOnly?: boolean;
17+
showLineNumbers?: boolean;
18+
changes?: CodeChange[];
19+
onSelectionChange?: (selection: any) => void;
20+
className?: string;
21+
height?: string;
22+
}
23+
24+
export const CodeEditor: React.FC<CodeEditorProps> = ({
25+
value,
26+
language,
27+
theme = 'light',
28+
readOnly = true,
29+
showLineNumbers = true,
30+
changes = [],
31+
onSelectionChange,
32+
className,
33+
height = '400px',
34+
}) => {
35+
const editorRef = useRef<any>(null);
36+
const monacoRef = useRef<Monaco | null>(null);
37+
38+
const handleEditorDidMount = (editor: any, monaco: Monaco) => {
39+
editorRef.current = editor;
40+
monacoRef.current = monaco;
41+
42+
// Configure editor options
43+
editor.updateOptions({
44+
readOnly,
45+
lineNumbers: showLineNumbers ? 'on' : 'off',
46+
minimap: { enabled: false },
47+
scrollBeyondLastLine: false,
48+
automaticLayout: true,
49+
wordWrap: 'on',
50+
fontSize: 14,
51+
fontFamily: 'JetBrains Mono, Fira Code, Monaco, Consolas, monospace',
52+
});
53+
54+
// Apply change decorations
55+
if (changes.length > 0) {
56+
applyChangeDecorations(editor, monaco, changes);
57+
}
58+
59+
// Handle selection changes
60+
if (onSelectionChange) {
61+
editor.onDidChangeCursorSelection((e: any) => {
62+
onSelectionChange(e.selection);
63+
});
64+
}
65+
};
66+
67+
const applyChangeDecorations = (editor: any, monaco: Monaco, changes: CodeChange[]) => {
68+
const decorations = changes.map(change => {
69+
let className = '';
70+
let glyphMarginClassName = '';
71+
72+
switch (change.type) {
73+
case 'added':
74+
className = 'code-line-added';
75+
glyphMarginClassName = 'code-glyph-added';
76+
break;
77+
case 'removed':
78+
className = 'code-line-removed';
79+
glyphMarginClassName = 'code-glyph-removed';
80+
break;
81+
case 'modified':
82+
className = 'code-line-modified';
83+
glyphMarginClassName = 'code-glyph-modified';
84+
break;
85+
}
86+
87+
return {
88+
range: new monaco.Range(change.startLine, 1, change.endLine, 1),
89+
options: {
90+
isWholeLine: true,
91+
className,
92+
glyphMarginClassName,
93+
glyphMarginHoverMessage: {
94+
value: `**${change.type.toUpperCase()}**${change.content ? `\n\n${change.content}` : ''}`,
95+
},
96+
},
97+
};
98+
});
99+
100+
editor.deltaDecorations([], decorations);
101+
};
102+
103+
useEffect(() => {
104+
if (editorRef.current && monacoRef.current && changes.length > 0) {
105+
applyChangeDecorations(editorRef.current, monacoRef.current, changes);
106+
}
107+
}, [changes]);
108+
109+
// Define custom CSS for change highlighting
110+
useEffect(() => {
111+
const style = document.createElement('style');
112+
style.textContent = `
113+
.code-line-added {
114+
background-color: rgba(34, 197, 94, 0.1) !important;
115+
border-left: 3px solid #22c55e !important;
116+
}
117+
.code-line-removed {
118+
background-color: rgba(239, 68, 68, 0.1) !important;
119+
border-left: 3px solid #ef4444 !important;
120+
}
121+
.code-line-modified {
122+
background-color: rgba(251, 191, 36, 0.1) !important;
123+
border-left: 3px solid #fbbf24 !important;
124+
}
125+
.code-glyph-added::before {
126+
content: '+';
127+
color: #22c55e;
128+
font-weight: bold;
129+
}
130+
.code-glyph-removed::before {
131+
content: '-';
132+
color: #ef4444;
133+
font-weight: bold;
134+
}
135+
.code-glyph-modified::before {
136+
content: '~';
137+
color: #fbbf24;
138+
font-weight: bold;
139+
}
140+
`;
141+
document.head.appendChild(style);
142+
143+
return () => {
144+
document.head.removeChild(style);
145+
};
146+
}, []);
147+
148+
return (
149+
<div className={clsx('border border-gray-300 rounded-lg overflow-hidden', className)}>
150+
<Editor
151+
height={height}
152+
language={language}
153+
value={value}
154+
theme={theme === 'dark' ? 'vs-dark' : 'vs'}
155+
onMount={handleEditorDidMount}
156+
options={{
157+
readOnly,
158+
lineNumbers: showLineNumbers ? 'on' : 'off',
159+
minimap: { enabled: false },
160+
scrollBeyondLastLine: false,
161+
automaticLayout: true,
162+
wordWrap: 'on',
163+
fontSize: 14,
164+
fontFamily: 'JetBrains Mono, Fira Code, Monaco, Consolas, monospace',
165+
glyphMargin: true,
166+
folding: true,
167+
lineDecorationsWidth: 10,
168+
lineNumbersMinChars: 3,
169+
renderLineHighlight: 'line',
170+
selectOnLineNumbers: true,
171+
roundedSelection: false,
172+
cursorStyle: 'line',
173+
automaticLayout: true,
174+
}}
175+
/>
176+
</div>
177+
);
178+
};

0 commit comments

Comments
 (0)