add save and reset button to playground

This commit is contained in:
Moritz Hölting 2024-06-21 09:26:04 +02:00
parent d04f61752d
commit 87f911e055
4 changed files with 147 additions and 64 deletions

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useMonaco, type Monaco } from "@monaco-editor/react"; import { useMonaco, type Monaco } from "@monaco-editor/react";
import { useImmer, type ImmerHook, type Updater } from "use-immer"; import { useImmer, type Updater } from "use-immer";
import "@styles/playground.scss"; import "@styles/playground.scss";
@ -20,8 +20,8 @@ export type Directory = {
}; };
export type SetState<T> = React.Dispatch<React.SetStateAction<T>>; export type SetState<T> = React.Dispatch<React.SetStateAction<T>>;
export default function Playground() { const FILE_STORAGE_KEY = "playground-files";
const [rootDir, updateRootDir] = useImmer({ const DEFAULT_FILES = {
dirs: { dirs: {
src: { src: {
files: { files: {
@ -31,14 +31,7 @@ export default function Playground() {
}, },
}, },
}, },
dist: { dist: {},
files: {
"test.mcfunction": {
content: "",
language: "mcfunction",
},
},
},
}, },
files: { files: {
"pack.toml": { "pack.toml": {
@ -46,23 +39,66 @@ export default function Playground() {
language: "toml", language: "toml",
}, },
}, },
} as Directory); };
export default function Playground() {
const [rootDir, updateRootDir] = useImmer(
getStorageOrDefault(FILE_STORAGE_KEY, DEFAULT_FILES) as Directory
);
const [fileName, setFileName] = useState("src/main.shu"); const [fileName, setFileName] = useState("src/main.shu");
const file = getFile(rootDir, fileName)!; const file = getFile(rootDir, fileName);
const onBuild: () => void = () => { const onBuild = () => {
if (monaco) { if (monaco) {
console.log(getFiles(monaco)); console.log(getFiles(monaco));
} else { } else {
console.error("monaco has not loaded"); console.error("monaco has not loaded");
} }
}; };
const onZip: () => void = () => { const onZip = () => {
if (monaco) { if (monaco) {
loadFile(monaco, updateRootDir, {content: "zip"}, "dist/pack.zip"); loadFile(
monaco,
updateRootDir,
{ content: "zip" },
"dist/pack.zip"
);
} else { } else {
console.error("onZip not set");} console.error("onZip not set");
}
};
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(); const monaco = useMonaco();
@ -82,14 +118,19 @@ export default function Playground() {
marginTop: "0.5cm", marginTop: "0.5cm",
}} }}
> >
<Header onBuild={onBuild} onZip={onZip} /> <Header
onSave={onSave}
onReset={onReset}
onBuild={onBuild}
onZip={onZip}
/>
<FileView <FileView
className="file-view" className="file-view"
root={rootDir} root={rootDir}
fileName={fileName} fileName={fileName}
setSelectedFileName={setFileName} setSelectedFileName={setFileName}
/> />
<Editor fileName={fileName} file={file} /> <Editor fileName={fileName} file={file ?? undefined} />
</main> </main>
</> </>
); );
@ -111,7 +152,7 @@ function getFiles(monaco: Monaco): Directory {
dir.dirs[part] = {}; dir.dirs[part] = {};
} }
dir = dir.dirs[part].files ?? {}; dir = dir.dirs[part];
} }
if (!dir.files) { if (!dir.files) {
@ -147,12 +188,20 @@ function getFile(root: Directory, path: string): File | null {
return root.files?.[path] ?? null; return root.files?.[path] ?? null;
} }
function loadFiles(monaco: Monaco, updater: Updater<Directory>, dir: Directory, prefix = "") { function loadFiles(
monaco: Monaco,
updater: Updater<Directory>,
dir: Directory,
prefix = ""
) {
for (const [name, d] of Object.entries(dir.dirs ?? {})) { for (const [name, d] of Object.entries(dir.dirs ?? {})) {
loadFiles(monaco, updater, d, prefix + name + "/"); loadFiles(monaco, updater, d, prefix + name + "/");
updater(dir => { updater((dir) => {
let current = dir; let current = dir;
for(const part of [...prefix.split("/").filter(s => s !== ""), name]) { for (const part of [
...prefix.split("/").filter((s) => s !== ""),
name,
]) {
if (!current.dirs) { if (!current.dirs) {
current.dirs = {}; current.dirs = {};
} }
@ -166,17 +215,21 @@ function loadFiles(monaco: Monaco, updater: Updater<Directory>, dir: Directory,
} }
} }
function loadFile(monaco: Monaco, updater: Updater<Directory>, file: File, name: string) { function loadFile(
monaco: Monaco,
updater: Updater<Directory>,
file: File,
name: string
) {
const uri = monaco.Uri.parse(name); const uri = monaco.Uri.parse(name);
if (!monaco.editor.getModel(uri)) { if (!monaco.editor.getModel(uri)) {
monaco.editor.createModel(file.content, file.language, uri); monaco.editor.createModel(file.content, file.language, uri);
} }
updater(dir => { updater((dir) => {
let current = dir; let current = dir;
const parts = name.split("/").filter(s => s !== ""); const parts = name.split("/").filter((s) => s !== "");
const last = parts.pop()!; const last = parts.pop()!;
for(const part of parts) { for (const part of parts) {
console.log(part);
if (!current.dirs) { if (!current.dirs) {
current.dirs = {}; current.dirs = {};
} }
@ -189,4 +242,11 @@ function loadFile(monaco: Monaco, updater: Updater<Directory>, file: File, name:
}); });
} }
function getStorageOrDefault(key: string, def: any) {
const item = window.localStorage.getItem(key);
if (item) {
return JSON.parse(item);
} else {
return def;
}
}

View File

@ -12,14 +12,14 @@ import { ThemeProvider } from "@mui/material";
import { customTheme } from "@utils/material-ui-theme"; import { customTheme } from "@utils/material-ui-theme";
export default function SplitButton({ export default function DropdownButton({
options, options,
children, visible,
onClick, style,
}: { }: {
options: [string, React.MouseEventHandler<HTMLLIElement>][]; options: [string, React.MouseEventHandler<HTMLLIElement>][];
children: React.ReactNode; visible: [React.ReactNode, React.MouseEventHandler<HTMLButtonElement>][];
onClick?: React.MouseEventHandler<HTMLButtonElement>; style?: React.CSSProperties;
}) { }) {
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef<HTMLDivElement>(null); const anchorRef = React.useRef<HTMLDivElement>(null);
@ -53,10 +53,11 @@ export default function SplitButton({
variant="contained" variant="contained"
ref={anchorRef} ref={anchorRef}
aria-label="Button group with a nested menu" aria-label="Button group with a nested menu"
style={style}
> >
<Button onClick={onClick}> {visible.map(([children, onClick], index) => {
{children} return <Button key={index} onClick={onClick}>{children}</Button>;
</Button> })}
<Button <Button
size="small" size="small"
aria-controls={open ? "split-button-menu" : undefined} aria-controls={open ? "split-button-menu" : undefined}

View File

@ -13,7 +13,7 @@ export default function Editor({
file, file,
}: { }: {
fileName: string; fileName: string;
file: File; file?: File;
}) { }) {
const [highlighter, setHighlighter] = useState<Highlighter | null>(null); const [highlighter, setHighlighter] = useState<Highlighter | null>(null);
@ -48,8 +48,8 @@ export default function Editor({
height="70vh" height="70vh"
theme="vs-dark" theme="vs-dark"
path={fileName} path={fileName}
defaultLanguage={file.language} defaultLanguage={file?.language}
defaultValue={file.content} defaultValue={file?.content}
/> />
</div> </div>
); );

View File

@ -1,15 +1,37 @@
import SplitButton from "./SplitButton"; import DropdownButton from "./DropdownButton";
export default function Header({onBuild, onZip}: {onBuild: () => void; onZip: () => void;}){
export default function Header({
onSave,
onReset,
onBuild,
onZip,
}: {
onSave: () => void;
onReset: () => void;
onBuild: () => void;
onZip: () => void;
}) {
return ( return (
<header style={{ <header
style={{
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
marginBottom: "0.5cm", marginBottom: "0.5cm",
}}> }}
>
<h1 id="_top">Playground</h1> <h1 id="_top">Playground</h1>
<SplitButton onClick={onBuild} options={[["Download zip", onZip]]}>Build</SplitButton> <div className="buttons" style={{ height: "100%" }}>
<DropdownButton
style={{ height: "100%", marginRight: "0.5cm"}}
visible={[["Save", onSave]]}
options={[["Reset", onReset]]}
/>
<DropdownButton
style={{ height: "100%" }}
visible={[["Build", onBuild]]}
options={[["Download zip", onZip]]}
/>
</div>
</header> </header>
); );
} }