Now let's start working with some logic, let's first define our workflow.
Workflow
Once the file manager is opened, we'll load the root
path then list its directories on the sidebar, and its content (Files And Directories) on the right side.
So we need the following states:
-
Loading
State to determine if the file manager is being loading directories. -
currentDirectoryNode
: State that contains the current loaded directory node.
That's it for now, let's see it in action.
// FileManager.tsx
export default function FileManager({
open,
onClose,
rootPath,
}: FileManagerProps) {
const [isLoading, setIsLoading] = useState(true);
const [currentDirectoryNode, setCurrentDirectoryNode] = useState<Node>();
return (
<>
<Modal size="xl" opened={open} onClose={onClose}>
<Toolbar />
<BodyWrapper>
<Grid>
<Grid.Col span={3}>
<Sidebar />
</Grid.Col>
<Grid.Col span={9}>
<Content />
</Grid.Col>
</Grid>
</BodyWrapper>
</Modal>
</>
);
}
FileManager.defaultProps = {
rootPath: "/",
};
I added rootPath
in the default props object, and created two states as mentioned earlier.
Now let's make a new instance of the file manager and store it in a ref
import { Grid, Modal } from "@mantine/core";
import BaseFileManager from "app/file-manager/utils/FileManager";
import { useRef, useState } from "react";
import Content from "./Content";
import { BodyWrapper } from "./FileManager.styles";
import { FileManagerProps } from "./FileManager.types";
import Sidebar from "./Sidebar";
import Toolbar from "./Toolbar";
import { Node } from "../../types/FileManager.types";
export default function FileManager({
open,
onClose,
rootPath,
}: FileManagerProps) {
const [isLoading, setIsLoading] = useState(true);
const [currentDirectoryNode, setCurrentDirectoryNode] = useState<Node>();
const fileManagerRef = useRef(new BaseFileManager());
return (
<>
<Modal size="xl" opened={open} onClose={onClose}>
<Toolbar />
<BodyWrapper>
<Grid>
<Grid.Col span={3}>
<Sidebar />
</Grid.Col>
<Grid.Col span={9}>
<Content />
</Grid.Col>
</Grid>
</BodyWrapper>
</Modal>
</>
);
}
FileManager.defaultProps = {
rootPath: "/",
};
We renamed it to BaseFileManager
so we don't get confused between it and the component itself.
We actually may enhance the ref by directly destructing the current
key
- const fileManagerRef = useRef(new BaseFileManager());
+ const { current: fileManager } = useRef(new BaseFileManager());
Next let's create the useEffect
hook that will load the root path.
import { Node } from "../../types/FileManager.types";
export default function FileManager({
open,
onClose,
rootPath,
}: FileManagerProps) {
const [isLoading, setIsLoading] = useState(true);
const [currentDirectoryNode, setCurrentDirectoryNode] = useState<Node>();
const { current: fileManager } = useRef(new BaseFileManager());
// load root directory
useEffect(() => {
if (!rootPath) return;
setIsLoading(true);
fileManager.load(rootPath).then(() => {
setIsLoading(false);
setCurrentDirectoryPath(rootPath);
});
}, [rootPath, fileManager]);
return (
<>
<Modal size="xl" opened={open} onClose={onClose}>
<Toolbar />
<BodyWrapper>
<Grid>
<Grid.Col span={3}>
<Sidebar />
</Grid.Col>
<Grid.Col span={9}>
<Content />
</Grid.Col>
</Grid>
</BodyWrapper>
</Modal>
</>
);
}
FileManager.defaultProps = {
rootPath: "/",
};
Now we added the rootPath
to the dependency array, so the useEffect
will be called once the rootPath
is changed, also we called load
as it will load the given path as current directory.
Before we jump to the load
method, let's add another condition that if the file manager is not open then ignore the load.
// load root directory
useEffect(() => {
if (!rootPath || !open) return;
setIsLoading(true);
fileManager.load(rootPath).then(directoryNode => {
setIsLoading(false);
currentDirectoryNode(directoryNode);
});
}, [rootPath, fileManager, open]);
Creating load method
The load
method will load the given path and return a promise that will be resolved once the path is loaded, also it will return a Node
with the loaded directory as well.
Also we need to define the root path in the file manager, so we'll create a setRootPath
method.
// file-manager/utils/FileManager.ts
import { Node } from "../types/FileManager.types";
export default class FileManager {
/**
* Root path
*/
protected rootPath = "/";
/**
* Current directory path
*/
protected currentDirectoryPath = "/";
/**
* Current directory node
*/
protected currentDirectoryNode?: Node;
/**
* Set root path
*/
public setRootPath(rootPath: string): FileManager {
this.rootPath = rootPath;
return this;
}
}
Heading to FileManagerService
let's create a new instance from it and export it so we can use it directly.
At the end of the file, we'll export the instance.
// file-manager-service.ts
const fileManagerService = new FileManagerService();
export default fileManagerService;
Now let's define the load
method in the FileManager
// FileManager.ts
import fileManagerService from "../services/file-manager-service";
import { Node } from "../types/FileManager.types";
export default class FileManager {
/**
* Root path
*/
protected rootPath = "/";
/**
* Current directory path
*/
protected currentDirectoryPath = "/";
/**
* Current directory node
*/
protected currentDirectoryNode?: Node;
/**
* Set root path
*/
public setRootPath(rootPath: string): FileManager {
this.rootPath = rootPath;
return this;
}
/**
* Load the given path
*/
public load(path: string): Promise<Node> {
return new Promise((resolve, reject) => {
fileManagerService
.list(path)
.then(response => {
this.currentDirectoryPath = path;
this.currentDirectoryNode = response.data.node;
resolve(this.currentDirectoryNode as Node);
})
.catch(reject);
});
}
}
We used the list
method from the FileManagerService
to get the directory node, then we stored it in the currentDirectoryNode
and returned it.
But we need to make a small modification in the list
method, we need to return a single node from the backend that contains all listed children inside it directly, so the backend handle it.
import FileManagerServiceInterface from "../types/FileManagerServiceInterface";
import {
+ newNode,
- listNodes
} from "../utils/data";
export class FileManagerService implements FileManagerServiceInterface {
/**
* {@inheritDoc}
*/
public list(directoryPath: string): Promise<any> {
return new Promise(resolve => {
resolve({
data: {
- node: listNodes(),
+ node: newNode(),
},
});
});
}
}
const fileManagerService = new FileManagerService();
export default fileManagerService;
Going back to the load
method, we also defined the current directory node which will be resolved for the load promise.
Now let's head to our FileManager.tsx
component and console.log currentDirectoryNode
to see what will be returned
...
const { current: fileManager } = useRef(new BaseFileManager());
console.log(currentDirectoryNode);
As it is being randomly generated let's make another generated node for directories to make sure we always have directory.
// utils/data.ts
export function newNode(): Node {
const isDirectory = faker.datatype.boolean();
const node: Node = {
name: isDirectory ? faker.system.directoryPath() : faker.system.fileName(),
path: faker.system.filePath(),
size: faker.datatype.number({ min: 1, max: 100000 }),
isDirectory,
};
if (node.isDirectory) {
node.children = listNodes(1, 3);
}
return node;
}
export function newDirectoryNode() {
const node = newNode();
node.children = listNodes(faker.datatype.number({ min: 3, max: 4 }), 5);
node.name = faker.system.directoryPath();
node.isDirectory = true;
return node;
}
I also made small modification on listNodes
to accept min and max values to make sure we always have a directory with at least three children.
Now let's update our service class to call the newDirectoryNode
instead of newNode
- import { newNode } from "../utils/data";
+ import { newDirectoryNode } from "../utils/data";
return new Promise(resolve => {
resolve({
data: {
- node: newNode(),
+ node: newDirectoryNode(),
},
});
});
We'll pause here and continue in the next article, if you get confused read the final code from the repository below then read the article again.
Next article will be about listing directories in the sidebar.
Article Repository
You can see chapter files in Github Repository
Don't forget the
main
branch has the latest updated code.
Salam.
Top comments (0)