Direct uppload with Uppy (according to wiki)

Hi there.

I’m a novice in development.
Looking for solution of uploading images with its preview before saving (submitting a form).

I’ve managed to set up Shrine for file uploading, but I stuck with adding Uppy funcionality.
I’ve used wiki page about it (https://github.com/shrinerb/shrine/wiki/Adding-Direct-App-Uploads), but it doesn’t work for me (nothing happens after choosing a file to upload - no image preview, no progress bar, nothing).

My code is below.

config/initializers/shrine.rb
require "shrine"

require "shrine/storage/file_system"

Shrine.storages = {

  # temporary

  cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), 

  # permanent

  store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"),       

}

# loads Active Record integration

Shrine.plugin :activerecord   

# enables retaining cached file across form redisplays

Shrine.plugin :cached_attachment_data 

# extracts metadata for assigned cached files

Shrine.plugin :restore_cached_data    

Shrine.plugin :determine_mime_type, analyzer: :marcel

Shrine.plugin :validation_helpers

Shrine.plugin :pretty_location

# for retaining selected files across form redisplays

Shrine.plugin :cached_attachment_data

Shrine.plugin :upload_endpoint, url: true
app/uploaders/image_uploader.rb
class ImageUploader < Shrine

  Attacher.validate do

    validate_max_size 3*1024*1024, message: "is too large (max is 3 MB)"

    validate_mime_type %w[image/jpeg image/png image/webp image/gif],

      message: "must be JPEG, PNG, GIF or WEBP"

  end

end
app/views/cards/_form.html.erb
<div class="upload-preview center">

  <%= image_tag @card.image_url.to_s, height: "200",

        class: "img-thumbnail file-upload-preview" %>

</div>

<%= form_with(model: @card, local: true) do |f| %>

  <%= render 'shared/error_messages', object: f.object %>

    <%= f.label :image %><br />

    <%= f.hidden_field :image, value: @card.cached_image_data, 

                        class: "upload-data" %>

    <%= f.file_field :image, 

                        accept: "image/jpeg,image/gif,image/png,image/webp",

                        class: "upload-data" %>

    <%= f.label :phrase %>

    <%= f.text_field :phrase, class: 'form-control' %>

    

    <%= f.label :translation_1, "Translation" %>

    <%= f.text_field :translation_1, class: 'form-control' %>

    <%= f.label :context_1_1, "Context #1" %>

    <%= f.text_area :context_1_1, class: 'form-control' %>

    <%= f.label :context_1_2, "Context #2" %>

    <%= f.text_area :context_1_2, class: 'form-control' %>

    <%= f.label :context_1_3, "Context #3" %>

    <%= f.text_area :context_1_3, class: 'form-control' %>

    <%= f.submit yield(:button_text), 

        class: "btn btn-success  btn-block btn-lg" %>

<% end %>
config/routes.rb
Rails.application.routes.draw do

  ...
  resources :cards

  mount ImageUploader.upload_endpoint(:cache) => "/upload"

end
app/javascript/fileUpload.js
import 'uppy/dist/uppy.min.css'

import {

  Core,

  FileInput,

  Informer,

  ProgressBar,

  ThumbnailGenerator,

  XHRUpload,

} from 'uppy'

function fileUpload(fileInput) {

  const hiddenInput = document.querySelector('.upload-data'),

        imagePreview = document.querySelector('.upload-preview img'),

        formGroup = fileInput.parentNode

  // remove our file input in favour of Uppy's

  formGroup.removeChild(fileInput)

  const uppy = Core({

      autoProceed: true,

    })

    .use(FileInput, {

      target: formGroup,

    })

    .use(Informer, {

      target: formGroup,

    })

    .use(ProgressBar, {

      target: imagePreview.parentNode,

    })

    .use(ThumbnailGenerator, {

      thumbnailHeight: 600,

    })

    .use(XHRUpload, {

      endpoint: '/upload', // path to the upload endpoint

    })

  uppy.on('thumbnail:generated', (file, preview) => {

    // show image preview while the file is being uploaded

    imagePreview.src = preview

  })

  uppy.on('upload-success', (file, response) => {

    // retrieve uploaded file data

    const uploadedFileData = response.body['data']

    // set hidden field value to the uploaded file data so that it's submitted

    // with the form as the attachment

    hiddenInput.value = JSON.stringify(uploadedFileData)

  })

}

export default fileUpload
app/javascript/packs/application.js
// This file is automatically compiled by Webpack, along with any other files

// present in this directory. You're encouraged to place your actual application logic in

// a relevant structure within app/javascript and only use these pack files to reference

// that code so it'll be compiled.

require("@rails/ujs").start()

require("turbolinks").start()

require("@rails/activestorage").start()

require("channels")

require("jquery")

import "bootstrap"

// * * * UPPY * * *

import fileUpload from 'fileUpload'

// listen on 'turbolinks:load' instead of 'DOMContentLoaded' if using Turbolinks

document.addEventListener('DOMContentLoaded', () => {

  document.querySelectorAll('.upload-file').forEach(fileInput => {

    fileUpload(fileInput)

  })

})

// * * *

// Uncomment to copy all static images under ../images to the output folder and reference

// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)

// or the `imagePath` JavaScript helper below.

//

// const images = require.context('../images', true)

// const imagePath = (name) => images(name, true)
app/assets/stylesheets/custom.scss
...
/* file upploading */
.upload-preview img {
  display: block;
  max-width: 100%;
}

.upload-preview {
  margin-bottom: 10px;
  display: inline-block;
  height: 300px;
}

img[src=""] {
  visibility: hidden;
}

If it matters, I’m using local windows environment.

After choosing a file, I can see such a picture:

What did I do wrong?

Windows brings with it a whole host of issues that make debugging this (especially for someone who uses Unix or Linux exclusively) very difficult indeed. I’ll do my best, but this is likely going to come to a snipe-hunt…

One thing to check – do you have ImageMagick or similar installed? Are you able to use the command-line to call convert /path/to/a/real/image.jpg image.png and get image.png in the current directory? If not, are the error messages in your console informative enough to get you further down the road?

Do you see any errors in your browser’s console when you load the page? Do you see any messages from Uppy saying that it is ready and loaded in the page?

While you are dragging or otherwise selecting the image to upload, what do you see happening in your Rails console (the output in your terminal that you see after you run rails server to start the development server from your project folder).

Walter

PS: PLEASE copy and paste terminal output into your messages, screenshots are likely to be overlooked as a source of information.

PPS: You didn’t mention whether you were trying to run this in your local development environment, or on a server. That’s critically important to know for a number of reasons.

As @walterdavis says, it is very difficult, if not impossible to debug without having the code in front of you. (as a side note: i would strongly urge you to consider using other platforms besides windows for non .net development - my two cents - because you will save yourself from a whole host of problems. )

Looks like your query selector in your application.js file uses, "‘upload-file’ but the classes you are using in your form is “upload-data”. You will have to change one or the other for it to be consistent.

In other words use the following code in your application.js (note how we are using .upload-data instead of .upload-file?:

document.addEventListener('DOMContentLoaded', () => {

  document.querySelectorAll('.upload-data').forEach(fileInput => {

    fileUpload(fileInput)

  })

You might want to also double check if you are using turbolinks. if so you will have to wait till that loads:

document.addEventListener(‘turbolinks:load’, () => {
document.querySelectorAll(’.upload-data’).forEach(fileInput => {
if (fileInput.multiple) {
multipleFileUpload(fileInput)
} else {
singleFileUpload(fileInput)
}
})
})