diff --git a/index.html b/index.html index 235dbac..bb349e2 100644 --- a/index.html +++ b/index.html @@ -4,9 +4,9 @@ - Ruby WASI Playground + RunRuby.dev - +
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index 98f94b2..28f5216 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -1,12 +1,12 @@ -import cs from "./styles.module.css"; import { Editor } from "../Editor/Editor.tsx"; +import cs from "./styles.module.css"; export default function App() { return (
-

Ruby WASI Playground

+

RunRuby.dev

diff --git a/src/components/App/styles.module.css b/src/components/App/styles.module.css index 23aa3f2..72cbeac 100644 --- a/src/components/App/styles.module.css +++ b/src/components/App/styles.module.css @@ -96,7 +96,7 @@ .editorPlaceholder { font-size: 16px; - padding: 8px 16px; + padding: 32px 32px; } .editorText { diff --git a/src/components/Editor/Editor.tsx b/src/components/Editor/Editor.tsx index f86dea7..f23de31 100644 --- a/src/components/Editor/Editor.tsx +++ b/src/components/Editor/Editor.tsx @@ -1,12 +1,16 @@ -import cs from "../App/styles.module.css"; import MonacoEditor from "@monaco-editor/react"; -import { decode, encode, gemFromURI, workDir } from "../../engines/wasi/editorFS.ts"; import { useEffect, useMemo, useRef, useState } from "react"; import { Directory, File, SyncOPFSFile } from "@bjorn3/browser_wasi_shim"; import { RbValue } from "@ruby/wasm-wasi"; +import { CreateHandler, DeleteHandler, RenameHandler, Tree, TreeApi, NodeApi } from "react-arborist"; +import { VscNewFile, VscNewFolder } from "react-icons/vsc"; +import { nanoid } from "nanoid"; + import { runWASI } from "../../engines/wasi"; +import { decode, encode, gemFromURI, workDir } from "../../engines/wasi/editorFS.ts"; -import Node from "../FileTree/Node"; +import Node from "../Node/Node"; +import cs from "../App/styles.module.css"; export type Entity = { id: string; @@ -14,11 +18,6 @@ export type Entity = { object: Directory | File | SyncOPFSFile; } -import { CreateHandler, DeleteHandler, RenameHandler, Tree, TreeApi } from "react-arborist"; -import { NodeApi } from "react-arborist/dist/module/interfaces/node-api"; -import { VscNewFile, VscNewFolder } from "react-icons/vsc"; -import { nanoid } from "nanoid"; - function sortChildren(node: Directory): Entity[] { const entries = Object.entries(node.contents).map((entry) => { const id = idsMap.get(entry[1]) || nanoid(); @@ -52,7 +51,14 @@ export const Editor = () => { const [log, setLog] = useState([]); const [editorValueSource, setEditorValueSource] = useState<"result" | "logs">("result"); - const [currentFilePath, setCurrentFilePath] = useState("main.rb"); + const [currentNodeId, setCurrentNodeId] = useState(null); + const treeRef = useRef>(null); + const [treeData, setTreeData] = useState(sortChildren(workDir.dir)); + + const currentFilePath = useMemo(() => { + const currentNode = treeRef.current?.get(currentNodeId); + return currentNode ? getPath(currentNode) : null; + }, [currentNodeId]); const currentFile = useMemo( () => { @@ -124,10 +130,17 @@ export const Editor = () => { if (!currentFilePath?.endsWith(".rb")) return; if (currentFile === null) return; - runVM(`require "bundler/setup";${decode(currentFile.data)}`); + runVM(`eval <<~CODE, binding, '${currentFilePath}', 0 + ${canRunBundleInstall ? "require \"bundler/setup\";" : ""}${decode(currentFile.data)} + CODE`); }; const bundleInstall = () => { - runVM(`require "bundler/cli";require "bundler/cli/install";Bundler::CLI::Install.new({path: './gems'}).run`, + runVM(`require "rubygems_stub" + require "thread_stub" + require "bundler_stub" + require "bundler/cli" + require "bundler/cli/install" + Bundler::CLI::Install.new({path: './gems'}).run`, () => { setResult("Bundle install successful (see logs for details)"); }, @@ -140,9 +153,6 @@ export const Editor = () => { const handleEditorChange = (value: string | undefined) => { setCode(value || ""); }; - const treeRef = useRef>(null); - - const [treeData, setTreeData] = useState(sortChildren(workDir.dir)); const onRename: RenameHandler = ({ name, node }) => { const parent = (node.parent == null || node.parent.isRoot) ? workDir.dir : node.parent.data.object as Directory; @@ -153,8 +163,12 @@ export const Editor = () => { } parent.contents[name] = node.data.object; delete parent.contents[node.data.name]; - node.data = { ...node.data, name }; + setTreeData(sortChildren(workDir.dir)); + + setTimeout(() => { + setCurrentNodeId(currentNodeId); + }, 20); } }; @@ -176,7 +190,7 @@ export const Editor = () => { const parent = (node.parent == null || node.parent.isRoot) ? workDir.dir : node.parent.data.object as Directory; delete parent.contents[node.data.name]; if (currentFilePath === getPath(node)) { - setCurrentFilePath(null); + setCurrentNodeId(null); } } }); @@ -187,8 +201,16 @@ export const Editor = () => { const canRunBundleInstall = useMemo(() => !loading && treeData.find((entry) => entry.name === "Gemfile"), [loading, treeData]); useEffect(() => { - !initializing && gemFromURI() && bundleInstall(); - }, [initializing]); + if (initializing) { + const tree = treeRef.current; + if (tree) { + const node = tree.visibleNodes.find((n) => n.data.name?.endsWith(".rb")); + node ? node.activate() : tree?.firstNode?.activate(); + } + } else { + gemFromURI() && bundleInstall(); + } + }, [initializing, treeRef]); return ( <> @@ -220,7 +242,7 @@ export const Editor = () => { onDelete={onDelete} onActivate={(node: NodeApi) => { if (node.isLeaf) { - setCurrentFilePath(getPath(node)); + setCurrentNodeId(node.id); } }} > @@ -256,9 +278,11 @@ export const Editor = () => { /> ) : ( -
- Select a file to edit -
+ !initializing && ( +
+ Select a file to edit +
+ ) ) } diff --git a/src/components/FileTree/FileTree.tsx b/src/components/FileTree/FileTree.tsx deleted file mode 100644 index 90b1cf0..0000000 --- a/src/components/FileTree/FileTree.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Directory } from "@bjorn3/browser_wasi_shim"; -import { useState } from "react"; - -import cs from "./styles.module.css"; - -const DirItem = ({ path, rootDir, currentFilePath, setCurrentFilePath }: { - currentFilePath: string, - setCurrentFilePath: (value: (((prevState: string) => string) | string)) => void, - rootDir: Directory - path: string -}) => { - const [isOpen, setIsOpen] = useState(false); - return ( -
-
setIsOpen(!isOpen)}> - {isOpen ? "▼" : "▶"}  {path} -
-
[+ dir]
-
[+ file]
-
[edit]
-
[del]
-
-
- {isOpen &&
- -
- } -
- ); -}; - -export const FileTree = ({ currentFilePath, setCurrentFilePath, rootDir, rootPath }: { - currentFilePath: string, - setCurrentFilePath: (value: (((prevState: string) => string) | string)) => void, - rootDir: Directory - rootPath?: string -}) => { - return ( -
- {rootPath ? null : - - } - {Object.keys(rootDir.contents).map((path) => ( - (rootDir.contents[path] instanceof Directory) ? - - : -
{ - setCurrentFilePath(`${rootPath ? `${rootPath}/` : ""}${path}`); - }}> - {path} -
-
[edit]
-
[del]
-
-
- ))} -
- ); -}; diff --git a/src/components/FileTree/styles.module.css b/src/components/FileTree/styles.module.css deleted file mode 100644 index f4d3e74..0000000 --- a/src/components/FileTree/styles.module.css +++ /dev/null @@ -1,103 +0,0 @@ -.menuLabel { - font-size: 14px; - font-weight: bold; - color: #f1f1f1; - padding: 8px 8px 16px; - display: block; -} - -.menuFolder { -} - -.menuFolderContent { - padding-left: 8px; -} - -.menuFolderName { - cursor: default; - padding: 8px 8px 8px 16px; - border-radius: 8px; -} - -.menuFile { - cursor: default; - padding: 8px 8px 8px 16px; - border-radius: 8px; -} - -.menuFolderName:hover, .menuFile:hover { - background-color: #595959; -} - -.menuFileActive { - background-color: #424242; -} - -.menuInputButton { - white-space: nowrap; -} - -.menuFileButtons { - display: inline-flex; - flex-direction: row; - float: right; -} - -.menuFileButton { - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 12px; -} - -.menuFileButton:hover { - background-color: #fefefe; - color: #1a1a1a; -} - -.menuInput { - background-color: transparent; - color: #f1f1f1; - padding: 8px 50px 8px 16px; - text-align: left; - text-decoration: none; - display: inline-block; - font-size: 14px; - margin-bottom: 8px; - border: 1px solid #ffffff14; - border-radius: 8px; -} - -.menuInstallButton { - transform: translateX(-100%); - background-color: #388E3C; - border: none; - color: #f1f1f1; - font-weight: bold; - padding: 8px 16px; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 14px; - cursor: pointer; - border-top-right-radius: 8px; - border-bottom-right-radius: 8px; - margin-top: 8px; -} - -.menuDependencies { - padding-left: 16px; -} - -.menuDependency { - padding-bottom: 4px; -} - -.menuSpinner { - fill: #f1f1f1; - width: 16px; - height: 16px; - margin-left: 8px; - display: inline-block; - vertical-align: middle; -} diff --git a/src/components/FileTree/Node.tsx b/src/components/Node/Node.tsx similarity index 94% rename from src/components/FileTree/Node.tsx rename to src/components/Node/Node.tsx index db35bf7..661e3cb 100644 --- a/src/components/FileTree/Node.tsx +++ b/src/components/Node/Node.tsx @@ -1,8 +1,8 @@ -import cs from "./Node.module.css"; -import { NodeRendererProps } from "react-arborist"; +import { NodeRendererProps, NodeApi } from "react-arborist"; import { VscEdit, VscFile, VscFolder, VscFolderOpened, VscTrash } from "react-icons/vsc"; -import { NodeApi } from "react-arborist/dist/module/interfaces/node-api"; + import { Entity } from "../Editor/Editor.tsx"; +import cs from "./styles.module.css"; function isValidFileName(fileName: string) { if (fileName.trim() === "") { diff --git a/src/components/FileTree/Node.module.css b/src/components/Node/styles.module.css similarity index 100% rename from src/components/FileTree/Node.module.css rename to src/components/Node/styles.module.css diff --git a/src/engines/wasi/wasi.ts b/src/engines/wasi/wasi.ts index a016f26..86c8733 100644 --- a/src/engines/wasi/wasi.ts +++ b/src/engines/wasi/wasi.ts @@ -37,7 +37,7 @@ async function createRuby(setStdout: TSetString, setStderr: TSetString) { await ruby.setInstance(instance); wasi.initialize(instance as any); - ruby.initialize(["ruby.wasm", "-e_=0", `-I${rubyStubsPath}`, "-rrubygems_stub", "-rthread_stub", "-rbundler_stub"]); + ruby.initialize(["ruby.wasm", "-e_=0", `-I${rubyStubsPath}`]); return ruby; }