Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions features/admin/page/sorting_content_elements_on_page.feature
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -33,6 +36,46 @@ trait ContentElementsCollectionFormComponentTrait
/** @var array<int|string, TemplateInterface> */
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
{
Expand Down
51 changes: 42 additions & 9 deletions templates/admin/shared/component_elements/form_theme.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,51 @@
{%- endblock live_collection_widget -%}

{%- block live_collection_entry_row -%}
<div id="{{ id }}" {{ block('attributes') }} {{ sylius_test_html_attribute('entry-row') }}>
{% set entryKeys = form.parent.children|keys %}
{% set isFirst = form.vars.name == entryKeys|first %}
{% set isLast = form.vars.name == entryKeys|last %}

<div class="sortable-item" id="{{ id }}" {{ block('attributes') }} {{ sylius_test_html_attribute('entry-row') }}>
{{- form_errors(form) -}}

<div class="alert text-body">
<div class="text-end">
{{- form_row(button_delete, sylius_test_form_attribute('delete-action')|sylius_merge_recursive({'attr': {'class': 'btn-close'}})) -}}
<div class="alert text-body d-flex align-items-center gap-4">
<div class="flex-grow-1">
{{- form_row(form.type) -}}
<div>
{{- form_row(form.configuration, {'label': false}) -}}
</div>
</div>

{{- form_row(form.type) -}}

<div>
{{- form_row(form.configuration, {'label': false}) -}}
<div class="d-flex flex-column align-items-center gap-2">
<button
type="button"
class="btn p-1 btn-outline-primary d-flex align-items-center justify-content-center"
data-action="live#action"
data-live-action-param="moveCollectionItem"
data-live-name-param="{{ form.parent.vars.full_name }}"
data-live-index-param="{{ form.vars.name }}"
data-live-direction-param="up"
{{ isFirst ? 'disabled' }}
>
{{ ux_icon('tabler:chevron-up', {style: 'display: block; margin: auto'}) }}
</button>
{{- 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'}
})) -}}
<button
type="button"
class="btn p-1 btn-outline-primary d-flex align-items-center justify-content-center lh-1"
data-action="live#action"
data-live-action-param="moveCollectionItem"
data-live-name-param="{{ form.parent.vars.full_name }}"
data-live-index-param="{{ form.vars.name }}"
data-live-direction-param="down"
{{ isLast ? 'disabled' }}
>
{{ ux_icon('tabler:chevron-down', {style: 'display: block; margin: auto'}) }}
</button>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion templates/admin/shared/editor/trix.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
<input type="hidden" id="{{ id }}" name="{{ full_name }}" value="{{ value }}" />

<trix-toolbar id="{{ toolbar_id }}"></trix-toolbar>
<trix-editor input="{{ id }}" toolbar="{{ toolbar_id }}"></trix-editor>
<trix-editor input="{{ id }}" toolbar="{{ toolbar_id }}" data-live-ignore></trix-editor>
</div>
{% endblock %}
52 changes: 52 additions & 0 deletions tests/Behat/Context/Ui/Admin/ContentCollectionContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
44 changes: 44 additions & 0 deletions tests/Behat/Element/Admin/ContentElementsCollectionElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(), [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Loading