From 076cc09f81fe3eee6408082a0649e5fd4488427b Mon Sep 17 00:00:00 2001 From: "yongen.loong" Date: Sat, 20 Jul 2024 22:44:32 +0800 Subject: [PATCH] feat: load templates --- README.md | 10 +- app/@left/@bottom/{page.tsx => default.tsx} | 0 app/@left/@top/page.tsx | 14 +- app/@left/@top/template/[id]/page.tsx | 19 +++ app/@right/@bottom/{page.tsx => default.tsx} | 0 app/@right/@top/default.tsx | 5 + app/@right/@top/template/[id]/[file]/page.tsx | 17 ++ app/@right/@top/template/[id]/page.tsx | 8 + components/editor-enum.ts | 13 ++ components/editor.tsx | 65 ++++++-- components/file-explorer.tsx | 154 ++++++++++-------- components/template-menu-item.tsx | 16 ++ components/templates-menu.tsx | 18 +- data/db.ts | 19 +++ data/template.ts | 15 ++ lib/env.ts | 7 + package-lock.json | 53 +++++- package.json | 6 + 18 files changed, 328 insertions(+), 111 deletions(-) rename app/@left/@bottom/{page.tsx => default.tsx} (100%) create mode 100644 app/@left/@top/template/[id]/page.tsx rename app/@right/@bottom/{page.tsx => default.tsx} (100%) create mode 100644 app/@right/@top/default.tsx create mode 100644 app/@right/@top/template/[id]/[file]/page.tsx create mode 100644 app/@right/@top/template/[id]/page.tsx create mode 100644 components/editor-enum.ts create mode 100644 components/template-menu-item.tsx create mode 100644 data/db.ts create mode 100644 data/template.ts create mode 100644 lib/env.ts diff --git a/README.md b/README.md index b5c00d7..18ec66d 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/app/@left/@bottom/page.tsx b/app/@left/@bottom/default.tsx similarity index 100% rename from app/@left/@bottom/page.tsx rename to app/@left/@bottom/default.tsx diff --git a/app/@left/@top/page.tsx b/app/@left/@top/page.tsx index e5985b2..7bd7d02 100644 --- a/app/@left/@top/page.tsx +++ b/app/@left/@top/page.tsx @@ -1,5 +1,13 @@ -import FileExplorer from "@/components/file-explorer"; - export default function LeftTop() { - return ; + return ( + <> +
+

Welcome to AElf Playground.

+

+ To begin, click on the New button on the top menu and choose a + template. +

+
+ + ); } diff --git a/app/@left/@top/template/[id]/page.tsx b/app/@left/@top/template/[id]/page.tsx new file mode 100644 index 0000000..2ef8207 --- /dev/null +++ b/app/@left/@top/template/[id]/page.tsx @@ -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 ( + <> + a.localeCompare(b))} + pathname={`/template/${id}`} + /> + + ); +} diff --git a/app/@right/@bottom/page.tsx b/app/@right/@bottom/default.tsx similarity index 100% rename from app/@right/@bottom/page.tsx rename to app/@right/@bottom/default.tsx diff --git a/app/@right/@top/default.tsx b/app/@right/@top/default.tsx new file mode 100644 index 0000000..29ddf78 --- /dev/null +++ b/app/@right/@top/default.tsx @@ -0,0 +1,5 @@ +import Editor from "@/components/editor"; + +export default function Top() { + return ; +} diff --git a/app/@right/@top/template/[id]/[file]/page.tsx b/app/@right/@top/template/[id]/[file]/page.tsx new file mode 100644 index 0000000..d6d2af0 --- /dev/null +++ b/app/@right/@top/template/[id]/[file]/page.tsx @@ -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 ; +} diff --git a/app/@right/@top/template/[id]/page.tsx b/app/@right/@top/template/[id]/page.tsx new file mode 100644 index 0000000..461e49a --- /dev/null +++ b/app/@right/@top/template/[id]/page.tsx @@ -0,0 +1,8 @@ +export default function Page({ params: { id } }: { params: { id: string } }) { + return ( +
+

You have chosen template {id}.

+

Choose a file on the left to edit.

+
+ ); +} diff --git a/components/editor-enum.ts b/components/editor-enum.ts new file mode 100644 index 0000000..2cc170e --- /dev/null +++ b/components/editor-enum.ts @@ -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; +} diff --git a/components/editor.tsx b/components/editor.tsx index 3315819..e4c2098 100644 --- a/components/editor.tsx +++ b/components/editor.tsx @@ -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 ( ); diff --git a/components/file-explorer.tsx b/components/file-explorer.tsx index 089566b..8066e9a 100644 --- a/components/file-explorer.tsx +++ b/components/file-explorer.tsx @@ -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 ( - - - -

app.tsx

-
- - - -

interface.ts

-
-
-
- - -

carousel.tsx

-
-
-
- + + {toc.map((element, _) => ( + + ))} + ); }; +type TreeItemProps = { + elements: TreeViewElement[]; + pathname: string; +}; + +export const TreeItem = ({ elements, pathname }: TreeItemProps) => { + return ( +
    + {elements.map((element) => ( +
  • + {element.children && element.children?.length > 0 ? ( + + + + ) : ( + + + {element?.name} + + + )} +
  • + ))} +
+ ); +}; + +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 ; +}; + export default FileExplorer; diff --git a/components/template-menu-item.tsx b/components/template-menu-item.tsx new file mode 100644 index 0000000..1917cd9 --- /dev/null +++ b/components/template-menu-item.tsx @@ -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 ( + + + {children} + + + ); +} diff --git a/components/templates-menu.tsx b/components/templates-menu.tsx index f95d5a8..5bd42c8 100644 --- a/components/templates-menu.tsx +++ b/components/templates-menu.tsx @@ -1,14 +1,10 @@ import * as React from "react"; -import { unstable_noStore as noStore } from "next/cache"; -import { MenubarRadioGroup, MenubarRadioItem } from "@/components/ui/menubar"; +import { getBuildServerBaseUrl } from "@/lib/env"; +import { TemplateMenuItem } from "@/components/template-menu-item"; async function getData() { - noStore(); - - const res = await fetch( - `${process.env["BUILD_SERVER_BASE_URL"]}/playground/templates` - ); + const res = await fetch(`${getBuildServerBaseUrl()}/playground/templates`); const data = await res.json(); return data as string[]; @@ -18,12 +14,10 @@ export async function TemplatesMenu() { const data = await getData(); return ( - + <> {data.map((i) => ( - - {i} - + {i} ))} - + ); } diff --git a/data/db.ts b/data/db.ts new file mode 100644 index 0000000..8f51b7e --- /dev/null +++ b/data/db.ts @@ -0,0 +1,19 @@ +"use client"; +import Dexie, { type EntityTable } from "dexie"; + +interface File { + path: string; + contents: string; +} + +const db = new Dexie("FileDatabase") as Dexie & { + files: EntityTable; +}; + +// Schema declaration: +db.version(1).stores({ + files: "path, contents", +}); + +export type { File as FileContent }; +export { db }; diff --git a/data/template.ts b/data/template.ts new file mode 100644 index 0000000..18631b8 --- /dev/null +++ b/data/template.ts @@ -0,0 +1,15 @@ +import { getBuildServerBaseUrl } from "@/lib/env"; +import { unzipSync } from "fflate"; + +export async function getTemplateData(id: string) { + const res = await fetch( + `${getBuildServerBaseUrl()}/playground/template?template=${id}&projectName=test`, + { cache: "force-cache" } + ); + + const data = await res.text(); + + const zipData = Buffer.from(data, "base64"); + + return unzipSync(zipData); +} diff --git a/lib/env.ts b/lib/env.ts new file mode 100644 index 0000000..a5334fa --- /dev/null +++ b/lib/env.ts @@ -0,0 +1,7 @@ +import { unstable_noStore as noStore } from "next/cache"; + +export function getBuildServerBaseUrl() { + noStore(); + + return process.env["BUILD_SERVER_BASE_URL"]; +} diff --git a/package-lock.json b/package-lock.json index 66dc1f5..1cfc8a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "aelf-playground-next", "version": "0.1.0", "dependencies": { + "@codemirror/language": "^6.10.2", + "@codemirror/legacy-modes": "^6.4.0", "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", @@ -20,6 +22,9 @@ "@uiw/react-codemirror": "^4.23.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "dexie": "^4.0.8", + "dexie-react-hooks": "^1.1.7", + "fflate": "^0.8.2", "lucide-react": "^0.408.0", "next": "14.2.5", "next-themes": "^0.3.0", @@ -29,6 +34,7 @@ "react-terminal": "^1.4.4", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", + "use-debounce": "^10.0.1", "use-resize-observer": "^9.1.0" }, "devDependencies": { @@ -110,6 +116,15 @@ "style-mod": "^4.0.0" } }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.4.0.tgz", + "integrity": "sha512-5m/K+1A6gYR0e+h/dEde7LoGimMjRtWXZFg4Lo70cc8HzjSdHe3fLwjWMR0VRl5KFT1SxalSap7uMgPKF28wBA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0" + } + }, "node_modules/@codemirror/lint": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.1.tgz", @@ -1390,14 +1405,12 @@ "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -2266,7 +2279,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2430,6 +2442,23 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/dexie": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.8.tgz", + "integrity": "sha512-1G6cJevS17KMDK847V3OHvK2zei899GwpDiqfEXHP1ASvme6eWJmAp9AU4s1son2TeGkWmC0g3y8ezOBPnalgQ==", + "license": "Apache-2.0" + }, + "node_modules/dexie-react-hooks": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-1.1.7.tgz", + "integrity": "sha512-Lwv5W0Hk+uOW3kGnsU9GZoR1er1B7WQ5DSdonoNG+focTNeJbHW6vi6nBoX534VKI3/uwHebYzSw1fwY6a7mTw==", + "license": "Apache-2.0", + "peerDependencies": { + "@types/react": ">=16", + "dexie": "^3.2 || ^4.0.1-alpha", + "react": ">=16" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -3189,6 +3218,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -6191,6 +6226,18 @@ } } }, + "node_modules/use-debounce": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.1.tgz", + "integrity": "sha512-0uUXjOfm44e6z4LZ/woZvkM8FwV1wiuoB6xnrrOmeAEjRDDzTLQNRFtYHvqUsJdrz1X37j0rVGIVp144GLHGKg==", + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/use-resize-observer": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz", diff --git a/package.json b/package.json index 0f8e428..ea6b2d7 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "lint": "next lint" }, "dependencies": { + "@codemirror/language": "^6.10.2", + "@codemirror/legacy-modes": "^6.4.0", "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", @@ -21,6 +23,9 @@ "@uiw/react-codemirror": "^4.23.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "dexie": "^4.0.8", + "dexie-react-hooks": "^1.1.7", + "fflate": "^0.8.2", "lucide-react": "^0.408.0", "next": "14.2.5", "next-themes": "^0.3.0", @@ -30,6 +35,7 @@ "react-terminal": "^1.4.4", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", + "use-debounce": "^10.0.1", "use-resize-observer": "^9.1.0" }, "devDependencies": {