Skip to content

Commit 50f9978

Browse files
committed
Move view management to context provider & allow changing view document
1 parent 6e136e6 commit 50f9978

File tree

7 files changed

+128
-74
lines changed

7 files changed

+128
-74
lines changed

app/layout.tsx

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,14 @@ export default function RootLayout({
3030
}: Readonly<{
3131
children: React.ReactNode;
3232
}>) {
33-
const editorStates: EditorStates = {
34-
isDrawing: false,
35-
isDrawHulls: true,
36-
isDownloadClip: false,
37-
isInlineChat: false,
38-
isOpenChatView: false,
39-
isRecording: false,
40-
isListening: false,
41-
isThinking: false,
42-
isSpeaking: false,
43-
isMuted: false,
44-
};
45-
4633
return (
4734
<html lang="en">
4835
<body
4936
className={`${geistSans.variable} ${geistMono.variable} h-screen w-screen antialiased`}
5037
>
5138
<CapacitorProvider>
5239
<WrappedNextUIProvider>
53-
<EditorContextProvider defaultEditorStates={editorStates}>
40+
<EditorContextProvider>
5441
<Toaster />
5542
<Nav>{children}</Nav>
5643
</EditorContextProvider>

app/page.tsx

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ import { CodeEditorAgent } from "@/lib/agent/code-editor-agent";
1212
import { BaseTTS, getModelTTS } from "@/lib/tts/tts";
1313
import AgentChatTerminalView from "@/components/views/agent-chat-terminal-view";
1414
import { AnimatePresence, motion } from "framer-motion";
15-
import { ViewRef } from "@/lib/types";
15+
import { ViewDocument, ViewRef } from "@/lib/types";
1616
import EditorToolbar from "@/components/editor-toolbar";
1717
import { getPlatform } from "@/lib/platforms/platform-checker";
1818
import { EditorContext } from "@/components/providers/editor-context-provider";
1919

2020
export default function Home() {
2121
const [isCanvasReady, setIsCanvasReady] = useState(false);
2222

23-
const viewMap = useRef<Map<string, ViewRef | null>>(new Map());
23+
// const viewMap = useRef<Map<string, ViewRef | null>>(new Map());
2424
const editorContext = useContext(EditorContext);
2525

2626
const sttModelRef = useRef<BaseSTT | undefined>(undefined);
@@ -61,7 +61,7 @@ export default function Home() {
6161
llmModelRef.current,
6262
ttsModelRef.current,
6363
);
64-
const codeEditor = viewMap.current.get("1") as CodeEditorViewRef;
64+
const codeEditor = editorContext?.getViewById("1") as CodeEditorViewRef;
6565
const viewDocument = codeEditor?.getViewDocument();
6666
editorContext?.setEditorStates((prev) => ({
6767
...prev,
@@ -84,7 +84,9 @@ export default function Home() {
8484
}));
8585

8686
// Apply changes
87-
const codeEditor = viewMap.current.get("1") as CodeEditorViewRef;
87+
const codeEditor = editorContext?.getViewById(
88+
"1",
89+
) as CodeEditorViewRef;
8890
codeEditor?.applyChanges(changes);
8991

9092
// Play the audio in the blob
@@ -182,6 +184,31 @@ export default function Home() {
182184
}
183185
}, [editorContext?.editorStates, vad]);
184186

187+
useEffect(() => {
188+
const url = "/test.tsx";
189+
if (url) {
190+
fetch(url)
191+
.then((res) => res.text())
192+
.then((text) => {
193+
const viewId = "1";
194+
195+
// Init a new viewDocument
196+
const viewDocument: ViewDocument = {
197+
fileContent: text,
198+
filePath: url,
199+
};
200+
201+
// Get the code editor view
202+
const codeEditor = editorContext?.getViewById(
203+
viewId,
204+
) as CodeEditorViewRef;
205+
206+
// Set the viewDocument
207+
codeEditor?.setViewDocument(viewDocument);
208+
});
209+
}
210+
}, []);
211+
185212
return (
186213
<div className="flex h-full w-full flex-col">
187214
<EditorToolbar />
@@ -192,16 +219,17 @@ export default function Home() {
192219
className={`min-h-0 w-full flex-grow`}
193220
style={{
194221
cursor:
195-
editorContext?.editorStates?.isDrawing && !isCanvasReady ? "wait" : "auto",
222+
editorContext?.editorStates?.isDrawing && !isCanvasReady
223+
? "wait"
224+
: "auto",
196225
}}
197226
>
198227
<CodeEditorView
199228
ref={(ref) => {
200-
viewMap.current.set("1", ref);
229+
if (ref) editorContext?.addView("1", ref);
201230
}}
202231
width="100%"
203232
height="100%"
204-
url="/test.tsx"
205233
isDrawingMode={editorContext?.editorStates?.isDrawing}
206234
isDownloadClip={editorContext?.editorStates?.isDownloadClip}
207235
isDrawHulls={editorContext?.editorStates?.isDrawHulls}
@@ -219,9 +247,8 @@ export default function Home() {
219247
>
220248
<AgentChatTerminalView
221249
ref={(ref) => {
222-
viewMap.current.set("2", ref);
250+
if (ref) editorContext?.addView("2", ref);
223251
}}
224-
viewMap={viewMap}
225252
/>
226253
</motion.div>
227254
)}

components/nav-menu.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { Button } from "@nextui-org/react";
22
import { AnimatePresence, motion } from "framer-motion";
33
import { useMediaQuery } from "react-responsive";
44
import { useFileSystem } from "@/lib/hooks/use-file-system";
5+
import { ViewDocument } from "@/lib/types";
6+
import { CodeEditorViewRef } from "./views/code-editor-view";
7+
import { EditorContext } from "./providers/editor-context-provider";
8+
import { useContext } from "react";
59

610
function MenuPanel({ children }: { children?: React.ReactNode }) {
711
const isDesktop = useMediaQuery({
@@ -54,6 +58,8 @@ function MenuPanel({ children }: { children?: React.ReactNode }) {
5458
export default function NavMenu({ isMenuOpen }: { isMenuOpen: boolean }) {
5559
const { projectPath, openFilePicker, readFile } = useFileSystem();
5660

61+
const editorContext = useContext(EditorContext);
62+
5763
return (
5864
<AnimatePresence>
5965
{isMenuOpen && (
@@ -80,11 +86,23 @@ export default function NavMenu({ isMenuOpen }: { isMenuOpen: boolean }) {
8086
onPress={() => {
8187
openFilePicker(false).then((filePaths) => {
8288
console.log(filePaths);
89+
const filePath = filePaths[0];
8390
// Open the first file
84-
readFile(filePaths[0]).then((file) => {
91+
readFile(filePath).then((file) => {
8592
console.log(file);
8693
file.text().then((text) => {
8794
console.log("File content:\n" + text);
95+
const viewDocument: ViewDocument = {
96+
fileContent: text,
97+
filePath: filePath,
98+
};
99+
// Get the code editor view
100+
const codeEditor = editorContext?.getViewById(
101+
"1",
102+
) as CodeEditorViewRef;
103+
104+
// Set the viewDocument
105+
codeEditor?.setViewDocument(viewDocument);
88106
});
89107
});
90108
});

components/providers/editor-context-provider.tsx

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,74 @@
11
"use client";
22

33
import usePersistSettings from "@/lib/hooks/use-persist-settings";
4-
import { EditorStates, EditorContextType, PersistSettings } from "@/lib/types";
5-
import { createContext, useEffect, useState } from "react";
4+
import {
5+
EditorStates,
6+
EditorContextType,
7+
PersistSettings,
8+
ViewRef,
9+
} from "@/lib/types";
10+
import { createContext, useEffect, useRef, useState } from "react";
611

712
export const EditorContext = createContext<EditorContextType | undefined>(
813
undefined,
914
);
1015

16+
const defaultEditorStates: EditorStates = {
17+
isDrawing: false,
18+
isDrawHulls: true,
19+
isDownloadClip: false,
20+
isInlineChat: false,
21+
isOpenChatView: false,
22+
isRecording: false,
23+
isListening: false,
24+
isThinking: false,
25+
isSpeaking: false,
26+
isMuted: false,
27+
};
28+
1129
export default function EditorContextProvider({
1230
children,
13-
defaultEditorStates,
1431
}: {
1532
children: React.ReactNode;
16-
defaultEditorStates: EditorStates;
1733
}) {
34+
// --- Editor States ---
1835
const [editorStates, setEditorStates] =
1936
useState<EditorStates>(defaultEditorStates);
2037

38+
// --- Persist Settings ---
39+
// Persist settings are loaded from local storage upon component mount
2140
const { getPersistSettings, setPersistSettings, clearPersistSettings } =
2241
usePersistSettings();
2342
const [settings, setSettings] = useState<PersistSettings | undefined>(
2443
undefined,
2544
);
2645
const [isSettingsLoaded, setIsSettingsLoaded] = useState(false);
2746

47+
// --- View Management ---
48+
const viewMap = useRef<Map<string, ViewRef>>(new Map());
49+
function addView(viewId: string, view: ViewRef) {
50+
viewMap.current.set(viewId, view);
51+
}
52+
53+
function getViewById(viewId: string) {
54+
return viewMap.current.get(viewId);
55+
}
56+
57+
function getViewByType(viewType: string) {
58+
const views: ViewRef[] = [];
59+
viewMap.current.forEach((view) => {
60+
if (view?.getType() === viewType) {
61+
views.push(view);
62+
}
63+
});
64+
65+
return views;
66+
}
67+
68+
function deleteView(viewId: string) {
69+
viewMap.current.delete(viewId);
70+
}
71+
2872
// Load settings from local storage
2973
useEffect(() => {
3074
getPersistSettings().then((loadedSettings: PersistSettings) => {
@@ -51,6 +95,10 @@ export default function EditorContextProvider({
5195
setEditorStates,
5296
persistSettings: settings,
5397
setPersistSettings: setSettings,
98+
addView,
99+
getViewById,
100+
getViewByType,
101+
deleteView,
54102
}}
55103
>
56104
{children}

components/views/agent-chat-terminal-view.tsx

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,8 @@ import {
1212
useState,
1313
} from "react";
1414
import ViewLayout from "./layout";
15-
import {
16-
AgentConfig,
17-
ChatMessage,
18-
ViewDocument,
19-
ViewRef,
20-
} from "@/lib/types";
21-
import {
22-
Avatar,
23-
Button,
24-
Divider,
25-
Input,
26-
Tooltip,
27-
} from "@nextui-org/react";
15+
import { AgentConfig, ChatMessage, ViewDocument, ViewRef } from "@/lib/types";
16+
import { Avatar, Button, Divider, Input, Tooltip } from "@nextui-org/react";
2817
import Icon from "../icon";
2918
import { getModelLLM } from "@/lib/llm/llm";
3019
import toast from "react-hot-toast";
@@ -261,10 +250,7 @@ function TerminalNavBar({
261250
}
262251

263252
const AgentChatTerminalView = forwardRef(
264-
(
265-
{ viewMap }: AgentChatTerminalViewProps,
266-
ref: React.Ref<AgentChatTerminalViewRef>,
267-
) => {
253+
({}, ref: React.Ref<AgentChatTerminalViewRef>) => {
268254
useImperativeHandle(ref, () => ({
269255
getType: () => "AgentChatTerminalView",
270256
}));
@@ -347,14 +333,14 @@ const AgentChatTerminalView = forwardRef(
347333
setIsThinking(true);
348334

349335
// Get all code editor views and their content
336+
const codeEditorViews =
337+
editorContext?.getViewByType("CodeEditorView") ?? [];
350338
const viewDocuments: ViewDocument[] = [];
351-
viewMap.current.forEach((view) => {
352-
if (view?.getType() === "CodeEditorView") {
353-
const codeEditorView = view as CodeEditorViewRef;
354-
const viewDocument = codeEditorView.getViewDocument();
355-
if (viewDocument) {
356-
viewDocuments.push(viewDocument);
357-
}
339+
codeEditorViews.forEach((view) => {
340+
const codeEditorView = view as CodeEditorViewRef;
341+
const viewDocument = codeEditorView.getViewDocument();
342+
if (viewDocument) {
343+
viewDocuments.push(viewDocument);
358344
}
359345
});
360346

components/views/code-editor-view.tsx

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import { EditorContext } from "../providers/editor-context-provider";
3939
interface CodeEditorViewProps {
4040
width?: string;
4141
height?: string;
42-
url?: string;
4342
isDrawingMode?: boolean;
4443
isDownloadClip?: boolean;
4544
isDrawHulls?: boolean;
@@ -48,6 +47,7 @@ interface CodeEditorViewProps {
4847

4948
export type CodeEditorViewRef = ViewRef & {
5049
getViewDocument: () => ViewDocument | undefined;
50+
setViewDocument: (document: ViewDocument) => void;
5151
applyChanges: (changes: LineChange[]) => void;
5252
};
5353

@@ -56,7 +56,6 @@ const CodeEditorView = forwardRef(
5656
{
5757
width,
5858
height,
59-
url,
6059
isDrawingMode,
6160
isDownloadClip,
6261
isDrawHulls,
@@ -69,6 +68,9 @@ const CodeEditorView = forwardRef(
6968
getViewDocument: () => {
7069
return viewDocument;
7170
},
71+
setViewDocument: (document: ViewDocument) => {
72+
setViewDocument(document);
73+
},
7274
applyChanges: (changes: LineChange[]) => {
7375
console.log("Applying changes", changes);
7476
const cmView = cmRef.current?.view;
@@ -155,24 +157,6 @@ const CodeEditorView = forwardRef(
155157
undefined,
156158
);
157159

158-
useEffect(() => {
159-
if (url) {
160-
fetch(url)
161-
.then((res) => res.text())
162-
.then((text) => {
163-
const viewId = "1";
164-
165-
// Init a new viewDocument
166-
const viewDocument: ViewDocument = {
167-
fileContent: text,
168-
filePath: url,
169-
selections: [],
170-
};
171-
setViewDocument(viewDocument);
172-
});
173-
}
174-
}, [url]);
175-
176160
useEffect(() => {
177161
if (resolvedTheme === "dark") {
178162
setTheme(vscodeDark);
@@ -343,7 +327,7 @@ const CodeEditorView = forwardRef(
343327

344328
return {
345329
...prev,
346-
selections: [...prev.selections, newInfo],
330+
selections: [...(prev.selections ?? []), newInfo],
347331
};
348332
});
349333
}, []);

0 commit comments

Comments
 (0)