novitacarlen commited on
Commit
463ef66
·
1 Parent(s): 8e36730

feat: enable edit source code

Browse files
src/components/app-container.tsx CHANGED
@@ -187,6 +187,30 @@ export function AppContainer() {
187
  latestVersionIdRef.current = null;
188
  }
189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  return (
191
  <div className="flex flex-col h-screen bg-novita-dark text-white">
192
  <Header
@@ -196,7 +220,11 @@ export function AppContainer() {
196
  />
197
  <div className="flex flex-1 overflow-hidden relative">
198
  <div className="w-1/3 border-r border-novita-gray/20 flex flex-col relative">
199
- <CodeEditor code={code} isLoading={loading} />
 
 
 
 
200
 
201
  <AuthErrorPopup
202
  show={showAuthError}
 
187
  latestVersionIdRef.current = null;
188
  }
189
 
190
+ const handleManualCodeEdit = (newCode: string) => {
191
+ setCode(newCode);
192
+
193
+ if (currentVersionId) {
194
+ updateVersionWithCode(newCode);
195
+ }
196
+ };
197
+
198
+ const updateVersionWithCode = (editedCode: string) => { // Get existing versions
199
+ const storedVersions = localStorage.getItem('novita-versions');
200
+ if (!storedVersions || !currentVersionId) return;
201
+
202
+ let versions: Version[] = JSON.parse(storedVersions);
203
+
204
+ const updatedVersions = versions.map(version => {
205
+ if (version.id === currentVersionId) {
206
+ return { ...version, code: editedCode };
207
+ }
208
+ return version;
209
+ });
210
+
211
+ localStorage.setItem('novita-versions', JSON.stringify(updatedVersions));
212
+ };
213
+
214
  return (
215
  <div className="flex flex-col h-screen bg-novita-dark text-white">
216
  <Header
 
220
  />
221
  <div className="flex flex-1 overflow-hidden relative">
222
  <div className="w-1/3 border-r border-novita-gray/20 flex flex-col relative">
223
+ <CodeEditor
224
+ code={code}
225
+ isLoading={loading}
226
+ onCodeChange={handleManualCodeEdit}
227
+ />
228
 
229
  <AuthErrorPopup
230
  show={showAuthError}
src/components/code-editor.tsx CHANGED
@@ -1,15 +1,38 @@
1
  "use client"
2
 
3
- import { useEffect, useRef, useState } from "react";
 
4
 
5
  interface CodeEditorProps {
6
  code: string;
7
  isLoading?: boolean;
 
8
  }
9
 
10
- export function CodeEditor({ code, isLoading = false }: CodeEditorProps) {
11
  const containerRef = useRef<HTMLDivElement>(null);
 
12
  const [isAtBottom, setIsAtBottom] = useState(true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  // Check if user is near bottom when scrolling
15
  const handleScroll = () => {
@@ -30,22 +53,48 @@ export function CodeEditor({ code, isLoading = false }: CodeEditorProps) {
30
  }, []);
31
 
32
  useEffect(() => {
33
- // Only auto-scroll if user was already at the bottom
34
- if (isAtBottom && containerRef.current) {
35
  containerRef.current.scrollTop = containerRef.current.scrollHeight;
36
  }
37
- }, [code, isLoading, isAtBottom]);
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  return (
40
  <div className="flex-1 overflow-auto p-4">
41
  <div
42
  ref={containerRef}
43
- className="code-area p-4 rounded-md font-mono text-sm h-full overflow-auto border border-novita-gray/20"
44
  >
45
- <pre className="whitespace-pre-wrap">
46
- {code}
47
- {isLoading && <span className="inline-block h-4 w-2 bg-white/70 animate-pulse"></span>}
48
- </pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  </div>
50
  </div>
51
  )
 
1
  "use client"
2
 
3
+ import { useEffect, useRef, useState, useCallback } from "react";
4
+ import { debounce } from "@/lib/utils";
5
 
6
  interface CodeEditorProps {
7
  code: string;
8
  isLoading?: boolean;
9
+ onCodeChange?: (code: string) => void;
10
  }
11
 
12
+ export function CodeEditor({ code, isLoading = false, onCodeChange }: CodeEditorProps) {
13
  const containerRef = useRef<HTMLDivElement>(null);
14
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
15
  const [isAtBottom, setIsAtBottom] = useState(true);
16
+ const [localCode, setLocalCode] = useState(code);
17
+
18
+ const debouncedSave = useCallback(
19
+ debounce((newCode: string) => {
20
+ if (onCodeChange) {
21
+ onCodeChange(newCode);
22
+ }
23
+ }, 1000),
24
+ [onCodeChange]
25
+ );
26
+
27
+ useEffect(() => {
28
+ setLocalCode(code);
29
+ }, [code]);
30
+
31
+ const handleCodeChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
32
+ const newCode = e.target.value;
33
+ setLocalCode(newCode);
34
+ debouncedSave(newCode);
35
+ };
36
 
37
  // Check if user is near bottom when scrolling
38
  const handleScroll = () => {
 
53
  }, []);
54
 
55
  useEffect(() => {
56
+ // Only auto-scroll if user was already at the bottom and not focused on textarea
57
+ if (isAtBottom && containerRef.current && document.activeElement !== textareaRef.current) {
58
  containerRef.current.scrollTop = containerRef.current.scrollHeight;
59
  }
60
+ }, [localCode, isLoading, isAtBottom]);
61
+
62
+ // Auto-resize textarea
63
+ const adjustTextareaHeight = () => {
64
+ if (textareaRef.current) {
65
+ textareaRef.current.style.height = 'auto';
66
+ textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px';
67
+ }
68
+ };
69
+
70
+ useEffect(() => {
71
+ adjustTextareaHeight();
72
+ }, [localCode]);
73
 
74
  return (
75
  <div className="flex-1 overflow-auto p-4">
76
  <div
77
  ref={containerRef}
78
+ className="code-area p-4 rounded-md font-mono text-sm h-full overflow-auto border border-novita-gray/20 relative"
79
  >
80
+ <textarea
81
+ ref={textareaRef}
82
+ value={localCode}
83
+ onChange={handleCodeChange}
84
+ disabled={isLoading}
85
+ className="w-full min-h-full bg-transparent text-white resize-none outline-none whitespace-pre-wrap font-mono text-sm leading-relaxed"
86
+ style={{
87
+ minHeight: '100%',
88
+ fontFamily: 'inherit',
89
+ fontSize: 'inherit',
90
+ lineHeight: 'inherit'
91
+ }}
92
+ placeholder="Your generated code will appear here..."
93
+ spellCheck={false}
94
+ />
95
+ {isLoading && (
96
+ <span className="absolute bottom-4 right-4 inline-block h-4 w-2 bg-white/70 animate-pulse"></span>
97
+ )}
98
  </div>
99
  </div>
100
  )
src/lib/utils.ts CHANGED
@@ -4,3 +4,14 @@ import { twMerge } from "tailwind-merge"
4
  export function cn(...inputs: ClassValue[]) {
5
  return twMerge(clsx(inputs))
6
  }
 
 
 
 
 
 
 
 
 
 
 
 
4
  export function cn(...inputs: ClassValue[]) {
5
  return twMerge(clsx(inputs))
6
  }
7
+
8
+ export function debounce<T extends (...args: any[]) => any>(
9
+ func: T,
10
+ wait: number
11
+ ): (...args: Parameters<T>) => void {
12
+ let timeout: NodeJS.Timeout;
13
+ return (...args: Parameters<T>) => {
14
+ clearTimeout(timeout);
15
+ timeout = setTimeout(() => func(...args), wait);
16
+ };
17
+ }