implement file creation
This commit is contained in:
parent
7096d02e8c
commit
f798b55e7a
|
@ -177,18 +177,31 @@ export default function Playground({ lang }: { lang: PlaygroundLang }) {
|
||||||
root={rootDir}
|
root={rootDir}
|
||||||
selectedFileName={fileName}
|
selectedFileName={fileName}
|
||||||
setSelectedFileName={setFileName}
|
setSelectedFileName={setFileName}
|
||||||
|
addFile={(name) => {
|
||||||
|
if (monaco) {
|
||||||
|
loadFile(
|
||||||
|
monaco,
|
||||||
|
updateRootDir,
|
||||||
|
{ content: "" },
|
||||||
|
name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
deleteFile={(name) => {
|
deleteFile={(name) => {
|
||||||
if (monaco) {
|
if (monaco) {
|
||||||
console.log(name);
|
if (name.endsWith("/")) {
|
||||||
deleteFile(monaco, updateRootDir, name);
|
deleteDir(monaco, updateRootDir, name);
|
||||||
if (name === fileName) {
|
} else {
|
||||||
const newFile = monaco.editor
|
deleteFile(monaco, updateRootDir, name);
|
||||||
.getModels()[0]
|
if (name === fileName) {
|
||||||
?.uri.path.slice(1);
|
const newFile = monaco.editor
|
||||||
if (newFile) {
|
.getModels()[0]
|
||||||
setFileName(newFile);
|
?.uri.path.slice(1);
|
||||||
} else {
|
if (newFile) {
|
||||||
setFileName("");
|
setFileName(newFile);
|
||||||
|
} else {
|
||||||
|
setFileName("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -384,10 +397,59 @@ function deleteFile(monaco: Monaco, updater: Updater<Directory>, name: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renameFile(monaco: Monaco, updater: Updater<Directory>, oldName: string, newName: string) {
|
function deleteDir(monaco: Monaco, updater: Updater<Directory>, path: string) {
|
||||||
|
const parts = path.split("/").filter((s) => s !== "");
|
||||||
|
const last = parts.pop()!;
|
||||||
|
|
||||||
|
let current = getFiles(monaco);
|
||||||
|
for (const part of parts) {
|
||||||
|
if (!current.dirs) {
|
||||||
|
current.dirs = {};
|
||||||
|
}
|
||||||
|
if (!current.dirs[part]) {
|
||||||
|
current.dirs[part] = {};
|
||||||
|
}
|
||||||
|
current = current.dirs[part];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.dirs) {
|
||||||
|
for (const [name, _] of Object.entries(current.dirs ?? {})) {
|
||||||
|
deleteDir(monaco, updater, path + name + "/");
|
||||||
|
}
|
||||||
|
for (const [name, _] of Object.entries(current.files ?? {})) {
|
||||||
|
deleteFile(monaco, updater, path + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete current.dirs[last];
|
||||||
|
}
|
||||||
|
|
||||||
|
updater((dir) => {
|
||||||
|
let current = dir;
|
||||||
|
for (const part of parts) {
|
||||||
|
if (!current.dirs) {
|
||||||
|
current.dirs = {};
|
||||||
|
}
|
||||||
|
if (!current.dirs[part]) {
|
||||||
|
current.dirs[part] = {};
|
||||||
|
}
|
||||||
|
current = current.dirs[part];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.dirs) {
|
||||||
|
delete current.dirs[last];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renameFile(
|
||||||
|
monaco: Monaco,
|
||||||
|
updater: Updater<Directory>,
|
||||||
|
oldName: string,
|
||||||
|
newName: string
|
||||||
|
) {
|
||||||
const file = getFile(getFiles(monaco), oldName);
|
const file = getFile(getFiles(monaco), oldName);
|
||||||
if (file) {
|
if (file) {
|
||||||
deleteFile(monaco, updater, oldName);
|
deleteFile(monaco, updater, oldName);
|
||||||
loadFile(monaco, updater, file, newName);
|
loadFile(monaco, updater, file, newName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
TextField,
|
||||||
|
} from "@mui/material";
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,10 +1,19 @@
|
||||||
import type { Directory, PlaygroundExplorerLang, SetState } from "@utils/playground";
|
import type {
|
||||||
|
Directory,
|
||||||
|
PlaygroundExplorerLang,
|
||||||
|
SetState,
|
||||||
|
} from "@utils/playground";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
GoChevronDown as ChevDown,
|
GoChevronDown as ChevDown,
|
||||||
GoChevronRight as ChevRight,
|
GoChevronRight as ChevRight,
|
||||||
} from "react-icons/go";
|
} from "react-icons/go";
|
||||||
import FileElement from "./FileElement";
|
import FileElement from "./FileElement";
|
||||||
|
import {
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
} from "@mui/material";
|
||||||
|
import AddFileDialog from "./AddFileDialog";
|
||||||
|
|
||||||
export default function DirElement({
|
export default function DirElement({
|
||||||
name,
|
name,
|
||||||
|
@ -14,6 +23,7 @@ export default function DirElement({
|
||||||
selectedFileName,
|
selectedFileName,
|
||||||
lang,
|
lang,
|
||||||
setSelectedFileName,
|
setSelectedFileName,
|
||||||
|
addFile,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
renameFile,
|
renameFile,
|
||||||
}: {
|
}: {
|
||||||
|
@ -24,6 +34,7 @@ export default function DirElement({
|
||||||
selectedFileName: string;
|
selectedFileName: string;
|
||||||
lang: PlaygroundExplorerLang;
|
lang: PlaygroundExplorerLang;
|
||||||
setSelectedFileName: SetState<string>;
|
setSelectedFileName: SetState<string>;
|
||||||
|
addFile: (name: string) => void;
|
||||||
deleteFile: (name: string) => void;
|
deleteFile: (name: string) => void;
|
||||||
renameFile: (oldName: string, newName: string) => void;
|
renameFile: (oldName: string, newName: string) => void;
|
||||||
}) {
|
}) {
|
||||||
|
@ -37,11 +48,32 @@ export default function DirElement({
|
||||||
Object.keys(currentDir.dirs ?? {}).length > 0 ||
|
Object.keys(currentDir.dirs ?? {}).length > 0 ||
|
||||||
Object.keys(currentDir.files ?? {}).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 (
|
return (
|
||||||
<div key={name} className="dir">
|
<div key={name} className="dir">
|
||||||
<button
|
<button
|
||||||
style={{ display: "block" }}
|
style={{ display: "block" }}
|
||||||
onClick={() => setCollapsed(!collapsed)}
|
onClick={() => setCollapsed(!collapsed)}
|
||||||
|
onContextMenu={handleContext}
|
||||||
>
|
>
|
||||||
{collapsed ? (
|
{collapsed ? (
|
||||||
<ChevRight
|
<ChevRight
|
||||||
|
@ -53,6 +85,30 @@ export default function DirElement({
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{name + "/" + (collapsed && hasChildren ? "..." : "")}
|
{name + "/" + (collapsed && hasChildren ? "..." : "")}
|
||||||
</button>
|
</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">
|
<div style={{ marginLeft: "0.5cm" }} className="dirChildren">
|
||||||
{collapsed ? null : (
|
{collapsed ? null : (
|
||||||
<div>
|
<div>
|
||||||
|
@ -66,6 +122,7 @@ export default function DirElement({
|
||||||
dir={dir}
|
dir={dir}
|
||||||
selectedFileName={selectedFileName}
|
selectedFileName={selectedFileName}
|
||||||
lang={lang}
|
lang={lang}
|
||||||
|
addFile={addFile}
|
||||||
setSelectedFileName={
|
setSelectedFileName={
|
||||||
setSelectedFileName
|
setSelectedFileName
|
||||||
}
|
}
|
||||||
|
@ -77,20 +134,21 @@ export default function DirElement({
|
||||||
)}
|
)}
|
||||||
{Object.entries(currentDir.files ?? {}).map(
|
{Object.entries(currentDir.files ?? {}).map(
|
||||||
([currentName, _]) => {
|
([currentName, _]) => {
|
||||||
const currentPath = fullPath + "/" + currentName;
|
const currentPath =
|
||||||
|
fullPath + "/" + currentName;
|
||||||
return (
|
return (
|
||||||
<FileElement
|
<FileElement
|
||||||
key={currentName}
|
key={currentName}
|
||||||
fullPath={currentPath}
|
fullPath={currentPath}
|
||||||
name={currentName}
|
name={currentName}
|
||||||
isSelected={selectedFileName == currentPath}
|
isSelected={
|
||||||
|
selectedFileName == currentPath
|
||||||
|
}
|
||||||
lang={lang}
|
lang={lang}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setSelectedFileName(currentPath)
|
setSelectedFileName(currentPath)
|
||||||
}
|
}
|
||||||
onDelete={() =>
|
onDelete={() => deleteFile(currentPath)}
|
||||||
deleteFile(currentPath)
|
|
||||||
}
|
|
||||||
renameFile={renameFile}
|
renameFile={renameFile}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Menu, MenuItem, TextField } from "@mui/material";
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
TextField,
|
||||||
|
} from "@mui/material";
|
||||||
import type { PlaygroundExplorerLang } from "@utils/playground";
|
import type { PlaygroundExplorerLang } from "@utils/playground";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
@ -50,10 +60,11 @@ export default function FileElement({
|
||||||
onClose={handleContextClose}
|
onClose={handleContextClose}
|
||||||
>
|
>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={(ev) => {
|
onClick={() => {
|
||||||
handleContextClose();
|
handleContextClose();
|
||||||
setRenameOpen(true);
|
setRenameOpen(true);
|
||||||
}}
|
}}
|
||||||
|
disabled={fullPath.startsWith("dist/")}
|
||||||
>
|
>
|
||||||
{lang.menu.rename}
|
{lang.menu.rename}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -85,7 +96,9 @@ export default function FileElement({
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle>{lang.menu.rename} - {fullPath}</DialogTitle>
|
<DialogTitle>
|
||||||
|
{lang.menu.rename} - {fullPath}
|
||||||
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
{lang.menu.renamePrompt.message}
|
{lang.menu.renamePrompt.message}
|
||||||
|
@ -104,10 +117,12 @@ export default function FileElement({
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleRenameClose}>{lang.menu.cancel}</Button>
|
<Button onClick={handleRenameClose}>
|
||||||
|
{lang.menu.cancel}
|
||||||
|
</Button>
|
||||||
<Button type="submit">{lang.menu.rename}</Button>
|
<Button type="submit">{lang.menu.rename}</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,16 @@ import type {
|
||||||
} from "@utils/playground";
|
} from "@utils/playground";
|
||||||
import DirElement from "./DirElement";
|
import DirElement from "./DirElement";
|
||||||
import FileElement from "./FileElement";
|
import FileElement from "./FileElement";
|
||||||
|
import { Menu, MenuItem } from "@mui/material";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import AddFileDialog from "./AddFileDialog";
|
||||||
|
|
||||||
export default function FileView({
|
export default function FileView({
|
||||||
lang,
|
lang,
|
||||||
root,
|
root,
|
||||||
selectedFileName,
|
selectedFileName,
|
||||||
setSelectedFileName,
|
setSelectedFileName,
|
||||||
|
addFile,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
renameFile,
|
renameFile,
|
||||||
className,
|
className,
|
||||||
|
@ -19,13 +23,45 @@ export default function FileView({
|
||||||
root: Directory;
|
root: Directory;
|
||||||
selectedFileName: string;
|
selectedFileName: string;
|
||||||
setSelectedFileName: SetState<string>;
|
setSelectedFileName: SetState<string>;
|
||||||
|
addFile: (name: string) => void;
|
||||||
deleteFile: (name: string) => void;
|
deleteFile: (name: string) => void;
|
||||||
renameFile: (oldName: string, newName: string) => void;
|
renameFile: (oldName: string, newName: string) => void;
|
||||||
className?: string;
|
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 (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<h3>{lang.title}</h3>
|
<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">
|
<div className="entries">
|
||||||
{Object.entries(root.dirs ?? {}).map(([name, dir]) => {
|
{Object.entries(root.dirs ?? {}).map(([name, dir]) => {
|
||||||
return (
|
return (
|
||||||
|
@ -37,6 +73,7 @@ export default function FileView({
|
||||||
selectedFileName={selectedFileName}
|
selectedFileName={selectedFileName}
|
||||||
lang={lang}
|
lang={lang}
|
||||||
setSelectedFileName={setSelectedFileName}
|
setSelectedFileName={setSelectedFileName}
|
||||||
|
addFile={addFile}
|
||||||
deleteFile={deleteFile}
|
deleteFile={deleteFile}
|
||||||
renameFile={renameFile}
|
renameFile={renameFile}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,6 +16,11 @@ const lang: PlaygroundLang = {
|
||||||
explorer: {
|
explorer: {
|
||||||
title: "Dateien",
|
title: "Dateien",
|
||||||
menu: {
|
menu: {
|
||||||
|
add: "Datei hinzufügen",
|
||||||
|
addPrompt: {
|
||||||
|
label: "Dateipfad",
|
||||||
|
message: "Pfad eingeben, an dem die Datei erstellt werden soll."
|
||||||
|
},
|
||||||
delete: "Löschen",
|
delete: "Löschen",
|
||||||
rename: "Umbenennen",
|
rename: "Umbenennen",
|
||||||
renamePrompt: {
|
renamePrompt: {
|
||||||
|
|
|
@ -16,6 +16,11 @@ const lang: PlaygroundLang = {
|
||||||
explorer: {
|
explorer: {
|
||||||
title: "Explorer",
|
title: "Explorer",
|
||||||
menu: {
|
menu: {
|
||||||
|
add: "Add file",
|
||||||
|
addPrompt: {
|
||||||
|
label: "File path",
|
||||||
|
message: "Enter the path you want the new file to be created at."
|
||||||
|
},
|
||||||
delete: "Delete",
|
delete: "Delete",
|
||||||
rename: "Rename",
|
rename: "Rename",
|
||||||
renamePrompt: {
|
renamePrompt: {
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.entries {
|
.entries {
|
||||||
button {
|
button {
|
||||||
border: none;
|
border: none;
|
||||||
|
|
|
@ -15,6 +15,11 @@ export type PlaygroundHeaderLang = {
|
||||||
export type PlaygroundExplorerLang = {
|
export type PlaygroundExplorerLang = {
|
||||||
title: string;
|
title: string;
|
||||||
menu: {
|
menu: {
|
||||||
|
add: string;
|
||||||
|
addPrompt: {
|
||||||
|
message: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
rename: string;
|
rename: string;
|
||||||
renamePrompt: {
|
renamePrompt: {
|
||||||
message: string;
|
message: string;
|
||||||
|
|
Loading…
Reference in New Issue