Commit be6bac03 authored by Mateu Aguiló Bosch's avatar Mateu Aguiló Bosch
Browse files

Issue #3313316 by e0ipso: Add component filter form helper

parent 101f5973
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -6,3 +6,8 @@ options-filter:
      src/assets/css/options-filter.css: {}
  dependencies:
    - core/once

filter-settings:
  css:
    component:
      src/assets/css/filter-settings.css: { }
+145 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\cl_editorial\Form;

use Drupal\cl_components\Component\ComponentMetadata;
use Drupal\cl_components\ComponentPluginManager;
use Drupal\cl_components\Plugin\Component;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;

trait ComponentFiltersFormTrait {

  /**
   * Builds the form.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param \Drupal\cl_components\ComponentPluginManager $plugin_manager
   *   The plugin manager.
   * @param array $settings
   *   Settings array.
   * @param array $parents
   *   The array parents where this form is embedded.
   * @param \Drupal\Component\Render\MarkupInterface $message
   *   The info message.
   */
  public static function buildSettingsForm(array &$form, FormStateInterface $form_state, ComponentPluginManager $plugin_manager, array $settings, array $parents, MarkupInterface $message): void {
    // Check the user input for the existing values.
    $values = $form_state->getValues();
    $raw_input = $form_state->getUserInput();
    $clean = static fn (array $item) => array_values(array_filter($item));
    $types = NestedArray::getValue($values, [...$parents, 'filters', 'types'])
      ?? NestedArray::getValue($raw_input, [...$parents, 'filters', 'types'])
      ?? $settings['types']
      ?? [];
    $types = $clean($types);
    $statuses = NestedArray::getValue($values, [...$parents, 'filters', 'statuses'])
      ?? NestedArray::getValue($raw_input, [...$parents, 'filters', 'statuses'])
      ?? $settings['statuses']
      ?? [];
    $statuses = $clean($statuses);
    $components = $plugin_manager->getAllComponentsWithoutDupesWithFilters([], [], $types, $statuses);
    $options = array_reduce(
      $components,
      static fn (array $carry, Component $component) => [
        ...$carry,
        $component->getId() => $component->getMetadata()->getName()
      ],
      []
    );
    $form['filters'] = [
      '#type' => 'fieldset',
      '#title' => t('Filters'),
      '#tree' => TRUE,
    ];
    $form['filters']['info'] = [
      '#type' => 'html_tag',
      '#tag' => 'p',
      '#value' => $message,
    ];
    $form['filters']['types'] = [
      '#type' => 'checkboxes',
      '#title' => t('Component Types'),
      '#description' => t('Only components of these types will show in the component list.'),
      '#options' => [
        ComponentMetadata::COMPONENT_TYPE_ATOM => t('Atom'),
        ComponentMetadata::COMPONENT_TYPE_MOLECULE => t('Molecule'),
        ComponentMetadata::COMPONENT_TYPE_ORGANISM => t('Organism'),
      ],
      '#default_value' => $settings['types'],
      '#ajax' => [
        'wrapper' => 'refinement-wrapper',
        'effect' => 'fade',
        'callback' => [static::class, 'onChangeAjaxCallback'],
      ],
    ];
    $form['filters']['statuses'] = [
      '#type' => 'checkboxes',
      '#title' => t('Component Status'),
      '#description' => t('Only components of these status will show in the component list.'),
      '#options' => [
        ComponentMetadata::COMPONENT_STATUS_READY => t('Ready'),
        ComponentMetadata::COMPONENT_STATUS_BETA => t('Beta'),
        ComponentMetadata::COMPONENT_STATUS_WIP => t('Work in progress'),
        ComponentMetadata::COMPONENT_STATUS_DEPRECATED => t('Deprecated'),
      ],
      '#default_value' => $settings['statuses'],
      '#ajax' => [
        'wrapper' => 'refinement-wrapper',
        'effect' => 'fade',
        'callback' => [static::class, 'onChangeAjaxCallback'],
      ],
    ];
    $form['filters']['refine'] = [
      '#type' => 'details',
      '#open' => TRUE,
      '#title' => t('Refine selection'),
      '#prefix' => '<div id="refinement-wrapper">',
      '#suffix' => '</div>',
    ];
    $form['filters']['refine']['forbidden'] = [
      '#type' => 'checkboxes',
      '#title' => t('Forbidden components'),
      '#description' => t('When listing the blocks for CL Components, all components will be displayed except the components selected here. This is used to limit the components available for a given content type.'),
      '#options' => $options,
      '#default_value' => $settings['forbidden'],
      '#attributes' => ['class' => ['long-checkboxes-list']],
    ];
    $form['filters']['refine']['allowed'] = [
      '#type' => 'checkboxes',
      '#title' => t('Allowed components'),
      '#description' => t('When listing the blocks for CL Components, only the components selected here will be displayed as an option. <strong>NOTE:</strong> "forbidden" components will not be shown even when allowed here.'),
      '#options' => $options,
      '#default_value' => $settings['allowed'],
      '#attributes' => ['class' => ['long-checkboxes-list']],
    ];
    $form['filters']['#attached']['library'][] = 'cl_editorial/filter-settings';
  }

  public static function validates(array &$form, FormStateInterface $form_state): void {
    $forbidden = array_keys(array_filter($form_state->getValue(['filters', 'refine', 'forbidden'])));
    $allowed = array_keys(array_filter($form_state->getValue(['filters', 'refine', 'allowed'])));
    $both_lists = array_intersect($forbidden, $allowed);
    if (!empty($both_lists)) {
      $message = t('The following components are added to both forbidden and allow lists. Please review: %components', ['%components' => implode(', ', $both_lists)]);
      $form_state->setErrorByName('filters][refine][forbidden', $message);
      $form_state->setErrorByName('filters][refine][allowed', $message);
    }
  }

  /**
   * Ajax callback to regenerate the list of components.
   */
  public static function onChangeAjaxCallback(array &$element, FormStateInterface $form_state) {
    $triggering_element = $form_state->getTriggeringElement();
    // Pop the checkbox value and the "statuses" or "types" from the parents.
    // This should leave us with the parents to the "filters".
    $parents = array_slice($triggering_element['#array_parents'], 0, -2);
    return NestedArray::getValue($element, [...$parents, 'refine']);
  }

}
+8 −0
Original line number Diff line number Diff line
.long-checkboxes-list .form-checkboxes {
  display: flex;
  flex-wrap: wrap;
}

.long-checkboxes-list .form-checkboxes .form-item {
  width: 14em;
}