phoenix_live_view 0.20.10 → 0.20.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -102,11 +102,12 @@ export default class LiveUploader {
102
102
  }
103
103
 
104
104
  constructor(inputEl, view, onComplete){
105
+ this.autoUpload = DOM.isAutoUpload(inputEl)
105
106
  this.view = view
106
107
  this.onComplete = onComplete
107
108
  this._entries =
108
109
  Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || [])
109
- .map(file => new UploadEntry(inputEl, file, view))
110
+ .map(file => new UploadEntry(inputEl, file, view, this.autoUpload))
110
111
 
111
112
  // prevent sending duplicate preflight requests
112
113
  LiveUploader.markPreflightInProgress(this._entries)
@@ -114,16 +115,23 @@ export default class LiveUploader {
114
115
  this.numEntriesInProgress = this._entries.length
115
116
  }
116
117
 
118
+ isAutoUpload(){ return this.autoUpload }
119
+
117
120
  entries(){ return this._entries }
118
121
 
119
122
  initAdapterUpload(resp, onError, liveSocket){
120
123
  this._entries =
121
124
  this._entries.map(entry => {
122
- entry.zipPostFlight(resp)
123
- entry.onDone(() => {
125
+ if(entry.isCancelled()){
124
126
  this.numEntriesInProgress--
125
127
  if(this.numEntriesInProgress === 0){ this.onComplete() }
126
- })
128
+ } else {
129
+ entry.zipPostFlight(resp)
130
+ entry.onDone(() => {
131
+ this.numEntriesInProgress--
132
+ if(this.numEntriesInProgress === 0){ this.onComplete() }
133
+ })
134
+ }
127
135
  return entry
128
136
  })
129
137
 
@@ -10,7 +10,6 @@ import {
10
10
  } from "./utils"
11
11
 
12
12
  import LiveUploader from "./live_uploader"
13
- import DOM from "./dom"
14
13
 
15
14
  export default class UploadEntry {
16
15
  static isActive(fileEl, file){
@@ -34,7 +33,7 @@ export default class UploadEntry {
34
33
  file._preflightInProgress = true
35
34
  }
36
35
 
37
- constructor(fileEl, file, view){
36
+ constructor(fileEl, file, view, autoUpload){
38
37
  this.ref = LiveUploader.genFileRef(file)
39
38
  this.fileEl = fileEl
40
39
  this.file = file
@@ -44,9 +43,10 @@ export default class UploadEntry {
44
43
  this._isDone = false
45
44
  this._progress = 0
46
45
  this._lastProgressSent = -1
47
- this._onDone = function (){ }
46
+ this._onDone = function(){ }
48
47
  this._onElUpdated = this.onElUpdated.bind(this)
49
48
  this.fileEl.addEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)
49
+ this.autoUpload = autoUpload
50
50
  }
51
51
 
52
52
  metadata(){ return this.meta }
@@ -69,6 +69,8 @@ export default class UploadEntry {
69
69
  }
70
70
  }
71
71
 
72
+ isCancelled(){ return this._isCancelled }
73
+
72
74
  cancel(){
73
75
  this.file._preflightInProgress = false
74
76
  this._isCancelled = true
@@ -81,9 +83,11 @@ export default class UploadEntry {
81
83
  error(reason = "failed"){
82
84
  this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)
83
85
  this.view.pushFileProgress(this.fileEl, this.ref, {error: reason})
84
- if(!DOM.isAutoUpload(this.fileEl)){ LiveUploader.clearFiles(this.fileEl) }
86
+ if(!this.isAutoUpload()){ LiveUploader.clearFiles(this.fileEl) }
85
87
  }
86
88
 
89
+ isAutoUpload(){ return this.autoUpload }
90
+
87
91
  //private
88
92
 
89
93
  onDone(callback){
@@ -1037,7 +1037,12 @@ export default class View {
1037
1037
  } else if(LiveUploader.inputsAwaitingPreflight(formEl).length > 0){
1038
1038
  let [ref, els] = refGenerator()
1039
1039
  let proxyRefGen = () => [ref, els, opts]
1040
- this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {
1040
+ this.uploadFiles(formEl, targetCtx, ref, cid, (uploads) => {
1041
+ // if we still having pending preflights it means we have invalid entries
1042
+ // and the phx-submit cannot be completed
1043
+ if(LiveUploader.inputsAwaitingPreflight(formEl).length > 0){
1044
+ return this.undoRefs(ref)
1045
+ }
1041
1046
  let meta = this.extractMeta(formEl)
1042
1047
  let formData = serializeForm(formEl, {submitter, ...meta})
1043
1048
  this.pushWithReply(proxyRefGen, "event", {
@@ -1088,15 +1093,20 @@ export default class View {
1088
1093
 
1089
1094
  this.pushWithReply(null, "allow_upload", payload, resp => {
1090
1095
  this.log("upload", () => ["got preflight response", resp])
1091
- if(resp.error){
1096
+ // the preflight will reject entries beyond the max entries
1097
+ // so we error and cancel entries on the client that are missing from the response
1098
+ uploader.entries().forEach(entry => {
1099
+ if(resp.entries && !resp.entries[entry.ref]){
1100
+ this.handleFailedEntryPreflight(entry.ref, "failed preflight", uploader)
1101
+ }
1102
+ })
1103
+ // for auto uploads, we may have an empty entries response from the server
1104
+ // for form submits that contain invalid entries
1105
+ if(resp.error || Object.keys(resp.entries).length === 0){
1092
1106
  this.undoRefs(ref)
1093
- resp.error.map(([entry_ref, reason]) => {
1094
- if(DOM.isAutoUpload(inputEl)){
1095
- uploader.entries().find(entry => entry.ref === entry_ref.toString()).cancel()
1096
- } else {
1097
- uploader.entries().map(entry => entry.cancel())
1098
- }
1099
- this.log("upload", () => [`error for entry ${entry_ref}`, reason])
1107
+ let errors = resp.error || []
1108
+ errors.map(([entry_ref, reason]) => {
1109
+ this.handleFailedEntryPreflight(entry_ref, reason, uploader)
1100
1110
  })
1101
1111
  } else {
1102
1112
  let onError = (callback) => {
@@ -1110,6 +1120,17 @@ export default class View {
1110
1120
  })
1111
1121
  }
1112
1122
 
1123
+ handleFailedEntryPreflight(uploadRef, reason, uploader){
1124
+ if(uploader.isAutoUpload()){
1125
+ // uploadRef may be top level upload config ref or entry ref
1126
+ let entry = uploader.entries().find(entry => entry.ref === uploadRef.toString())
1127
+ if(entry){ entry.cancel() }
1128
+ } else {
1129
+ uploader.entries().map(entry => entry.cancel())
1130
+ }
1131
+ this.log("upload", () => [`error for entry ${uploadRef}`, reason])
1132
+ }
1133
+
1113
1134
  dispatchUploads(targetCtx, name, filesOrBlobs){
1114
1135
  let targetElement = this.targetCtxElement(targetCtx) || this.el
1115
1136
  let inputs = DOM.findUploadInputs(targetElement).filter(el => el.name === name)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phoenix_live_view",
3
- "version": "0.20.10",
3
+ "version": "0.20.11",
4
4
  "description": "The Phoenix LiveView JavaScript client.",
5
5
  "license": "MIT",
6
6
  "repository": {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phoenix_live_view",
3
- "version": "0.20.10",
3
+ "version": "0.20.12",
4
4
  "description": "The Phoenix LiveView JavaScript client.",
5
5
  "license": "MIT",
6
6
  "module": "./priv/static/phoenix_live_view.esm.js",
@@ -1139,7 +1139,7 @@ var UploadEntry = class {
1139
1139
  static markPreflightInProgress(file) {
1140
1140
  file._preflightInProgress = true;
1141
1141
  }
1142
- constructor(fileEl, file, view) {
1142
+ constructor(fileEl, file, view, autoUpload) {
1143
1143
  this.ref = LiveUploader.genFileRef(file);
1144
1144
  this.fileEl = fileEl;
1145
1145
  this.file = file;
@@ -1153,6 +1153,7 @@ var UploadEntry = class {
1153
1153
  };
1154
1154
  this._onElUpdated = this.onElUpdated.bind(this);
1155
1155
  this.fileEl.addEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated);
1156
+ this.autoUpload = autoUpload;
1156
1157
  }
1157
1158
  metadata() {
1158
1159
  return this.meta;
@@ -1174,6 +1175,9 @@ var UploadEntry = class {
1174
1175
  }
1175
1176
  }
1176
1177
  }
1178
+ isCancelled() {
1179
+ return this._isCancelled;
1180
+ }
1177
1181
  cancel() {
1178
1182
  this.file._preflightInProgress = false;
1179
1183
  this._isCancelled = true;
@@ -1186,10 +1190,13 @@ var UploadEntry = class {
1186
1190
  error(reason = "failed") {
1187
1191
  this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated);
1188
1192
  this.view.pushFileProgress(this.fileEl, this.ref, { error: reason });
1189
- if (!dom_default.isAutoUpload(this.fileEl)) {
1193
+ if (!this.isAutoUpload()) {
1190
1194
  LiveUploader.clearFiles(this.fileEl);
1191
1195
  }
1192
1196
  }
1197
+ isAutoUpload() {
1198
+ return this.autoUpload;
1199
+ }
1193
1200
  onDone(callback) {
1194
1201
  this._onDone = () => {
1195
1202
  this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated);
@@ -1313,24 +1320,35 @@ var LiveUploader = class {
1313
1320
  entries.forEach((entry) => UploadEntry.markPreflightInProgress(entry.file));
1314
1321
  }
1315
1322
  constructor(inputEl, view, onComplete) {
1323
+ this.autoUpload = dom_default.isAutoUpload(inputEl);
1316
1324
  this.view = view;
1317
1325
  this.onComplete = onComplete;
1318
- this._entries = Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || []).map((file) => new UploadEntry(inputEl, file, view));
1326
+ this._entries = Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || []).map((file) => new UploadEntry(inputEl, file, view, this.autoUpload));
1319
1327
  LiveUploader.markPreflightInProgress(this._entries);
1320
1328
  this.numEntriesInProgress = this._entries.length;
1321
1329
  }
1330
+ isAutoUpload() {
1331
+ return this.autoUpload;
1332
+ }
1322
1333
  entries() {
1323
1334
  return this._entries;
1324
1335
  }
1325
1336
  initAdapterUpload(resp, onError, liveSocket) {
1326
1337
  this._entries = this._entries.map((entry) => {
1327
- entry.zipPostFlight(resp);
1328
- entry.onDone(() => {
1338
+ if (entry.isCancelled()) {
1329
1339
  this.numEntriesInProgress--;
1330
1340
  if (this.numEntriesInProgress === 0) {
1331
1341
  this.onComplete();
1332
1342
  }
1333
- });
1343
+ } else {
1344
+ entry.zipPostFlight(resp);
1345
+ entry.onDone(() => {
1346
+ this.numEntriesInProgress--;
1347
+ if (this.numEntriesInProgress === 0) {
1348
+ this.onComplete();
1349
+ }
1350
+ });
1351
+ }
1334
1352
  return entry;
1335
1353
  });
1336
1354
  let groupedEntries = this._entries.reduce((acc, entry) => {
@@ -3820,7 +3838,10 @@ var View = class {
3820
3838
  } else if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
3821
3839
  let [ref, els] = refGenerator();
3822
3840
  let proxyRefGen = () => [ref, els, opts];
3823
- this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {
3841
+ this.uploadFiles(formEl, targetCtx, ref, cid, (uploads) => {
3842
+ if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
3843
+ return this.undoRefs(ref);
3844
+ }
3824
3845
  let meta = this.extractMeta(formEl);
3825
3846
  let formData = serializeForm(formEl, { submitter, ...meta });
3826
3847
  this.pushWithReply(proxyRefGen, "event", {
@@ -3865,15 +3886,16 @@ var View = class {
3865
3886
  this.log("upload", () => ["sending preflight request", payload]);
3866
3887
  this.pushWithReply(null, "allow_upload", payload, (resp) => {
3867
3888
  this.log("upload", () => ["got preflight response", resp]);
3868
- if (resp.error) {
3889
+ uploader.entries().forEach((entry) => {
3890
+ if (resp.entries && !resp.entries[entry.ref]) {
3891
+ this.handleFailedEntryPreflight(entry.ref, "failed preflight", uploader);
3892
+ }
3893
+ });
3894
+ if (resp.error || Object.keys(resp.entries).length === 0) {
3869
3895
  this.undoRefs(ref);
3870
- resp.error.map(([entry_ref, reason]) => {
3871
- if (dom_default.isAutoUpload(inputEl)) {
3872
- uploader.entries().find((entry) => entry.ref === entry_ref.toString()).cancel();
3873
- } else {
3874
- uploader.entries().map((entry) => entry.cancel());
3875
- }
3876
- this.log("upload", () => [`error for entry ${entry_ref}`, reason]);
3896
+ let errors = resp.error || [];
3897
+ errors.map(([entry_ref, reason]) => {
3898
+ this.handleFailedEntryPreflight(entry_ref, reason, uploader);
3877
3899
  });
3878
3900
  } else {
3879
3901
  let onError = (callback) => {
@@ -3888,6 +3910,17 @@ var View = class {
3888
3910
  });
3889
3911
  });
3890
3912
  }
3913
+ handleFailedEntryPreflight(uploadRef, reason, uploader) {
3914
+ if (uploader.isAutoUpload()) {
3915
+ let entry = uploader.entries().find((entry2) => entry2.ref === uploadRef.toString());
3916
+ if (entry) {
3917
+ entry.cancel();
3918
+ }
3919
+ } else {
3920
+ uploader.entries().map((entry) => entry.cancel());
3921
+ }
3922
+ this.log("upload", () => [`error for entry ${uploadRef}`, reason]);
3923
+ }
3891
3924
  dispatchUploads(targetCtx, name, filesOrBlobs) {
3892
3925
  let targetElement = this.targetCtxElement(targetCtx) || this.el;
3893
3926
  let inputs = dom_default.findUploadInputs(targetElement).filter((el) => el.name === name);