Skip to content

Commit

Permalink
feat: load templates
Browse files Browse the repository at this point in the history
  • Loading branch information
yongenaelf committed Jul 20, 2024
1 parent 9d2f108 commit 076cc09
Show file tree
Hide file tree
Showing 18 changed files with 328 additions and 111 deletions.
10 changes: 1 addition & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,8 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next

## Getting Started

First, build the development container:

```bash
docker compose up --build
```

Press `Ctrl+C` to stop the services.

```bash
docker compose up --watch
docker compose up --build --watch
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
Expand Down
File renamed without changes.
14 changes: 11 additions & 3 deletions app/@left/@top/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import FileExplorer from "@/components/file-explorer";

export default function LeftTop() {
return <FileExplorer />;
return (
<>
<div className="p-4">
<h1 className="text-2xl">Welcome to AElf Playground.</h1>
<p>
To begin, click on the New button on the top menu and choose a
template.
</p>
</div>
</>
);
}
19 changes: 19 additions & 0 deletions app/@left/@top/template/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import FileExplorer from "@/components/file-explorer";
import { getTemplateData } from "@/data/template";

export default async function Page({
params: { id },
}: {
params: { id: string };
}) {
const data = await getTemplateData(id);

return (
<>
<FileExplorer
paths={Object.keys(data).sort((a, b) => a.localeCompare(b))}
pathname={`/template/${id}`}
/>
</>
);
}
File renamed without changes.
5 changes: 5 additions & 0 deletions app/@right/@top/default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Editor from "@/components/editor";

export default function Top() {
return <Editor />;
}
17 changes: 17 additions & 0 deletions app/@right/@top/template/[id]/[file]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Editor from "@/components/editor";
import { getLang } from "@/components/editor-enum";
import { getTemplateData } from "@/data/template";
import { strFromU8 } from "fflate";

export default async function Page({
params: { id, file },
}: {
params: { id: string; file: string };
}) {
const data = await getTemplateData(id);
const [_, fileData] =
Object.entries(data).find(([key]) => key === decodeURIComponent(file)) ||
[];

return <Editor defaultValue={strFromU8(fileData!)} lang={getLang(file)} />;
}
8 changes: 8 additions & 0 deletions app/@right/@top/template/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function Page({ params: { id } }: { params: { id: string } }) {
return (
<div className="p-4">
<h1 className="text-2xl">You have chosen template {id}.</h1>
<p>Choose a file on the left to edit.</p>
</div>
);
}
13 changes: 13 additions & 0 deletions components/editor-enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export enum Languages {
CSHARP = "csharp",
PROTOBUF = "protobuf",
XML = "xml",
}

export function getLang(file: string) {
if (file.endsWith("cs")) return Languages.CSHARP;

if (file.endsWith("proto")) return Languages.PROTOBUF;

if (file.endsWith("csproj")) return Languages.XML;
}
65 changes: 51 additions & 14 deletions components/editor.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,75 @@
"use client";

import React from "react";
import React, { useEffect, useMemo } from "react";
import CodeMirror from "@uiw/react-codemirror";
import { csharp } from "@replit/codemirror-lang-csharp";
import { githubLight, githubDark } from "@uiw/codemirror-theme-github";
import { useTheme } from "next-themes";
import { StreamLanguage } from "@codemirror/language";
import { protobuf } from "@codemirror/legacy-modes/mode/protobuf";
import { Languages } from "./editor-enum";
import { xml } from "@codemirror/legacy-modes/mode/xml";
import { usePathname } from "next/navigation";
import { db } from "@/data/db";
import { useDebounce } from "use-debounce";

export default function Editor() {
export default function Editor({
defaultValue,
lang,
}: {
defaultValue?: string;
lang?: Languages;
}) {
const pathname = usePathname();
const { theme, systemTheme } = useTheme();

const currentTheme = theme !== "system" ? theme : systemTheme;
const editorTheme = currentTheme === "light" ? githubLight : githubDark;

const [value, setValue] = React.useState(`using System;
namespace Test
{
class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, world!");
const [value, setValue] = React.useState(defaultValue || "");
const [debouncedValue] = useDebounce(value, 1000);

const extensions = useMemo(() => {
switch (lang) {
case Languages.CSHARP:
return Array.from([csharp()]);
case Languages.PROTOBUF:
return Array.from([StreamLanguage.define(protobuf)]);
case Languages.XML:
return Array.from([StreamLanguage.define(xml)]);
default:
return Array.from([]);
}
}
}`);
}, [lang]);

const onChange = React.useCallback((val: string, viewUpdate: any) => {
console.log("val:", val);
setValue(val);
}, []);

useEffect(() => {
(async () => {
const existing = await db.files.get(pathname);

if (existing) {
setValue(existing.contents);
} else {
await db.files.add({ path: pathname, contents: value });
}
})();
}, [pathname]);

useEffect(() => {
(async () => {
await db.files.update(pathname, { contents: debouncedValue });
})();
}, [debouncedValue]);

return (
<CodeMirror
value={value}
height="1000px"
theme={editorTheme}
extensions={[csharp()]}
extensions={extensions}
onChange={onChange}
/>
);
Expand Down
154 changes: 84 additions & 70 deletions components/file-explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,83 +2,97 @@

import {
Tree,
Folder,
TreeViewElement,
File,
Folder,
CollapseButton,
} from "@/components/extension/tree-view-api";
import Link from "next/link";

type TOCProps = {
toc: TreeViewElement[];
pathname: string;
};

const FileExplorer = () => {
const elements = [
{
id: "1",
isSelectable: true,
name: "src",
children: [
{
id: "2",
isSelectable: true,
name: "app.tsx",
},
{
id: "3",
isSelectable: true,
name: "components",
children: [
{
id: "20",
isSelectable: true,
name: "pages",
children: [
{
id: "21",
isSelectable: true,
name: "interface.ts",
},
],
},
],
},
{
id: "6",
isSelectable: true,
name: "ui",
children: [
{
id: "7",
isSelectable: true,
name: "carousel.tsx",
},
],
},
],
},
];
const TOC = ({ toc, pathname }: TOCProps) => {
return (
<Tree
className="rounded-md h-60 bg-background overflow-hidden p-2"
initialSelectedId="21"
elements={elements}
>
<Folder element="src" value="1">
<File value="2">
<p> app.tsx </p>
</File>
<Folder value="3" element="components">
<Folder value="20" element="pages">
<File value="21">
<p>interface.ts</p>
</File>
</Folder>
</Folder>
<Folder value="6" element="ui">
<File value="7">
<p>carousel.tsx</p>
</File>
</Folder>
</Folder>
<CollapseButton elements={elements} />
<Tree className="w-full bg-background p-2 rounded-md" indicator={true}>
{toc.map((element, _) => (
<TreeItem key={element.id} elements={[element]} pathname={pathname} />
))}
<CollapseButton elements={toc} expandAll={true} />
</Tree>
);
};

type TreeItemProps = {
elements: TreeViewElement[];
pathname: string;
};

export const TreeItem = ({ elements, pathname }: TreeItemProps) => {
return (
<ul className="w-full space-y-1">
{elements.map((element) => (
<li key={element.id} className="w-full space-y-2">
{element.children && element.children?.length > 0 ? (
<Folder
element={element.name}
value={element.id}
isSelectable={element.isSelectable}
className="px-px pr-1"
>
<TreeItem
key={element.id}
aria-label={`folder ${element.name}`}
elements={element.children}
pathname={pathname}
/>
</Folder>
) : (
<File
key={element.id}
value={element.id}
isSelectable={element.isSelectable}
>
<Link href={`${pathname}/${encodeURIComponent(element.id)}`}>
{element?.name}
</Link>
</File>
)}
</li>
))}
</ul>
);
};

function convert(data: string[]) {
const map = new Map();
const root: { children: TreeViewElement[] } = { children: [] };
for (const name of data) {
let path = "";
let parent = root;
for (const label of name.split("/")) {
path += "/" + label;
let node = map.get(path);
if (!node) {
map.set(path, (node = { id: name, name: label } as TreeViewElement));
(parent.children ??= []).push(node);
}
parent = node;
}
}
return root.children;
}

const FileExplorer = ({
paths,
pathname,
}: {
paths: string[];
pathname: string;
}) => {
return <TOC toc={convert(paths)} pathname={pathname} />;
};

export default FileExplorer;
16 changes: 16 additions & 0 deletions components/template-menu-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use client";

import * as React from "react";

import { MenubarItem } from "@/components/ui/menubar";
import Link from "next/link";

export function TemplateMenuItem({ children }: React.PropsWithChildren) {
return (
<MenubarItem>
<Link href={`/template/${children}`} className="w-full">
{children}
</Link>
</MenubarItem>
);
}
Loading

0 comments on commit 076cc09

Please sign in to comment.