Building a Custom Image Editor with Xata
Tags: xata, vue, javascript, hackathon
Published: 2023-09-26 10:07:14

Imagine having the ability to build our very own image editor, one that is not just functional but also oozes with style and user-friendliness. In this article, we will unveil the secret recipe for creating a custom image editor that is as delightful to use as it is to build.

"*Building an image editor? Isn't that too complex?*" Well, fret not! We are about to prove that with a pinch of [Vue.js](https://vuejs.org/), a dash of [Xata](https://xata.io/), and a sprinkle of imagination, we can create an image editor that will leave users clicking and smiling.

This article will discuss building an image editing tool that allows users to upload, transform, and download images effortlessly. We will walk through the process of building an image editor step by step using Vue.js and Xata.

[Xata](https://xata.io/) is a Serverless Data platform designed to simplify how developers work with data. It combines the power of a traditional database with the ease of use found in SaaS (Software as a Service) spreadsheet applications.

Xata recently introduced a new feature - [File Attachments](https://xata.io/blog/file-attachments). This feature lets developers integrate files, images, and documents directly into their database records. It eliminates the need for separate storage services and streamlines the process of managing structured data and binary objects like images, videos, and documents.

In essence, Xata's File Attachments revolutionize data app development by simplifying the architecture and reducing the number of services developers need to manage while enhancing the user experience and versatility of applications.

## GitHub

Check out the complete source code [here](https://github.com/MoeRayo/image-editor).

## Netlify

We can access the live demo [here](https://xata-image-transformer.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';
```

## Creating a Xata Database

To set up a database in Xata, we will start by logging into our existing account or creating a new one. After accessing our account, we will navigate to the database section in the user dashboard. Here, we can create a new database. We will name this database `image-transformation` for our specific use case since it aligns with the task at hand.

Once we create this database, the next step is to establish a table within that database and define the necessary records or fields we want the table to contain. In our project, we will name the table `images`. Within the **images** table, we will create a record called `image` with a type of **file**. This image record will be the focal point for all our manipulations. 

The accompanying media below provides a visual guide to creating a record.

![Creating a record](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/chlu5yj45lupc9wvtco8.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

In an ideal scenario, our solution should include a welcoming landing page that provides users with essential information about the product. For our project, we will design a simple landing page that offers users a quick overview of the product's purpose. To achieve this, we will create a `views/HomeView.vue` file and insert the following code:

```javascript
<template>
  <header class="vh-100 bg-light-pink dt w-100 helvetica">
    <div
      style="
        background: url(http://mrmrs.github.io/photos/display.jpg) no-repeat
          center right;
        background-size: cover;
      "
      class="dtc cover ph3 ph4-m ph5-l tc">
      <div class="tc mt4 mb5">
        <h1 class="f2 f-subheadline-l measure lh-title nb1 fw9">X-Transformr</h1>
        <h2 class="f6 fw6 black">
          Store, transform and update images using Xata (File Attachments)
        </h2>
      </div>
    </div>
  </header>
</template>
```

At the addition of the code above, our view should appear like the below:

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


## Adding the uploading mechanism

**Creating the upload UI**
We will include a user-friendly input interface that enables users to upload images from which we can extract text. We will integrate the following code block into the `components/Uploadcontainer.vue` file to achieve this.


```javascript
<template>
  <div class="flex flex-column flex-row-ns pa3 calisto bg-black-05">
    <div class="w-50-ns w-100 ph3">
      <form enctype="multipart/form-data">
        <h2>Add Image</h2>
        <div
          class="b--dashed bw1 b--light-purple pa3 hover-bg-black-10 bg-animate pointer relative h4">
          <input
            type="file"
            id="fileInput"
            accept="image/*"
            class="input-file absolute w-100 h4 pointer o-0" />
          <p class="tc f4">
            Drag your image 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 image files using the `**accept="image/*"` attribute."

**Creating the transformation UI**
Xata has a number of image [transformation options](https://xata.io/docs/sdk/image-transformations#options) that we can apply to images to transform them to our desired taste in real-time. In our case, we will focus on four transformation options, which include the **height**, **width** **blur,** and **sharpness** of the image. To achieve this, we want to create a mechanism that allows users to add the transformation option values. For this, in the `components/Uploadcontainer.vue` we will create a `<form></form>` and add input methods that can be fed to Xata.

{% gist https://gist.github.com/MoeRayo/c041e35a92e1e6047ec3d006a468e5f1 %}

In the code above, we achieved the following:


- Created a form with input fields that allow the user to adjust the parameters of interest
- In the data property, we initialized the input fields and set default values for each parameter.
- Added a download button to allow users to get the transformed images

In the `views/HomeView.vue`, we imported the component like so:


```javascript
<template>
  <header class="vh-100 bg-light-pink dt w-100 helvetica">
    <div
      style="
        background: url(http://mrmrs.github.io/photos/display.jpg) no-repeat
          center right;
        background-size: cover;
      "
      class="dtc cover ph3 ph4-m ph5-l tc">
      <div class="tc mt4 mb5">
        ...
      </div>
      <Uploadcontainer />
    </div>
  </header>
</template>
<script>
  import Uploadcontainer from "@/components/Uploadcontainer.vue";
  export default {
    components: {
      Uploadcontainer,
    },
  };
</script>
```
We did the above to facilitate the component to show up on the landing page. At this point, our landing page should look like the below:


![transformation ui](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1s27nkiuy410mvvo7gs5.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 image to Xata
Before we can transform the image, we will have to first upload the image to Xata. To do this, we will edit the `components/Uploadcontainer.vue` to the below:

{% gist https://gist.github.com/MoeRayo/05ff9d440c962a386b6c10bbe90d5ddb %}

The code block above does the following:

- Represented various component states during image processing using `STATUS_INITIAL`, `STATUS_SAVING`, `STATUS_SUCCESS`, and `STATUS_FAILED` definitions, which we initialised in the corresponding computed properties - `isInitial`, `isSaving`, `isSuccess`, and `isFailed`. 
- **Image Upload Section**:
    - This section contains a form (`<form>`) with `enctype="multipart/form-data"` for file upload.
    - It displays an "Add Image" heading and a drag-and-drop or file input field that allows users to select image files.
    - Depending on the component's state, it shows messages like "Drag your image here to begin" or "Adding image..." to provide user feedback.
    - When an image is successfully added, it displays a success message along with an option to upload another image and previews the uploaded image.
- **Component Data**:
    - The component manages various data properties, including `uploadedImages`, `uploadError`, `currentStatus`, `uploadFieldName`, `imageDataArray`, `recordId`, `imageURL`, and `imageName`, to track the state and information related to image uploads and processing.
- Methods:
    - `reset()`: Resets the form and component's state to its initial state - `STATUS_INITIAL`
    - `save(formData)`: Initiate the image `uploadImageToXata()` to foster uploading to Xata by using the `upload` service
    - `filesChange(fieldName, fileList)`: Handles changes in the selected image files and prepares a `formData` object for upload.
    - `prepareFormData()`: Processes uploaded image data to create an object with metadata, base64 content, name, and URL for the uploaded image.
    - `uploadImageToXata()`: Uploads the processed image data to a database using the Xata client.

At this point, if you check your Xata dashboard, you should see an image uploaded like the below:

![Xata dashboard showing uploaded file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yugslkytalee473xmz1x.png)

In this image, we can see the `images` table with the `image` record. This `image` record currently has the uploaded image with the `id` (record_id) which we will be using later in our project.

When we upload an image, we should see our UI look like this:


![UI after uploading](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cyffwqil1c8pjzlrt18q.png)

## Transforming images

Now that we have successfully uploaded our image, it is now time to apply transformations. To do this, we will edit the `components/Uploadcontainer.vue` file and add the following:

{% gist https://gist.github.com/MoeRayo/af98fa73935f4816e1401c5dc490fc2c %}

In this code block, we achieved the following:

- Added a conditional render (`v-if="imageURL"`) based on the existence of an `imageURL` property. If the image has been uploaded successfully to Xata and we have a URL, the form component for customising the transformation values will become visible to the user
- `debounceTransform`: This function is a debounced version of the `editImage` method, which means it waits for a specified delay (2000 milliseconds or 2 seconds) after the user stops typing before executing `editImage`.
- `editImage`: This asynchronous function performs image editing and transformation using data from the Xata API. It calculates a new image URL based on the user's height, width, sharpening, and blur input and updates the component's properties accordingly.
- Installed **lodash** and used the `debounce()` function to control the rate at which the `debounceTransform` method is executed.

## Download image

After editing the pictures, we made available functionality for the user to download the edited image. We will edit the `components/Uploadcontainer.vue` to reflect this change.

```javascript
<template>
  <div class="flex flex-column flex-row-ns pa3 calisto bg-black-05">
    <div class="w-50-ns w-100 ph3">
      <!-- upload image -->
    </div>
    <section class="w-40-l w-50-m w-100 ph3 tl" v-if="imageURL">
      <h2>Transform Image</h2>
      <form class="ba b--black bw2 bg-white br2 mw6 center pv2 ph4 shadow-5 f6">
        <!-- input fields -->
        <button
          @click="downloadimage"
          class="center db pv2 ph3 mb3 tracked bg-black ba br3 white hover-black hover-bg-black-30 bg-animate pointer f7">
          Download
        </button>
      </form>
    </section>
  </div>
</template>
<script>
  import { transformImage } from "@xata.io/client";
  export default {
    data() {
      return {
        imageURL: "",
        imageName: "",
        transformedImageUrl: "",
      };
    },
    computed: {...},
    methods: {
    downloadimage() {
        this.transformedImageUrl = transformImage(this.imageURL, {
          download: this.imageName,
          format: "jpeg",
        });
        window.open(this.transformedImageUrl, "_blank");
      },
    },
    mounted() {...},
  };
</script>
```
At this point, our whole application should look like the below:


![Final application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2yroth0h8wg7yq0qw73e.gif)


## Conclusion

In this article, we talked about building an image editor using Xata. Xata acts as our database and the image transformer using the options made available by the Xata service.

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

- [Xata documentation](https://xata.io/docs)
- [Xata framework starter guide](https://xata.io/docs/getting-started/intro)
- [Xata image transformer options](https://xata.io/docs/sdk/image-transformations)