How to Build a File Explorer using Xata and Vue.js
Tags: webdev, javascript, xata, hackathon
Published: 2023-09-30 13:39:05

In our fast-paced digital age, where data rules supreme, having a robust and user-friendly file explorer is no longer a luxury but a necessity. Whether you are a developer seeking to streamline your file management tasks or a business owner looking to provide your users with an intuitive file navigation experience, building a File Explorer is a powerful solution. In this ever-evolving tech landscape, harnessing the right tools and frameworks is critical to creating efficient and responsive applications.

Enter [Xata](https://xata.io/) and [Vue.js](https://vuejs.org/) – a dynamic duo that promises to revolutionize how you approach file management. Xata, a modern, lightweight database designed for developers, and Vue.js, the progressive JavaScript framework known for its flexibility, combine seamlessly to help you craft a feature-rich File Explorer that is easy to develop and delightful to use.

Let us unlock the potential of Xata and Vue.js as we explore the fusion of Xata and Vue.js in building our own File Explorer that caters to our every need.


## GitHub

Check out the complete source code [here](https://github.com/MoeRayo/xata-file-explorer).

## Netlify

We can access the live demo [here](https://xata-file-explorer.netlify.app/).

## Prerequisite

Understanding this article requires the following:

- Installation of Node.js
- Basic knowledge of JavaScript
- Creating a free account with [Xata](https://xata.io)


## Creating a Vue app

We can create a new Vue app using the `npm create vue@latest` command.
Scaffolding the project would provide a list of options from which we can select the best fit for the project.

## Styling

The CSS framework we will use in this project is [Tachyons CSS](https://tachyons.io/), which we will install by running the command below in the terminal.

```javascript
npm i tachyons
```
Afterward, we will make it globally available for usage in the project by adding the line below in our `src/main.js`:

```javascript
import 'tachyons/css/tachyons.css';
```

## Installing dependencies

We will need some vital dependencies to supercharge our file explorer application. We will install them accordingly using the command below:

```javascript
npm install --save axios vue-axios @kyvg/vue3-notification vue-pdf-embed
```

From the above command, we installed the following packages in our application:


1. [axios](https://www.npmjs.com/package/axios): This is a popular JavaScript library for making HTTP requests from the browser. It allows Vue.js applications to interact with APIs and fetch data from external sources
2. [vue-axios](https://www.npmjs.com/package/vue-axios): This is a package that provides integration between Axios and Vue.js. It makes it easier to use Axios in Vue components.
3. [@kyvg/vue3-notification](https://www.npmjs.com/package/@kyvg/vue3-notification): This package is a Vue 3 notification plugin that shows notifications or alerts to the user in Vue applications. It is a handy tool for informing users about various events or updates.
4. [vue-pdf-embed](https://www.npmjs.com/package/vue-pdf-embed): This package embeds PDF files into Vue.js application. It allows displaying PDF documents within Vue applications, making it useful for a File Explorer where users might want to preview or interact with PDF files.

To use these new dependencies in our application, we will edit our `src/main.js` like so:

```javascript
import Notifications from "@kyvg/vue3-notification";
import axios from "axios";
import VueAxios from "vue-axios";
app.use(VueAxios, axios);
app.use(Notifications);
```
## Setting up the Xata database

We will harness the full potential of Xata as we build out our project. To kick things off, we will either log into our existing Xata account or create a new one. Once inside our Xata account, we will navigate to the database section and establish a fresh database tailored to our project's needs. In our specific case, we have aptly named our database `file-explorer` to align with the project's core objectives.

Once our database is in place, our next step involves crafting the essential tables and their corresponding records. We require two distinct tables within our project's scope: the 'users' and 'files' tables. The 'users' table serves as the repository for user information, while the 'files' table is designated to house detailed data about the files we intend to store within the Xata platform.

The media below shows we would create a **‘users’** table in Xata. The same process applies to creating the **'files'** table.

![creating users table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ozik4xvbigwud05k2sdu.gif)

The ‘users’ table encompasses records such as usernames, email addresses, and passwords. To add these records to the table, we will follow the process in the media below:
![adding records to the users table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxm8jtz9spy0bzfvz9pn.gif)

We will also create the relevant records for the **files** table as shown below:
![adding records to the files table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9xe9iweghg0r15isqas6.gif)

### Links and relationships in Xata
When we upload a file, we want to associate the file with the user responsible for the upload. To do this, Xata provides a record type of `link`, which allows us to set up relationships between entities (tables). In other words, we can link the 'users' table to the 'files' table in a one-to-many relationship.
 
We will have a target table and a source table for this to happen. In our case, the 'files' table will be the source, and the 'users' table will be the target. This arrangement means we target a single user within the 'users' table from the 'files' table.

Here is how we can create a new record in the **files** table called `user` and give it a type of **'link to table'**:

![creating records with the 'link to table' file type](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fpoojmqdbv88vvya84vc.gif)

## Setting up Xata in our Vue application

To incorporate Xata into our project, we will follow these installation steps:

### Install Xata SDK
We will run the following command in the command line interface:

```javascript
npx xata
```
### Initialize Xata
Now, we can initialize Xata for use within the application with this command:

```javascript
xata init
```
### Configuration Options
Xata will present us with various configuration options to choose from. After making our selections, Xata will generate essential files, including `.xatrc` and `.env`.

### Edit API Key
To ensure our Vue application can access the Xata environment, we will need to modify the Xata API key within the Vue app like so:

```javascript
VITE_XATA_API_KEY="add your api key"
```
We can now modify the `xata.js`  file to use this intialised `VITE_XATA_API_KEY` like so:

```javascript
const defaultOptions = {
  enableBrowser: true,
  apiKey: import.meta.env.VITE_XATA_API_KEY,
  databaseURL:
    "https://...",
};
```
 We would have seamlessly integrated Xata into our project by following the steps above.

## Creating the landing page

First, we will create a basic homepage and progressively enhance it as functionality requires. To do this, we will create a file in `views/HomeView.vue` and add the code block below:

```javascript
<template>
  <header class="vh-100 bg-light-pink dt w-100 helvetica">
    <div class="background-container dtc v-mid cover ph3 ph4-m ph5-l">
      <div class="flex justify-around">
        <div>
          <h1 class="f2 f-subheadline-l measure lh-title nb1 fw9 white">
            Xata File Explorer
          </h1>
          <h2 class="f6 fw6 white">
            Storing and managing files made easy with Xata (File Attachments)
          </h2>
        </div>
      </div>
    </div>
  </header>
</template>
<style>
  .background-container {
    background-image: url("../assets/tamanna-rumee-QR56LDZADZ4-unsplash.jpg");
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center;
  }
</style>
```
Now, when users open the link to the application, the landing page shows the below:


![landing page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6gjtgr25a0wpff85wbbl.png)

## Building the Signup Page

To enable user registration for our application, a signup page is essential. We will create a new file called `components/Signup.vue` to achieve this. Then, we will populate it with the following code:


```javascript
<template>
  <form
    @submit.prevent="signUp"
    class="ba b--white dark-pink bw4 bg-black br2 mw6 center pv2 ph4 shadow-5 f6">
    <h2 class="ttc tc">Sign Up</h2>
    <label for="name" class="db mb2">Username</label>
    <input
      id="username"
      v-model="username"
      name="username"
      type="text"
      class="db mb3 w-100 br2 pa2 ba bw1 b--black"
      placeholder="John Doe" />
    <label for="email" class="db mb2">Email</label>
    <input
      id="email"
      v-model="email"
      name="email"
      type="email"
      class="db mb3 w-100 br2 pa2 ba bw1 b--black"
      placeholder="example@email.com" />
    <label for="password" class="db mb2">Password</label>
    <input
      id="password"
      v-model="password"
      name="password"
      type="password"
      class="db mb3 w-100 br2 pa2 ba bw1 b--black"
      placeholder="••••••••" />
    <button
      type="submit"
      class="center db pv2 ph3 mb3 tracked bg-black ba br3 white hover-black hover-bg-dark-pink bg-animate pointer f7">
      Sign up
    </button>
    <p class="f7">
      Already have an account?
      <a @click="switchToSignin" class="white pointer b">Sign in</a>
    </p>
  </form>
</template>
```
With the above, we have effectively established the user interface for our Signup page. Our next focus will be on implementing the essential functionality for authentication and authorization.

We will leverage Xata to achieve our authentication objectives. To integrate Xata into our SignupView, let us start by adding the following code to the `components/Signup.vue` file:
```javascript
<script>
  import { getXataClient } from "@/xata";
  export default {
    name: "Signup",
    data: () => ({
      username: "",
      email: "",
      password: "",
    }),
    methods: {
      switchToSignin() {
        this.$emit("user-has-account", true);
      },
      async signUp() {
        const xata = getXataClient();
        const user = await xata.db.users.filter("email", this.email).getFirst();
        if (!user) {
          await xata.db.users
            .create({
              username: this.username,
              password: this.password,
              email: this.email,
            })
            .then((res) => {
              this.$router.push({
                name: "dashboard",
                params: { username: res.username },
                query: { user: res },
              });
              this.$notify({
                type: "success",
                text: "Account creation successful!",
              });
            });
        }
      },
    },
  };
</script>
```
The code block provided above allowed us to accomplish the following:

- Imported the Xata client, establishing the connection necessary for interacting with our database
- Implemented a mechanism to check for the existence of a user. Our code creates a new user profile if the current user doesn't exist
- Upon successfully creating a user account, the user is seamlessly redirected to the ***dashboard*** (which we will create later). Here, the users can access the functionality required to manage their files efficiently


## Building the sign-in page

In addition to user registration, providing a seamless sign-in experience for individuals who already possess an account is essential. We will generate a new file named `Signin.vue` to achieve this. Within this file, we will insert the code below:

```javascript
<template>
  <form
    class="ba f6 b--white bw4 bg-black dark-pink br2 center pa3 shadow-5"
    @submit.prevent="signIn">
    <h2 class="ttc tc">Sign In</h2>
    <label for="email" class="db mb2">Email</label>
    <input
      id="email"
      v-model="email"
      name="email"
      type="email"
      class="db mb3 w-100 br2 pa2 ba bw1 b--black"
      placeholder="example@email.com" />
    <label for="password" class="db mb2">Password</label>
    <input
      id="password"
      v-model="password"
      name="password"
      type="password"
      class="db mb3 w-100 br2 pa2 ba bw1 b--black"
      placeholder="••••••••" />
    <button
      type="submit"
      class="f7 center db pv2 ph3 mb3 tracked bg-black ba br3 white pointer hover-black hover-bg-dark-pink bg-animate pointer">
      Sign in
    </button>
    <p>
      Don't have an account?
      <a @click="goToSignUp" class="white b f7 pointer">Sign up</a>
    </p>
  </form>
</template>
```

The code provided above furnishes us with the foundation for our sign-in interface. However, we must incorporate some key functionalities to ensure it function as intended.
To achieve this, let us add the following code to the `<script></script>` section of the `SigninView.vue` file:

```javascript
<script>
  import { getXataClient } from "@/xata";
  export default {
    name: "Signin",
    data: () => ({
      email: "",
      password: "",
    }),
    methods: {
      goToSignUp() {
        this.$emit("go-to-sign-up");
      },
      async signIn() {
        const xata = getXataClient();
        const user = await xata.db.users.filter("email", this.email).getFirst();
        if (!this.email || !this.password) {
          this.$notify({ type: "error", text: "Please fill all empty fields" });
        } else if (
          this.email !== user.email ||
          this.password !== user.password
        ) {
          this.$notify({ type: "error", text: "Incorrect credentials" });
          this.email = "";
          this.password = "";
        } else {
          this.$router.push({
            name: "dashboard",
            params: { id: user.id },
          });
          this.$notify({ type: "success", text: "Login successful!" });
        }
      },
    },
  };
</script>
```
The code block above does the following:

- Imported the Xata client, establishing the connection needed to interact with our database
- Our code checks if the user has provided an **email** and a **password**. If either of these is missing, it returns the relevant error message
- Implemented a verification step by comparing the entered **email** and **password** with the corresponding records stored in the database
- Upon successful validation, the user is seamlessly redirected to the ***dashboard***. Here, they gain access to the functionality required for managing files effortlessly

To view the *sign-in* and *signup* components, we will import them into the `views/HomeView.vue`, like so:

```javascript
<template>
  <header class="vh-100 bg-light-pink dt w-100 helvetica">
    <div class="background-container dtc v-mid cover ph3 ph4-m ph5-l">
      <div class="flex justify-around">
        <!-- main code -->
        <div class="mv2">
          <div v-if="userHasAccount">
            <Signin @go-to-sign-up="switchToSignUp" />
          </div>
          <div v-else>
            <Signup @user-has-account="handleUserHasAccount" />
          </div>
        </div>
      </div>
    </div>
  </header>
</template>
<script>
  import Signin from "@/components/Signin.vue";
  import Signup from "@/components/Signup.vue";
  export default {
    components: {
      Signin,
      Signup,
    },
    data() {
      return {
        userHasAccount: false,
      };
    },
    methods: {
      handleUserHasAccount(value) {
        this.userHasAccount = value;
      },
      switchToSignUp() {
        this.userHasAccount = false;
      },
    },
  };
</script>
```
The code block above does the following:

- Imported the two child components, `Signin` and `Signup`, which handle the sign-in and signup functionality
- The `data` property initialises the component's `userHasAccountdata` property as `false`. This property controls whether the sign-in or signup form should be displayed
- `handleUserHasAccount(value)`: This method updates the `userHasAccount` property based on whether the user has an account. It is called when the `Signup` component emits the `user-has-account` event
- `switchToSignUp()`: This method sets `userHasAccount` to `false`, switching the view to the signup form

At this point, our interface should look like the below:


![landing page with sign-in and sign-up interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q69nzpwsbaoswx481s40.gif)

## Adding upload mechanism

**Designing the Upload User Interface**
To streamline the process of uploading files, we will create a new file named, `components/Uploader.vue`, and then we will add the following code block into the  file:

```javascript
<template>
  <div class="flex flex-column flex-row-ns pa3 calisto bg-black-05">
    <div class="w-100 ph3">
      <form enctype="multipart/form-data" v-if="isInitial || isSaving">
        <h2>Upload File</h2>
        <div
          class="b--dashed bw1 b--light-purple pa3 hover-bg-black-10 bg-animate pointer relative h4">
          <input
            type="file"
            accept="image/*,application/pdf"
            class="input-file absolute w-100 h4 pointer o-0" />
          <p  class="tc f4">
            Drag your file here to begin<br />
            or click to browse
          </p>
        </div>
      </form>
    </div>
  </div>
</template>
```
We achieved the following from the above:

- Implemented a `<form>` element with the `enctype="multipart/form-data"` attribute, facilitating file uploads
- Incorporated an `input` field with the `file` type attribute, allowing file uploads and restricting the input to accept only pdf and image files using the `accept="``image/*,application/pdf"` attribute

Now, our upload interface should look like the below:


![uploader interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j21ksx1qyj6oxbqo01ss.png)

### Adding an upload service
To handle the file upload to be displayed in the UI, we will create a `utils/file-upload.service.js` and add the code block below:

```javascript
function upload(formData) {
  const photos = formData.getAll('photos');
  const promises = photos.map((x) => getImage(x)
  .then(img => ({
    id: img,
    originalName: x.name,
    fileName: x.name,
    url: img
  })));
  return Promise.all(promises);
}
function getImage(file) {
  return new Promise((resolve, reject) => {
    const fReader = new FileReader();
    const img = document.createElement('img');
    fReader.onload = () => {
      img.src = fReader.result;
      resolve(getBase64Image(img));
    }
    fReader.readAsDataURL(file);
  })
}
function getBase64Image(img) {
  const canvas = document.createElement('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0);
  const dataURL = img.src;
  return dataURL;
}
export { upload }
```
The code above reads the image we will upload, draws it into a canvas, and then converts it to a Base64 string.

## Uploading the file to Xata

To upload the file to Xata, we will edit the `components/Uploader.vue` to the below:

{% gist https://gist.github.com/MoeRayo/093118566296a163ca5efcc0820ba132 %}

In the code block above, we achieved the following:

- Represented various component states during file uploading using `STATUS_INITIAL`, `STATUS_SAVING`, `STATUS_SUCCESS`, and `STATUS_FAILED` definitions, which we initialised in the corresponding computed properties - `isInitial`, `isSaving`, `isSuccess`, and `isFailed` 
- **File Upload Section**:
    - This section contains a form (`<form>`) with `enctype="multipart/form-data"` for file upload
    - It displays an "Upload File" heading and a drag-and-drop or file input field that allows users to select files
    - Depending on the component's state, it shows messages like "Drag your file here to begin" or "Adding file..." to provide user feedback
    - When a file is successfully added, it displays a success message and an option to upload another file and previews the uploaded file just by the side
        - If the uploaded file is an image, then the `<img />` element will have its `:src` attribute populated with the right URL
        - However, if the file is a pdf, the earlier installed `vue-pdf-embed` package has its `:source` filled with the right pdf URL to show the document
- **Component Data**:
    - The component manages various data properties, including `uploadError`, `currentStatus`, `fileDataObject`, `fileURL`, and `fileName`, to track the state and information related to the file uploads and processing
- Methods:
    - `reset()`: Resets the form and component's state to its initial state - `STATUS_INITIAL`
    - `save(formData)`: Initiate the file `uploadFileToXata()` to foster uploading to Xata by using the `upload` service
    - `filesChange(fieldName, fileList)`: Handles changes in the selected files and prepares a `formData` object for upload
    - `prepareFormData()`: Processes uploaded file data to create an object with metadata, base64 content, name, and URL for the uploaded image
    - `uploadFileToXata()`: Uploads the processed file data to a database using the Xata client. The `xata.db.files.create({ … })` takes in the `user` object, which is necessary to link the file we will upload to the user who uploads it
    

At this stage, our user interface will look like this:

![file upload to xata](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h9ileuva678zuj798p8o.gif)

## Listing uploaded files

We have done a great job by allowing the user upload their files to Xata database. But our file explorer will not be enough if the users cannot view these files and download them if need be. To allow the user view all the files we have uploaded, we will create a new file called, `components/Filelist.vue`. In this newly created file, we will add the code below:

```javascript
<template>
  <main class="flex flex-wrap items-center justify-between">
    <div v-if="files">
      <section
        class="w-33-l w-50-m w-100 h-50"
        v-for="file in files"
        :key="file.id">
        <div
          class="link dt bb b--black-10 pb2 mt2 blue pointer flex items-center">
          <div class="dtc w3">
            <div v-if="file.file.mediaType.toLowerCase().includes('image')">
              <img :src="file.file.url" alt="Uploaded Image" />
            </div>
            <div v-else-if="file.file.mediaType.toLowerCase().includes('pdf')">
              <img
                src="../assets//pdf-placeholder.png"
                alt="pdf placeholder Image" />
            </div>
          </div>
          <div class="dtc v-top pl2">
            <h1 class="f6 f5-ns fw6 lh-title black mv0">File Name:</h1>
            <h2 class="f7 fw4 mt2 mb0 black-60">{{ file.file.name }}</h2>
            <a
              class="bg-purple dib mv2 f7 pa1 br2 pointer black"
              @click="viewFile(file)"
              >View File</a
            >
          </div>
        </div>
      </section>
    </div>
    <div v-else class="tc f4">No files uploaded yet</div>
  </main>
</template>
<script>
  import { getXataClient } from "@/xata";
  const xata = getXataClient();
  export default {
    data() {
      return {
        files: [],
      };
    },
    components: {
      VuePdfEmbed,
    },
    mounted() {
      this.getFilesFromXata();
    },
    methods: {
      async getFilesFromXata() {
        const userId = this.$route.params.id;
        this.files = await xata.db.files
          .filter({ "user.id": userId })
          .getMany();
      },
    },
  };
</script>
```
The code above does the following:

- Vue.js directives like `v-if,` `v-for,` and `v-else` conditionally render content based on whether there are files to display
- Iterates through the list of files and displays each file's details, such as its type (image or PDF), name, and an option to view the file
- The `data` function initializes the `files` array, storing the list of files to be displayed
- In the `mounted` lifecycle hook, the component calls the `getFilesFromXata` method to fetch the list of files associated with the user when the component is mounted
- The `getFilesFromXata` method retrieves files associated with a specific user using Xata's database query capabilities. It filters files based on the user's ID and populates the `files` array with the retrieved data

Our interface should appear like below:
![Listing uploaded files interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u26kt9jj095u05uvbpv1.gif)

## Viewing file content

The implementation above allows users to see a list of all the files they have uploaded to Xata, but it does not cover viewing the file content. To allow users view the uploaded files, we will create a new component called, `Fileviewer.vue` and add the code below:

```javascript
    <template>
      <div class="overlay">
        <!-- Overlay content -->
        <div class="overlay-content">
          <!-- Close button -->
          <span
            class="close-button f3 bg-dark-pink white b h1 w1 br3 pa2 center auto flex items-center dim pointer"
            @click="closeOverlay"
            >X</span
          >
          <div v-if="fileType === 'image'">
            <img :src="fileUrl" alt="File" />
            <img
              @click="downloadImage"
              src="../assets/icon_download.svg"
              class="bg-dark-pink pa2 flex items-center br2 white download-button dim" />
          </div>
          <div v-else-if="fileType === 'pdf'">
            <a ref="downloadLink" @click="downloadPdf(fileUrl)">
              <img
                src="../assets/icon_download.svg"
                class="bg-dark-pink pa2 flex items-center br2 white download-button dim" />
            </a>
            <vue-pdf-embed :source="fileUrl" />
          </div>
        </div>
      </div>
    </template>
    <script>
      import VuePdfEmbed from "vue-pdf-embed";
      export default {
        props: {
          fileUrl: String,
          fileType: String,
        },
        components: {
          VuePdfEmbed,
        },
        methods: {
          closeOverlay() {
            this.$emit("close-overlay");
          },
          downloadImage() {
            this.$emit("download-image");
          },
          async downloadPdf(file) {
            try {
              const response = await this.axios.get(file, {
                responseType: "blob", // Set the response type to 'blob' for binary data
              });
              const blob = new Blob([response.data], { type: "application/pdf" });
              const url = window.URL.createObjectURL(blob);
              const link = document.createElement("a");
              link.href = url;
              link.download = "downloaded.pdf"; // Set the desired file name
              link.click();
              window.URL.revokeObjectURL(url);
            } catch (error) {
              console.error("Error downloading PDF:", error);
            }
          },
        },
      };
    </script>
    <style scoped>
      .overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.8);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 9999;
      }
      .overlay-content {
        background: #fff;
        padding: 20px;
        max-width: 80%;
        max-height: 80%;
        overflow: auto;
      }
      .close-button {
        position: absolute;
        top: 25px;
        right: 30px;
      }
      .download-button {
        position: absolute;
        top: 25px;
        left: 30px;
      }
    </style>

```
- An "X" button for closing the overlay (the `closeOverlay` method is called on click)
- `fileType` prop (either 'image' or 'pdf') displays an image or a PDF viewer component
- It imports the `VuePdfEmbed` component for displaying PDFs
- The component receives two props: `fileUrl` (the file URL to display or download) and `fileType` (indicating whether it's an image or PDF)
- `closeOverlay()`: This method emits the "close-overlay" event to signal that the overlay should be closed
- `downloadImage()`: This method emits the "download-image" event to trigger image download
- `downloadPdf(file)`: This method handles PDF download. It uses Axios to fetch the PDF file as binary data, creates a Blob from the response, and generates a downloadable link for the user. If an error occurs during the download process, it's logged to the console

In the `Filelist.vue` component, we will import the `Fileviewer.vue` and add the necessary methods like so:

```javascript
<template>
  <main class="flex flex-wrap items-center justify-between">
    <div v-if="files">
      <section
        class="w-33-l w-50-m w-100 h-50"
        v-for="file in files"
        :key="file.id">
        <div
          class="link dt bb b--black-10 pb2 mt2 blue pointer flex items-center">
          <div class="dtc w3">
            <!-- codes -->
          </div>
          <div class="dtc v-top pl2">
            <!-- codes -->
            <file-viewer
              v-if="showOverlay"
              ref="goat"
              :fileUrl="overlayFileUrl"
              :fileType="overlayFileType"
              @close-overlay="closeOverlay"
              @download-image="downloadImage"></file-viewer>
          </div>
        </div>
      </section>
    </div>
  </main>
</template>
<script>
  import { transformImage } from "@xata.io/client";
  import { getXataClient } from "@/xata";
  import Fileviewer from "@/components/Fileviewer.vue";
  const xata = getXataClient();
  export default {
    data() {
      return {
        showOverlay: false,
        overlayFileUrl: "",
        overlayFileType: "",
      };
    },
    components: {
      Fileviewer,
    },
    mounted() {
      this.getFilesFromXata();
    },
    methods: {
    // other methods
      viewFile(file) {
        this.overlayFileUrl = file.file.url;
        file.file.mediaType.toLowerCase().includes("image")
          ? (this.overlayFileType = "image")
          : (this.overlayFileType = "pdf");
        this.showOverlay = true;
      },
      closeOverlay() {
        this.showOverlay = false;
      },
      downloadImage() {
        const imageToDownload = transformImage(this.overlayFileUrl, {
          download: "image",
          format: "jpeg",
        });
        window.open(imageToDownload, "_blank");
      },
    },
  };
</script>
```
We achieved the following using the code block above:

- Imported `transformImage` from "@xata.io/client" and `getXataClient` from "@/xata"
- `viewFile(file)`: When a user clicks on a file, this method is called. It sets the `overlayFileUrl` to the URL of the clicked file, determines the file type (image or PDF) based on its media type, and sets `showOverlay` to true to display the overlay
- `closeOverlay()`: This method is called when the user wants to close the overlay. It sets `showOverlay` to false, hiding the overlay
- `downloadImage()`: This method is called when the user wants to download an image. It transforms the image using Xata's `transformImage` function, then opens a new browser window to download the transformed image

We should have our UI looking like this at this stage:


![interface for file viewing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9o4nc22y7wx0vk1ookd4.gif)



Now we will create a new file called, `views/DashboardView.vue` which will be our dashboard. In this file, we will import the `components/Uploader.vue` and the `components/Filelist.vue` file, like so:

```javascript
<template>
  <div class="bg-light-purple vh-100 dt w-100 helvetica pv2 ph3">
    <div class="flex justify-between items-center">
      <h1 class="ttc">Xata File Explorer</h1>
      <span class="b db underline-hover pointer" @click="signOut"
        >Sign out</span
      >
    </div>
    <Uploader />
  </div>
</template>
<script>
  import Uploader from "@/components/Uploader.vue";
  export default {
    components: {
      Uploader,
    },
    methods: {
      signOut() {
        this.$router.push({ name: "home" });
        this.$route.params.id = "";
        this.$notify({ type: "success", text: "User logged out!" });
      },
    },
  };
</script>
```

In the code above, we achieved the following:

- Created a new file called `views/DashboardView.vue`
- Imported the `Upload` component
- Created a `signOut` method that logs users out

At this point, our file explorer application built with Xata should look like the below:


![the complete file explorer application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mpg68293ls2bywzibhu0.gif)

## Conclusion

In this article, we talked about building a File Explorer using Xata. This project exemplifies the capabilities of Xata and Vue.js and the amazing tools we can build from combining these two tools together or individually. The File Explorer we built is the beginning of what we can achieve.

## Resources

Here are some additional resources that will be helpful when working with Xata:

- [Xata documentation](https://xata.io/docs)
- [Schema on Xata](https://xata.io/docs/concepts/schema)
- [Xata framework starter guide](https://xata.io/docs/getting-started/intro)
- [Xata data model](https://xata.io/docs/concepts/data-model)