Skip to content

Commit db33a8e

Browse files
Merge pull request #15 from /issues/2
feat(editor): Upgrade the Monaco editor
2 parents 3c64927 + 5280d69 commit db33a8e

9 files changed

Lines changed: 772 additions & 121 deletions

File tree

src/components/CodeEditor.tsx

Lines changed: 0 additions & 117 deletions
This file was deleted.

src/components/Playground/Main.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import CodeEditor from "@/components/CodeEditor";
1+
import { CodeEditor, SupportedNetwork } from "@/lib/editor";
22
import Console from "@/components/Console";
33
import TutorialPanel from "@/components/TutorialPanel";
44
import Badge from "@/components/ui/Badge";
@@ -73,6 +73,7 @@ export default function Main({
7373
onChange={updateCode}
7474
disabled={isRunning}
7575
height="400px"
76+
network={selectedNetwork.id as SupportedNetwork}
7677
/>
7778
)}
7879
</div>

src/lib/editor/CodeEditor.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"use client";
2+
3+
import type React from "react";
4+
import { Editor } from "@monaco-editor/react";
5+
import { useTheme } from "@/lib/theme/ThemeProvider";
6+
import {
7+
EditorContainer,
8+
EditorLoadingPlaceholder,
9+
EditorErrorBoundary,
10+
} from "./components";
11+
import {
12+
useMonacoConfiguration,
13+
useEditorInstance,
14+
useMountedState,
15+
} from "./hooks";
16+
import {
17+
type CodeEditorProps,
18+
DEFAULT_EDITOR_OPTIONS,
19+
type SupportedNetwork,
20+
} from "./types";
21+
22+
const CodeEditor: React.FC<CodeEditorProps> = ({
23+
code,
24+
onChange,
25+
disabled = false,
26+
language = "typescript",
27+
height = "400px",
28+
network = "westend",
29+
}) => {
30+
const { isDarkTheme } = useTheme();
31+
const isMounted = useMountedState();
32+
33+
useMonacoConfiguration(network as SupportedNetwork);
34+
const { handleEditorDidMount } = useEditorInstance(code, onChange, disabled);
35+
36+
if (!isMounted) {
37+
return (
38+
<EditorContainer height={height}>
39+
<EditorLoadingPlaceholder />
40+
</EditorContainer>
41+
);
42+
}
43+
44+
return (
45+
<EditorErrorBoundary>
46+
<EditorContainer height={height}>
47+
<Editor
48+
height="100%"
49+
defaultLanguage={language}
50+
value={code}
51+
theme={isDarkTheme ? "vs-dark" : "light"}
52+
onChange={(value) => onChange(value || "")}
53+
onMount={handleEditorDidMount}
54+
loading={<EditorLoadingPlaceholder />}
55+
options={{
56+
...DEFAULT_EDITOR_OPTIONS,
57+
readOnly: disabled,
58+
cursorBlinking: "blink",
59+
}}
60+
/>
61+
</EditorContainer>
62+
</EditorErrorBoundary>
63+
);
64+
};
65+
66+
export default CodeEditor;

src/lib/editor/components.tsx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* UI components for the Monaco editor
3+
*/
4+
import React from "react";
5+
import { useTheme } from "@/lib/theme/ThemeProvider";
6+
7+
/**
8+
* Loading placeholder for the Monaco editor
9+
*/
10+
export function EditorLoadingPlaceholder() {
11+
const { getColor } = useTheme();
12+
13+
return (
14+
<div
15+
style={{
16+
backgroundColor: getColor("surface"),
17+
display: "flex",
18+
justifyContent: "center",
19+
alignItems: "center",
20+
height: "100%",
21+
width: "100%",
22+
}}
23+
>
24+
<div className="animate-pulse flex flex-col items-center">
25+
<svg
26+
width="40"
27+
height="40"
28+
viewBox="0 0 24 24"
29+
fill="none"
30+
stroke={getColor("textTertiary")}
31+
strokeWidth="2"
32+
strokeLinecap="round"
33+
strokeLinejoin="round"
34+
>
35+
<polyline points="16 18 22 12 16 6"></polyline>
36+
<polyline points="8 6 2 12 8 18"></polyline>
37+
</svg>
38+
<span style={{ color: getColor("textSecondary"), marginTop: "8px" }}>
39+
Loading editor...
40+
</span>
41+
</div>
42+
</div>
43+
);
44+
}
45+
46+
/**
47+
* Editor container component with styling
48+
*/
49+
export function EditorContainer({
50+
children,
51+
height = "400px",
52+
}: {
53+
children: React.ReactNode;
54+
height?: string;
55+
}) {
56+
const { getColor } = useTheme();
57+
58+
const containerStyle = {
59+
border: `1px solid ${getColor("border")}`,
60+
borderRadius: "0.375rem",
61+
overflow: "hidden",
62+
height,
63+
};
64+
65+
return <div style={containerStyle}>{children}</div>;
66+
}
67+
68+
/**
69+
* Error boundary component for the Monaco editor
70+
*/
71+
export class EditorErrorBoundary extends React.Component<
72+
{ children: React.ReactNode; fallback?: React.ReactNode },
73+
{ hasError: boolean }
74+
> {
75+
constructor(props: {
76+
children: React.ReactNode;
77+
fallback?: React.ReactNode;
78+
}) {
79+
super(props);
80+
this.state = { hasError: false };
81+
}
82+
83+
static getDerivedStateFromError() {
84+
return { hasError: true };
85+
}
86+
87+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
88+
console.error("Error in Monaco Editor:", error, errorInfo);
89+
}
90+
91+
render() {
92+
if (this.state.hasError) {
93+
return (
94+
this.props.fallback || (
95+
<div className="p-4 text-red-500">
96+
An error occurred while loading the editor. Please refresh the page.
97+
</div>
98+
)
99+
);
100+
}
101+
102+
return this.props.children;
103+
}
104+
}

0 commit comments

Comments
 (0)