Skip to content

Commit

Permalink
Fix auto upload issues
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismccord committed Feb 29, 2024
1 parent df073b6 commit 80ddf35
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 70 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.20.11

### Bug fixes
* Fix auto uploads with invalid entries incorrectly proceeding with a form submit instead of halting, causing entries in progress errors
* Fix auto upload entries failing to be uploaded on submit after moving into a valid state, such as falling within `max_entries`
* Fix TagEngine clause warning

## 0.20.10 (2024-02-28)

### Bug fixes
Expand Down
16 changes: 12 additions & 4 deletions assets/js/phoenix_live_view/live_uploader.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,28 +102,36 @@ export default class LiveUploader {
}

constructor(inputEl, view, onComplete){
this.autoUpload = DOM.isAutoUpload(inputEl)
this.view = view
this.onComplete = onComplete
this._entries =
Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || [])
.map(file => new UploadEntry(inputEl, file, view))
.map(file => new UploadEntry(inputEl, file, view, this.autoUpload))

// prevent sending duplicate preflight requests
LiveUploader.markPreflightInProgress(this._entries)

this.numEntriesInProgress = this._entries.length
}

isAutoUpload(){ return this.autoUpload }

entries(){ return this._entries }

initAdapterUpload(resp, onError, liveSocket){
this._entries =
this._entries.map(entry => {
entry.zipPostFlight(resp)
entry.onDone(() => {
if(entry.isCancelled()){
this.numEntriesInProgress--
if(this.numEntriesInProgress === 0){ this.onComplete() }
})
} else {
entry.zipPostFlight(resp)
entry.onDone(() => {
this.numEntriesInProgress--
if(this.numEntriesInProgress === 0){ this.onComplete() }
})
}
return entry
})

Expand Down
12 changes: 8 additions & 4 deletions assets/js/phoenix_live_view/upload_entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
} from "./utils"

import LiveUploader from "./live_uploader"
import DOM from "./dom"

export default class UploadEntry {
static isActive(fileEl, file){
Expand All @@ -34,7 +33,7 @@ export default class UploadEntry {
file._preflightInProgress = true
}

constructor(fileEl, file, view){
constructor(fileEl, file, view, autoUpload){
this.ref = LiveUploader.genFileRef(file)
this.fileEl = fileEl
this.file = file
Expand All @@ -44,9 +43,10 @@ export default class UploadEntry {
this._isDone = false
this._progress = 0
this._lastProgressSent = -1
this._onDone = function (){ }
this._onDone = function(){ }
this._onElUpdated = this.onElUpdated.bind(this)
this.fileEl.addEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)
this.autoUpload = autoUpload
}

metadata(){ return this.meta }
Expand All @@ -69,6 +69,8 @@ export default class UploadEntry {
}
}

isCancelled(){ return this._isCancelled }

cancel(){
this.file._preflightInProgress = false
this._isCancelled = true
Expand All @@ -81,9 +83,11 @@ export default class UploadEntry {
error(reason = "failed"){
this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)
this.view.pushFileProgress(this.fileEl, this.ref, {error: reason})
if(!DOM.isAutoUpload(this.fileEl)){ LiveUploader.clearFiles(this.fileEl) }
if(!this.isAutoUpload()){ LiveUploader.clearFiles(this.fileEl) }
}

isAutoUpload(){ return this.autoUpload }

//private

onDone(callback){
Expand Down
39 changes: 30 additions & 9 deletions assets/js/phoenix_live_view/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -1037,7 +1037,12 @@ export default class View {
} else if(LiveUploader.inputsAwaitingPreflight(formEl).length > 0){
let [ref, els] = refGenerator()
let proxyRefGen = () => [ref, els, opts]
this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {
this.uploadFiles(formEl, targetCtx, ref, cid, (uploads) => {
// if we still having pending preflights it means we have invalid entries
// and the phx-submit cannot be completed
if(LiveUploader.inputsAwaitingPreflight(formEl).length > 0){
return this.undoRefs(ref)
}
let meta = this.extractMeta(formEl)
let formData = serializeForm(formEl, {submitter, ...meta})
this.pushWithReply(proxyRefGen, "event", {
Expand Down Expand Up @@ -1088,15 +1093,20 @@ export default class View {

this.pushWithReply(null, "allow_upload", payload, resp => {
this.log("upload", () => ["got preflight response", resp])
if(resp.error){
// the preflight will reject entries beyond the max entries
// so we error and cancel entries on the client that are missing from the response
uploader.entries().forEach(entry => {
if(resp.entries && !resp.entries[entry.ref]){
this.handleFailedEntryPreflight(entry.ref, "failed preflight", uploader)
}
})
// for auto uploas, we may have an empty entries response from the server
// for form submits that contain invalid entries
if(resp.error || Object.keys(resp.entries).length === 0){
this.undoRefs(ref)
resp.error.map(([entry_ref, reason]) => {
if(DOM.isAutoUpload(inputEl)){
uploader.entries().find(entry => entry.ref === entry_ref.toString()).cancel()
} else {
uploader.entries().map(entry => entry.cancel())
}
this.log("upload", () => [`error for entry ${entry_ref}`, reason])
let errors = resp.error || []
errors.map(([entry_ref, reason]) => {
this.handleFailedEntryPreflight(entry_ref, reason, uploader)
})
} else {
let onError = (callback) => {
Expand All @@ -1110,6 +1120,17 @@ export default class View {
})
}

handleFailedEntryPreflight(uploadRef, reason, uploader){
if(uploader.isAutoUpload()){
// uploadRef may be top level upload config ref or entry ref
let entry = uploader.entries().find(entry => entry.ref === uploadRef.toString())
if(entry){ entry.cancel() }
} else {
uploader.entries().map(entry => entry.cancel())
}
this.log("upload", () => [`error for entry ${uploadRef}`, reason])
}

dispatchUploads(targetCtx, name, filesOrBlobs){
let targetElement = this.targetCtxElement(targetCtx) || this.el
let inputs = DOM.findUploadInputs(targetElement).filter(el => el.name === name)
Expand Down
63 changes: 48 additions & 15 deletions priv/static/phoenix_live_view.cjs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions priv/static/phoenix_live_view.cjs.js.map

Large diffs are not rendered by default.

Loading

0 comments on commit 80ddf35

Please sign in to comment.