LiveView Uploads: resuming on cancel with auto_upload

I’m currently implementing the brand new LiveView Uploads, using the auto_upload setting, and I have a situation which I’m not sure how to resolve.

The uploader is configured to handle multiple images, with auto_upload: true:

socket
|> allow_upload(:images,
  accept: [".jpg", ".jpeg", ".png"],
  auto_upload: true,
  progress: &handle_progress/3,
  max_file_size: 5 * 1024 * 1024,
  max_entries: 20
)

The actual file upoad input is hidden, so we have drag and drop only.

When you drop a bunch a files, and they are all valid, the upload starts and everything works as expected. However, if there is a single file that’s invalid, the whole batch of files stops. I have implemented a cancel button so you can cancel individual files from the UI:

  def handle_event("cancel-upload", %{"ref" => ref}, socket) do
    {:noreply, cancel_upload(socket, :images, ref)}
  end

The problem I’m facing is that after I cancel the invalid file(s), the remaining valid files does not resume uploading. Unless I submit the form or add drag and drop more files, nothing will get uploaded.

I’m looking for a way to “retry” uploading files or trigger auto_upload again, if all entries on the uploader are valid. Does anyone have any ideas how to do it?

Here is my template markup (simplified version) just for completeness:

<%= live_file_input @uploads.images, class: "is-hidden" %>

<div class="dropzone" phx-hook="DragOver" phx-drop-target="<%= @uploads.images.ref %>">
  Drag and drop your files here
</div>

<table class="uploads-in-progress table">
  <tbody>
    <%= for entry <- @uploads.images.entries do %>
      <tr>
        <td class="<%= if !entry.valid?, do: "has-text-danger" %>"><%= entry.client_name %></td>
        <td><progress class="progress" value="<%= entry.progress %>" max="100"><%= entry.progress %></progress></td>
        <td class="has-text-danger"><span class="is-clickable" phx-click="cancel-upload" phx-value-ref="<%= entry.ref %>">Cancel</span></td>
      </tr>
    <% end %>
  </tbody>
</table>
3 Likes

I have a workaround, which is a bit of a hack… Here it is for anyone interested:

I noticed that live_view.js simply dispatches an input event to the drop target here so I created a hook that triggers the event:

Hooks.ResumeUpload = {
  mounted() {
    this.handleEvent('resume_upload', ({id}) => {
      const dropTarget = document.getElementById(id);
      dropTarget.dispatchEvent(new Event('input', { bubbles: true }));
    });
  },
};

The hook subscribes to a resume_upload event, which is triggered when you cancel an upload:

def handle_event("cancel_upload", %{"ref" => ref}, socket) do
  socket =
    socket
    |> cancel_upload(:images, ref)
    |> push_event("resume_upload", %{id: socket.assigns.uploads.images.ref})

  {:noreply, socket}
end

and in the template I just had to find a place for the hook:

<div id="upload-section" phx-hook="ResumeUpload">
  <!-- same code as before ... -->
</div>

I wonder if there is a better way to accomplish this :thinking:

6 Likes

Thanks for sharing the solution.