Package
filament/forms
Package Version
v5.5.1
Laravel Version
v13.5.0
Livewire Version
No response
PHP Version
PHP 8.3.6
Problem description
When using the automaticallyOpenImageEditorForAspectRatio mode for FileUploads, it's possible to trigger a race condition where the file field ends up containing 2 values, even when isMultiple is false, maxFiles is set to 1, and maxParallelUploads is set to 1.
The 2 values contain both the original and cropped versions of the image. The root cause seems to be due to being able to trigger the cropped 'upload' before the original (uncropped) upload has completed. This in turn means that the 'removeUploadedFile' call (to remove the original file) is never triggered by the cropper since it is as yet unaware of the upload, and hence the field ends up with 2 files attached.
I've had a quick look through the code layout and but as I'm a newly-minted Filament fan I'm not sure what the correct solution is here. Ideally from a usability point of view, if automaticallyOpenImageEditorForAspectRatio is set and the user cancels the cropper, than nothing should be uploaded at all. To do this it looks like the 'addfile' listener on FilePond in filament/forms file-upload.js line 431 or so is the general area, but I couldn't see a way to hook that and abort the filepond file upload. Of course we need the addfile listener to get the image mime type and dimensions, so we cannot abort earlier.
As a second option - and again, I couldn't see a simple way to do this, sorry - the hook to open the cropper automatically could be attached after the first upload finishes entirely (filepond seems to indicate this is the 'processfile' event but in my tests that still seemed to fire before the upload call returned?). In that case, it would be a nice touch (if possible) to have the cropper call the removeUploadedFile function if the user cancels the cropper, since we do not want the original, uncropped, image to be submittable at all.
I've attached the response to the final call to _finishUpload to clarify how the 2 values are attached.
Let me know if there's anything I can help clarify?
PS thanks so much for Filament, it's an absolute lifesaver.
Expected behavior
Only the final cropped image should be stored in the field value.
Steps to reproduce
- Create a FileUpload field with automaticallyOpenImageEditorForAspectRatio set to true (you'll need to configure an aspect ratio too)
- Open your browser dev tools on the network tab as it makes the timing a lot easier
- Start the image upload with any image
- The image cropper should open very quickly
- Shortly after that you'll see a network request to 'update' fire off (this is the '_startUpload' call)
- As soon as you see that network request fire, press 'Save' on the image cropper.
- Wait for all network requests to complete
- Note the lack of 'removeUploadedFile' call before the cropper starts, and the 2 values for the field in the response to the final '_finishUpload' call
Reproduction repository (issue will be closed if this is not valid)
https://github.com/puzzledmonkey/filament-upload-problem
Relevant log output
{"_token":"RUNNhRC6rxyzVuzBh9wIPySqLmGZCjTIQlGty0NQ","components":[{"snapshot":"{\"data\":{\"record\":null,\"data\":[{\"image\":[{\"1e2859d2-7e84-47f6-b296-0ed5da1de951\":[\"livewire-file:1lTpicBpNX2w5D7BHdKyi4RCZQApw9vEHeiQZ4Ap.jpg\",{\"s\":\"fil\"}]},{\"s\":\"arr\"}]},{\"s\":\"arr\"}],\"previousUrl\":\"https:\\/\\/laravel.localhost\\/admin\\/tests\",\"isCreating\":false,\"mountedActions\":[[],{\"s\":\"arr\"}],\"defaultAction\":null,\"defaultActionArguments\":null,\"defaultActionContext\":null,\"defaultTableAction\":null,\"defaultTableActionRecord\":null,\"defaultTableActionArguments\":null,\"componentFileAttachments\":[[],{\"s\":\"arr\"}],\"areSchemaStateUpdateHooksDisabledForTesting\":false,\"discoveredSchemaNames\":[[\"form\",\"content\",\"headerWidgets\",\"footerWidgets\"],{\"s\":\"arr\"}],\"parentRecord\":null,\"savedDataHash\":null},\"memo\":{\"id\":\"09KWnf9K0lpfulu2qgN3\",\"name\":\"App\\\\Filament\\\\Resources\\\\Tests\\\\Pages\\\\CreateTest\",\"path\":\"admin\\/tests\\/create\",\"method\":\"GET\",\"release\":\"a-a-a\",\"children\":[],\"scripts\":[],\"assets\":[],\"errors\":[],\"locale\":\"en\",\"islands\":[]},\"checksum\":\"49b9788efa36f9e82b900579d72a6458f7b7b42de060b2814118a73abb35735c\"}","updates":{},"calls":[{"method":"_finishUpload","params":["data.image.5041b9ed-9c34-4d47-a66e-61e20a8d2fb1",["d2fe10e9:/E6VwjnZuVRT6FkDwGaZ2rMNQvHLBXt4YNXOf6YvF.jpg"],false,false],"metadata":{}}]}]}
Package
filament/forms
Package Version
v5.5.1
Laravel Version
v13.5.0
Livewire Version
No response
PHP Version
PHP 8.3.6
Problem description
When using the automaticallyOpenImageEditorForAspectRatio mode for FileUploads, it's possible to trigger a race condition where the file field ends up containing 2 values, even when isMultiple is false, maxFiles is set to 1, and maxParallelUploads is set to 1.
The 2 values contain both the original and cropped versions of the image. The root cause seems to be due to being able to trigger the cropped 'upload' before the original (uncropped) upload has completed. This in turn means that the 'removeUploadedFile' call (to remove the original file) is never triggered by the cropper since it is as yet unaware of the upload, and hence the field ends up with 2 files attached.
I've had a quick look through the code layout and but as I'm a newly-minted Filament fan I'm not sure what the correct solution is here. Ideally from a usability point of view, if automaticallyOpenImageEditorForAspectRatio is set and the user cancels the cropper, than nothing should be uploaded at all. To do this it looks like the 'addfile' listener on FilePond in filament/forms file-upload.js line 431 or so is the general area, but I couldn't see a way to hook that and abort the filepond file upload. Of course we need the addfile listener to get the image mime type and dimensions, so we cannot abort earlier.
As a second option - and again, I couldn't see a simple way to do this, sorry - the hook to open the cropper automatically could be attached after the first upload finishes entirely (filepond seems to indicate this is the 'processfile' event but in my tests that still seemed to fire before the upload call returned?). In that case, it would be a nice touch (if possible) to have the cropper call the removeUploadedFile function if the user cancels the cropper, since we do not want the original, uncropped, image to be submittable at all.
I've attached the response to the final call to _finishUpload to clarify how the 2 values are attached.
Let me know if there's anything I can help clarify?
PS thanks so much for Filament, it's an absolute lifesaver.
Expected behavior
Only the final cropped image should be stored in the field value.
Steps to reproduce
Reproduction repository (issue will be closed if this is not valid)
https://github.com/puzzledmonkey/filament-upload-problem
Relevant log output
{"_token":"RUNNhRC6rxyzVuzBh9wIPySqLmGZCjTIQlGty0NQ","components":[{"snapshot":"{\"data\":{\"record\":null,\"data\":[{\"image\":[{\"1e2859d2-7e84-47f6-b296-0ed5da1de951\":[\"livewire-file:1lTpicBpNX2w5D7BHdKyi4RCZQApw9vEHeiQZ4Ap.jpg\",{\"s\":\"fil\"}]},{\"s\":\"arr\"}]},{\"s\":\"arr\"}],\"previousUrl\":\"https:\\/\\/laravel.localhost\\/admin\\/tests\",\"isCreating\":false,\"mountedActions\":[[],{\"s\":\"arr\"}],\"defaultAction\":null,\"defaultActionArguments\":null,\"defaultActionContext\":null,\"defaultTableAction\":null,\"defaultTableActionRecord\":null,\"defaultTableActionArguments\":null,\"componentFileAttachments\":[[],{\"s\":\"arr\"}],\"areSchemaStateUpdateHooksDisabledForTesting\":false,\"discoveredSchemaNames\":[[\"form\",\"content\",\"headerWidgets\",\"footerWidgets\"],{\"s\":\"arr\"}],\"parentRecord\":null,\"savedDataHash\":null},\"memo\":{\"id\":\"09KWnf9K0lpfulu2qgN3\",\"name\":\"App\\\\Filament\\\\Resources\\\\Tests\\\\Pages\\\\CreateTest\",\"path\":\"admin\\/tests\\/create\",\"method\":\"GET\",\"release\":\"a-a-a\",\"children\":[],\"scripts\":[],\"assets\":[],\"errors\":[],\"locale\":\"en\",\"islands\":[]},\"checksum\":\"49b9788efa36f9e82b900579d72a6458f7b7b42de060b2814118a73abb35735c\"}","updates":{},"calls":[{"method":"_finishUpload","params":["data.image.5041b9ed-9c34-4d47-a66e-61e20a8d2fb1",["d2fe10e9:/E6VwjnZuVRT6FkDwGaZ2rMNQvHLBXt4YNXOf6YvF.jpg"],false,false],"metadata":{}}]}]}