Last updated: January 26

<span aria-hidden="true" id="creating-an-einstein-quote-generator-with-transloadit"></span>

# Creating an Einstein quote generator with Transloadit

![Tyler McAllister](/assets/images/teammates/tyler.png?dpl=dpl_AsMTCxwVXNuJ5TmdPevSCe63JuJo)

**Tyler McAllister**

Technical Writer · Trondheim, Norway · Show bio

Animators, writers, musicians, and designers of all kinds have managed to flourish using the web as a vehicle to spread their work. And of course, many programmers can attribute their success and experience to the internet. But if there's one creative force on the web that has exploded in popularity - it's memes. Cue our state-of-the-meme-art[Einstein quote generator⁠](https://mcallistertyler95.github.io/einstein-quotes/), made with React and using [Robodog⁠](https://uppy.io/docs/robodog/).

Note: Robodog has been deprecated. For new integrations, use Uppy’s[Transloadit plugin⁠](https://uppy.io/docs/transloadit/) (Dashboard UI or a custom UI).

At Transloadit, we're always thinking of creative and unique ways to utilize our services. Some of our ideas may consist of building extremely useful tools with a great amount of utility, but sometimes we just want to have some good silly fun. I'm sure you know which category an Einstein quote generator falls into 😉

Regardless of how silly it may seem, this is a great opportunity for us to take a look at some of the more unique ways our services can be used. This is also a nice little showcase for the recently released [Uppy 1.0⁠](https://uppy.io/blog/2019/04/1.0/). The quote generator will overlay a fancy-looking HTML canvas onto one of your selected Einstein images and display a quote that Einstein... has (probably) never said!

<span aria-hidden="true" id="setting-everything-up"></span>

## Setting everything up

For the webpage, we'll be looking at the React library. I'd definitely recommend it for quick generation of webpages and its popularity means there's plenty of support out there. The star of the show is [Robodog⁠](https://uppy.io/docs/robodog/), one of the newer tools that we've released. Robodog is based on [Uppy⁠](https://uppy.io/), our file uploader that handles all of the intricacies involved with uploading files. Using Robodog, we can interface with the Transloadit API to runAssemblies using our uploaded files. Finally, the results of the quote generator are going to be stored on [Firebase⁠](https://firebase.google.com/), so we have a place to access some of our favorite quotes.

Before getting started, ensure that you have a valid account on Transloadit and have created a Google Firebase account. There's some setup involved on both sites, which we will go through shortly. Additionally, ensure you've got[Node.js and npm⁠](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) installed, as well as [React⁠](https://reactjs.org/).

<span aria-hidden="true" id="transloadit-and-firebase"></span>

## Transloadit and Firebase

Firstly, we will set up a [Firebase account⁠](https://firebase.google.com) and create a new project. You can find the specifics of setting up a Firebase account in one of our previous[Let's Build posts](/blog/2018/12/2h-youtube-clone.md). Ideally, we should set up our Firebase credentials on the Template Credentials page located on Transloadit. This will require a Google Project ID, the link to the storage bucket where we will export our files to and the JSON key file which contains an abundance of information - including our private key. Note that I had to generate my JSON key file from the[Google Cloud Platform⁠](https://console.cloud.google.com) rather than the[Firebase console⁠](https://console.firebase.google.com) to get things fully up and running.

<span aria-hidden="true" id="an-overview-of-the-quote-generator"></span>

## An overview of the quote generator

Before jumping straight into the Assembly that we will use with the generator, I will give an overview of the basic features of[the webpage⁠](https://mcallistertyler95.github.io/einstein-quotes/).

The Einstein Quote Generator is a single webpage created in React. In terms of UI, I'm using[React Bootstrap⁠](https://react-bootstrap.github.io/) specifically for the[FormControl⁠](https://react-bootstrap.github.io/components/forms/),[InputGroup⁠](https://react-bootstrap.github.io/components/input-group/),[Alert⁠](https://react-bootstrap.github.io/components/alerts/),[Button⁠](https://react-bootstrap.github.io/components/buttons/) and[Modal⁠](https://react-bootstrap.github.io/components/modal/) components.

The [react-image-gallery⁠](https://www.npmjs.com/package/react-image-gallery) package will be used to display five images of Einstein that the user can cycle through to pick for their quote. Both buttons beneath the Einstein image will allow the user to generate a random quote or create their own. Finally, the [react-loading-overlay⁠](https://www.npmjs.com/package/react-loading-overlay)package will display a simple loading spinner on top of the page while the user waits for Transloadit to return the results from the Assembly.

The entirety of the code can be found in my[repository⁠](https://github.com/mcallistertyler95/einstein-quotes/tree/master), but the render method for the webpage looks like this:

```jsx
<LoadingOverlay
  active={this.state.loading}
  spinner
  text="Generating quote..."
>
  <div className="App">
    <header className="App-header">
      <img src={logo} className="App-logo" alt="logo" />
      <div className="header-title">
        "Definitely the best webpage ever made." - Albert Einstein
      </div>
    </header>
    <div className="App-page">
      Generate your own Einstein quote! (Quotes possibly not produced by Einstein)
    </div>
    <div className="App-gallery">
    <Modal id="modal-size" centered={true} show={this.state.modalIsOpen} onHide={this.handleModal}>
      <Modal.Body>
        <img
          src={this.state.returnedImage}
          alt="generated-einstein"
          />
      </Modal.Body>
    </Modal>
    <ImageGallery disableArrowKeys={true} onSlide={(e)=>this.handleImage(e)} items={images} showThumbnails={false} showFullscreenButton={false} showPlayButton={false}/>
    </div>
    <div className="App-form">
    <Button disabled={this.state.loading} onClick={() => this.prepareQuote(generateQuote())} variant="outline-primary" >Generate a random quote!</Button>
    <div className="divider"/>
    <Button disabled={this.state.loading} onClick={() => this.handleHidden()} variant="outline-primary" >Make your own quote!</Button>
    {!this.state.isHidden &&
    <div className="App-userinput">
    <InputGroup className="mb-3" onInput={(e)=>this.handleInput(e)}>
        <FormControl
          placeholder="Your quote"
          aria-label="Your quote"
          aria-describedby="basic-addon1"
          maxLength="50"
          disabled={this.state.loading}
        />
        <InputGroup.Append>
          <Button disabled={this.state.loading} onClick={()=>this.handleOriginalQuote()} variant="outline-primary">Submit</Button>
        </InputGroup.Append>
    </InputGroup>
    </div>
    }
    </div>
    <Alert variant="danger" defaultShow={this.state.error}>
      <Alert.Heading>It's not a bug...it's a feature</Alert.Heading>
      <p>Seems like an unexpected error occurred, contact the owner (https://github.com/mcallistertyler95/) of this site and let them know about it!</p>
    </Alert>
    <canvas id="canvas" ref="canvas" width={500} height={200}/>
    <footer className="App-footer">
      Icons made by <a href="https://www.freepik.com/" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> is licensed by <a href="https://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0">CC 3.0 BY</a>
    </footer>
  </div>
</LoadingOverlay>

```

The render method is relatively basic. Near the end of the page, before the footer, there is a canvas element. This is where we will draw our quote before combining it with our selected image of Einstein. Within the webpage's CSS, I make the canvas invisible so it's hidden away from the user. Additionally, the most important function here is `prepareQuote()`, which creates a[HTML canvas⁠](https://developer.mozilla.org/en-US/docs/Web/API/Canvas%5FAPI), draws a quote onto it and sends it over to Robodog along with a URL to the current image of Einstein displaying on the page. Robodog will then interact with the Transloadit API to run our Assembly. The quotes themselves can be user-generated from an input form, or are randomly selected from an array of strings.

<span aria-hidden="true" id="utilizing-robodog"></span>

## Utilizing Robodog

A quick look at the [Robodog docs⁠](https://uppy.io/docs/robodog/) gives some instructions on installation and integration within a basic webpage. For the Einstein quote generator, the user won't be uploading a file in the traditional sense of dragging and dropping a file from their disk. Instead, we will programmatically upload a file based on the user's actions. Robodog can take an array of [File⁠](https://developer.mozilla.org/en-US/docs/Web/API/File) or[Blob⁠](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects, meaning we can use some simple JavaScript to upload files. As mentioned above, we will be uploading a HTML canvas to Robodog. To do this, I first create a HTML canvas and convert it to a Blob, like so:

```javascript
async function prepareQuote(quote){
  let ctx = this.refs.canvas.getContext('2d');
  ctx.clearRect(0, 0, this.refs.canvas.width, this.refs.canvas.height);
  ctx = drawQuote(ctx, quote);
  this.refs.canvas.toBlob((blob) =>{
    this.uploadQuote(blob);
  });
}

```

This function takes a quote (a string in this case), creates a canvas context and uses the function`drawQuote()` to draw onto it and format the text correctly to stop it from running off the image. The code can be found below:

```javascript
function drawQuote(ctx, genquote) {
  genquote = '“' + genquote + '” - Albert Einstein'
  ctx.shadowOffsetX = 5
  ctx.shadowOffsetY = 3
  ctx.shadowBlur = 2
  ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'
  ctx.font = '35px serif'
  let words = genquote.split(' ')
  let lines = []
  let currentLine = words[0]
  for (let i = 1; i < words.length; i++) {
    let word = words[i]
    let width = ctx.measureText(currentLine + ' ' + word).width
    if (width < 500) {
      currentLine += ' ' + word
    } else {
      lines.push(currentLine)
      currentLine = word
    }
    intracacies
  }
  lines.push(currentLine)
  ctx.strokeStyle = 'black'
  ctx.lineWidth = 4
  let linebreak = 60
  for (let j = 0; j < lines.length; j++) {
    ctx.strokeText(lines[j], 0, linebreak)
    ctx.fillStyle = 'white'
    ctx.fillText(lines[j], 0, linebreak)
    linebreak += 40
  }
}

```

Thankfully, canvas elements can be turned into Blobs easily using the[.toBlob() method⁠](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob). This Blob is then passed to `uploadQuote()` where Robodog receives it and begins running ourAssembly.

Four Robots will comprise the Assembly used by Robodog:

* [/upload/handle](/docs/robots/upload-handle.md): to take our HTML canvas.
* [/http/import](/docs/robots/http-import.md): to find the image of Einstein displaying on our webpage.
* [/image/resize](/docs/robots/image-resize.md): to resize our image and combine it with the uploaded canvas.
* [/google/store](/docs/robots/google-store.md): to export the final result to Firebase.

The Assembly executed by Robodog looks this in my code:

```javascript
async function uploadQuote(blob) {
  let image_url = window.location.protocol + '//' + window.location.hostname + images[this.state.imageIndex].original;
  this.setState({loading: true});
  const resultPromise = robodog.upload([blob],{
    params: {
      auth: { key: '*****************************' },
      "steps": {
        ":original": {
          "robot": "/upload/handle"
        },
        "imported": {
          "robot": "/http/import",
          "url": image_url
        },
        "quote": {
          "robot": "/image/resize",
          "imagemagick_stack": "v1.0.0",
          "watermark_size": "60%",
          "watermark_position": "bottom-left",
          "result": false,
          "watermark_x_offset": 10,
          "watermark_y_offset": -15,
          "use": {
            "steps": [
              { "name": "imported", "as": "base" },
              { "name": ":original", "as": "watermark" }
            ]
          }
        },
        "watermarked": {
          "robot": "/image/resize",
          "use": [
            "quote"
          ],
          "result": true,
          "imagemagick_stack": "v1.0.0",
          "height": 600,
          "width": 600,
          "resize_strategy": "fit",
          "text": [
            {
              "text": "by fake einstein quote generator\nfrom transloadit.com/blog/einstein",
              "size": 12,
              "font": "Ubuntu",
              "color": "white",
              "valign": "bottom",
              "align": "right",
              "x_offset": 5,
              "y_offset": -15
            }
          ]
      },
      "export": {
        "use": [
          "watermarked"
        ],
        "robot": "/google/store",
        "credentials": "einstein_firebase",
        "path": "subfolder/${file.id}.${file.ext}"
      }
    }
    },
  waitForEncoding: true
  })
  resultPromise.then((bundle) =>{
    if(bundle.failed.length === 0){
      this.setState({
        modalIsOpen: true,
        loading: false,
        message: '',
        returnedImage: bundle.data.results[0].ssl_url.replace('/gs:/','/')
      });
    } else {
      this.setState({
        loading: false,
        modalIsOpen: false,
        message: '',
        error: true
      });
    }
  })
}

```

Starting from the first two Assembly Steps, we prepare the files for upload. The HTML canvas is taken as an array containing a Blob and is picked up by the[/upload/handle](/docs/robots/upload-handle.md) Robot, while the Einstein image is fetched by the [/http/import](/docs/robots/http-import.md) Robot from a URL determined by the current image displaying on the page. The third and fourth Assembly Steps utilize the [/image/resize](/docs/robots/image-resize.md) Robot. The first Step overlays the canvas on top of the Einstein image as a watermark and the next displays a small Transloadit watermark just for this tutorial. Lastly, the result is sent to Firebase and stored under a unique ID, like so:

Robodog will then return the resultant image within an array named `bundle.results`. The Firebase link to the image can be found under `ssl_url`. I parse this and set it to one of my state objects so it can re-render the page and display for the user to see.

<span aria-hidden="true" id="the-end-result"></span>

## The end result

So, what have we learned here? Well, for starters, you can now see how Robodog can be integrated into a React page and how you can programmatically upload with it. Additionally, we've taken a short look at how to convert canvas to a blob and overlay images over each other in a TransloaditAssembly.

The concept of the quote generator is also more broadly applicable than you think. Maybe you want to create a site where users can combine image together to create a logo or their own special message. If that's the case, then a lot of the code used here will be useful for you. In the presumed words of the legendary genius Albert Einstein: "Never memorize something that you can look up!"

[#letsbuild](/blog/tags/letsbuild.md)[#image-processing-service](/blog/tags/image-processing-service.md)[#uppy](/blog/tags/uppy.md)[#walkthrough](/blog/tags/walkthrough.md)[#image-resize-robot](/blog/tags/image-resize-robot.md)[#google-store-robot](/blog/tags/google-store-robot.md)[#upload-handle-robot](/blog/tags/upload-handle-robot.md)[#http-import-robot](/blog/tags/http-import-robot.md)

### 👩‍💻 Join 20k+ developers

Sign up for our [monthly newsletter](/newsletters.md) to receive direct links to 3 exclusive tech — and 2 product updates. No less, no more.

Your email:

Get access

## File uploading and encoding. Made simple.

Transloadit streamlines file handling for developers, trusted by brands like Coursera and The New York Times. We’re known for a reliable API, top-notch support, and a strong commitment to open source, with projects like [Uppy⁠](https://uppy.io) and [Tus⁠](https://tus.io) setting standards in file processing.

[Sign up](/c/)[Book a Demo](https://survey.typeform.com/to/kRg47Xi5)

No credit card needed

Cancel anytime
