Skip to content

Commit

Permalink
Merge pull request #21 from cu8code/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
cu8code authored Oct 19, 2024
2 parents 22a3f5c + e0eac32 commit 03aa159
Show file tree
Hide file tree
Showing 48 changed files with 19,374 additions and 3,126 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
54 changes: 33 additions & 21 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
# Logs
logs
*.log
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
Binary file added app/favicon.ico
Binary file not shown.
Binary file added app/fonts/GeistMonoVF.woff
Binary file not shown.
Binary file added app/fonts/GeistVF.woff
Binary file not shown.
File renamed without changes.
35 changes: 35 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";

const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}
189 changes: 189 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
"use client"

import React, { useEffect, useState, useRef } from "react";
import { WebContainer } from "@webcontainer/api";
import { useVSCodeStore } from "@/utils/store";
import FileExplorer from "@/components/FileExplorer";
import EditorPanel from "@/components/EditorPanel";
import TerminalPanel from "@/components/TerminalPanel";
import {
ImperativePanelHandle,
Panel,
PanelGroup,
PanelResizeHandle,
} from "react-resizable-panels";
import { Sidebar } from "@/components/SideBar";
import Loading from "@/components/Loading";

export default function VSCodeClone() {
const {
selectedFile,
openFiles,
webcontainerInstance,
setSelectedFile,
setOpenFiles,
setWebcontainerInstance,
updateFileSystem,
updateFile,
showExplorer,
showTerminal,
} = useVSCodeStore();

const [loading, setLoading] = useState<boolean>(true);

// Panel refs with the appropriate type that includes collapse/expand
const fileExplorerPanelRef = useRef<ImperativePanelHandle | null>(null);
const terminalPanelRef = useRef<ImperativePanelHandle | null>(null);

const s = async (ins: WebContainer) => {
const blob = await fetch("/_vite-react-starter-main-bin")
ins.mount(await blob.arrayBuffer() as unknown as ArrayBuffer)
}

useEffect(() => {
if (!webcontainerInstance) {
bootWebContainer();
}

return () => {
if (webcontainerInstance) {
webcontainerInstance.teardown();
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const bootWebContainer = async (): Promise<void> => {
if (!webcontainerInstance) {
const instance = await WebContainer.boot();
setWebcontainerInstance(instance);
await s(instance)
await updateFileSystem();
instance.fs.watch("/", { recursive: true }, updateFileSystem);
setLoading(false);
}
};

const handleEditorChange = async (
value: string | undefined,
): Promise<void> => {
console.assert(
value !== undefined,
"Editor value should not be undefined.",
);

if (selectedFile && value !== undefined) {
console.assert(
selectedFile !== null,
"Selected file should not be null.",
);
updateFile(selectedFile, value); // Update Zustand store (in-memory)

if (webcontainerInstance) {
await webcontainerInstance.fs.writeFile(selectedFile, value); // Write to the WebContainer FS
await updateFileSystem(); // Sync the Zustand store with WebContainer FS
}
} else {
console.warn(
"Editor change detected, but no file is selected or value is undefined.",
);
}
};

// Handle file explorer collapse/expand on showExplorer change
useEffect(() => {
if (showExplorer) {
handleExpandFileExplorer();
} else {
handleCollapseFileExplorer();
}
}, [showExplorer]);

// Handle terminal collapse/expand on showTerminal change
useEffect(() => {
if (showTerminal) {
handleExpandTerminal();
} else {
handleCollapseTerminal();
}
}, [showTerminal]);

const handleFileClick = (fileName: string): void => {
console.assert(
fileName !== undefined,
"File name should not be undefined.",
);

setSelectedFile(fileName);

if (!openFiles.includes(fileName)) {
setOpenFiles([...openFiles, fileName]);
}
};

const handleCollapseFileExplorer = (): void => {
if (fileExplorerPanelRef.current) {
fileExplorerPanelRef.current.collapse();
}
};

const handleExpandFileExplorer = (): void => {
if (fileExplorerPanelRef.current) {
fileExplorerPanelRef.current.expand();
}
};

const handleCollapseTerminal = (): void => {
if (terminalPanelRef.current) {
terminalPanelRef.current.collapse();
}
};

const handleExpandTerminal = (): void => {
if (terminalPanelRef.current) {
terminalPanelRef.current.expand();
}
};

return (
<div className="h-screen w-screen p-0 m-0 overflow-hidden">
{loading ? (
<Loading />
) : (
<PanelGroup autoSave="primary-layout" direction="horizontal">
<Panel minSize={3} defaultSize={3} maxSize={3}>
<Sidebar />
</Panel>
<PanelResizeHandle disabled={true} />
<Panel defaultSize={97}>
<PanelGroup autoSave="secondary-layout" direction="horizontal">
<Panel
ref={fileExplorerPanelRef}
defaultSize={20}
collapsible={true}
>
<FileExplorer handleFileClick={handleFileClick} />
</Panel>
<PanelResizeHandle />
<Panel defaultSize={80}>
<PanelGroup autoSave="tertiary-layout" direction="vertical">
<Panel defaultSize={80}>
<EditorPanel handleEditorChange={handleEditorChange} />
</Panel>
<PanelResizeHandle />
<Panel
ref={terminalPanelRef}
defaultSize={20}
collapsible={true}
>
<TerminalPanel />
</Panel>
</PanelGroup>
</Panel>
</PanelGroup>
</Panel>
</PanelGroup>
)}
</div>
);
};
59 changes: 59 additions & 0 deletions components/EditorPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use client"

import React from 'react';
import { useVSCodeStore } from "@/utils/store";
import { Editor } from "@monaco-editor/react";
import { getFileContent } from '../utils/fileSystemTree';
import TopBar from './TopBar';

export const EditorPanel: React.FC<{
handleEditorChange: (value: string | undefined) => void;
}> = ({ handleEditorChange }) => {
const { selectedFile, files, getTheme } = useVSCodeStore();
const theme = getTheme();

return (
<div className="flex-grow flex flex-col h-full">
<TopBar />
{selectedFile ? (
<Editor
defaultLanguage={getLanguageId(selectedFile)}
theme={theme.main.editor.theme}
onChange={handleEditorChange}
value={getFileContent(selectedFile, files)}

/>
) : (
<div className="h-full flex items-center justify-center" style={{
color: theme.main.topbar.text_color,
backgroundColor: theme.main.editor.backgroundColor
}}>
Select a file to edit
</div>
)}
</div>
);
};

function getLanguageId(filePath: string): string {
const extension = filePath.split('.').pop()!.toLowerCase();
const languageMap: { [key: string]: string } = {
js: 'javascript',
ts: 'typescript',
jsx: 'javascript',
tsx: 'typescript',
json: 'json',
css: 'css',
less: 'less',
scss: 'scss',
sass: 'sass',
html: 'html',
md: 'markdown',
xml: 'xml',
yaml: 'yaml',
yml: 'yaml',
};
return languageMap[extension] || 'plain text';
}

export default EditorPanel;
Loading

0 comments on commit 03aa159

Please sign in to comment.