diff --git a/features/admin/page/sorting_content_elements_on_page.feature b/features/admin/page/sorting_content_elements_on_page.feature new file mode 100644 index 00000000..aaac0dfb --- /dev/null +++ b/features/admin/page/sorting_content_elements_on_page.feature @@ -0,0 +1,53 @@ +@managing_pages +Feature: Sorting content elements on a page + In order to manage the order of content on a page + As an Administrator + I want to be able to reorder content elements + + Background: + Given I am logged in as an administrator + And the store operates on a single channel in "United States" + + @ui @javascript + Scenario: Moving a content element down + When I go to the create page page + And I fill the code with "sort-test-page" + And I fill the name with "Sort Test Page" + And I fill the slug with "sort-test-page" + And I add a heading content element with type "h1" and "My Title" content + And I add a textarea content element with "My body text" content + When I move the 1st content element down + Then the 1st content element should be a "Textarea" element + And the 2nd content element should be a "Heading" element + + @ui @javascript + Scenario: Moving a content element up + When I go to the create page page + And I fill the code with "sort-test-page" + And I fill the name with "Sort Test Page" + And I fill the slug with "sort-test-page" + And I add a heading content element with type "h1" and "My Title" content + And I add a textarea content element with "My body text" content + When I move the 2nd content element up + Then the 1st content element should be a "Textarea" element + And the 2nd content element should be a "Heading" element + + @ui @javascript + Scenario: The first content element cannot be moved up + When I go to the create page page + And I fill the code with "sort-test-page" + And I fill the name with "Sort Test Page" + And I fill the slug with "sort-test-page" + And I add a heading content element with type "h1" and "My Title" content + And I add a textarea content element with "My body text" content + Then the move up button of the 1st content element should be disabled + + @ui @javascript + Scenario: The last content element cannot be moved down + When I go to the create page page + And I fill the code with "sort-test-page" + And I fill the name with "Sort Test Page" + And I fill the slug with "sort-test-page" + And I add a heading content element with type "h1" and "My Title" content + And I add a textarea content element with "My body text" content + Then the move down button of the 2nd content element should be disabled diff --git a/src/Twig/Component/Trait/ContentElementsCollectionFormComponentTrait.php b/src/Twig/Component/Trait/ContentElementsCollectionFormComponentTrait.php index e52001f0..5b189e56 100644 --- a/src/Twig/Component/Trait/ContentElementsCollectionFormComponentTrait.php +++ b/src/Twig/Component/Trait/ContentElementsCollectionFormComponentTrait.php @@ -13,15 +13,18 @@ namespace Sylius\CmsPlugin\Twig\Component\Trait; +use Sylius\Bundle\UiBundle\Twig\Component\LiveCollectionTrait; use Sylius\CmsPlugin\Entity\TemplateInterface; use Sylius\CmsPlugin\Form\Type\Translation\ContentConfigurationTranslationsType; use Sylius\CmsPlugin\Repository\TemplateRepositoryInterface; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\UX\LiveComponent\Attribute\LiveAction; use Symfony\UX\LiveComponent\Attribute\LiveArg; use Symfony\UX\LiveComponent\ComponentWithFormTrait; /** * @mixin ComponentWithFormTrait + * @mixin LiveCollectionTrait * * @see ContentConfigurationTranslationsType */ @@ -33,6 +36,46 @@ trait ContentElementsCollectionFormComponentTrait /** @var array */ protected array $templatesCache = []; + #[LiveAction] + public function moveCollectionItem( + PropertyAccessorInterface $propertyAccessor, + #[LiveArg] + string $name, + #[LiveArg] + int $index, + #[LiveArg] + string $direction, + ): void { + if (null === $this->formName) { + return; + } + + $propertyPath = $this->fieldNameToPropertyPath($name, $this->formName); + $data = $propertyAccessor->getValue($this->formValues, $propertyPath); + + if (!\is_array($data)) { + return; + } + + $keys = array_keys($data); + $currentPos = array_search($index, $keys, true); + + if (false === $currentPos) { + return; + } + + $swapPos = 'up' === $direction ? $currentPos - 1 : $currentPos + 1; + + if ($swapPos < 0 || $swapPos >= \count($keys)) { + return; + } + + $swapKey = $keys[$swapPos]; + [$data[$index], $data[$swapKey]] = [$data[$swapKey], $data[$index]]; + + $propertyAccessor->setValue($this->formValues, $propertyPath, $data); + } + #[LiveAction] public function applyContentTemplate(#[LiveArg] string $localeCode): void { diff --git a/templates/admin/shared/component_elements/form_theme.html.twig b/templates/admin/shared/component_elements/form_theme.html.twig index 5eea20f9..1be47c66 100644 --- a/templates/admin/shared/component_elements/form_theme.html.twig +++ b/templates/admin/shared/component_elements/form_theme.html.twig @@ -5,18 +5,51 @@ {%- endblock live_collection_widget -%} {%- block live_collection_entry_row -%} -
+ {% set entryKeys = form.parent.children|keys %} + {% set isFirst = form.vars.name == entryKeys|first %} + {% set isLast = form.vars.name == entryKeys|last %} + +
{{- form_errors(form) -}} -
-
- {{- form_row(button_delete, sylius_test_form_attribute('delete-action')|sylius_merge_recursive({'attr': {'class': 'btn-close'}})) -}} +
+
+ {{- form_row(form.type) -}} +
+ {{- form_row(form.configuration, {'label': false}) -}} +
- - {{- form_row(form.type) -}} - -
- {{- form_row(form.configuration, {'label': false}) -}} +
+ + {{- form_row(button_delete, sylius_test_form_attribute('delete-action')|sylius_merge_recursive({ + 'label': ux_icon('tabler:trash', {style: 'display: block; margin: auto'}), + 'label_html': true, + 'attr': {'class': 'btn p-1 btn-outline-danger'}, + 'row_attr': {'class': 'mb-0'} + })) -}} +
diff --git a/templates/admin/shared/editor/trix.html.twig b/templates/admin/shared/editor/trix.html.twig index 9ff45c75..150b169d 100644 --- a/templates/admin/shared/editor/trix.html.twig +++ b/templates/admin/shared/editor/trix.html.twig @@ -6,6 +6,6 @@ - +
{% endblock %} diff --git a/tests/Behat/Context/Ui/Admin/ContentCollectionContext.php b/tests/Behat/Context/Ui/Admin/ContentCollectionContext.php index 2afb15fd..58f200de 100644 --- a/tests/Behat/Context/Ui/Admin/ContentCollectionContext.php +++ b/tests/Behat/Context/Ui/Admin/ContentCollectionContext.php @@ -205,4 +205,56 @@ public function iShouldNotSeeContentElementInTheContentElementsSection(string $c { Assert::false($this->contentElementsCollectionElement->hasContentElement($contentElement)); } + + /** + * @When I move the :ordinal content element up + */ + public function iMoveTheContentElementUp(string $ordinal): void + { + $this->contentElementsCollectionElement->moveContentElementUp($this->parseOrdinal($ordinal)); + } + + /** + * @When I move the :ordinal content element down + */ + public function iMoveTheContentElementDown(string $ordinal): void + { + $this->contentElementsCollectionElement->moveContentElementDown($this->parseOrdinal($ordinal)); + } + + /** + * @Then the :ordinal content element should be a :type element + */ + public function theContentElementAtPositionShouldBeOfType(string $ordinal, string $type): void + { + Assert::same( + $this->contentElementsCollectionElement->getContentElementTypeAtPosition($this->parseOrdinal($ordinal)), + $type, + ); + } + + /** + * @Then the move up button of the :ordinal content element should be disabled + */ + public function theMoveUpButtonOfTheContentElementShouldBeDisabled(string $ordinal): void + { + Assert::true( + $this->contentElementsCollectionElement->isContentElementMoveUpButtonDisabled($this->parseOrdinal($ordinal)), + ); + } + + /** + * @Then the move down button of the :ordinal content element should be disabled + */ + public function theMoveDownButtonOfTheContentElementShouldBeDisabled(string $ordinal): void + { + Assert::true( + $this->contentElementsCollectionElement->isContentElementMoveDownButtonDisabled($this->parseOrdinal($ordinal)), + ); + } + + private function parseOrdinal(string $ordinal): int + { + return (int) preg_replace('/\D/', '', $ordinal); + } } diff --git a/tests/Behat/Element/Admin/ContentElementsCollectionElement.php b/tests/Behat/Element/Admin/ContentElementsCollectionElement.php index 04e98b19..20f72132 100644 --- a/tests/Behat/Element/Admin/ContentElementsCollectionElement.php +++ b/tests/Behat/Element/Admin/ContentElementsCollectionElement.php @@ -157,6 +157,50 @@ public function removeContentElement(string $type): void $this->waitForFormUpdate(); } + public function moveContentElementUp(int $position): void + { + $button = $this->getSortButton($position, 'up'); + $button->click(); + } + + public function moveContentElementDown(int $position): void + { + $button = $this->getSortButton($position, 'down'); + $button->click(); + } + + public function getContentElementTypeAtPosition(int $position): string + { + $elements = $this->getContentElements(); + Assert::keyExists($elements, $position - 1, sprintf('No content element at position %d.', $position)); + + $selectedOption = $elements[$position - 1]->find('css', 'option[selected]'); + Assert::notNull($selectedOption, sprintf('No selected type option found at position %d.', $position)); + + return $selectedOption->getText(); + } + + public function isContentElementMoveUpButtonDisabled(int $position): bool + { + return $this->getSortButton($position, 'up')->hasAttribute('disabled'); + } + + public function isContentElementMoveDownButtonDisabled(int $position): bool + { + return $this->getSortButton($position, 'down')->hasAttribute('disabled'); + } + + private function getSortButton(int $position, string $direction): NodeElement + { + $elements = $this->getContentElements(); + Assert::keyExists($elements, $position - 1, sprintf('No content element at position %d.', $position)); + + $button = $elements[$position - 1]->find('css', sprintf('[data-live-direction-param="%s"]', $direction)); + Assert::notNull($button, sprintf('Sort %s button not found at position %d.', $direction, $position)); + + return $button; + } + protected function getDefinedElements(): array { return array_merge(parent::getDefinedElements(), [ diff --git a/tests/Behat/Element/Admin/ContentElementsCollectionElementInterface.php b/tests/Behat/Element/Admin/ContentElementsCollectionElementInterface.php index 3a494d73..cb035781 100644 --- a/tests/Behat/Element/Admin/ContentElementsCollectionElementInterface.php +++ b/tests/Behat/Element/Admin/ContentElementsCollectionElementInterface.php @@ -33,4 +33,14 @@ public function updateContentElementOfType(string $type, array|string $content): public function addContentElementOfTypeWithContent(string $type, array|string $content): void; public function removeContentElement(string $type): void; + + public function moveContentElementUp(int $position): void; + + public function moveContentElementDown(int $position): void; + + public function getContentElementTypeAtPosition(int $position): string; + + public function isContentElementMoveUpButtonDisabled(int $position): bool; + + public function isContentElementMoveDownButtonDisabled(int $position): bool; }