From 94dda7dd3ade5732b8837ebb885fdef257c33db1 Mon Sep 17 00:00:00 2001 From: Svyatoslav Kryukov Date: Wed, 6 Mar 2024 23:41:44 +0300 Subject: [PATCH] Use gists as a backend for scratches --- .env.production | 1 + README.md | 1 + package-lock.json | 474 +++++++++++++++++++++++- package.json | 7 +- public/404.html | 7 +- src/components/App/App.tsx | 11 + src/components/Editor/Editor.tsx | 46 +-- src/components/Header/Header.module.css | 185 ++++++++- src/components/Header/Header.tsx | 86 ++++- src/components/Menu/Menu.module.css | 48 +++ src/components/Menu/Menu.tsx | 55 ++- src/components/Node/Node.module.css | 14 +- src/components/Node/Node.tsx | 5 +- src/components/Output/InfoTab.tsx | 3 + src/components/Output/Output.tsx | 1 - src/downloadZip.ts | 18 +- src/engines/wasi/editorFS.ts | 178 +++++---- src/engines/wasi/wasi.ts | 16 +- src/engines/wasi/wasiImports.ts | 8 +- src/fsInitializer.ts | 65 ++++ src/fsMap.ts | 27 +- src/gist.ts | 27 +- src/stores/editor.ts | 26 +- src/stores/gists.ts | 105 ++++++ src/stores/oauth.ts | 58 +++ src/stores/output.ts | 4 +- src/stubs/bundler_stub.rb | 2 +- src/stubs/js/connection.rb | 2 +- src/useComponentVisible.ts | 21 ++ vite.config.ts | 3 +- 30 files changed, 1294 insertions(+), 210 deletions(-) create mode 100644 .env.production create mode 100644 src/fsInitializer.ts create mode 100644 src/stores/gists.ts create mode 100644 src/stores/oauth.ts create mode 100644 src/useComponentVisible.ts diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..6c93fc6 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +VITE_GITHUB_CLIENT_ID=6211bec51d65250aee37 diff --git a/README.md b/README.md index 4d858be..bf14244 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [RunRuby.dev](https://runruby.dev) brings Ruby programming into your browser, streamlining the process of writing and running Ruby code. Here's what sets it apart: - **Simple Editing**: Easily create, rename, and manage text files and directories directly in your browser. +- **Gist integration**: Save your work to a GitHub Gist and share it with others. - **Bundler Support**: Add a `Gemfile` to your project and click "Bundle Install". The absence of native networking in WASI is not a problem. - **Networking**: For networking, try using `Faraday` with the `Faraday::Adapter::JS` adapter to make web requests from Ruby.wasm. - **Quick start**: Access gems or gists quickly with `runruby.dev/{gem_name}` and `runruby.dev/gist/{id}` URLs. diff --git a/package-lock.json b/package-lock.json index d09186b..f54f309 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ }, "devDependencies": { "@types/file-saver": "^2.0.7", + "@types/node": "^20.11.24", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^6.19.0", @@ -40,6 +41,7 @@ "sass": "^1.70.0", "typescript": "^5.3.3", "vite": "^5.0.12", + "vite-plugin-mkcert": "^1.17.3", "vite-plugin-svgr": "^4.2.0" }, "optionalDependencies": { @@ -701,6 +703,161 @@ "node": ">= 8" } }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "dev": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.1.0.tgz", + "integrity": "sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==", + "dev": true, + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.0.0", + "@octokit/request": "^8.0.2", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.4.tgz", + "integrity": "sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", + "integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", + "dev": true, + "dependencies": { + "@octokit/request": "^8.0.1", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz", + "integrity": "sha512-6G+ywGClliGQwRsjvqVYpklIfa7oRPA0vyhPQG/1Feh+B+wU0vGH1JiJ5T25d3g1JZYBHzR2qefLi9x8Gt+cpw==", + "dev": true + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.1.5.tgz", + "integrity": "sha512-WKTQXxK+bu49qzwv4qKbMMRXej1DU2gq017euWyKVudA6MldaSSQuxtz+vGbhxV4CjxpUxjZu6rM2wfc1FiWVg==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.4.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz", + "integrity": "sha512-2uJI1COtYCq8Z4yNSnM231TgH50bRkheQ9+aH8TnZanB6QilOnx8RMD2qsnamSOXtDj0ilxvevf5fGsBhBBzKA==", + "dev": true, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.3.0.tgz", + "integrity": "sha512-c/fjpoHispRvBZuRoTVt/uALg7pXa9RQbXWJiDMk6NDkGNomuAZG7YuYYpZoxeoXv+kVRjIDTsO0e1z0pei+PQ==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.4.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" + } + }, + "node_modules/@octokit/request": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.2.0.tgz", + "integrity": "sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==", + "dev": true, + "dependencies": { + "@octokit/endpoint": "^9.0.0", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", + "integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest": { + "version": "20.0.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.2.tgz", + "integrity": "sha512-Ux8NDgEraQ/DMAU1PlAohyfBBXDwhnX2j33Z1nJNziqAfHi70PuxkFYIcIt8aIAxtRE7KVuKp8lSR8pA0J5iOQ==", + "dev": true, + "dependencies": { + "@octokit/core": "^5.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-request-log": "^4.0.0", + "@octokit/plugin-rest-endpoint-methods": "^10.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.5.0.tgz", + "integrity": "sha512-YJEKcb0KkJlIUNU/zjnZwHEP8AoVh/OoIcP/1IyR4UHxExz7fzpe/a8IG4wBtQi7QDEqiomVLX88S6FpxxAJtg==", + "dev": true, + "dependencies": { + "@octokit/openapi-types": "^19.1.0" + } + }, "node_modules/@react-dnd/asap": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz", @@ -1052,9 +1209,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.10", - "license": "MIT", - "peer": true, + "version": "20.11.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", + "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", "dependencies": { "undici-types": "~5.26.4" } @@ -1503,6 +1660,23 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1517,6 +1691,12 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1772,6 +1952,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1947,6 +2139,15 @@ "node": ">= 0.4" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1956,6 +2157,12 @@ "node": ">= 0.8" } }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -2696,7 +2903,6 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], - "peer": true, "engines": { "node": ">=4.0" }, @@ -2706,6 +2912,20 @@ } } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -3467,7 +3687,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "peer": true, "engines": { "node": ">= 0.6" } @@ -3476,7 +3695,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -3860,6 +4078,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4650,14 +4874,19 @@ }, "node_modules/undici-types": { "version": "5.26.5", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/unfetch": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -4799,6 +5028,24 @@ } } }, + "node_modules/vite-plugin-mkcert": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/vite-plugin-mkcert/-/vite-plugin-mkcert-1.17.3.tgz", + "integrity": "sha512-C8iLz4Su4kEyJb1GdlGIMfKkYMqfH5qvhqVP1x/hrc+1mo9GOzqU88mevvP5epgoqawkhVpcQVQ+HTFXsRuC0g==", + "dev": true, + "dependencies": { + "@octokit/rest": "^20.0.2", + "axios": "^1.6.5", + "debug": "^4.3.4", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=v16.7.0" + }, + "peerDependencies": { + "vite": ">=3" + } + }, "node_modules/vite-plugin-svgr": { "version": "4.2.0", "dev": true, @@ -5441,6 +5688,123 @@ "fastq": "^1.6.0" } }, + "@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "dev": true + }, + "@octokit/core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.1.0.tgz", + "integrity": "sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==", + "dev": true, + "requires": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.0.0", + "@octokit/request": "^8.0.2", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.4.tgz", + "integrity": "sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==", + "dev": true, + "requires": { + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/graphql": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", + "integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", + "dev": true, + "requires": { + "@octokit/request": "^8.0.1", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz", + "integrity": "sha512-6G+ywGClliGQwRsjvqVYpklIfa7oRPA0vyhPQG/1Feh+B+wU0vGH1JiJ5T25d3g1JZYBHzR2qefLi9x8Gt+cpw==", + "dev": true + }, + "@octokit/plugin-paginate-rest": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.1.5.tgz", + "integrity": "sha512-WKTQXxK+bu49qzwv4qKbMMRXej1DU2gq017euWyKVudA6MldaSSQuxtz+vGbhxV4CjxpUxjZu6rM2wfc1FiWVg==", + "dev": true, + "requires": { + "@octokit/types": "^12.4.0" + } + }, + "@octokit/plugin-request-log": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz", + "integrity": "sha512-2uJI1COtYCq8Z4yNSnM231TgH50bRkheQ9+aH8TnZanB6QilOnx8RMD2qsnamSOXtDj0ilxvevf5fGsBhBBzKA==", + "dev": true, + "requires": {} + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.3.0.tgz", + "integrity": "sha512-c/fjpoHispRvBZuRoTVt/uALg7pXa9RQbXWJiDMk6NDkGNomuAZG7YuYYpZoxeoXv+kVRjIDTsO0e1z0pei+PQ==", + "dev": true, + "requires": { + "@octokit/types": "^12.4.0" + } + }, + "@octokit/request": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.2.0.tgz", + "integrity": "sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==", + "dev": true, + "requires": { + "@octokit/endpoint": "^9.0.0", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/request-error": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", + "integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", + "dev": true, + "requires": { + "@octokit/types": "^12.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/rest": { + "version": "20.0.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.2.tgz", + "integrity": "sha512-Ux8NDgEraQ/DMAU1PlAohyfBBXDwhnX2j33Z1nJNziqAfHi70PuxkFYIcIt8aIAxtRE7KVuKp8lSR8pA0J5iOQ==", + "dev": true, + "requires": { + "@octokit/core": "^5.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-request-log": "^4.0.0", + "@octokit/plugin-rest-endpoint-methods": "^10.0.0" + } + }, + "@octokit/types": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.5.0.tgz", + "integrity": "sha512-YJEKcb0KkJlIUNU/zjnZwHEP8AoVh/OoIcP/1IyR4UHxExz7fzpe/a8IG4wBtQi7QDEqiomVLX88S6FpxxAJtg==", + "dev": true, + "requires": { + "@octokit/openapi-types": "^19.1.0" + } + }, "@react-dnd/asap": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz", @@ -5642,8 +6006,9 @@ "dev": true }, "@types/node": { - "version": "20.11.10", - "peer": true, + "version": "20.11.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", + "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", "requires": { "undici-types": "~5.26.4" } @@ -5941,6 +6306,23 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "dev": true, + "requires": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5952,6 +6334,12 @@ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "peer": true }, + "before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -6124,6 +6512,15 @@ "version": "1.1.3", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6254,12 +6651,24 @@ "has-property-descriptors": "^1.0.1" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "peer": true }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true + }, "destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -6830,8 +7239,18 @@ "follow-redirects": { "version": "1.15.5", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", - "peer": true + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } }, "fs-extra": { "version": "8.1.0", @@ -7411,14 +7830,12 @@ "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "peer": true + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "peer": true, "requires": { "mime-db": "1.52.0" } @@ -7655,6 +8072,12 @@ "react-is": "^16.13.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -8190,14 +8613,19 @@ "peer": true }, "undici-types": { - "version": "5.26.5", - "peer": true + "version": "5.26.5" }, "unfetch": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" }, + "universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -8260,6 +8688,18 @@ "rollup": "^4.2.0" } }, + "vite-plugin-mkcert": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/vite-plugin-mkcert/-/vite-plugin-mkcert-1.17.3.tgz", + "integrity": "sha512-C8iLz4Su4kEyJb1GdlGIMfKkYMqfH5qvhqVP1x/hrc+1mo9GOzqU88mevvP5epgoqawkhVpcQVQ+HTFXsRuC0g==", + "dev": true, + "requires": { + "@octokit/rest": "^20.0.2", + "axios": "^1.6.5", + "debug": "^4.3.4", + "picocolors": "^1.0.0" + } + }, "vite-plugin-svgr": { "version": "4.2.0", "dev": true, diff --git a/package.json b/package.json index 01471be..d2d0f34 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,10 @@ "name": "ruby-wasi-playground", "private": true, "type": "module", - "version": "0.0.0", + "version": "0.1.0", + "description": "RunRuby.dev WASI playground", + "author": "Svyatoslav Kryukov ", + "license": "MIT", "scripts": { "dev": "vite", "build": "tsc && vite build", @@ -31,6 +34,7 @@ }, "devDependencies": { "@types/file-saver": "^2.0.7", + "@types/node": "^20.11.24", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^6.19.0", @@ -42,6 +46,7 @@ "sass": "^1.70.0", "typescript": "^5.3.3", "vite": "^5.0.12", + "vite-plugin-mkcert": "^1.17.3", "vite-plugin-svgr": "^4.2.0" }, "optionalDependencies": { diff --git a/public/404.html b/public/404.html index 75ccf4d..4ae91a2 100644 --- a/public/404.html +++ b/public/404.html @@ -28,7 +28,12 @@ const host = l.protocol + "//" + l.hostname + (l.port ? ":" + l.port : ""); let search = (l.search ? l.search.slice(1) : ""); const paths = l.pathname.slice(1).split("/"); - if (paths[0] === "gist") { + if (paths[0] === "callback" || paths[0] === "404.html") { + const bc = new BroadcastChannel("oauth") + bc.postMessage(search); + bc.close(); + window.close(); + } else if (paths[0] === "gist") { search = "gist=" + paths[1] + (search ? "&" + search : ""); } else if (paths[0]) { search = "gem=" + paths[0] + (search ? "&" + search : ""); diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index ea4ad59..a6b94cd 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -1,9 +1,20 @@ +import { useEffect } from "react"; + import { Content } from "../Content/Content.tsx"; import { Header } from "../Header/Header.tsx"; import cs from "./App.module.css"; export default function App() { + useEffect(() => { + const onPopState = () => location.reload(); + + window.addEventListener("popstate", onPopState); + return () => { + window.removeEventListener("popstate", onPopState); + }; + }, []); + return (
diff --git a/src/components/Editor/Editor.tsx b/src/components/Editor/Editor.tsx index 8378a8a..5885e67 100644 --- a/src/components/Editor/Editor.tsx +++ b/src/components/Editor/Editor.tsx @@ -1,16 +1,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { Directory, File } from "@bjorn3/browser_wasi_shim"; import MonacoEditor from "@monaco-editor/react"; import { useStore } from "@nanostores/react"; +import { VscLoading } from "react-icons/vsc"; -import { - decode, embedFromURI, - encode, - gemFromURI, - gistFromURI, - workDir -} from "../../engines/wasi/editorFS.ts"; -import importFromGist from "../../gist.ts"; +import { decode, writeFile } from "../../engines/wasi/editorFS.ts"; import { RunVMParams } from "../../useVM.ts"; import { db } from "../../db.ts"; import { bundleDir, gemsDir } from "../../engines/wasi/wasi.ts"; @@ -24,9 +17,9 @@ import { setCode } from "../../stores/editor.ts"; import { useEditorTheme } from "../../useEditorTheme.ts"; +import { getQueryParam } from "../../fsInitializer.ts"; import cs from "./Editor.module.css"; -import { VscLoading } from "react-icons/vsc"; type EditorProps = { loading: boolean; @@ -127,42 +120,19 @@ export const Editor = ({ }, [currentFile]); useEffect(() => { - const gist = gistFromURI(); - if (gist) { - importFromGist(gist).then(({ files }) => { - files.forEach(({ filename, content }) => { - const parts = filename.split("/"); - const name = parts.pop() as string; - let dir = workDir.dir; - parts.forEach((part) => { - if (!dir.contents[part]) { - dir.contents[part] = new Directory({}); - } - dir = dir.contents[part] as Directory; - }); - workDir.dir.contents[name] = new File(encode(content)); - }); - - refreshTreeData(); - setTimeout(() => activateFirstFile(), 20); - }); - } - }, [activateFirstFile]); - - useEffect(() => { - if (editorInitializing) { + if (editorInitializing && treeData) { activateFirstFile(); } else if (!bundleInstalled.current) { - ((gemFromURI() || gistFromURI()) && !embedFromURI()) && canRunBundleInstall && bundleInstall(); + ((getQueryParam("gem") || getQueryParam("gist")) && !getQueryParam("embed")) && canRunBundleInstall && bundleInstall(); bundleInstalled.current = true; } - }, [activateFirstFile, bundleInstall, canRunBundleInstall, editorInitializing]); + }, [treeData, activateFirstFile, bundleInstall, canRunBundleInstall, editorInitializing]); const handleEditorChange = (value: string | undefined) => { setCode(value || ""); - if (currentFile) { - currentFile.data = encode(value || ""); + if (currentFilePath) { + writeFile(currentFilePath, value || "") } }; diff --git a/src/components/Header/Header.module.css b/src/components/Header/Header.module.css index 3bb6299..883129f 100644 --- a/src/components/Header/Header.module.css +++ b/src/components/Header/Header.module.css @@ -31,29 +31,31 @@ } } -.logoContainer { - display: flex; - align-items: center; - gap: 8px; - margin-left: 16px +.gistLoading { + margin: 0 16px; + display: inline-flex; + vertical-align: middle; } -@media (max-width: 1280px) { - .logoContainer { - margin-left: 8px - } +.gistLoading svg { + animation: spin 1s linear infinite; } -@media (max-width: 860px) { - .logoContainer { - margin: 0; - flex: 1; - /* put content in center*/ - justify-content: center; - +@keyframes spin { + 100% { + transform: rotate(360deg); } } +.logoContainer { + display: flex; + align-items: center; + gap: 8px; + margin: 0; + flex: 1; + justify-content: center; +} + .logo { width: 40px; height: 40px; @@ -78,10 +80,18 @@ } } +@media (max-width: 860px) { + .title { + display: none; + } +} + .links { display: flex; align-items: center; margin: 0 8px 0 auto; + flex: 1; + justify-content: flex-end; } .link { @@ -96,3 +106,146 @@ .link:hover { color: var(--link-hover-color); } + +.buttons { + display: flex; + align-items: center; + gap: 8px; + justify-content: flex-start; + padding-left: 16px; + flex: 1; +} + +@media (max-width: 1280px) { + .buttons { + padding-left: 8px; + } +} + +.forkButton { + background-color: var(--secondary-color); + border: 1px solid var(--secondary-color); + color: var(--font-color); + font-weight: bold; + padding: 8px 16px; + text-align: center; + text-decoration: none; + font-size: 12px; + cursor: default; + border-radius: 8px; + display: inline-flex; + gap: 8px; + align-items: center; +} + +.forkButton:hover { + background-color: var(--secondary-hover-color); + border: 1px solid var(--secondary-hover-color); + color: var(--font-color); +} + +.saveButton { + background-color: var(--primary-color); + border: 1px solid var(--primary-color); + color: var(--font-color); + font-weight: bold; + padding: 8px 16px; + text-align: center; + text-decoration: none; + font-size: 12px; + cursor: default; + border-radius: 8px; + display: inline-flex; + gap: 8px; + align-items: center; +} + +.saveButton:hover { + background-color: var(--primary-hover-color); + border: 1px solid var(--primary-hover-color); + color: var(--font-color); +} + +.disabledButton { + background-color: var(--tag-color) !important; + border: 1px solid var(--tag-color)!important; +} + +.SignInButton { + background-color: var(--primary-color); + border: 1px solid var(--primary-color); + color: var(--font-color); + font-weight: bold; + padding: 8px 16px; + text-align: center; + text-decoration: none; + font-size: 12px; + cursor: default; + border-radius: 8px; + display: inline-flex; + gap: 8px; + align-items: center; +} + +.SignInButton:hover { + background-color: var(--primary-hover-color); +} + +.userContainer { + display: flex; + align-items: center; + gap: 8px; + margin: 0; + padding: 0; + justify-content: flex-end; + position: relative; +} + +.avatar { + width: 24px; + height: 24px; + border-radius: 50%; +} + +.userMenu { + display: none; + position: absolute; + top: 100%; + right: 0; + background-color: var(--bg-color); + border: 1px solid var(--tag-color); + border-radius: 8px; + padding: 8px 16px; + z-index: 100; +} + +.userMenu.show { + display: block; +} + +.signOutButton { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + border: none; + padding: 8px; + border-radius: 16px; + margin: 0 0 0 8px; + color: var(--menu-icon-color); + background-color: transparent; + white-space: nowrap; +} + +.userMenuButton { + display: flex; + align-items: center; + gap: 8px; + font-size: 16px; + border: none; + padding: 8px; + border-radius: 16px; + margin: 0 0 0 8px; + color: var(--menu-icon-color); + background-color: transparent; +} diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 2acdf86..0af9c55 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,10 +1,15 @@ -import { VscClose, VscMenu } from "react-icons/vsc"; +import { VscClose, VscGithub, VscLoading, VscMenu, VscRepoForked, VscSave } from "react-icons/vsc"; +import { TbMoon, TbSun, TbSunMoon } from "react-icons/tb"; import { useStore } from "@nanostores/react"; import { $menu, toggleMenu } from "../../stores/menu.ts"; -import cs from "./Header.module.css"; -import { TbMoon, TbSun, TbSunMoon } from "react-icons/tb"; import { $theme, toggleTheme } from "../../stores/theme.ts"; +import { $currentUser, $oauth, signOut } from "../../stores/oauth.ts"; +import { $gist, $gistLoading, forkGist, saveGist, updateGist } from "../../stores/gists.ts"; +import { $editor } from "../../stores/editor.ts"; +import useComponentVisible from "../../useComponentVisible.ts"; + +import cs from "./Header.module.css"; const themeIcons = { ["light"]: , @@ -15,12 +20,60 @@ const themeIcons = { export const Header = () => { const { isOpen } = useStore($menu); const theme = useStore($theme); + const { state } = useStore($oauth); + const openedGist = useStore($gist); + const currentUser = useStore($currentUser); + const gistLoading = useStore($gistLoading); + const dirtyFiles = useStore($editor).dirtyFiles; + const showFork = openedGist.id && (!currentUser.id || currentUser.id && openedGist.username !== currentUser?.username); + const canFork = openedGist.id && currentUser.id && !gistLoading; + const showSave = openedGist.id === undefined; + const canSave = currentUser.id && openedGist.id === undefined && !gistLoading; + const showUpdate = openedGist.id && currentUser.id && openedGist.username === currentUser.username; + const canUpdate = dirtyFiles.length > 0 && !gistLoading; + + const { ref, isComponentVisible, setIsComponentVisible } = useComponentVisible(false); return (
+
+ {showFork && ( + + )} + {(showSave) && ( + + )} + {showUpdate && ( + + )} +
+ {gistLoading && } +
+
+
RunRuby.dev

RunRuby.dev

@@ -30,6 +83,33 @@ export const Header = () => { {themeIcons[theme]} + {currentUser.id ? ( +
setIsComponentVisible(true)} + ref={ref} + > + {currentUser.username} +
+ +
+
+ ) : ( + + )}
); diff --git a/src/components/Menu/Menu.module.css b/src/components/Menu/Menu.module.css index 60a6332..5551548 100644 --- a/src/components/Menu/Menu.module.css +++ b/src/components/Menu/Menu.module.css @@ -27,3 +27,51 @@ color: var(--tree-icon-hover-color); border-radius: 8px; } + +.gistInfo { + padding-left: 16px; + margin-bottom: 16px; + border-bottom: 1px solid var(--border-color); +} + +.gistLabel { + font-size: 14px; + font-weight: bold; + color: var(--font-color); + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.gistLink { + font-size: 12px; + font-weight: normal; + float: right; + display: flex; + align-items: center; + gap: 8px; +} + +.userInfo { + display: flex; + align-items: center; + padding: 8px 0 0; + font-size: 12px; +} + +.avatar { + width: 24px; + height: 24px; + border-radius: 50%; + margin-right: 8px; +} + +.gistDescription { + display: block; + font-size: 12px; + color: var(--font-color); + opacity: 65%; + padding: 8px 0; +} + diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 8c62307..d2dd3e2 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -1,12 +1,13 @@ import { useCallback, useRef } from "react"; import { CreateHandler, DeleteHandler, NodeApi, RenameHandler, Tree, TreeApi } from "react-arborist"; -import { Directory, File } from "@bjorn3/browser_wasi_shim"; +import { Directory } from "@bjorn3/browser_wasi_shim"; import { nanoid } from "nanoid"; -import { VscFileZip, VscNewFile, VscNewFolder } from "react-icons/vsc"; +import { VscFileZip, VscLinkExternal, VscNewFile, VscNewFolder } from "react-icons/vsc"; +import { useStore } from "@nanostores/react"; -import { encode, workDir } from "../../engines/wasi/editorFS.ts"; +import { mkdir, rename, rm, writeFile } from "../../engines/wasi/editorFS.ts"; import { Node } from "../Node/Node.tsx"; -import { Entity, getPath, idsMap, sortChildren } from "../../fsMap.ts"; +import { Entity, idsMap, sortChildren } from "../../fsMap.ts"; import cs from "../Menu/Menu.module.css"; import { $editor, @@ -16,22 +17,18 @@ import { setCurrentNodeId, setTree } from "../../stores/editor.ts"; +import { $gist } from "../../stores/gists.ts"; import { downloadZip } from "../../downloadZip.ts"; -import { useStore } from "@nanostores/react"; export const Menu = () => { const { treeData, tree } = useStore($editor); const currentFilePath = useStore(currentFilePathStore); const onRename: RenameHandler = ({ name, node }) => { - const parent = (node.parent == null || node.parent.isRoot) ? workDir.dir : node.parent.data.object as Directory; + const oldPath = node.data.fullPath; + const newPath = oldPath.replace(new RegExp(`${node.data.name}$`), name); - if (node && node.data.name !== name) { - if (parent.contents[name] !== undefined) { - throw new Error(`File or directory with name ${name} already exists`); - } - parent.contents[name] = node.data.object; - delete parent.contents[node.data.name]; + if (rename(oldPath, newPath)) { refreshTreeData(); setTimeout(() => { @@ -41,10 +38,10 @@ export const Menu = () => { }; const onCreate: CreateHandler = ({ parentNode, type }) => { - const parent = (parentNode?.data?.object || workDir.dir) as Directory; - const object = (type === "leaf") ? new File(encode("")) : new Directory({}); const name = (type === "leaf") ? `new_file_${Date.now()}.rb` : `new_dir_${Date.now()}`; - parent.contents[name] = object; + const path = `${parentNode ? parentNode.data.fullPath : ""}/${name}`; + const object = (type === "leaf") ? writeFile(path, "") : mkdir(path); + const id = nanoid(); idsMap.set(object, id); refreshTreeData(); @@ -55,9 +52,8 @@ export const Menu = () => { ids.forEach((id) => { const node = tree?.get(id); if (node) { - 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)) { + rm(node.data.fullPath); + if (currentFilePath === node.data.fullPath) { setCurrentNodeId(null); } } @@ -72,10 +68,29 @@ export const Menu = () => { setTree(tree); }, []); + const gist = useStore($gist); + return ( <> + {gist.id && ( +
+
+ Gist info + open + gist +
+
+ {gist.username} + {gist.username} +
+ {gist.description && (

+ {gist.description} +

)} +
+ )} +
- +
Files