diff --git a/src/Cropperjs/CHANGELOG.md b/src/Cropperjs/CHANGELOG.md index 3f0d735f7ec..0cbd60d917a 100644 --- a/src/Cropperjs/CHANGELOG.md +++ b/src/Cropperjs/CHANGELOG.md @@ -5,6 +5,8 @@ - Support for `stimulus` version 2 was removed and support for `@hotwired/stimulus` version 3 was added. See the [@symfony/stimulus-bridge CHANGELOG](https://github.com/symfony/stimulus-bridge/blob/main/CHANGELOG.md#300) for more details. +- The individual Cropper.js options in `CropperType` were moved under + a single `cropper_options` option. - Support added for Symfony 6 ## 1.3 diff --git a/src/Cropperjs/Form/CropperType.php b/src/Cropperjs/Form/CropperType.php index 1baaf5e638f..3f5987b8e7f 100644 --- a/src/Cropperjs/Form/CropperType.php +++ b/src/Cropperjs/Form/CropperType.php @@ -34,38 +34,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'error_bubbling' => true, 'attr' => [ 'data-controller' => trim(($options['attr']['data-controller'] ?? '').' symfony--ux-cropperjs--cropper'), - 'data-public-url' => $options['public_url'], - 'data-view-mode' => $options['view_mode'], - 'data-drag-mode' => $options['drag_mode'], - 'data-aspect-ratio' => $options['aspect_ratio'] ?: false, - 'data-initial-aspect-ratio' => $options['initial_aspect_ratio'] ?: false, - 'data-responsive' => $options['responsive'], - 'data-restore' => $options['restore'], - 'data-check-cross-origin' => $options['check_cross_origin'], - 'data-check-orientation' => $options['check_orientation'], - 'data-modal' => $options['modal'], - 'data-guides' => $options['guides'], - 'data-center' => $options['center'], - 'data-highlight' => $options['highlight'], - 'data-background' => $options['background'], - 'data-auto-crop' => $options['auto_crop'], - 'data-auto-crop-area' => $options['auto_crop_area'], - 'data-movable' => $options['movable'], - 'data-rotatable' => $options['rotatable'], - 'data-scalable' => $options['scalable'], - 'data-zoomable' => $options['zoomable'], - 'data-zoom-on-touch' => $options['zoom_on_touch'], - 'data-zoom-on-wheel' => $options['zoom_on_wheel'], - 'data-wheel-zoom-ratio' => $options['wheel_zoom_ratio'], - 'data-crop-box-movable' => $options['crop_box_movable'], - 'data-crop-box-resizable' => $options['crop_box_resizable'], - 'data-toggle-drag-mode-on-dblclick' => $options['toggle_drag_mode_on_dblclick'], - 'data-min-container-width' => $options['min_container_width'], - 'data-min-container-height' => $options['min_container_height'], - 'data-min-canvas-width' => $options['min_canvas_width'], - 'data-min-canvas-height' => $options['min_canvas_height'], - 'data-min-crop-box-width' => $options['min_crop_box_width'], - 'data-min-crop-box-height' => $options['min_crop_box_height'], + 'data-symfony--ux-cropperjs--cropper-public-url-value' => $options['public_url'], + 'data-symfony--ux-cropperjs--cropper-options-value' => json_encode($options['cropper_options']), ], ]) ; @@ -85,107 +55,11 @@ public function configureOptions(OptionsResolver $resolver) { $resolver->setRequired('public_url'); $resolver->setAllowedTypes('public_url', 'string'); - - $resolver->setDefined([ - 'view_mode', - 'drag_mode', - 'aspect_ratio', - 'initial_aspect_ratio', - 'responsive', - 'restore', - 'check_cross_origin', - 'check_orientation', - 'modal', - 'guides', - 'center', - 'highlight', - 'background', - 'auto_crop', - 'auto_crop_area', - 'movable', - 'rotatable', - 'scalable', - 'zoomable', - 'zoom_on_touch', - 'zoom_on_wheel', - 'wheel_zoom_ratio', - 'crop_box_movable', - 'crop_box_resizable', - 'toggle_drag_mode_on_dblclick', - 'min_container_width', - 'min_container_height', - 'min_canvas_width', - 'min_canvas_height', - 'min_crop_box_width', - 'min_crop_box_height', - ]); - - $resolver->setAllowedTypes('view_mode', ['int']); - $resolver->setAllowedTypes('drag_mode', ['string']); - $resolver->setAllowedTypes('aspect_ratio', ['double', 'null']); - $resolver->setAllowedTypes('initial_aspect_ratio', ['double', 'null']); - $resolver->setAllowedTypes('responsive', ['bool']); - $resolver->setAllowedTypes('restore', ['bool']); - $resolver->setAllowedTypes('check_cross_origin', ['bool']); - $resolver->setAllowedTypes('check_orientation', ['bool']); - $resolver->setAllowedTypes('modal', ['bool']); - $resolver->setAllowedTypes('guides', ['bool']); - $resolver->setAllowedTypes('center', ['bool']); - $resolver->setAllowedTypes('highlight', ['bool']); - $resolver->setAllowedTypes('background', ['bool']); - $resolver->setAllowedTypes('auto_crop', ['bool']); - $resolver->setAllowedTypes('auto_crop_area', ['float']); - $resolver->setAllowedTypes('movable', ['bool']); - $resolver->setAllowedTypes('rotatable', ['bool']); - $resolver->setAllowedTypes('scalable', ['bool']); - $resolver->setAllowedTypes('zoomable', ['bool']); - $resolver->setAllowedTypes('zoom_on_touch', ['bool']); - $resolver->setAllowedTypes('zoom_on_wheel', ['bool']); - $resolver->setAllowedTypes('wheel_zoom_ratio', ['float']); - $resolver->setAllowedTypes('crop_box_movable', ['bool']); - $resolver->setAllowedTypes('crop_box_resizable', ['bool']); - $resolver->setAllowedTypes('toggle_drag_mode_on_dblclick', ['bool']); - $resolver->setAllowedTypes('min_container_width', ['int']); - $resolver->setAllowedTypes('min_container_height', ['int']); - $resolver->setAllowedTypes('min_canvas_width', ['int']); - $resolver->setAllowedTypes('min_canvas_height', ['int']); - $resolver->setAllowedTypes('min_crop_box_width', ['int']); - $resolver->setAllowedTypes('min_crop_box_height', ['int']); + $resolver->setDefault('cropper_options', []); $resolver->setDefaults([ 'label' => false, 'data_class' => Crop::class, - 'view_mode' => 0, - 'drag_mode' => 'crop', - 'initial_aspect_ratio' => null, - 'aspect_ratio' => null, - 'responsive' => true, - 'restore' => true, - 'check_cross_origin' => true, - 'check_orientation' => true, - 'modal' => true, - 'guides' => true, - 'center' => true, - 'highlight' => true, - 'background' => true, - 'auto_crop' => true, - 'auto_crop_area' => 0.8, - 'movable' => false, - 'rotatable' => true, - 'scalable' => false, - 'zoomable' => false, - 'zoom_on_touch' => true, - 'zoom_on_wheel' => true, - 'wheel_zoom_ratio' => 0.1, - 'crop_box_movable' => true, - 'crop_box_resizable' => true, - 'toggle_drag_mode_on_dblclick' => true, - 'min_container_width' => 200, - 'min_container_height' => 100, - 'min_canvas_width' => 0, - 'min_canvas_height' => 0, - 'min_crop_box_width' => 0, - 'min_crop_box_height' => 0, ]); } diff --git a/src/Cropperjs/README.md b/src/Cropperjs/README.md index 5e6949830aa..6b193a47f25 100644 --- a/src/Cropperjs/README.md +++ b/src/Cropperjs/README.md @@ -44,7 +44,9 @@ class HomeController extends AbstractController $form = $this->createFormBuilder(['crop' => $crop]) ->add('crop', CropperType::class, [ 'public_url' => '/public/url/to/the/image.jpg', - 'aspect_ratio' => 2000 / 1500, + 'cropper_options' => [ + 'aspectRatio' => 2000 / 1500, + ], ]) ->getForm() ; @@ -68,49 +70,7 @@ class HomeController extends AbstractController } ``` -You can pass the following options to the `CropperType` field: - -```php -$form = $this->createFormBuilder(['crop' => $crop]) - ->add('crop', CropperType::class, [ - 'public_url' => '/public/url/to/the/image.jpg', - 'view_mode' => 1, - 'drag_mode' => 'move', - 'initial_aspect_ratio' => 2000 / 1800, - 'aspect_ratio' => 2000 / 1800, - 'responsive' => true, - 'restore' => true, - 'check_cross_origin' => true, - 'check_orientation' => true, - 'modal' => true, - 'guides' => true, - 'center' => true, - 'highlight' => true, - 'background' => true, - 'auto_crop' => true, - 'auto_crop_area' => 0.1, - 'movable' => true, - 'rotatable' => true, - 'scalable' => true, - 'zoomable' => true, - 'zoom_on_touch' => true, - 'zoom_on_wheel' => true, - 'wheel_zoom_ratio' => 0.2, - 'crop_box_movable' => true, - 'crop_box_resizable' => true, - 'toggle_drag_mode_on_dblclick' => true, - 'min_container_width' => 200, - 'min_container_height' => 100, - 'min_canvas_width' => 0, - 'min_canvas_height' => 0, - 'min_crop_box_width' => 0, - 'min_crop_box_height' => 0, - ]) - ->getForm() -; -``` - -These options are associated to [the Cropper.js options](https://github.com/fengyuanchen/cropperjs/blob/master/README.md#options). +These `cropper_options` can be any [the Cropper.js option](https://github.com/fengyuanchen/cropperjs/blob/main/README.md#options). Once created in PHP, a crop form is a normal form, meaning you can display it using Twig as you would normally with any form: @@ -130,14 +90,22 @@ import { Controller } from '@hotwired/stimulus'; export default class extends Controller { connect() { + this.element.addEventListener('cropperjs:pre-connect', this._onPreConnect); this.element.addEventListener('cropperjs:connect', this._onConnect); } disconnect() { // You should always remove listeners when the controller is disconnected to avoid side effects + this.element.removeEventListener('cropperjs:pre-connect', this._onConnect); this.element.removeEventListener('cropperjs:connect', this._onConnect); } + _onPreConnect(event) { + // The cropper has not yet been created and options can be modified + console.log(event.detail.options); + console.log(event.detail.img); + } + _onConnect(event) { // The cropper was just created and you can access details from the event console.log(event.detail.cropper); @@ -158,7 +126,9 @@ Then in your form, add your controller as an HTML attribute: $form = $this->createFormBuilder(['crop' => $crop]) ->add('crop', CropperType::class, [ 'public_url' => '/public/url/to/the/image.jpg', - 'aspect_ratio' => 2000 / 1800, + 'cropper_options' => [ + 'aspectRatio' => 2000 / 1800, + ], 'attr' => ['data-controller' => 'mycropper'], ]) ->getForm() diff --git a/src/Cropperjs/Resources/assets/src/controller.ts b/src/Cropperjs/Resources/assets/src/controller.ts index 3e15fe043f9..0240ef7a6c1 100644 --- a/src/Cropperjs/Resources/assets/src/controller.ts +++ b/src/Cropperjs/Resources/assets/src/controller.ts @@ -11,68 +11,41 @@ import { Controller } from '@hotwired/stimulus'; import Cropper from 'cropperjs'; +import CropEvent = Cropper.CropEvent; + +export default class CropperController extends Controller { + static values = { + publicUrl: String, + options: Object, + }; -export default class extends Controller { connect() { // Create image view const img = document.createElement('img'); img.classList.add('cropperjs-image'); - img.src = this.element.getAttribute('data-public-url'); - - const parent = this.element.parentNode; - parent.appendChild(img); - - // Build the cropper - const options = { - viewMode: parseInt(this.element.getAttribute('data-view-mode')), - dragMode: this.element.getAttribute('data-drag-mode'), - responsive: this.element.hasAttribute('data-responsive'), - restore: this.element.hasAttribute('data-restore'), - checkCrossOrigin: this.element.hasAttribute('data-check-cross-origin'), - checkOrientation: this.element.hasAttribute('data-check-orientation'), - modal: this.element.hasAttribute('data-modal'), - guides: this.element.hasAttribute('data-guides'), - center: this.element.hasAttribute('data-center'), - highlight: this.element.hasAttribute('data-highlight'), - background: this.element.hasAttribute('data-background'), - autoCrop: this.element.hasAttribute('data-auto-crop'), - autoCropArea: parseFloat(this.element.getAttribute('data-auto-crop-area')), - movable: this.element.hasAttribute('data-movable'), - rotatable: this.element.hasAttribute('data-rotatable'), - scalable: this.element.hasAttribute('data-scalable'), - zoomable: this.element.hasAttribute('data-zoomable'), - zoomOnTouch: this.element.hasAttribute('data-zoom-on-touch'), - zoomOnWheel: this.element.hasAttribute('data-zoom-on-wheel'), - wheelZoomRatio: parseFloat(this.element.getAttribute('data-wheel-zoom-ratio')), - cropBoxMovable: this.element.hasAttribute('data-crop-box-movable'), - cropBoxResizable: this.element.hasAttribute('data-crop-box-resizable'), - toggleDragModeOnDblclick: this.element.hasAttribute('data-toggle-drag-mode-on-dblclick'), - minContainerWidth: parseInt(this.element.getAttribute('data-min-container-width')), - minContainerHeight: parseInt(this.element.getAttribute('data-min-container-height')), - minCanvasWidth: parseInt(this.element.getAttribute('data-min-canvas-width')), - minCanvasHeight: parseInt(this.element.getAttribute('data-min-canvas-height')), - minCropBoxWidth: parseInt(this.element.getAttribute('data-min-crop-box-width')), - minCropBoxHeight: parseInt(this.element.getAttribute('data-min-crop-box-height')), - }; + img.src = (this as any).publicUrlValue; - if (this.element.getAttribute('data-aspect-ratio')) { - options.aspectRatio = parseFloat(this.element.getAttribute('data-aspect-ratio')); + const parent = (this.element as HTMLInputElement).parentNode; + if (!parent) { + throw new Error('Missing parent node for Cropperjs'); } - if (this.element.getAttribute('data-initial-aspect-ratio')) { - options.initialAspectRatio = parseFloat(this.element.getAttribute('data-initial-aspect-ratio')); - } + parent.appendChild(img); + const options = this.optionsValue; + this._dispatchEvent('cropperjs:pre-connect', { options, img }); + + // Build the cropper const cropper = new Cropper(img, options); img.addEventListener('crop', (event) => { - this.element.value = JSON.stringify(event.detail); + (this.element as HTMLInputElement).value = JSON.stringify((event as CropEvent).detail); }); this._dispatchEvent('cropperjs:connect', { cropper, options, img }); } - _dispatchEvent(name, payload = null, canBubble = false, cancelable = false) { + _dispatchEvent(name: string, payload: any = null, canBubble = false, cancelable = false) { const userEvent = document.createEvent('CustomEvent'); userEvent.initCustomEvent(name, canBubble, cancelable, payload); diff --git a/src/Cropperjs/Resources/assets/test/controller.test.ts b/src/Cropperjs/Resources/assets/test/controller.test.ts index 9274efc1d5d..6027c9e0f9f 100644 --- a/src/Cropperjs/Resources/assets/test/controller.test.ts +++ b/src/Cropperjs/Resources/assets/test/controller.test.ts @@ -14,11 +14,14 @@ import { getByTestId, waitFor } from '@testing-library/dom'; import { clearDOM, mountDOM } from '@symfony/stimulus-testing'; import CropperjsController from '../src/controller'; +let cropper: Cropper|null = null; + // Controller used to check the actual controller was properly booted class CheckController extends Controller { connect() { - this.element.addEventListener('cropperjs:connect', () => { + this.element.addEventListener('cropperjs:connect', (event: any) => { this.element.classList.add('connected'); + cropper = event.detail.cropper; }); } } @@ -29,8 +32,16 @@ const startStimulus = () => { application.register('cropperjs', CropperjsController); }; +const dataToJsonAttribute = (data: any) => { + const container = document.createElement('div'); + container.dataset.foo = JSON.stringify(data); + + // returns the now-escaped string, ready to be used in an HTML attribute + return container.outerHTML.match(/data-foo="(.+)"/)[1] +} + describe('CropperjsController', () => { - let container; + let container: any; beforeEach(() => { container = mountDOM(` @@ -38,44 +49,19 @@ describe('CropperjsController', () => { + data-cropperjs-public-url-value="https://symfony.com/logos/symfony_black_02.png" + data-cropperjs-options-value="${dataToJsonAttribute({ + viewMode: 1, + dragMode: "move" + })}" + > `); }); afterEach(() => { clearDOM(); + cropper = null; }); it('connect', async () => { @@ -83,5 +69,7 @@ describe('CropperjsController', () => { startStimulus(); await waitFor(() => expect(getByTestId(container, 'input')).toHaveClass('connected')); + expect(cropper.options.viewMode).toBe(1); + expect(cropper.options.dragMode).toBe('move'); }); }); diff --git a/src/Cropperjs/Tests/Form/CropperTypeTest.php b/src/Cropperjs/Tests/Form/CropperTypeTest.php index b716b557357..5e1bc6ff478 100644 --- a/src/Cropperjs/Tests/Form/CropperTypeTest.php +++ b/src/Cropperjs/Tests/Form/CropperTypeTest.php @@ -34,37 +34,10 @@ public function testRenderFull() ->add('photo', CropperType::class, [ 'public_url' => '/public/url.jpg', 'attr' => ['data-controller' => 'mycropper'], - 'view_mode' => 1, - 'drag_mode' => 'move', - 'initial_aspect_ratio' => 2000 / 1800, - 'aspect_ratio' => 2000 / 1800, - 'responsive' => true, - 'restore' => true, - 'check_cross_origin' => true, - 'check_orientation' => true, - 'modal' => true, - 'guides' => true, - 'center' => true, - 'highlight' => true, - 'background' => true, - 'auto_crop' => true, - 'auto_crop_area' => 0.1, - 'movable' => true, - 'rotatable' => true, - 'scalable' => true, - 'zoomable' => true, - 'zoom_on_touch' => true, - 'zoom_on_wheel' => true, - 'wheel_zoom_ratio' => 0.2, - 'crop_box_movable' => true, - 'crop_box_resizable' => true, - 'toggle_drag_mode_on_dblclick' => true, - 'min_container_width' => 1, - 'min_container_height' => 2, - 'min_canvas_width' => 3, - 'min_canvas_height' => 4, - 'min_crop_box_width' => 5, - 'min_crop_box_height' => 6, + 'cropper_options' => [ + 'viewMode' => 1, + 'dragMode' => 'move', + ], ]) ->getForm() ; @@ -78,38 +51,8 @@ public function testRenderFull() '
'. ''. + 'data-symfony--ux-cropperjs--cropper-public-url-value="/public/url.jpg" '. + 'data-symfony--ux-cropperjs--cropper-options-value="{"viewMode":1,"dragMode":"move"}" />'. '
'. ''. ''. @@ -142,33 +85,8 @@ public function testRenderNoOptions() '
'. ''. + 'data-symfony--ux-cropperjs--cropper-public-url-value="/public/url.jpg" '. + 'data-symfony--ux-cropperjs--cropper-options-value="[]" />'. '
'. ''. ''.