mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-29 07:51:01 +00:00
Basic working file explorer
This commit is contained in:
@@ -4,6 +4,7 @@ import { Filesystem } from "./filesystem";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "./components/ui/button";
|
||||
import { Input } from "./components/ui/input";
|
||||
import { DialogDescription } from "@radix-ui/react-dialog";
|
||||
|
||||
export interface FilesystemExplorerProps {
|
||||
filesystem: Filesystem;
|
||||
@@ -19,10 +21,34 @@ export interface FilesystemExplorerState {
|
||||
path: string[];
|
||||
createFolder: boolean;
|
||||
errorText: string;
|
||||
removeFile: string;
|
||||
renameFile: string;
|
||||
}
|
||||
|
||||
function makeFullPath(path: string[]) {
|
||||
return "/root/filesys/" + path.join("/");
|
||||
}
|
||||
|
||||
function makeFullPathAndJoin(path: string[], element: string) {
|
||||
return makeFullPath([...path, element]);
|
||||
}
|
||||
|
||||
function makeFullPathWithState(
|
||||
state: FilesystemExplorerState,
|
||||
element: string,
|
||||
) {
|
||||
return makeFullPathAndJoin(state.path, element);
|
||||
}
|
||||
|
||||
function makeRelativePathWithState(
|
||||
state: FilesystemExplorerState,
|
||||
element: string,
|
||||
) {
|
||||
return [...state.path, element].join("/");
|
||||
}
|
||||
|
||||
function getFolderElements(filesystem: Filesystem, path: string[]) {
|
||||
const fullPath = "/root/filesys/" + path.join("/");
|
||||
const fullPath = makeFullPath(path);
|
||||
const files = filesystem.readDir(fullPath);
|
||||
|
||||
return files
|
||||
@@ -56,15 +82,14 @@ export class FilesystemExplorer extends React.Component<
|
||||
constructor(props: FilesystemExplorerProps) {
|
||||
super(props);
|
||||
|
||||
this._showError = this._showError.bind(this);
|
||||
this._onFolderCreate = this._onFolderCreate.bind(this);
|
||||
this._onElementSelect = this._onElementSelect.bind(this);
|
||||
this._showFolderCreateDialog = this._showFolderCreateDialog.bind(this);
|
||||
|
||||
this.state = {
|
||||
path: [],
|
||||
createFolder: false,
|
||||
errorText: "",
|
||||
removeFile: "",
|
||||
renameFile: "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,16 +117,18 @@ export class FilesystemExplorer extends React.Component<
|
||||
});
|
||||
}
|
||||
|
||||
_showFolderCreateDialog() {
|
||||
this.setState({
|
||||
createFolder: true,
|
||||
});
|
||||
async _onFileRename(file: string, newFile: string) {
|
||||
const oldPath = makeFullPathWithState(this.state, file);
|
||||
const newPath = makeFullPathWithState(this.state, newFile);
|
||||
|
||||
this.setState({ renameFile: "" });
|
||||
|
||||
await this.props.filesystem.rename(oldPath, newPath);
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
async _onFolderCreate(name: string) {
|
||||
this.setState({
|
||||
createFolder: false,
|
||||
});
|
||||
this.setState({ createFolder: false });
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
@@ -110,75 +137,185 @@ export class FilesystemExplorer extends React.Component<
|
||||
}
|
||||
|
||||
if (name.includes("/") || name.includes("\\")) {
|
||||
this._showError("Folder must not contain special characters");
|
||||
return;
|
||||
}
|
||||
this._showError("Folder must not contain special characters");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.path.length == 0 && name.length > 1) {
|
||||
this._showError("Drives must be a single letter");
|
||||
return;
|
||||
}
|
||||
|
||||
const fullPath = "/root/filesys/" + [...this.state.path, name].join("/");
|
||||
const fullPath = makeFullPathWithState(this.state, name);
|
||||
await this.props.filesystem.createFolder(fullPath);
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
_renderCreateFolderDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
open={this.state.createFolder}
|
||||
onOpenChange={(open) => this.setState({ createFolder: open })}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
const folderName = (e.target as any).elements.name.value;
|
||||
this._onFolderCreate(folderName);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create new folder</DialogTitle>
|
||||
<DialogDescription className="hidden">
|
||||
Create new folder
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<Input id="name" defaultValue="New Folder" />
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" className="fancy rounded-lg">
|
||||
Create
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_renderRenameDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
open={this.state.renameFile.length > 0}
|
||||
onOpenChange={(open) => (open ? {} : this.setState({ renameFile: "" }))}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
const newName = (e.target as any).elements.name.value;
|
||||
this._onFileRename(this.state.renameFile, newName);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Rename {this.state.renameFile}</DialogTitle>
|
||||
<DialogDescription className="hidden">
|
||||
Rename {this.state.renameFile}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<Input id="name" defaultValue={this.state.renameFile} />
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" className="fancy rounded-lg">
|
||||
Rename
|
||||
</Button>
|
||||
<DialogClose asChild>
|
||||
<Button variant="secondary" className="fancy rounded-lg">
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_renderErrorDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
open={this.state.errorText.length > 0}
|
||||
onOpenChange={(open) => (open ? {} : this.setState({ errorText: "" }))}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Error</DialogTitle>
|
||||
<DialogDescription className="hidden">
|
||||
Error: {this.state.errorText}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">{this.state.errorText}</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => this.setState({ errorText: "" })}
|
||||
>
|
||||
Ok
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_renderRemoveDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
open={this.state.removeFile.length > 0}
|
||||
onOpenChange={(open) => (open ? {} : this.setState({ removeFile: "" }))}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete {this.state.removeFile}?</DialogTitle>
|
||||
<DialogDescription className="hidden">
|
||||
Delete {this.state.removeFile}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
Are you sure you want to delete{" "}
|
||||
<b>
|
||||
{makeRelativePathWithState(this.state, this.state.removeFile)}
|
||||
</b>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
const file = makeFullPathWithState(
|
||||
this.state,
|
||||
this.state.removeFile,
|
||||
);
|
||||
this.setState({ removeFile: "" });
|
||||
this.props.filesystem
|
||||
.unlink(file)
|
||||
.then(() => this.forceUpdate());
|
||||
}}
|
||||
>
|
||||
Ok
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
this.setState({ removeFile: "" });
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const elements = getFolderElements(this.props.filesystem, this.state.path);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
open={this.state.createFolder}
|
||||
onOpenChange={(open) => this.setState({ createFolder: open })}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
const folderName = (e.target as any).elements.name.value;
|
||||
this._onFolderCreate(folderName);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create new folder</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<Input id="name" defaultValue="New Folder" />
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" className="fancy rounded-lg">
|
||||
Create
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
open={this.state.errorText.length > 0}
|
||||
onOpenChange={(open) =>
|
||||
open ? {} : this.setState({ errorText: "" })
|
||||
}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Error</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="py-4">{this.state.errorText}</div>
|
||||
<DialogFooter>
|
||||
<Button variant="destructive" onClick={() => this.setState({ errorText: "" })}>
|
||||
Ok
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{this._renderCreateFolderDialog()}
|
||||
{this._renderRenameDialog()}
|
||||
{this._renderErrorDialog()}
|
||||
{this._renderRemoveDialog()}
|
||||
|
||||
<Folder
|
||||
elements={elements}
|
||||
clickHandler={this._onElementSelect}
|
||||
createFolderHandler={this._showFolderCreateDialog}
|
||||
createFolderHandler={() => this.setState({ createFolder: true })}
|
||||
removeElementHandler={(e) => this.setState({ removeFile: e.name })}
|
||||
renameElementHandler={(e) => this.setState({ renameFile: e.name })}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user