Welcome back!
In Chapter 5: Frontend Data Layer, we built the invisible "Messenger" that fetches data from the server. We have the data (JSON), but right now, it looks like this: [{"name": "holiday.jpg", "size": 2048}].
That is not very user-friendly.
In this chapter, we will build the Painter. We will take that raw data and turn it into a beautiful, interactive interface with icons, progress bars, and clickable buttons. We are using a framework called Vue.js.
Humans don't want to read text lists of files.
Vue.js allows us to build the interface using Components. Think of components as LEGO blocks.
Files.vue): The main container that decides if we should show a loading bar, an error message, or the actual file list.ListingItem.vue): A single component that represents one file. We use this block 100 times to show 100 files.Upload.vue): A specialized component that handles dragging files from your desktop into the browser.Files.vue)
This is the "Page Controller." It watches the URL. If you navigate to /files/photos, this component wakes up, asks the API for data, and decides what to show on the screen.
This is the magic of Vue.js. We don't manually tell the screen to update. We just change the Data.
isLoading = trueisLoading = falseLet's look at the most common scenario: A user navigates to a new folder.
/files/documents.Files.vue notices the change.loadingProgress = 10 (The red bar at the top starts moving).ListingItem for every file found.
Let's look at frontend/src/views/Files.vue. This is simplified to show how it manages the loading state.
// frontend/src/views/Files.vue
// When the URL changes, this function runs
async fetchData() {
// 1. Start the loading bar (visual feedback)
this.loadingProgress = 10;
mutations.setLoading("files", true);
try {
// 2. Ask the API (Chapter 5) for the files
let res = await filesApi.fetchFiles(source, path);
// 3. Save the data to the Store
mutations.replaceRequest(res);
// 4. Finish the loading bar
this.loadingProgress = 100;
} catch (e) {
// 5. If it fails, show the Error component
this.error = e;
}
}
Explanation:
this.loadingProgress: A variable linked to the width of the red line at the top of your screen.mutations.replaceRequest: Updates the global state. Vue notices this change and instantly paints the files on the screen.
Now that the page is loaded, we need to draw the files. We use ListingItem.vue. This component takes a file's details and renders an icon and a name.
// frontend/src/components/files/ListingItem.vue
export default {
// 1. Props: Input data received from the parent list
props: {
name: String,
isDir: Boolean,
type: String, // e.g., "image", "text"
size: Number,
},
computed: {
// 2. Logic to decide what the icon looks like
thumbnailUrl() {
// If it's an image, build the URL to fetch the preview
return filesApi.getPreviewURL(this.source, this.path);
}
},
methods: {
// 3. Logic for when the user clicks this item
click(event) {
if (this.isDir) {
// If it's a folder, open it (Change URL)
this.open();
} else {
// If it's a file, select it (Highlight it)
mutations.addSelected(this.index);
}
}
}
};
Explanation:
type props.click(): This handles the difference between navigation (opening a folder) and selection (highlighting a file to delete it).One of the most complex interactions in a file manager is Uploading. We want users to be able to drag a file from their computer directly into the browser.
This is handled by frontend/src/components/prompts/Upload.vue.
// frontend/src/components/prompts/Upload.vue
const onDrop = async (event) => {
// 1. Stop the browser from opening the file directly
isDragging.value = false;
event.preventDefault();
// 2. Get the list of files dropped
const droppedFiles = event.dataTransfer.files;
// 3. Process them (handle folders vs files)
processFileList(droppedFiles);
};
const processFileList = (fileList) => {
// 4. Format the files for the Upload Manager
const filesToAdd = Array.from(fileList).map((file) => ({
file,
relativePath: file.name,
}));
// 5. Add to the queue (starts the upload)
uploadManager.add(state.req.path, filesToAdd);
};
Explanation:
event.dataTransfer: This is a standard browser feature that holds the files you are dragging.uploadManager: A utility that takes these files and sends them to the backend API we built in Chapter 3: HTTP API & Routing.How does the application go from a URL change to a visible icon?
If you have a folder with 1,000 photos, loading 1,000 thumbnails at once will freeze the browser.
In ListingItem.vue, we use a technique called Intersection Observer.
// frontend/src/components/files/ListingItem.vue
mounted() {
// 1. Create an observer that watches for scrolling
this.observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// 2. If this item is visible on screen...
if (entry.isIntersecting) {
// 3. Load the thumbnail image!
this.isThumbnailInView = true;
}
});
});
// 4. Start watching this element
this.observer.observe(this.$el);
}
Explanation: The browser only downloads the image for the file after you scroll down to see it. This makes the interface feel incredibly fast, even with massive folders.
In this chapter, we brought the application to life visually:
Files.vue acts as the conductor, managing loading states and fetching data based on the URL.ListingItem.vue is the building block for the interface, converting data into clickable icons.We now have a fully functional File Manager! You can browse, upload, and manage files.
However, if you click on a photo or a video, we just download it. We don't see it yet. To make this a true media center, we need to generate previews and thumbnails on the server.
Next Chapter: Media Preview Pipeline
Generated by Code IQ