component_library-1.0.x-dev/src/Form/ComponentLibraryVariantForm.php

src/Form/ComponentLibraryVariantForm.php
<?php

declare(strict_types=1);

namespace Drupal\component_library\Form;

use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Component\Serialization\Yaml;
use Drupal\component_library\Entity\ComponentLibraryPattern;
use Drupal\component_library\Entity\ComponentLibraryVariant;
use Drupal\component_library\Event\ConvertYamlPlaceholdersEvent;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Twig\Error\SyntaxError;

/**
 * Form controller for the component library variant form.
 */
final class ComponentLibraryVariantForm extends EntityForm {

  protected RendererInterface $renderer;

  public function __construct(RendererInterface $renderer) {
    $this->renderer = $renderer;
  }

  public static function create(ContainerInterface $container): self {
    return new self(
      $container->get('renderer'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state): array {
    $form = parent::form($form, $form_state);

    $form['label'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Label'),
      '#maxlength' => 255,
      '#default_value' => $this->entity->label(),
      '#description' => $this->t('Label for the component library variant.'),
      '#required' => TRUE,
    ];
    $form['id'] = [
      '#type' => 'machine_name',
      '#default_value' => $this->entity->id(),
      '#maxlength' => 31,
      '#machine_name' => [
        'exists' => [self::class, 'exists'],
      ],
      '#disabled' => !$this->entity->isNew(),
    ];

    $code_mirror_settings = [
      'autoCloseTags' => TRUE,
      'buttons' => [
        'undo',
        'redo',
        'enlarge',
        'shrink',
      ],
      'foldGutter' => TRUE,
      'lineNumbers' => TRUE,
      'lineWrapping' => TRUE,
      'styleActiveLine' => TRUE,
      'toolbar' => TRUE,
      'lineSeparator' => "\n",
    ];
    $form['template'] = [
      '#type' => 'codemirror',
      '#codemirror' => $code_mirror_settings + ['mode' => 'html_twig'],
      '#title' => $this->t('Template'),
      '#default_value' => $this->entity->get('template'),
      '#element_validate' => [[self::class, 'twigValidate']],
    ];

    $form['pattern'] = [
      '#title' => $this->t('Pattern'),
      '#type' => 'select',
      '#options' => $this->getPatternOptions(),
      '#require' => TRUE,
      '#default_value' => $this->entity->get('pattern') ?? $this->getRequest()->get('pattern'),
      '#ajax' => [
        'callback' => [self::class, 'previewDataCallback'],
        'event' => 'change',
      ],
      '#disabled' => !$this->entity->isNew(),
    ];

    $code_mirror_settings = [
      'foldGutter' => TRUE,
      'lineNumbers' => TRUE,
      'lineWrapping' => TRUE,
      'toolbar' => FALSE,
      'readOnly' => TRUE,
      'mode' => 'yaml',
    ];

    $pattern_entity = $this->entity->getPatternEntity();
    $form['data'] = [
      '#type' => 'codemirror',
      '#codemirror' => $code_mirror_settings,
      '#title' => $this->t('Preview data'),
      '#default_value' => $pattern_entity ? $pattern_entity->get('data') : NULL,
      '#suffix' => '<output id="component-library-variant"></output>',
    ];

    $form['preview'] = [
      '#type' => 'submit',
      '#value' => $this->t('Preview'),
      '#submit' => [],
      '#ajax' => [
        'callback' => [self::class, 'previewCallback'],
        'event' => 'click',
      ],
    ];
    $form['status'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enabled'),
      '#default_value' => $this->entity->status(),
    ];

    $form['#attached']['library'][] = 'component_library/component_library';
    return $form;
  }

  public static function exists(string $value, array $element, FormStateInterface $form_state): bool {
    return ComponentLibraryVariant::load(self::idFormat($value, $form_state->getValue('pattern'))) !== NULL;
  }

  public static function idFormat(string $variation, string $pattern): string {
    return \sprintf('%s__%s', $pattern, $variation);
  }

  /**
   * {@inheritdoc}
   */
  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state): void {
    parent::copyFormValuesToEntity($entity, $form, $form_state);
    // Convert config into line feed endings so YAML config is well formed.
    $value = \implode("\n", \preg_split('(\r\n|\r)', $form_state->getValue('template')));
    $entity->set('template', $value);
    // ID of variant should include pattern ID as a prefix, on first creation.
    if ($entity->isNew()) {
      $entity->set('id', self::idFormat($form_state->getValue('id'), $form_state->getValue('pattern')));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function save(array $form, FormStateInterface $form_state): void {
    $entity = $this->getEntity();
    $result = $entity->save();
    $link = $entity->toLink($this->t('View'))->toRenderable();

    $message_arguments = [
      '@link' => $entity->toUrl('canonical')->toString(),
      '%label' => $this->entity->label(),
    ];
    $logger_arguments = $message_arguments + ['link' => $this->renderer->render($link)];

    if ($result === \SAVED_NEW) {
      $this->messenger()->addStatus($this->t('New component variation <a href="@link">%label</a> has been created.', $message_arguments));
      $this->logger('component_library')->notice('Created new component variation %label', $logger_arguments);
    }
    else {
      $this->messenger()->addStatus($this->t('The component variation <a href="@link">%label</a> has been updated.', $message_arguments));
      $this->logger('component_library')->notice('Updated new component variation %label.', $logger_arguments);
    }

    $form_state->setRedirect('entity.component_library_pattern.edit_form', ['component_library_pattern' => $entity->getPatternEntity()->id()]);
  }

  /**
   * Use element validator to make sure that Twig is valid.
   */
  public static function twigValidate(array $element, FormStateInterface $form_state): void {
    $template = $form_state->getValue($element['#parents']);
    $encoded_context = $form_state->getValue('data');
    try {
      $context = Yaml::decode($encoded_context) ?? [];
    }
    catch (InvalidDataTypeException $execption) {
      $context = [];
    }

    $markup = [
      '#type' => 'inline_template',
      '#template' => $template,
      '#context' => $context,
    ];
    try {
      \Drupal::service('renderer')->render($markup);
    }
    catch (SyntaxError $exception) {
      $form_state->setError($element, new TranslatableMarkup('Twig error: %error', ['%error' => $exception->getMessage()]));
    }
  }

  /**
   * Ajax callback triggered by pattern select list.
   */
  public static function previewDataCallback(array $form, FormStateInterface $form_state): AjaxResponse {

    $response = new AjaxResponse();

    $pattern_id = $form_state->getValue('pattern');
    if ($pattern_id !== NULL) {
      $pattern = ComponentLibraryPattern::load($pattern_id);
      $data = $pattern->get('data');
    }
    else {
      $data = '';
    }

    return $response->addCommand(new HtmlCommand('#edit-data', $data));
  }

  /**
   * Ajax callback triggered by click.
   */
  public static function previewCallback(array $form, FormStateInterface $form_state): AjaxResponse {
    $template = $form_state->getValue('template');
    $preview_text = $form_state->getValue('data');
    $yaml_error = '';
    try {
      $context = Yaml::decode($preview_text) ?? [];
    }
    catch (InvalidDataTypeException $e) {
      $context = [];
      $yaml_error = '<p>' . (new TranslatableMarkup('Error in preview YAML.'))->render() . '</p>';
    }
    $context = self::convertYamlPlaceholders($context);
    $markup = [
      '#type' => 'inline_template',
      '#template' => $template,
      '#context' => $context,
    ];
    $iframe = [
      '#type' => 'html_tag',
      '#tag' => 'iframe',
      '#attributes' => [
        'src' => Url::fromRoute('component_library.variant_preview')->toString(),
        'frameborder' => 1,
        'scrolling' => FALSE,
        'allowtransparency' => TRUE,
        'allowfullscreen' => TRUE,
      ],
    ];

    try {
      $rendered = \Drupal::service('renderer')->render($markup);
    }
    catch (SyntaxError $exception) {
      $rendered = new TranslatableMarkup('Twig error: %error', ['%error' => $exception->getMessage()]);
    }
    if ($yaml_error !== '') {
      $rendered = $yaml_error;
    }
    \Drupal::service('tempstore.private')->get('component_library')->set('variant_preview', $rendered);
    $response = new AjaxResponse();
    $response->addCommand(new HtmlCommand('#component-library-variant', \Drupal::service('renderer')->render($iframe)));
    return $response;
  }

  public static function convertYamlPlaceholders(array $context): array {
    $event_dispatcher = \Drupal::service('event_dispatcher');
    \assert($event_dispatcher instanceof EventDispatcherInterface);
    $event = new ConvertYamlPlaceholdersEvent($context);
    $event_dispatcher->dispatch($event);
    return $event->getContext();
  }

  /**
   * Returns available pattern options.
   */
  private function getPatternOptions(): array {
    $storage = $this->entityTypeManager->getStorage('component_library_pattern');
    $options = ['' => $this->t('- Select option -')];
    foreach ($storage->loadMultiple() as $id => $pattern) {
      $options[$id] = $pattern->label();
    }
    return $options;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc