Merge branch 'feature/playground'
This commit is contained in:
commit
b3677165ce
|
@ -20,12 +20,20 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout your repository using git
|
||||
uses: actions/checkout@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: "webcompiler"
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
- name: Install wasm-pack-cli
|
||||
uses: jetli/wasm-pack-action@v0.2.0
|
||||
- name: Install, build, and upload your site output
|
||||
uses: withastro/action@v2
|
||||
with:
|
||||
# path: . # The root location of your Astro project inside the repository. (optional)
|
||||
# node-version: 18 # The specific version of Node that should be used to build your site. Defaults to 18. (optional)
|
||||
package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional)
|
||||
package-manager: pnpm@latest
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
|
|
|
@ -16,7 +16,10 @@ pnpm-debug.log*
|
|||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
secrets.env
|
||||
*.env
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# wasm build files
|
||||
/target_rust
|
14
README.md
14
README.md
|
@ -2,17 +2,29 @@
|
|||
This is the documentation for Shulkerscript. It is a work in progress and will be updated as the language evolves.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This documentation is created using Astro and Starlight. To get started, you need to install the dependencies and start the development server.
|
||||
|
||||
## Requirements
|
||||
|
||||
Required tools:
|
||||
|
||||
- [Node.js](https://nodejs.org)
|
||||
- [pnpm](https://pnpm.io)
|
||||
- [Cargo](https://rustup.rs)
|
||||
- with `wasm32-unknown-unknown` target installed
|
||||
- [`wasm-bindgen-cli`](https://crates.io/crates/wasm-bindgen-cli)
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| :------------------------- | :----------------------------------------------- |
|
||||
| `pnpm install` | Installs dependencies |
|
||||
| `pnpm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `pnpm run build` | Build your production site to `./dist/` |
|
||||
| `pnpm run build-wasm` | Build your wasm modules |
|
||||
| `pnpm run preview` | Preview your build locally, before deploying |
|
||||
| `pnpm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `pnpm run astro -- --help` | Get help using the Astro CLI |
|
|
@ -1,12 +1,25 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import starlight from '@astrojs/starlight';
|
||||
import starlightLinksValidator from "starlight-links-validator";
|
||||
import shikiConfig from './src/utils/shiki';
|
||||
import { defineConfig } from "astro/config";
|
||||
import starlight from "@astrojs/starlight";
|
||||
import react from "@astrojs/react";
|
||||
// import starlightLinksValidator from "starlight-links-validator";
|
||||
import starlightUtils from "@lorenzo_lewis/starlight-utils";
|
||||
import wasm from "vite-plugin-wasm";
|
||||
|
||||
import shikiConfig from "./src/utils/shiki";
|
||||
|
||||
const playgroundSidebarEntry = {
|
||||
label: 'Playground',
|
||||
link: '/playground',
|
||||
translations: { de: 'Spielplatz' },
|
||||
};
|
||||
|
||||
const navLinks = [playgroundSidebarEntry];
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: "https://shulkerscript.hoelting.dev",
|
||||
integrations: [
|
||||
react(),
|
||||
starlight({
|
||||
title: 'Shulkerscript',
|
||||
logo: {
|
||||
|
@ -34,16 +47,31 @@ export default defineConfig({
|
|||
baseUrl: 'https://github.com/moritz-hoelting/shulkerscript-webpage/edit/main',
|
||||
},
|
||||
customCss: ['./src/styles/style.css'],
|
||||
plugins: [starlightLinksValidator({
|
||||
errorOnFallbackPages: false,
|
||||
})],
|
||||
plugins: [starlightUtils({
|
||||
navLinks: {
|
||||
leading: {
|
||||
useSidebarLabelled: "leadingNavLinks",
|
||||
},
|
||||
},
|
||||
}),
|
||||
// starlightLinksValidator({
|
||||
// errorOnFallbackPages: false,
|
||||
// })
|
||||
],
|
||||
expressiveCode: {
|
||||
shiki: shikiConfig,
|
||||
},
|
||||
components: {
|
||||
PageTitle: "./src/components/override/PageTitle.astro",
|
||||
ContentPanel: "./src/components/override/ContentPanel.astro",
|
||||
Pagination: "./src/components/override/Pagination.astro",
|
||||
SocialIcons: './src/components/override/SocialIcons.astro',
|
||||
},
|
||||
sidebar: [
|
||||
{
|
||||
label: "leadingNavLinks",
|
||||
items: navLinks,
|
||||
},
|
||||
{
|
||||
label: 'Guides',
|
||||
autogenerate: {
|
||||
|
@ -53,6 +81,19 @@ export default defineConfig({
|
|||
de: 'Anleitungen',
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "More",
|
||||
translations: {
|
||||
de: "Mehr",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
label: "Roadmap",
|
||||
link: "/roadmap",
|
||||
translations: {
|
||||
de: "Zukunftspläne",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Differences to other languages',
|
||||
link: '/differences',
|
||||
|
@ -61,11 +102,13 @@ export default defineConfig({
|
|||
},
|
||||
},
|
||||
{
|
||||
label: 'Roadmap',
|
||||
link: '/roadmap',
|
||||
translations: {
|
||||
de: 'Zukunftspläne',
|
||||
...playgroundSidebarEntry,
|
||||
badge: {
|
||||
text: "WIP",
|
||||
variant: "caution",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Reference',
|
||||
|
@ -84,4 +127,9 @@ export default defineConfig({
|
|||
]
|
||||
}),
|
||||
],
|
||||
vite: {
|
||||
plugins: [
|
||||
wasm(),
|
||||
],
|
||||
}
|
||||
});
|
||||
|
|
30
package.json
30
package.json
|
@ -3,20 +3,40 @@
|
|||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro check && astro build",
|
||||
"dev": "pnpm build-wasm && astro dev",
|
||||
"start": "pnpm build-wasm && astro dev",
|
||||
"build-wasm": "wasm-pack build --release --no-pack ./src/wasm/webcompiler -- --features wee_alloc",
|
||||
"build": "pnpm build-wasm && astro check && astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.3",
|
||||
"@astrojs/react": "^3.6.0",
|
||||
"@astrojs/starlight": "^0.28.2",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@lorenzo_lewis/starlight-utils": "^0.1.1",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@mui/icons-material": "^6.1.1",
|
||||
"@mui/material": "^6.1.1",
|
||||
"@shikijs/monaco": "^1.7.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"astro": "^4.15.9",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.2.1",
|
||||
"sharp": "^0.33.5",
|
||||
"shiki": "^1.14.1",
|
||||
"shiki": "^1.7.0",
|
||||
"starlight-links-validator": "^0.12.1",
|
||||
"typescript": "^5.4.5"
|
||||
"tm-themes": "^1.4.3",
|
||||
"typescript": "^5.4.5",
|
||||
"use-immer": "^0.10.0",
|
||||
"vite-plugin-wasm": "^3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sass": "^1.77.6"
|
||||
},
|
||||
"packageManager": "pnpm@9.7.0+"
|
||||
}
|
1594
pnpm-lock.yaml
1594
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,7 @@
|
|||
namespace "my-shulkerscript-pack";
|
||||
|
||||
#[tick]
|
||||
fn main() {
|
||||
// Change this
|
||||
/say Hello World!
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
[pack]
|
||||
name = "my-shulkerscript-pack"
|
||||
description = "A Minecraft datapack created with shulkerscript"
|
||||
format = 48
|
||||
version = "0.1.0"
|
|
@ -0,0 +1,493 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useMonaco, type Monaco } from "@monaco-editor/react";
|
||||
import { useImmer, type Updater } from "use-immer";
|
||||
import ThemeProvider from "@mui/material/styles/ThemeProvider";
|
||||
import ErrorDisplay from "./playground/ErrorDisplay";
|
||||
import FileView from "./playground/FileView";
|
||||
import Editor from "./playground/Editor";
|
||||
import Header from "./playground/Header";
|
||||
|
||||
import {
|
||||
compile,
|
||||
compileZip,
|
||||
} from "@wasm/webcompiler/pkg/webcompiler";
|
||||
import type { Directory, File, PlaygroundLang } from "@utils/playground";
|
||||
import { customTheme } from "@utils/material-ui-theme";
|
||||
|
||||
import "@styles/playground.scss";
|
||||
|
||||
import mainFileContent from "@assets/playground/main.shu?raw";
|
||||
import packTomlContent from "@assets/playground/pack.toml?raw";
|
||||
|
||||
const FILE_STORAGE_KEY = "playground-files";
|
||||
const DEFAULT_FILES = {
|
||||
dirs: {
|
||||
src: {
|
||||
files: {
|
||||
"main.shu": {
|
||||
content: mainFileContent,
|
||||
language: "shulkerscript",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
files: {
|
||||
"pack.toml": {
|
||||
content: packTomlContent,
|
||||
language: "toml",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function Playground({ lang }: { lang: PlaygroundLang }) {
|
||||
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
(window as any).playground = {
|
||||
showError: (message: string) => {
|
||||
if (message.length > 0) {
|
||||
setErrorMsg(message);
|
||||
}
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [rootDir, updateRootDir] = useImmer(
|
||||
getStorageOrDefault(FILE_STORAGE_KEY, DEFAULT_FILES) as Directory
|
||||
);
|
||||
|
||||
const [theme, setTheme] = useState<"light" | "dark">("dark");
|
||||
const [fileName, setFileName] = useState("src/main.shu");
|
||||
const file = getFile(rootDir, fileName);
|
||||
|
||||
const onBuild = () => {
|
||||
if (monaco) {
|
||||
const compiled = compile(getFiles(monaco));
|
||||
if (compiled) {
|
||||
const dist = JSON.parse(JSON.stringify(compiled, jsonReplacer));
|
||||
const withRoot = {
|
||||
dirs: {
|
||||
dist: dist,
|
||||
},
|
||||
} as Directory;
|
||||
loadFiles(monaco, updateRootDir, withRoot);
|
||||
}
|
||||
} else {
|
||||
console.error("monaco has not loaded");
|
||||
}
|
||||
};
|
||||
const onZip = () => {
|
||||
if (monaco) {
|
||||
const zipped = compileZip(getFiles(monaco));
|
||||
if (zipped) {
|
||||
const data = "data:application/zip;base64," + zipped;
|
||||
const a = document.createElement("a");
|
||||
a.href = data;
|
||||
a.download = "shulkerscript-pack.zip";
|
||||
a.click();
|
||||
}
|
||||
} else {
|
||||
console.error("monaco has not loaded");
|
||||
}
|
||||
};
|
||||
const onSave = () => {
|
||||
if (monaco) {
|
||||
const currentFiles = getFiles(monaco);
|
||||
updateRootDir((dir) => {
|
||||
dir.dirs = currentFiles.dirs;
|
||||
dir.files = currentFiles.files;
|
||||
});
|
||||
window.localStorage.setItem(
|
||||
FILE_STORAGE_KEY,
|
||||
JSON.stringify(currentFiles)
|
||||
);
|
||||
}
|
||||
};
|
||||
const onReset = () => {
|
||||
if (monaco) {
|
||||
monaco.editor.getModels().forEach((model) => {
|
||||
if (model.uri.path != "/src/main.shu") {
|
||||
model.dispose();
|
||||
} else {
|
||||
model.setValue(mainFileContent);
|
||||
}
|
||||
});
|
||||
|
||||
updateRootDir((dir) => {
|
||||
dir.dirs = DEFAULT_FILES.dirs;
|
||||
dir.files = DEFAULT_FILES.files;
|
||||
});
|
||||
|
||||
loadFiles(monaco, updateRootDir, DEFAULT_FILES);
|
||||
setFileName("src/main.shu");
|
||||
}
|
||||
};
|
||||
|
||||
const monaco = useMonaco();
|
||||
useEffect(() => {
|
||||
if (monaco) {
|
||||
loadFiles(monaco, updateRootDir, rootDir);
|
||||
}
|
||||
}, [monaco]);
|
||||
|
||||
useEffect(() => {
|
||||
if (monaco) {
|
||||
let isReadOnly = fileName.startsWith("dist/");
|
||||
monaco.editor.getEditors().forEach((e) =>
|
||||
e.updateOptions({
|
||||
readOnly: isReadOnly,
|
||||
readOnlyMessage: {
|
||||
value: "Generated files are read-only",
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [fileName]);
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.querySelector(":root") as HTMLElement;
|
||||
if (root) {
|
||||
function reactToThemeChange() {
|
||||
const selectedTheme = root.getAttribute("data-theme");
|
||||
if (selectedTheme !== theme && selectedTheme !== null) {
|
||||
setTheme(selectedTheme as "light" | "dark");
|
||||
}
|
||||
}
|
||||
reactToThemeChange();
|
||||
|
||||
root.onchange = () => {
|
||||
reactToThemeChange();
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={customTheme(theme)}>
|
||||
<main
|
||||
className="playground not-content"
|
||||
style={{
|
||||
maxWidth: "95vw",
|
||||
marginInline: "auto",
|
||||
marginTop: "0.5cm",
|
||||
}}
|
||||
>
|
||||
<ErrorDisplay
|
||||
lang={lang.errorDisplay}
|
||||
error={errorMsg}
|
||||
setError={setErrorMsg}
|
||||
/>
|
||||
<Header
|
||||
lang={lang.header}
|
||||
onSave={onSave}
|
||||
onReset={onReset}
|
||||
onBuild={onBuild}
|
||||
onZip={onZip}
|
||||
/>
|
||||
<FileView
|
||||
className="file-view"
|
||||
root={rootDir}
|
||||
selectedFileName={fileName}
|
||||
setSelectedFileName={setFileName}
|
||||
addFile={(name) => {
|
||||
if (monaco) {
|
||||
loadFile(
|
||||
monaco,
|
||||
updateRootDir,
|
||||
{ content: "" },
|
||||
name
|
||||
);
|
||||
}
|
||||
}}
|
||||
deleteFile={(name) => {
|
||||
if (monaco) {
|
||||
if (name.endsWith("/")) {
|
||||
deleteDir(monaco, updateRootDir, name);
|
||||
} else {
|
||||
deleteFile(monaco, updateRootDir, name);
|
||||
if (name === fileName) {
|
||||
const newFile = monaco.editor
|
||||
.getModels()[0]
|
||||
?.uri.path.slice(1);
|
||||
if (newFile) {
|
||||
setFileName(newFile);
|
||||
} else {
|
||||
setFileName("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
renameFile={(oldName, newName) => {
|
||||
if (monaco) {
|
||||
renameFile(monaco, updateRootDir, oldName, newName);
|
||||
if (oldName === fileName) {
|
||||
setFileName(newName);
|
||||
}
|
||||
}
|
||||
}}
|
||||
lang={lang.explorer}
|
||||
/>
|
||||
<Editor
|
||||
fileName={fileName}
|
||||
file={file ?? undefined}
|
||||
theme={theme}
|
||||
/>
|
||||
</main>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function getFiles(monaco: Monaco): Directory {
|
||||
const files: Directory = {};
|
||||
|
||||
for (const model of monaco.editor.getModels()) {
|
||||
const parts = model.uri.path.slice(1).split("/");
|
||||
const name = parts.pop()!;
|
||||
|
||||
let dir = files;
|
||||
for (const part of parts) {
|
||||
if (!dir.dirs) {
|
||||
dir.dirs = {};
|
||||
}
|
||||
if (!dir.dirs[part]) {
|
||||
dir.dirs[part] = {};
|
||||
}
|
||||
|
||||
dir = dir.dirs[part];
|
||||
}
|
||||
|
||||
if (!dir.files) {
|
||||
dir.files = {};
|
||||
}
|
||||
|
||||
dir.files[name] = {
|
||||
content: model.getValue(),
|
||||
language: model.getLanguageId(),
|
||||
};
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function getFile(root: Directory, path: string): File | null {
|
||||
if (path.includes("/")) {
|
||||
let dir = root;
|
||||
const split = path.split("/");
|
||||
let last = split.pop()!;
|
||||
|
||||
for (const dirName of split) {
|
||||
if (dir && dir.dirs) {
|
||||
dir = dir.dirs[dirName];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return dir.files?.[last] ?? null;
|
||||
}
|
||||
|
||||
return root.files?.[path] ?? null;
|
||||
}
|
||||
|
||||
function loadFiles(
|
||||
monaco: Monaco,
|
||||
updater: Updater<Directory>,
|
||||
dir: Directory,
|
||||
prefix = ""
|
||||
) {
|
||||
for (const [name, d] of Object.entries(dir.dirs ?? {})) {
|
||||
loadFiles(monaco, updater, d, prefix + name + "/");
|
||||
updater((dir) => {
|
||||
let current = dir;
|
||||
for (const part of [
|
||||
...prefix.split("/").filter((s) => s !== ""),
|
||||
name,
|
||||
]) {
|
||||
if (!current.dirs) {
|
||||
current.dirs = {};
|
||||
}
|
||||
current = current.dirs[part];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const [name, file] of Object.entries(dir.files ?? {})) {
|
||||
loadFile(monaco, updater, file, prefix + name);
|
||||
}
|
||||
}
|
||||
|
||||
function loadFile(
|
||||
monaco: Monaco,
|
||||
updater: Updater<Directory>,
|
||||
file: File,
|
||||
name: string
|
||||
) {
|
||||
let extension = name.split(".").pop()!;
|
||||
let lang = undefined;
|
||||
if (extension === "shu") {
|
||||
lang = "shulkerscript";
|
||||
} else if (extension === "toml") {
|
||||
lang = "toml";
|
||||
} else if (extension === "mcfunction") {
|
||||
lang = "mcfunction";
|
||||
} else if (extension === "json") {
|
||||
lang = "json";
|
||||
}
|
||||
const uri = monaco.Uri.parse(name);
|
||||
let prevModel = monaco.editor.getModel(uri);
|
||||
if (prevModel) {
|
||||
prevModel.setValue(file.content);
|
||||
} else {
|
||||
monaco.editor.createModel(file.content, lang, uri);
|
||||
}
|
||||
updater((dir) => {
|
||||
if (dir) {
|
||||
let current = dir;
|
||||
const parts = name.split("/").filter((s) => s !== "");
|
||||
const last = parts.pop()!;
|
||||
for (const part of parts) {
|
||||
if (!current.dirs) {
|
||||
current.dirs = {};
|
||||
}
|
||||
if (!current.dirs[part]) {
|
||||
current.dirs[part] = {};
|
||||
}
|
||||
current = current.dirs[part];
|
||||
}
|
||||
if (!current.files) {
|
||||
current.files = {};
|
||||
}
|
||||
current.files[last] = {
|
||||
content: file.content,
|
||||
language: lang,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getStorageOrDefault(key: string, def: any) {
|
||||
const item = window.localStorage.getItem(key);
|
||||
if (item) {
|
||||
return JSON.parse(item);
|
||||
} else {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
function jsonReplacer(_key: any, value: any): any {
|
||||
if (value instanceof Map) {
|
||||
const res: { [key: string]: any } = {};
|
||||
for (const [k, v] of value.entries()) {
|
||||
res[k] = v;
|
||||
}
|
||||
return res;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function deleteFile(monaco: Monaco, updater: Updater<Directory>, name: string) {
|
||||
const uri = monaco.Uri.parse(name);
|
||||
const model = monaco.editor.getModel(uri);
|
||||
if (model) {
|
||||
model.dispose();
|
||||
} else {
|
||||
console.error("Model not found: ", name);
|
||||
}
|
||||
updater((dir) => {
|
||||
let current = dir;
|
||||
const parts = name.split("/").filter((s) => s !== "");
|
||||
const last = parts.pop()!;
|
||||
for (const part of parts) {
|
||||
if (!current.dirs) {
|
||||
current.dirs = {};
|
||||
}
|
||||
if (!current.dirs[part]) {
|
||||
current.dirs[part] = {};
|
||||
}
|
||||
current = current.dirs[part];
|
||||
}
|
||||
if (current.files) {
|
||||
delete current.files[last];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteDir(monaco: Monaco, updater: Updater<Directory>, path: string) {
|
||||
const parts = path.split("/");
|
||||
const firstCheck = parts.at(0);
|
||||
if (firstCheck != undefined && firstCheck == "") {
|
||||
parts.splice(0, 1);
|
||||
}
|
||||
const lastCheck = parts.at(-1);
|
||||
if (lastCheck != undefined && lastCheck == "") {
|
||||
parts.pop();
|
||||
}
|
||||
|
||||
let current = getFiles(monaco) as Directory | null;
|
||||
for (let part of parts) {
|
||||
if (current?.dirs) {
|
||||
current = current.dirs[part];
|
||||
}
|
||||
}
|
||||
if (current) {
|
||||
destroyModels(monaco, path, current);
|
||||
}
|
||||
|
||||
updater((dir) => {
|
||||
const last = parts.pop();
|
||||
if (!last) return;
|
||||
let current = dir;
|
||||
|
||||
for (const part of parts) {
|
||||
if (current.dirs) {
|
||||
current = current.dirs[part];
|
||||
if (!current) return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (current.dirs) {
|
||||
delete current.dirs[last];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function destroyModels(monaco: Monaco, path: string, dir: Directory) {
|
||||
if (!path.endsWith("/")) {
|
||||
path += "/";
|
||||
}
|
||||
if (dir.files) {
|
||||
for (const file of Object.keys(dir.files)) {
|
||||
const filepath = path + file;
|
||||
const uri = monaco.Uri.parse(filepath);
|
||||
const model = monaco.editor.getModel(uri);
|
||||
if (model) {
|
||||
model.dispose();
|
||||
} else {
|
||||
console.error("Model not found: ", filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dir.dirs) {
|
||||
for (const dirname of Object.keys(dir.dirs)) {
|
||||
const dirpath = path + dirname;
|
||||
const subdir = dir.dirs[dirname];
|
||||
destroyModels(monaco, dirpath, subdir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renameFile(
|
||||
monaco: Monaco,
|
||||
updater: Updater<Directory>,
|
||||
oldName: string,
|
||||
newName: string
|
||||
) {
|
||||
const file = getFile(getFiles(monaco), oldName);
|
||||
if (file) {
|
||||
deleteFile(monaco, updater, oldName);
|
||||
loadFile(monaco, updater, file, newName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
import type { Props } from "@astrojs/starlight/props";
|
||||
import Default from "@astrojs/starlight/components/ContentPanel.astro";
|
||||
import { isPlaygroundPage } from '@utils/playground';
|
||||
|
||||
const isPlayground = isPlaygroundPage(Astro.props.slug, Astro.currentLocale);
|
||||
---
|
||||
|
||||
{
|
||||
isPlayground ? (
|
||||
<slot />
|
||||
) : (
|
||||
<Default {...Astro.props}>
|
||||
<slot />
|
||||
</Default>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
import type { Props } from "@astrojs/starlight/props";
|
||||
import Default from "@astrojs/starlight/components/PageTitle.astro";
|
||||
import { isPlaygroundPage } from '@utils/playground';
|
||||
|
||||
const isPlayground = isPlaygroundPage(Astro.props.slug, Astro.currentLocale);
|
||||
---
|
||||
|
||||
|
||||
{isPlayground ? <></> : <Default {...Astro.props}><slot /></Default>}
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
import type { Props } from "@astrojs/starlight/props";
|
||||
import Default from "@astrojs/starlight/components/Pagination.astro";
|
||||
import { isPlaygroundPage } from '@utils/playground';
|
||||
|
||||
const isPlayground = isPlaygroundPage(Astro.props.slug, Astro.currentLocale);
|
||||
---
|
||||
|
||||
|
||||
{isPlayground ? <></> : <Default {...Astro.props}><slot /></Default>}
|
|
@ -0,0 +1,117 @@
|
|||
import * as React from "react";
|
||||
import Button from "@mui/material/Button";
|
||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
||||
import ClickAwayListener from "@mui/material/ClickAwayListener";
|
||||
import Grow from "@mui/material/Grow";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Popper from "@mui/material/Popper";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import MenuList from "@mui/material/MenuList";
|
||||
|
||||
export default function DropdownButton({
|
||||
options,
|
||||
visible,
|
||||
style,
|
||||
}: {
|
||||
options: [string, React.MouseEventHandler<HTMLLIElement>][];
|
||||
visible: [React.ReactNode, React.MouseEventHandler<HTMLButtonElement>][];
|
||||
style?: React.CSSProperties;
|
||||
}) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const anchorRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleMenuItemClick = (
|
||||
event: React.MouseEvent<HTMLLIElement, MouseEvent>,
|
||||
index: number
|
||||
) => {
|
||||
options[index][1](event);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleToggle = () => {
|
||||
setOpen((prevOpen) => !prevOpen);
|
||||
};
|
||||
|
||||
const handleClose = (event: Event) => {
|
||||
if (
|
||||
anchorRef.current &&
|
||||
anchorRef.current.contains(event.target as HTMLElement)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonGroup
|
||||
variant="contained"
|
||||
ref={anchorRef}
|
||||
aria-label="Button group with a nested menu"
|
||||
style={style}
|
||||
>
|
||||
{visible.map(([children, onClick], index) => {
|
||||
return (
|
||||
<Button key={index} onClick={onClick}>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
<Button
|
||||
size="small"
|
||||
aria-controls={open ? "split-button-menu" : undefined}
|
||||
aria-expanded={open ? "true" : undefined}
|
||||
aria-label="select merge strategy"
|
||||
aria-haspopup="menu"
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<ArrowDropDownIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Popper
|
||||
sx={{
|
||||
zIndex: 1,
|
||||
}}
|
||||
open={open}
|
||||
anchorEl={anchorRef.current}
|
||||
role={undefined}
|
||||
transition
|
||||
disablePortal
|
||||
>
|
||||
{({ TransitionProps, placement }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin:
|
||||
placement === "bottom"
|
||||
? "center top"
|
||||
: "center bottom",
|
||||
}}
|
||||
>
|
||||
<Paper>
|
||||
<ClickAwayListener onClickAway={handleClose}>
|
||||
<MenuList id="split-button-menu" autoFocusItem>
|
||||
{options.map((option, index) => (
|
||||
<MenuItem
|
||||
key={option[0]}
|
||||
onClick={(event) =>
|
||||
handleMenuItemClick(
|
||||
event,
|
||||
index
|
||||
)
|
||||
}
|
||||
>
|
||||
{option[0]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import type { File } from "@utils/playground";
|
||||
import MonacoEditor, { useMonaco } from "@monaco-editor/react";
|
||||
import { getHighlighter, type Highlighter } from "shiki";
|
||||
import { shikiToMonaco } from "@shikijs/monaco";
|
||||
import { useEffect, useState } from "react";
|
||||
import darkPlus from "tm-themes/themes/dark-plus.json";
|
||||
import lightPlus from "tm-themes/themes/light-plus.json";
|
||||
|
||||
import { shulkerscriptGrammar } from "@utils/shulkerscript-grammar";
|
||||
import { mcfunctionGrammar } from "@utils/mcfunction-grammar";
|
||||
|
||||
export default function Editor({
|
||||
theme,
|
||||
fileName,
|
||||
file,
|
||||
}: {
|
||||
theme: "light" | "dark";
|
||||
fileName: string;
|
||||
file?: File;
|
||||
}) {
|
||||
const [highlighter, setHighlighter] = useState<Highlighter | null>(null);
|
||||
|
||||
const monaco = useMonaco();
|
||||
useEffect(() => {
|
||||
if (monaco) {
|
||||
if (highlighter == null) {
|
||||
getHighlighter({
|
||||
themes: [darkPlus as any, lightPlus],
|
||||
langs: ["toml", shulkerscriptGrammar, mcfunctionGrammar],
|
||||
}).then((highlighter) => {
|
||||
highlighter.setTheme(
|
||||
theme === "dark" ? "dark-plus" : "light-plus"
|
||||
);
|
||||
setHighlighter(highlighter);
|
||||
});
|
||||
} else {
|
||||
shikiToMonaco(highlighter, monaco);
|
||||
}
|
||||
|
||||
monaco.languages.register({ id: "toml" });
|
||||
monaco.languages.register({ id: "shulkerscript" });
|
||||
monaco.languages.register({ id: "mcfunction" });
|
||||
}
|
||||
}, [monaco]);
|
||||
useEffect(() => {
|
||||
if (highlighter != null) {
|
||||
shikiToMonaco(highlighter, monaco);
|
||||
}
|
||||
}, [highlighter]);
|
||||
|
||||
return (
|
||||
<MonacoEditor
|
||||
height="70vh"
|
||||
theme={theme === "dark" ? "dark-plus" : "light-plus"}
|
||||
path={fileName}
|
||||
defaultLanguage={file?.language}
|
||||
defaultValue={file?.content}
|
||||
wrapperProps={{ className: "editor" }}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import type { PlaygroundErrorDisplayLang } from "@utils/playground";
|
||||
|
||||
export default function ErrorDisplay({
|
||||
lang,
|
||||
error,
|
||||
setError,
|
||||
}: {
|
||||
lang: PlaygroundErrorDisplayLang;
|
||||
error: string | null;
|
||||
setError: (error: string | null) => void;
|
||||
}) {
|
||||
return (
|
||||
<Dialog open={error !== null} onClose={() => setError(null)}>
|
||||
<DialogTitle>{lang.title}</DialogTitle>
|
||||
<DialogContent>
|
||||
<div className="error-terminal-display">
|
||||
<code
|
||||
dangerouslySetInnerHTML={{ __html: error ?? "" }}
|
||||
></code>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setError(null)}>
|
||||
{lang.buttons.close}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import type { PlaygroundExplorerLang } from "@utils/playground";
|
||||
|
||||
export default function AddFileDialog({
|
||||
open,
|
||||
handleClose,
|
||||
addFile,
|
||||
lang,
|
||||
defaultPath,
|
||||
}: {
|
||||
open: boolean;
|
||||
defaultPath: string;
|
||||
lang: PlaygroundExplorerLang;
|
||||
handleClose: () => void;
|
||||
addFile: (filepath: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
PaperProps={{
|
||||
component: "form",
|
||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const formJson = Object.fromEntries(
|
||||
(formData as any).entries()
|
||||
);
|
||||
const filepath = formJson.filepath;
|
||||
if (addFile) {
|
||||
addFile(filepath);
|
||||
}
|
||||
handleClose();
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle>{lang.menu.add}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
{lang.menu.addPrompt.message}
|
||||
</DialogContentText>
|
||||
<TextField
|
||||
autoFocus
|
||||
required
|
||||
margin="dense"
|
||||
id="filepath"
|
||||
name="filepath"
|
||||
label={lang.menu.addPrompt.label}
|
||||
type="text"
|
||||
fullWidth
|
||||
variant="filled"
|
||||
defaultValue={defaultPath}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>{lang.menu.cancel}</Button>
|
||||
<Button type="submit">{lang.menu.add}</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
import type {
|
||||
Directory,
|
||||
PlaygroundExplorerLang,
|
||||
SetState,
|
||||
} from "@utils/playground";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
GoChevronDown as ChevDown,
|
||||
GoChevronRight as ChevRight,
|
||||
} from "react-icons/go";
|
||||
import FileElement from "./FileElement";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import AddFileDialog from "./AddFileDialog";
|
||||
|
||||
export default function DirElement({
|
||||
name,
|
||||
fullPath,
|
||||
dir: currentDir,
|
||||
collapsed: pCollapsed,
|
||||
selectedFileName,
|
||||
lang,
|
||||
setSelectedFileName,
|
||||
addFile,
|
||||
deleteFile,
|
||||
renameFile,
|
||||
}: {
|
||||
name: string;
|
||||
fullPath: string;
|
||||
dir: Directory;
|
||||
collapsed?: boolean;
|
||||
selectedFileName: string;
|
||||
lang: PlaygroundExplorerLang;
|
||||
setSelectedFileName: SetState<string>;
|
||||
addFile: (name: string) => void;
|
||||
deleteFile: (name: string) => void;
|
||||
renameFile: (oldName: string, newName: string) => void;
|
||||
}) {
|
||||
const [collapsed, setCollapsed] = useState(pCollapsed ?? false);
|
||||
|
||||
const chevStyles: React.CSSProperties = {
|
||||
marginBottom: "-2px",
|
||||
};
|
||||
|
||||
const hasChildren =
|
||||
Object.keys(currentDir.dirs ?? {}).length > 0 ||
|
||||
Object.keys(currentDir.files ?? {}).length > 0;
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const contextOpen = Boolean(anchorEl);
|
||||
const handleContext = (event: React.MouseEvent<HTMLElement>) => {
|
||||
event.preventDefault();
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleContextClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const [addOpen, setAddOpen] = React.useState(false);
|
||||
|
||||
const handleAddClose = () => {
|
||||
setAddOpen(false);
|
||||
};
|
||||
|
||||
const onDelete = () => {
|
||||
deleteFile(fullPath + "/");
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={name} className="dir">
|
||||
<button
|
||||
style={{ display: "block" }}
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
onContextMenu={handleContext}
|
||||
>
|
||||
{collapsed ? (
|
||||
<ChevRight
|
||||
aria-description="collapsed"
|
||||
style={chevStyles}
|
||||
/>
|
||||
) : (
|
||||
<ChevDown aria-description="expanded" style={chevStyles} />
|
||||
)}{" "}
|
||||
{name + "/" + (collapsed && hasChildren ? "..." : "")}
|
||||
</button>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={contextOpen}
|
||||
onClose={handleContextClose}
|
||||
>
|
||||
<MenuItem
|
||||
disabled={(fullPath + "/").startsWith("dist/")}
|
||||
onClick={() => {
|
||||
handleContextClose();
|
||||
setAddOpen(true);
|
||||
}}
|
||||
>
|
||||
{lang.menu.add}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleContextClose();
|
||||
onDelete?.();
|
||||
}}
|
||||
>
|
||||
{lang.menu.delete}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<AddFileDialog
|
||||
lang={lang}
|
||||
defaultPath={fullPath + "/"}
|
||||
open={addOpen}
|
||||
addFile={addFile}
|
||||
handleClose={handleAddClose}
|
||||
/>
|
||||
<div style={{ marginLeft: "0.5cm" }} className="dirChildren">
|
||||
{collapsed ? null : (
|
||||
<div>
|
||||
{Object.entries(currentDir.dirs ?? {}).map(
|
||||
([dirname, dir]) => {
|
||||
return (
|
||||
<DirElement
|
||||
key={dirname}
|
||||
name={dirname}
|
||||
fullPath={fullPath + "/" + dirname}
|
||||
dir={dir}
|
||||
selectedFileName={selectedFileName}
|
||||
lang={lang}
|
||||
addFile={addFile}
|
||||
setSelectedFileName={
|
||||
setSelectedFileName
|
||||
}
|
||||
deleteFile={deleteFile}
|
||||
renameFile={renameFile}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
{Object.entries(currentDir.files ?? {}).map(
|
||||
([currentName, _]) => {
|
||||
const currentPath =
|
||||
fullPath + "/" + currentName;
|
||||
return (
|
||||
<FileElement
|
||||
key={currentName}
|
||||
fullPath={currentPath}
|
||||
name={currentName}
|
||||
isSelected={
|
||||
selectedFileName == currentPath
|
||||
}
|
||||
lang={lang}
|
||||
onClick={() =>
|
||||
setSelectedFileName(currentPath)
|
||||
}
|
||||
onDelete={() => deleteFile(currentPath)}
|
||||
renameFile={renameFile}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import type { PlaygroundExplorerLang } from "@utils/playground";
|
||||
import React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function FileElement({
|
||||
name,
|
||||
fullPath,
|
||||
isSelected,
|
||||
lang,
|
||||
onClick,
|
||||
renameFile,
|
||||
onDelete,
|
||||
}: {
|
||||
name: string;
|
||||
fullPath: string;
|
||||
isSelected: boolean;
|
||||
lang: PlaygroundExplorerLang;
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
renameFile?: (oldName: string, newName: string) => void;
|
||||
onDelete?: React.MouseEventHandler<HTMLLIElement>;
|
||||
}) {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const contextOpen = Boolean(anchorEl);
|
||||
const handleContext = (event: React.MouseEvent<HTMLElement>) => {
|
||||
event.preventDefault();
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleContextClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const [renameOpen, setRenameOpen] = React.useState(false);
|
||||
|
||||
const handleRenameClose = () => {
|
||||
setRenameOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="file">
|
||||
<button
|
||||
onClick={onClick}
|
||||
onContextMenu={handleContext}
|
||||
className={"file" + (isSelected ? " selected" : "")}
|
||||
>
|
||||
{name}
|
||||
</button>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={contextOpen}
|
||||
onClose={handleContextClose}
|
||||
>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleContextClose();
|
||||
setRenameOpen(true);
|
||||
}}
|
||||
disabled={fullPath.startsWith("dist/")}
|
||||
>
|
||||
{lang.menu.rename}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={(ev) => {
|
||||
handleContextClose();
|
||||
onDelete?.(ev);
|
||||
}}
|
||||
>
|
||||
{lang.menu.delete}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Dialog
|
||||
open={renameOpen}
|
||||
onClose={handleRenameClose}
|
||||
PaperProps={{
|
||||
component: "form",
|
||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const formJson = Object.fromEntries(
|
||||
(formData as any).entries()
|
||||
);
|
||||
const filepath = formJson.filepath;
|
||||
if (renameFile) {
|
||||
renameFile(fullPath, filepath);
|
||||
}
|
||||
handleRenameClose();
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle>
|
||||
{lang.menu.rename} - {fullPath}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
{lang.menu.renamePrompt.message}
|
||||
</DialogContentText>
|
||||
<TextField
|
||||
autoFocus
|
||||
required
|
||||
margin="dense"
|
||||
id="filepath"
|
||||
name="filepath"
|
||||
label={lang.menu.renamePrompt.label}
|
||||
type="text"
|
||||
fullWidth
|
||||
variant="filled"
|
||||
defaultValue={fullPath}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleRenameClose}>
|
||||
{lang.menu.cancel}
|
||||
</Button>
|
||||
<Button type="submit">{lang.menu.rename}</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
import type {
|
||||
Directory,
|
||||
PlaygroundExplorerLang,
|
||||
SetState,
|
||||
} from "@utils/playground";
|
||||
import DirElement from "./DirElement";
|
||||
import FileElement from "./FileElement";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import React, { useState } from "react";
|
||||
import AddFileDialog from "./AddFileDialog";
|
||||
|
||||
export default function FileView({
|
||||
lang,
|
||||
root,
|
||||
selectedFileName,
|
||||
setSelectedFileName,
|
||||
addFile,
|
||||
deleteFile,
|
||||
renameFile,
|
||||
className,
|
||||
}: {
|
||||
lang: PlaygroundExplorerLang;
|
||||
root: Directory;
|
||||
selectedFileName: string;
|
||||
setSelectedFileName: SetState<string>;
|
||||
addFile: (name: string) => void;
|
||||
deleteFile: (name: string) => void;
|
||||
renameFile: (oldName: string, newName: string) => void;
|
||||
className?: string;
|
||||
}) {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const contextOpen = Boolean(anchorEl);
|
||||
const handleContext = (event: React.MouseEvent<HTMLElement>) => {
|
||||
event.preventDefault();
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleContextClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const [addOpen, setAddOpen] = React.useState(false);
|
||||
|
||||
const handleAddClose = () => {
|
||||
setAddOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<h3 onContextMenu={handleContext}>{lang.title}</h3>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={contextOpen}
|
||||
onClose={handleContextClose}
|
||||
>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleContextClose();
|
||||
setAddOpen(true);
|
||||
}}
|
||||
>
|
||||
{lang.menu.add}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<AddFileDialog lang={lang} defaultPath="" addFile={addFile} handleClose={handleAddClose} open={addOpen} />
|
||||
<div className="entries">
|
||||
{Object.entries(root.dirs ?? {}).map(([name, dir]) => {
|
||||
return (
|
||||
<DirElement
|
||||
key={name}
|
||||
name={name}
|
||||
fullPath={name}
|
||||
dir={dir}
|
||||
selectedFileName={selectedFileName}
|
||||
lang={lang}
|
||||
setSelectedFileName={setSelectedFileName}
|
||||
addFile={addFile}
|
||||
deleteFile={deleteFile}
|
||||
renameFile={renameFile}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{Object.entries(root.files ?? {}).map(([name, _]) => {
|
||||
const isSelected = selectedFileName == name;
|
||||
return (
|
||||
<span key={name}>
|
||||
<FileElement
|
||||
name={name}
|
||||
fullPath={name}
|
||||
isSelected={isSelected}
|
||||
lang={lang}
|
||||
onClick={
|
||||
isSelected
|
||||
? () => {}
|
||||
: () => setSelectedFileName(name)
|
||||
}
|
||||
onDelete={() => deleteFile(name)}
|
||||
renameFile={renameFile}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import type { PlaygroundHeaderLang } from "@utils/playground";
|
||||
import DropdownButton from "./DropdownButton";
|
||||
|
||||
export default function Header({
|
||||
lang,
|
||||
onSave,
|
||||
onReset,
|
||||
onBuild,
|
||||
onZip,
|
||||
}: {
|
||||
lang: PlaygroundHeaderLang;
|
||||
onSave: () => void;
|
||||
onReset: () => void;
|
||||
onBuild: () => void;
|
||||
onZip: () => void;
|
||||
}) {
|
||||
return (
|
||||
<header>
|
||||
<h1 id="_top">{lang.title}</h1>
|
||||
<div className="buttons" style={{ height: "100%" }}>
|
||||
<DropdownButton
|
||||
style={{ height: "100%", marginRight: "0.5cm" }}
|
||||
visible={[[lang.buttons.save, onSave]]}
|
||||
options={[[lang.buttons.reset, onReset]]}
|
||||
/>
|
||||
<DropdownButton
|
||||
style={{ height: "100%" }}
|
||||
visible={[[lang.buttons.build, onBuild]]}
|
||||
options={[[lang.buttons.zip, onZip]]}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
|
@ -6,12 +6,13 @@ import { Steps, FileTree, Tabs, TabItem } from '@astrojs/starlight/components';
|
|||
|
||||
## Installation
|
||||
|
||||
|
||||
To get started with Shulkerscript, you need to install the Shulkerscript CLI.
|
||||
You can either [download](#download-from-github) the latest release from the GitHub releases page or [build it from source](#building-from-source).
|
||||
|
||||
{/* :::tip
|
||||
Before you install the CLI, you can try it out in your browser by using the [online playground](/playground).
|
||||
::: */}
|
||||
:::tip
|
||||
If you want to try out Shulkerscript without installing anything, you can use the [online playground](../../playground) right in your browser.
|
||||
:::
|
||||
|
||||
### Quickinstall script *(recommended)*
|
||||
<Steps>
|
||||
|
|
|
@ -16,8 +16,7 @@ hero:
|
|||
icon: external
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from '@astrojs/starlight/components';
|
||||
|
||||
import { Card, LinkCard, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
<CardGrid stagger>
|
||||
<Card title="Simple Syntax" icon="seti:html">
|
||||
|
@ -33,6 +32,5 @@ import { Card, CardGrid } from '@astrojs/starlight/components';
|
|||
<Card title="Contribute to this project" icon="github">
|
||||
Contribute to [the CLI](https://github.com/moritz-hoelting/shulkerscript-cli) or [the compiler](https://github.com/moritz-hoelting/shulkerscript-lang).
|
||||
</Card>
|
||||
<LinkCard title="Online Playground" href="./playground" description="Get started without downloading anything" />
|
||||
</CardGrid>
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
import PlaygroundComponent from "@components/Playground";
|
||||
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
|
||||
import type { PlaygroundLang } from "@utils/playground";
|
||||
|
||||
const lang: PlaygroundLang = {
|
||||
header: {
|
||||
title: "Spielplatz",
|
||||
buttons: {
|
||||
build: "Bauen",
|
||||
zip: "Als Zip herunterladen",
|
||||
save: "Speichern",
|
||||
reset: "Zurücksetzen",
|
||||
},
|
||||
},
|
||||
explorer: {
|
||||
title: "Dateien",
|
||||
menu: {
|
||||
add: "Datei hinzufügen",
|
||||
addPrompt: {
|
||||
label: "Dateipfad",
|
||||
message: "Pfad eingeben, an dem die Datei erstellt werden soll."
|
||||
},
|
||||
delete: "Löschen",
|
||||
rename: "Umbenennen",
|
||||
renamePrompt: {
|
||||
label: "Neuer Dateipfad",
|
||||
message: "Pfad eingeben, zu dem die Datei umbenannt/verschoben werden soll."
|
||||
},
|
||||
cancel: "Abbrechen",
|
||||
}
|
||||
},
|
||||
errorDisplay: {
|
||||
title: "Fehler beim kompilieren!",
|
||||
buttons: {
|
||||
close: "Schließen",
|
||||
},
|
||||
}
|
||||
};
|
||||
---
|
||||
|
||||
<StarlightPage frontmatter={{ title: "Playground", template: "splash" }}>
|
||||
<PlaygroundComponent client:only="react" {lang} />
|
||||
</StarlightPage>
|
||||
|
||||
<style is:global>
|
||||
.pagination-links {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
import PlaygroundComponent from "@components/Playground";
|
||||
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
|
||||
import type { PlaygroundLang } from "@utils/playground";
|
||||
|
||||
const lang: PlaygroundLang = {
|
||||
header: {
|
||||
title: "Playground",
|
||||
buttons: {
|
||||
build: "Build",
|
||||
zip: "Download zip",
|
||||
save: "Save",
|
||||
reset: "Reset",
|
||||
},
|
||||
},
|
||||
explorer: {
|
||||
title: "Explorer",
|
||||
menu: {
|
||||
add: "Add file",
|
||||
addPrompt: {
|
||||
label: "File path",
|
||||
message:
|
||||
"Enter the path you want the new file to be created at.",
|
||||
},
|
||||
delete: "Delete",
|
||||
rename: "Rename",
|
||||
renamePrompt: {
|
||||
label: "New file path",
|
||||
message:
|
||||
"Enter the path you want the file to be renamed/moved to.",
|
||||
},
|
||||
cancel: "Cancel",
|
||||
},
|
||||
},
|
||||
errorDisplay: {
|
||||
title: "Error during compilation!",
|
||||
buttons: {
|
||||
close: "Close",
|
||||
},
|
||||
},
|
||||
};
|
||||
---
|
||||
|
||||
<StarlightPage frontmatter={{ title: "Playground", template: "splash" }}>
|
||||
<PlaygroundComponent client:only="react" {lang} />
|
||||
</StarlightPage>
|
||||
|
||||
<style is:global>
|
||||
.pagination-links {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,106 @@
|
|||
.playground {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-areas:
|
||||
"header"
|
||||
"files"
|
||||
"editor";
|
||||
|
||||
@media only screen and (min-width: 640px) {
|
||||
grid-template-columns: clamp(200px, 15%, 500px) auto;
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"files editor";
|
||||
}
|
||||
|
||||
> header {
|
||||
grid-area: header;
|
||||
display: flex;
|
||||
margin-bottom: 0.5cm;
|
||||
flex-direction: column;
|
||||
max-width: 95dvw;
|
||||
|
||||
@media only screen and (min-width: 450px) {
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
> .file-view {
|
||||
grid-area: files;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
max-height: 70vh;
|
||||
|
||||
@media only screen and (min-width: 640px) {
|
||||
margin-right: 0.5cm;
|
||||
}
|
||||
|
||||
> h3 {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.entries {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
word-wrap: break-word;
|
||||
|
||||
button {
|
||||
border: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--sl-color-gray-5);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
color: var(--sl-color-text);
|
||||
background-color: var(--sl-color-gray-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .editor {
|
||||
grid-area: editor;
|
||||
|
||||
@media only screen and (max-width: 640px) {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.monaco-editor {
|
||||
padding-block: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-terminal-display {
|
||||
background-color: black;
|
||||
padding: 15px;
|
||||
border-radius: 15px;
|
||||
font-size: 1.2em;
|
||||
line-height: 0.8em;
|
||||
|
||||
--red: #ff0000;
|
||||
--cyan: #00d6d6;
|
||||
|
||||
code {
|
||||
white-space: break-spaces;
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme="light"] {
|
||||
.error-terminal-display {
|
||||
background-color: lightgray;
|
||||
|
||||
--red: #ff3f3f;
|
||||
--cyan: #00a6a6;
|
||||
--black: lightgray;
|
||||
--white: #4f4f4f;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { createTheme } from "@mui/material/styles";
|
||||
|
||||
export function customTheme(mode: "light" | "dark") {
|
||||
return createTheme({
|
||||
palette: {
|
||||
mode,
|
||||
primary: {
|
||||
light: "#a700c3",
|
||||
main: "#a700c3",
|
||||
dark: "#a700c3",
|
||||
contrastText: "#fff",
|
||||
},
|
||||
secondary: {
|
||||
light: "#ff7961",
|
||||
main: "#f44336",
|
||||
dark: "#ba000d",
|
||||
contrastText: "#000",
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: [
|
||||
"-apple-system",
|
||||
"BlinkMacSystemFont",
|
||||
'"Segoe UI"',
|
||||
'"Helvetica Neue"',
|
||||
"Arial",
|
||||
"sans-serif",
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"',
|
||||
].join(","),
|
||||
},
|
||||
});
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
export type PlaygroundLang = {
|
||||
header: PlaygroundHeaderLang;
|
||||
explorer: PlaygroundExplorerLang;
|
||||
errorDisplay: PlaygroundErrorDisplayLang;
|
||||
};
|
||||
|
||||
export type PlaygroundHeaderLang = {
|
||||
title: string;
|
||||
buttons: {
|
||||
save: string;
|
||||
reset: string;
|
||||
build: string;
|
||||
zip: string;
|
||||
};
|
||||
};
|
||||
export type PlaygroundExplorerLang = {
|
||||
title: string;
|
||||
menu: {
|
||||
add: string;
|
||||
addPrompt: {
|
||||
message: string;
|
||||
label: string;
|
||||
}
|
||||
rename: string;
|
||||
renamePrompt: {
|
||||
message: string;
|
||||
label: string;
|
||||
}
|
||||
delete: string;
|
||||
cancel: string;
|
||||
}
|
||||
};
|
||||
export type PlaygroundErrorDisplayLang = {
|
||||
title: string;
|
||||
buttons: {
|
||||
close: string;
|
||||
}
|
||||
}
|
||||
|
||||
export type File = {
|
||||
language?: string;
|
||||
content: string;
|
||||
};
|
||||
export type Directory = {
|
||||
dirs?: { [key: string]: Directory };
|
||||
files?: { [key: string]: File };
|
||||
};
|
||||
export type SetState<T> = React.Dispatch<React.SetStateAction<T>>;
|
||||
|
||||
export function isPlaygroundPage(slug: string, lang?: string): boolean {
|
||||
return (
|
||||
slug === (!lang || lang == "en" ? "playground" : `${lang}/playground`)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
target-dir = "target"
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/pkg
|
|
@ -0,0 +1,879 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi-to-html"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d73c455ae09fa2223a75114789f30ad605e9e297f79537953523366c05995f5f"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chksum-core"
|
||||
version = "0.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6db20071fdeca52ed6a7745519fb2d343fddcb93af81448373b851f072aaec5"
|
||||
dependencies = [
|
||||
"chksum-hash-core",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chksum-hash-core"
|
||||
version = "0.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "221456234d441c788a2c51a27b91c4380f499de560670a67d3303e621d37b3bd"
|
||||
|
||||
[[package]]
|
||||
name = "chksum-hash-md5"
|
||||
version = "0.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80c33d01c33c9e193fe33e719a29a7eb900c08583375dd1d3269991aacbe434a"
|
||||
dependencies = [
|
||||
"chksum-hash-core",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chksum-md5"
|
||||
version = "0.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95dda0f76fbb6069e042c370a928457086e1b4eabc7e75f5f49fe1b913634351"
|
||||
dependencies = [
|
||||
"chksum-core",
|
||||
"chksum-hash-md5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
|
||||
dependencies = [
|
||||
"derive_more-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more-impl"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "enum-as-inner"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getset"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f636605b743120a8d32ed92fc27b6cde1a769f8f936c065151eb66f88ded513c"
|
||||
dependencies = [
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.159"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
|
||||
|
||||
[[package]]
|
||||
name = "lockfree-object-pool"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memory_units"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-absolutize"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5"
|
||||
dependencies = [
|
||||
"path-dedot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-dedot"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.210"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.210"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shulkerbox"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/moritz-hoelting/shulkerbox?rev=6e956fbe7438158c6a29c1a92d057f0f3093405a#6e956fbe7438158c6a29c1a92d057f0f3093405a"
|
||||
dependencies = [
|
||||
"chksum-md5",
|
||||
"getset",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shulkerscript"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/moritz-hoelting/shulkerscript-lang.git?rev=a9a8aff13b0ad0986ee1bdf3d44b74676385dfcd#a9a8aff13b0ad0986ee1bdf3d44b74676385dfcd"
|
||||
dependencies = [
|
||||
"chksum-md5",
|
||||
"colored",
|
||||
"derive_more",
|
||||
"enum-as-inner",
|
||||
"getset",
|
||||
"itertools",
|
||||
"path-absolutize",
|
||||
"pathdiff",
|
||||
"serde",
|
||||
"shulkerbox",
|
||||
"strsim",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"once_cell",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
|
||||
|
||||
[[package]]
|
||||
name = "webcompiler"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ansi-to-html",
|
||||
"anyhow",
|
||||
"base64",
|
||||
"cfg-if 1.0.0",
|
||||
"colored",
|
||||
"console_error_panic_hook",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"shulkerscript",
|
||||
"toml",
|
||||
"wasm-bindgen",
|
||||
"wee_alloc",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wee_alloc"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"memory_units",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"displaydoc",
|
||||
"flate2",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
"thiserror",
|
||||
"zopfli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"lockfree-object-pool",
|
||||
"log",
|
||||
"once_cell",
|
||||
"simd-adler32",
|
||||
]
|
|
@ -0,0 +1,30 @@
|
|||
[package]
|
||||
name = "webcompiler"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
|
||||
[features]
|
||||
wee_alloc = ["dep:wee_alloc"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
ansi-to-html = "0.2.1"
|
||||
anyhow = "1.0.86"
|
||||
base64 = "0.22.1"
|
||||
cfg-if = "1.0.0"
|
||||
colored = "2.1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
serde = "1.0"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
shulkerscript = { git = "https://github.com/moritz-hoelting/shulkerscript-lang.git", default-features = false, features = ["serde", "shulkerbox"], rev = "a9a8aff13b0ad0986ee1bdf3d44b74676385dfcd" }
|
||||
toml = "0.8.19"
|
||||
wasm-bindgen = "0.2.93"
|
||||
wee_alloc = { version = "0.4.5", optional = true }
|
||||
zip = { version = "2.1.3", default-features = false, features = ["deflate"] }
|
|
@ -0,0 +1,102 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shulkerscript::shulkerbox::virtual_fs::{VFile, VFolder};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct File {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) language: Option<String>,
|
||||
pub(crate) content: String,
|
||||
}
|
||||
impl File {
|
||||
pub fn with_lang(self, lang: String) -> Self {
|
||||
Self {
|
||||
language: Some(lang),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Directory {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) dirs: Option<BTreeMap<String, Directory>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) files: Option<BTreeMap<String, File>>,
|
||||
}
|
||||
|
||||
impl From<Directory> for VFolder {
|
||||
fn from(value: Directory) -> Self {
|
||||
let mut folder = VFolder::new();
|
||||
|
||||
if let Some(dirs) = value.dirs {
|
||||
for (name, dir) in dirs {
|
||||
folder.add_existing_folder(&name, dir.into())
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(files) = value.files {
|
||||
for (name, file) in files {
|
||||
folder.add_file(&name, file.into());
|
||||
}
|
||||
}
|
||||
|
||||
folder
|
||||
}
|
||||
}
|
||||
impl From<File> for VFile {
|
||||
fn from(value: File) -> Self {
|
||||
VFile::Text(value.content)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VFolder> for Directory {
|
||||
fn from(value: VFolder) -> Self {
|
||||
let mut dirs = BTreeMap::new();
|
||||
let mut files = BTreeMap::new();
|
||||
|
||||
for (name, item) in value.get_folders() {
|
||||
dirs.insert(name.to_string(), item.clone().into());
|
||||
}
|
||||
|
||||
for (name, item) in value.get_files() {
|
||||
files.insert(
|
||||
name.to_string(),
|
||||
File::from(item.clone()).correct_lang(name),
|
||||
);
|
||||
}
|
||||
|
||||
Self {
|
||||
dirs: Some(dirs),
|
||||
files: Some(files),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VFile> for File {
|
||||
fn from(value: VFile) -> Self {
|
||||
let content = match value {
|
||||
VFile::Text(content) => content,
|
||||
VFile::Binary(bin) => String::from_utf8_lossy(&bin).to_string(),
|
||||
};
|
||||
Self {
|
||||
content,
|
||||
language: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl File {
|
||||
pub fn correct_lang(self, name: &str) -> Self {
|
||||
let language = match name.split('.').last() {
|
||||
Some("shu") => Some("shulkerscript".to_string()),
|
||||
Some("mcfunction") => Some("mcfunction".to_string()),
|
||||
Some("json" | "mcmeta") => Some("json".to_string()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Self { language, ..self }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
use std::{
|
||||
fmt::Display,
|
||||
io::{Cursor, Write},
|
||||
path::PathBuf,
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use base64::prelude::*;
|
||||
use fs::Directory;
|
||||
use shulkerscript::{
|
||||
base::Handler,
|
||||
shulkerbox::virtual_fs::{VFile, VFolder},
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use zip::{write::SimpleFileOptions, ZipWriter};
|
||||
|
||||
mod fs;
|
||||
mod pack_toml;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "wee_alloc")] {
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
#[wasm_bindgen(js_namespace = console, js_name = error)]
|
||||
fn log_err(s: &str);
|
||||
|
||||
#[wasm_bindgen(js_namespace = ["window", "playground"], js_name = showError)]
|
||||
fn show_err(s: &str);
|
||||
}
|
||||
|
||||
/// Compiles the given directory into datapack files.
|
||||
#[wasm_bindgen]
|
||||
pub fn compile(root_dir: JsValue) -> JsValue {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let root_dir = VFolder::from(serde_wasm_bindgen::from_value::<Directory>(root_dir).unwrap());
|
||||
|
||||
log("Compiling...");
|
||||
if let Ok(folder) = _compile(&root_dir) {
|
||||
let folder = Directory::from(folder);
|
||||
serde_wasm_bindgen::to_value(&folder).unwrap()
|
||||
} else {
|
||||
JsValue::null()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a base64 encoded zip file containing the compiled datapack.
|
||||
#[wasm_bindgen(js_name = compileZip)]
|
||||
pub fn compile_zip(root_dir: JsValue) -> Option<String> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let root_dir = VFolder::from(serde_wasm_bindgen::from_value::<Directory>(root_dir).unwrap());
|
||||
|
||||
let datapack = _compile(&root_dir).ok()?;
|
||||
|
||||
let mut buffer = Cursor::new(Vec::new());
|
||||
let mut writer = ZipWriter::new(&mut buffer);
|
||||
let virtual_files = datapack.flatten();
|
||||
|
||||
// write each file to the zip archive
|
||||
for (path, file) in virtual_files {
|
||||
writer.start_file(path, SimpleFileOptions::default()).ok()?;
|
||||
match file {
|
||||
VFile::Text(text) => {
|
||||
writer.write_all(text.as_bytes()).ok()?;
|
||||
}
|
||||
VFile::Binary(data) => {
|
||||
writer.write_all(data).ok()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.set_comment("Data pack created with Shulkerscript web compiler");
|
||||
|
||||
writer.finish().ok()?;
|
||||
|
||||
Some(BASE64_STANDARD.encode(buffer.into_inner()))
|
||||
}
|
||||
|
||||
fn _compile(root_dir: &VFolder) -> Result<VFolder> {
|
||||
colored::control::set_override(true);
|
||||
let printer = Printer::new();
|
||||
|
||||
let pack_format = {
|
||||
let pack_toml = root_dir.get_file("pack.toml").ok_or_else(|| {
|
||||
printer.receive_str("Could not find pack.toml. Make sure it is in the root directory.");
|
||||
anyhow::anyhow!("Could not find pack.toml. Make sure it is in the root directory.")
|
||||
})?;
|
||||
toml::from_str::<pack_toml::PackToml>(pack_toml.as_text().unwrap())
|
||||
.map_err(|e| {
|
||||
printer.receive_str(&format!("Error parsing pack.toml: {}", e));
|
||||
anyhow::anyhow!("Error parsing pack.toml: {}", e)
|
||||
})
|
||||
.map(|toml| toml.pack.format)
|
||||
};
|
||||
|
||||
let res = pack_format.and_then(|pack_format| {
|
||||
shulkerscript::compile(&printer, root_dir, pack_format, &get_script_paths(root_dir))
|
||||
.map_err(|e| e.into())
|
||||
});
|
||||
|
||||
printer.display();
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Printer {
|
||||
queue: Mutex<Vec<String>>,
|
||||
}
|
||||
impl<T: Display> Handler<T> for Printer {
|
||||
fn receive<E: Into<T>>(&self, error: E) {
|
||||
self.queue.lock().unwrap().push(format!("{}", error.into()));
|
||||
}
|
||||
|
||||
fn has_received(&self) -> bool {
|
||||
self.has_printed()
|
||||
}
|
||||
}
|
||||
impl Printer {
|
||||
/// Creates a new [`Printer`].
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
queue: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn display(self) {
|
||||
let queue = self
|
||||
.queue
|
||||
.into_inner()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|el| ansi_to_html::convert(&el).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
show_err(&queue.join("\n\n"));
|
||||
}
|
||||
|
||||
fn has_printed(&self) -> bool {
|
||||
!self.queue.lock().unwrap().is_empty()
|
||||
}
|
||||
|
||||
fn receive_str(&self, error: &str) {
|
||||
<Printer as Handler<&str>>::receive::<&str>(self, error);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_script_paths(root: &VFolder) -> Vec<(String, PathBuf)> {
|
||||
root.flatten()
|
||||
.into_iter()
|
||||
.filter_map(|(p, _)| {
|
||||
p.strip_suffix(".shu")
|
||||
.and_then(|p| p.strip_prefix("src/"))
|
||||
.map(|ident| (ident.to_string(), PathBuf::from(&p)))
|
||||
})
|
||||
.collect()
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PackToml {
|
||||
pub pack: Pack,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Pack {
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub version: Option<String>,
|
||||
pub format: u8,
|
||||
}
|
Loading…
Reference in New Issue