Skip to content

Commit 65e2794

Browse files
Lustmoredweaverryan
authored andcommitted
Rewire files handling to actually send files only once and clear input afterwards
1 parent 1f74e6d commit 65e2794

File tree

4 files changed

+41
-18
lines changed

4 files changed

+41
-18
lines changed

src/LiveComponent/assets/dist/Component/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default class Component {
4141
set(model: string, value: any, reRender?: boolean, debounce?: number | boolean): Promise<BackendResponse>;
4242
getData(model: string): any;
4343
action(name: string, args?: any, debounce?: number | boolean): Promise<BackendResponse>;
44-
files(key: string, fileList: FileList): void;
44+
files(key: string, input: HTMLInputElement): void;
4545
render(): Promise<BackendResponse>;
4646
getUnsyncedModels(): string[];
4747
addChild(child: Component, modelBindings?: ModelBinding[]): void;

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,8 +1802,8 @@ class Component {
18021802
this.debouncedStartRequest(debounce);
18031803
return promise;
18041804
}
1805-
files(key, fileList) {
1806-
this.pendingFiles[key] = fileList;
1805+
files(key, input) {
1806+
this.pendingFiles[key] = input;
18071807
}
18081808
render() {
18091809
const promise = this.nextRequestPromise;
@@ -1904,16 +1904,24 @@ class Component {
19041904
const thisPromiseResolve = this.nextRequestPromiseResolve;
19051905
this.resetPromise();
19061906
this.unsyncedInputsTracker.resetUnsyncedFields();
1907-
this.backendRequest = this.backend.makeRequest(this.valueStore.getOriginalProps(), this.pendingActions, this.valueStore.getDirtyProps(), this.getChildrenFingerprints(), this.valueStore.getUpdatedPropsFromParent(), this.pendingFiles);
1907+
const filesToSend = {};
1908+
for (const [key, value] of Object.entries(this.pendingFiles)) {
1909+
if (value.files) {
1910+
filesToSend[key] = value.files;
1911+
}
1912+
}
1913+
this.backendRequest = this.backend.makeRequest(this.valueStore.getOriginalProps(), this.pendingActions, this.valueStore.getDirtyProps(), this.getChildrenFingerprints(), this.valueStore.getUpdatedPropsFromParent(), filesToSend);
19081914
this.hooks.triggerHook('loading.state:started', this.element, this.backendRequest);
19091915
this.pendingActions = [];
1910-
this.pendingFiles = {};
19111916
this.valueStore.flushDirtyPropsToPending();
19121917
this.isRequestPending = false;
19131918
this.backendRequest.promise.then(async (response) => {
19141919
this.backendRequest = null;
19151920
const backendResponse = new BackendResponse(response);
19161921
const html = await backendResponse.getBody();
1922+
for (const input of Object.values(this.pendingFiles)) {
1923+
input.value = '';
1924+
}
19171925
const headers = backendResponse.response.headers;
19181926
if (headers.get('Content-Type') !== 'application/vnd.live-component+html' && !headers.get('X-Live-Redirect')) {
19191927
const controls = { displayError: true };
@@ -2755,8 +2763,10 @@ class LiveControllerDefault extends Controller {
27552763
}
27562764
console.warn(`Unknown modifier ${modifier.name} in action "${rawAction}". Available modifiers are: ${Array.from(validModifiers.keys()).join(', ')}.`);
27572765
});
2758-
for (const [key, files] of Object.entries(pendingFiles)) {
2759-
this.component.files(key, files);
2766+
for (const [key, input] of Object.entries(pendingFiles)) {
2767+
if (input.files) {
2768+
this.component.files(key, input);
2769+
}
27602770
delete this.pendingFiles[key];
27612771
}
27622772
this.component.action(directive.action, directive.named, debounce);
@@ -2838,7 +2848,7 @@ class LiveControllerDefault extends Controller {
28382848
if (element instanceof HTMLInputElement && element.type === 'file') {
28392849
const key = element.name;
28402850
if ((_a = element.files) === null || _a === void 0 ? void 0 : _a.length) {
2841-
this.pendingFiles[key] = element.files;
2851+
this.pendingFiles[key] = element;
28422852
}
28432853
else if (this.pendingFiles[key]) {
28442854
delete this.pendingFiles[key];

src/LiveComponent/assets/src/Component/index.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export default class Component {
5656
/** Actions that are waiting to be executed */
5757
private pendingActions: BackendAction[] = [];
5858
/** Files that are waiting to be sent */
59-
private pendingFiles: {[key: string]: FileList} = {};
59+
private pendingFiles: {[key: string]: HTMLInputElement} = {};
6060
/** Is a request waiting to be made? */
6161
private isRequestPending = false;
6262
/** Current "timeout" before the pending request should be sent. */
@@ -196,8 +196,8 @@ export default class Component {
196196
return promise;
197197
}
198198

199-
files(key: string, fileList: FileList): void {
200-
this.pendingFiles[key] = fileList;
199+
files(key: string, input: HTMLInputElement): void {
200+
this.pendingFiles[key] = input;
201201
}
202202

203203
render(): Promise<BackendResponse> {
@@ -358,18 +358,24 @@ export default class Component {
358358
// they are now "in sync" (with some exceptions noted inside)
359359
this.unsyncedInputsTracker.resetUnsyncedFields();
360360

361+
const filesToSend: {[key: string]: FileList} = {};
362+
for(const [key, value] of Object.entries(this.pendingFiles)) {
363+
if (value.files) {
364+
filesToSend[key] = value.files;
365+
}
366+
}
367+
361368
this.backendRequest = this.backend.makeRequest(
362369
this.valueStore.getOriginalProps(),
363370
this.pendingActions,
364371
this.valueStore.getDirtyProps(),
365372
this.getChildrenFingerprints(),
366373
this.valueStore.getUpdatedPropsFromParent(),
367-
this.pendingFiles,
374+
filesToSend,
368375
);
369376
this.hooks.triggerHook('loading.state:started', this.element, this.backendRequest);
370377

371378
this.pendingActions = [];
372-
this.pendingFiles = {};
373379
this.valueStore.flushDirtyPropsToPending();
374380
this.isRequestPending = false;
375381

@@ -378,6 +384,11 @@ export default class Component {
378384
const backendResponse = new BackendResponse(response);
379385
const html = await backendResponse.getBody();
380386

387+
// clear sent files inputs
388+
for(const input of Object.values(this.pendingFiles)) {
389+
input.value = '';
390+
}
391+
381392
// if the response does not contain a component, render as an error
382393
const headers = backendResponse.response.headers;
383394
if (headers.get('Content-Type') !== 'application/vnd.live-component+html' && !headers.get('X-Live-Redirect')) {

src/LiveComponent/assets/src/live_controller.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
6666
{ event: 'change', callback: (event) => this.handleChangeEvent(event) },
6767
{ event: 'live:connect', callback: (event) => this.handleConnectedControllerEvent(event) },
6868
];
69-
private pendingFiles: { [key: string]: FileList } = {};
69+
private pendingFiles: { [key: string]: HTMLInputElement } = {};
7070

7171
static componentRegistry = new ComponentRegistry();
7272

@@ -160,7 +160,7 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
160160
let debounce: number | boolean = false;
161161

162162
directives.forEach((directive) => {
163-
let pendingFiles: { [key: string]: FileList } = {};
163+
let pendingFiles: { [key: string]: HTMLInputElement } = {};
164164
const validModifiers: Map<string, (modifier: DirectiveModifier) => void> = new Map();
165165
validModifiers.set('prevent', () => {
166166
event.preventDefault();
@@ -200,8 +200,10 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
200200
);
201201
});
202202

203-
for (const [key, files] of Object.entries(pendingFiles)) {
204-
this.component.files(key, files);
203+
for (const [key, input] of Object.entries(pendingFiles)) {
204+
if (input.files) {
205+
this.component.files(key, input);
206+
}
205207
delete this.pendingFiles[key];
206208
}
207209
this.component.action(directive.action, directive.named, debounce);
@@ -340,7 +342,7 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
340342
if (element instanceof HTMLInputElement && element.type === 'file') {
341343
const key = element.name;
342344
if (element.files?.length) {
343-
this.pendingFiles[key] = element.files;
345+
this.pendingFiles[key] = element;
344346
} else if (this.pendingFiles[key]) {
345347
delete this.pendingFiles[key];
346348
}

0 commit comments

Comments
 (0)