diff --git a/page/src/FilesystemExplorer.tsx b/page/src/FilesystemExplorer.tsx index 0ef015c9..1a0db87c 100644 --- a/page/src/FilesystemExplorer.tsx +++ b/page/src/FilesystemExplorer.tsx @@ -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 ( + this.setState({ createFolder: open })} + > + +
{ + const folderName = (e.target as any).elements.name.value; + this._onFolderCreate(folderName); + e.preventDefault(); + }} + > + + Create new folder + + Create new folder + + +
+ +
+ + + +
+
+
+ ); + } + + _renderRenameDialog() { + return ( + 0} + onOpenChange={(open) => (open ? {} : this.setState({ renameFile: "" }))} + > + +
{ + const newName = (e.target as any).elements.name.value; + this._onFileRename(this.state.renameFile, newName); + e.preventDefault(); + }} + > + + Rename {this.state.renameFile} + + Rename {this.state.renameFile} + + +
+ +
+ + + + + + +
+
+
+ ); + } + + _renderErrorDialog() { + return ( + 0} + onOpenChange={(open) => (open ? {} : this.setState({ errorText: "" }))} + > + + + Error + + Error: {this.state.errorText} + + +
{this.state.errorText}
+ + + +
+
+ ); + } + + _renderRemoveDialog() { + return ( + 0} + onOpenChange={(open) => (open ? {} : this.setState({ removeFile: "" }))} + > + + + Delete {this.state.removeFile}? + + Delete {this.state.removeFile} + + +
+ Are you sure you want to delete{" "} + + {makeRelativePathWithState(this.state, this.state.removeFile)} + +
+ + + + +
+
+ ); + } + render() { const elements = getFolderElements(this.props.filesystem, this.state.path); return ( <> - this.setState({ createFolder: open })} - > - -
{ - const folderName = (e.target as any).elements.name.value; - this._onFolderCreate(folderName); - e.preventDefault(); - }} - > - - Create new folder - -
- -
- - - -
-
-
- - 0} - onOpenChange={(open) => - open ? {} : this.setState({ errorText: "" }) - } - > - - - Error - -
{this.state.errorText}
- - - -
-
+ {this._renderCreateFolderDialog()} + {this._renderRenameDialog()} + {this._renderErrorDialog()} + {this._renderRemoveDialog()} this.setState({ createFolder: true })} + removeElementHandler={(e) => this.setState({ removeFile: e.name })} + renameElementHandler={(e) => this.setState({ renameFile: e.name })} /> ); diff --git a/page/src/Playground.tsx b/page/src/Playground.tsx index ac1e691a..e4bbb80c 100644 --- a/page/src/Playground.tsx +++ b/page/src/Playground.tsx @@ -225,11 +225,12 @@ export function Playground() { - {/*Filesystem Explorer - Description - - - */} + + Filesystem Explorer + + + Filesystem Explorer + diff --git a/page/src/components/folder.tsx b/page/src/components/folder.tsx index 0ff777e2..a32ea608 100644 --- a/page/src/components/folder.tsx +++ b/page/src/components/folder.tsx @@ -33,13 +33,15 @@ export interface FolderElement { type ClickHandler = (element: FolderElement) => void; type CreateFolderHandler = () => void; +type RemoveElementHandler = (element: FolderElement) => void; +type RenameElementHandler = (element: FolderElement) => void; export interface FolderProps { elements: FolderElement[]; clickHandler: ClickHandler; createFolderHandler: CreateFolderHandler; - //deleteHandler: (element: FolderElement) => void; - //renameHandler: (element: FolderElement, name: string) => void; + removeElementHandler: RemoveElementHandler; + renameElementHandler: RenameElementHandler; } function elementComparator(e1: FolderElement, e2: FolderElement) { @@ -90,12 +92,9 @@ function renderElement(element: FolderElement, clickHandler: ClickHandler) { ); } -function renderElementWithContext( - element: FolderElement, - clickHandler: ClickHandler, -) { +function renderElementWithContext(element: FolderElement, props: FolderProps) { if (element.name == "..") { - return renderElement(element, clickHandler); + return renderElement(element, props.clickHandler); } return ( @@ -103,7 +102,7 @@ function renderElementWithContext( - {renderElement(element, clickHandler)} + {renderElement(element, props.clickHandler)}

{element.name}

@@ -113,18 +112,23 @@ function renderElementWithContext( {element.name} - Rename - Delete + props.renameElementHandler(element)}> + Rename + + props.removeElementHandler(element)}> + Delete + ); } -function renderElementWrapper( element: FolderElement, - clickHandler: ClickHandler,) { - return
- {renderElementWithContext(element, clickHandler)} +function renderElementWrapper(element: FolderElement, props: FolderProps) { + return ( +
+ {renderElementWithContext(element, props)}
+ ); } function renderFolderCreator(createFolderHandler: CreateFolderHandler) { @@ -144,7 +148,7 @@ export function Folder(props: FolderProps) {
{props.elements .sort(elementComparator) - .map((e) => renderElementWrapper(e, props.clickHandler))} + .map((e) => renderElementWrapper(e, props))}
diff --git a/page/src/filesystem.ts b/page/src/filesystem.ts index f2a864f2..b7165a49 100644 --- a/page/src/filesystem.ts +++ b/page/src/filesystem.ts @@ -51,6 +51,31 @@ export class Filesystem { await this.sync(); } + _unlinkRecursive(element: string) { + if (!this.isFolder(element)) { + this.idbfs.FS.unlink(element); + return; + } + + this.readDir(element) // + .filter((e) => e != "." && e != "..") + .forEach((e) => { + this._unlinkRecursive(`${element}/${e}`); + }); + + this.idbfs.FS.rmdir(element); + } + + async rename(oldFile: string, newFile: string) { + this.idbfs.FS.rename(oldFile, newFile); + await this.sync(); + } + + async unlink(file: string) { + this._unlinkRecursive(file); + await this.sync(); + } + async createFolder(folder: string) { this.idbfs.FS.mkdir(folder, 777); await this.sync();