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}
-
-
- ))}
-
- );
-};
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;
}