Mercurial > hg > isophonics-drupal-site
view core/modules/layout_builder/src/Element/LayoutBuilder.php @ 19:fa3358dc1485 tip
Add ndrum files
author | Chris Cannam |
---|---|
date | Wed, 28 Aug 2019 13:14:47 +0100 |
parents | af1871eacc83 |
children |
line wrap: on
line source
<?php namespace Drupal\layout_builder\Element; use Drupal\Core\Ajax\AjaxHelperTrait; use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\Render\Element; use Drupal\Core\Render\Element\RenderElement; use Drupal\Core\Url; use Drupal\layout_builder\Context\LayoutBuilderContextTrait; use Drupal\layout_builder\LayoutBuilderHighlightTrait; use Drupal\layout_builder\LayoutTempstoreRepositoryInterface; use Drupal\layout_builder\OverridesSectionStorageInterface; use Drupal\layout_builder\SectionStorageInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Defines a render element for building the Layout Builder UI. * * @RenderElement("layout_builder") * * @internal * Plugin classes are internal. */ class LayoutBuilder extends RenderElement implements ContainerFactoryPluginInterface { use AjaxHelperTrait; use LayoutBuilderContextTrait; use LayoutBuilderHighlightTrait; /** * The layout tempstore repository. * * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface */ protected $layoutTempstoreRepository; /** * The messenger service. * * @var \Drupal\Core\Messenger\MessengerInterface */ protected $messenger; /** * Constructs a new LayoutBuilder. * * @param array $configuration * A configuration array containing information about the plugin instance. * @param string $plugin_id * The plugin ID for the plugin instance. * @param mixed $plugin_definition * The plugin implementation definition. * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository * The layout tempstore repository. * @param \Drupal\Core\Messenger\MessengerInterface $messenger * The messenger service. */ public function __construct(array $configuration, $plugin_id, $plugin_definition, LayoutTempstoreRepositoryInterface $layout_tempstore_repository, MessengerInterface $messenger) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->layoutTempstoreRepository = $layout_tempstore_repository; $this->messenger = $messenger; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $configuration, $plugin_id, $plugin_definition, $container->get('layout_builder.tempstore_repository'), $container->get('messenger') ); } /** * {@inheritdoc} */ public function getInfo() { return [ '#section_storage' => NULL, '#pre_render' => [ [$this, 'preRender'], ], ]; } /** * Pre-render callback: Renders the Layout Builder UI. */ public function preRender($element) { if ($element['#section_storage'] instanceof SectionStorageInterface) { $element['layout_builder'] = $this->layout($element['#section_storage']); } return $element; } /** * Renders the Layout UI. * * @param \Drupal\layout_builder\SectionStorageInterface $section_storage * The section storage. * * @return array * A render array. */ protected function layout(SectionStorageInterface $section_storage) { $this->prepareLayout($section_storage); $output = []; if ($this->isAjax()) { $output['status_messages'] = [ '#type' => 'status_messages', ]; } $count = 0; for ($i = 0; $i < $section_storage->count(); $i++) { $output[] = $this->buildAddSectionLink($section_storage, $count); $output[] = $this->buildAdministrativeSection($section_storage, $count); $count++; } $output[] = $this->buildAddSectionLink($section_storage, $count); $output['#attached']['library'][] = 'layout_builder/drupal.layout_builder'; // As the Layout Builder UI is typically displayed using the frontend theme, // it is not marked as an administrative page at the route level even though // it performs an administrative task. Mark this as an administrative page // for JavaScript. $output['#attached']['drupalSettings']['path']['currentPathIsAdmin'] = TRUE; $output['#type'] = 'container'; $output['#attributes']['id'] = 'layout-builder'; $output['#attributes']['class'][] = 'layout-builder'; // Mark this UI as uncacheable. $output['#cache']['max-age'] = 0; return $output; } /** * Prepares a layout for use in the UI. * * @param \Drupal\layout_builder\SectionStorageInterface $section_storage * The section storage. */ protected function prepareLayout(SectionStorageInterface $section_storage) { // If the layout has pending changes, add a warning. if ($this->layoutTempstoreRepository->has($section_storage)) { $this->messenger->addWarning($this->t('You have unsaved changes.')); } // If the layout is an override that has not yet been overridden, copy the // sections from the corresponding default. elseif ($section_storage instanceof OverridesSectionStorageInterface && !$section_storage->isOverridden()) { $sections = $section_storage->getDefaultSectionStorage()->getSections(); foreach ($sections as $section) { $section_storage->appendSection($section); } $this->layoutTempstoreRepository->set($section_storage); } } /** * Builds a link to add a new section at a given delta. * * @param \Drupal\layout_builder\SectionStorageInterface $section_storage * The section storage. * @param int $delta * The delta of the section to splice. * * @return array * A render array for a link. */ protected function buildAddSectionLink(SectionStorageInterface $section_storage, $delta) { $storage_type = $section_storage->getStorageType(); $storage_id = $section_storage->getStorageId(); // If the delta and the count are the same, it is either the end of the // layout or an empty layout. if ($delta === count($section_storage)) { if ($delta === 0) { $title = $this->t('Add Section'); } else { $title = $this->t('Add Section <span class="visually-hidden">at end of layout</span>'); } } // If the delta and the count are different, it is either the beginning of // the layout or in between two sections. else { if ($delta === 0) { $title = $this->t('Add Section <span class="visually-hidden">at start of layout</span>'); } else { $title = $this->t('Add Section <span class="visually-hidden">between @first and @second</span>', ['@first' => $delta, '@second' => $delta + 1]); } } return [ 'link' => [ '#type' => 'link', '#title' => $title, '#url' => Url::fromRoute('layout_builder.choose_section', [ 'section_storage_type' => $storage_type, 'section_storage' => $storage_id, 'delta' => $delta, ], [ 'attributes' => [ 'class' => [ 'use-ajax', 'layout-builder__link', 'layout-builder__link--add', ], 'data-dialog-type' => 'dialog', 'data-dialog-renderer' => 'off_canvas', ], ] ), ], '#type' => 'container', '#attributes' => [ 'class' => ['layout-builder__add-section'], 'data-layout-builder-highlight-id' => $this->sectionAddHighlightId($delta), ], ]; } /** * Builds the render array for the layout section while editing. * * @param \Drupal\layout_builder\SectionStorageInterface $section_storage * The section storage. * @param int $delta * The delta of the section. * * @return array * The render array for a given section. */ protected function buildAdministrativeSection(SectionStorageInterface $section_storage, $delta) { $storage_type = $section_storage->getStorageType(); $storage_id = $section_storage->getStorageId(); $section = $section_storage->getSection($delta); $layout = $section->getLayout(); $build = $section->toRenderArray($this->getAvailableContexts($section_storage), TRUE); $layout_definition = $layout->getPluginDefinition(); $region_labels = $layout_definition->getRegionLabels(); foreach ($layout_definition->getRegions() as $region => $info) { if (!empty($build[$region])) { foreach (Element::children($build[$region]) as $uuid) { $build[$region][$uuid]['#attributes']['class'][] = 'js-layout-builder-block'; $build[$region][$uuid]['#attributes']['class'][] = 'layout-builder-block'; $build[$region][$uuid]['#attributes']['data-layout-block-uuid'] = $uuid; $build[$region][$uuid]['#attributes']['data-layout-builder-highlight-id'] = $this->blockUpdateHighlightId($uuid); $build[$region][$uuid]['#contextual_links'] = [ 'layout_builder_block' => [ 'route_parameters' => [ 'section_storage_type' => $storage_type, 'section_storage' => $storage_id, 'delta' => $delta, 'region' => $region, 'uuid' => $uuid, ], // Add metadata about the current operations available in // contextual links. This will invalidate the client-side cache of // links that were cached before the 'move' link was added. // @see layout_builder.links.contextual.yml 'metadata' => [ 'operations' => 'move:update:remove', ], ], ]; } } $build[$region]['layout_builder_add_block']['link'] = [ '#type' => 'link', // Add one to the current delta since it is zero-indexed. '#title' => $this->t('Add Block <span class="visually-hidden">in section @section, @region region</span>', ['@section' => $delta + 1, '@region' => $region_labels[$region]]), '#url' => Url::fromRoute('layout_builder.choose_block', [ 'section_storage_type' => $storage_type, 'section_storage' => $storage_id, 'delta' => $delta, 'region' => $region, ], [ 'attributes' => [ 'class' => [ 'use-ajax', 'layout-builder__link', 'layout-builder__link--add', ], 'data-dialog-type' => 'dialog', 'data-dialog-renderer' => 'off_canvas', ], ] ), ]; $build[$region]['layout_builder_add_block']['#type'] = 'container'; $build[$region]['layout_builder_add_block']['#attributes'] = [ 'class' => ['layout-builder__add-block'], 'data-layout-builder-highlight-id' => $this->blockAddHighlightId($delta, $region), ]; $build[$region]['layout_builder_add_block']['#weight'] = 1000; $build[$region]['#attributes']['data-region'] = $region; $build[$region]['#attributes']['class'][] = 'layout-builder__region'; $build[$region]['#attributes']['class'][] = 'js-layout-builder-region'; $build[$region]['#attributes']['role'] = 'group'; $build[$region]['#attributes']['aria-label'] = $this->t('@region region in section @section', [ '@region' => $info['label'], '@section' => $delta + 1, ]); // Get weights of all children for use by the region label. $weights = array_map(function ($a) { return isset($a['#weight']) ? $a['#weight'] : 0; }, $build[$region]); // The region label is made visible when the move block dialog is open. $build[$region]['region_label'] = [ '#type' => 'container', '#attributes' => [ 'class' => ['layout__region-info', 'layout-builder__region-label'], // A more detailed version of this information is already read by // screen readers, so this label can be hidden from them. 'aria-hidden' => TRUE, ], '#markup' => $this->t('Region: @region', ['@region' => $info['label']]), // Ensures the region label is displayed first. '#weight' => min($weights) - 1, ]; } $build['#attributes']['data-layout-update-url'] = Url::fromRoute('layout_builder.move_block', [ 'section_storage_type' => $storage_type, 'section_storage' => $storage_id, ])->toString(); $build['#attributes']['data-layout-delta'] = $delta; $build['#attributes']['class'][] = 'layout-builder__layout'; $build['#attributes']['data-layout-builder-highlight-id'] = $this->sectionUpdateHighlightId($delta); return [ '#type' => 'container', '#attributes' => [ 'class' => ['layout-builder__section'], 'role' => 'group', 'aria-label' => $this->t('Section @section', ['@section' => $delta + 1]), ], 'remove' => [ '#type' => 'link', '#title' => $this->t('Remove section <span class="visually-hidden">@section</span>', ['@section' => $delta + 1]), '#url' => Url::fromRoute('layout_builder.remove_section', [ 'section_storage_type' => $storage_type, 'section_storage' => $storage_id, 'delta' => $delta, ]), '#attributes' => [ 'class' => [ 'use-ajax', 'layout-builder__link', 'layout-builder__link--remove', ], 'data-dialog-type' => 'dialog', 'data-dialog-renderer' => 'off_canvas', ], ], // The section label is added to sections without a "Configure Section" // link, and is only visible when the move block dialog is open. 'section_label' => [ '#markup' => $this->t('<span class="layout-builder__section-label" aria-hidden="true">Section @section</span>', ['@section' => $delta + 1]), '#access' => !$layout instanceof PluginFormInterface, ], 'configure' => [ '#type' => 'link', // There are two instances of @section, the one wrapped in // .visually-hidden is for screen readers. The one wrapped in // .layout-builder__section-label is only visible when the // move block dialog is open and it is not seen by screen readers. '#title' => $this->t('Configure section <span class="visually-hidden">@section</span><span aria-hidden="true" class="layout-builder__section-label">@section</span>', ['@section' => $delta + 1]), '#access' => $layout instanceof PluginFormInterface, '#url' => Url::fromRoute('layout_builder.configure_section', [ 'section_storage_type' => $storage_type, 'section_storage' => $storage_id, 'delta' => $delta, ]), '#attributes' => [ 'class' => [ 'use-ajax', 'layout-builder__link', 'layout-builder__link--configure', ], 'data-dialog-type' => 'dialog', 'data-dialog-renderer' => 'off_canvas', ], ], 'layout-builder__section' => $build, ]; } }