Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
PreviewPanel
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 4
56
0.00% covered (danger)
0.00%
0 / 1
 create
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 keyboardShortcuts
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 build
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 alterPreviewPlaceholder
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder\Plugin\display_builder\Island;
6
7use Drupal\Core\Render\RendererInterface;
8use Drupal\Core\StringTranslation\TranslatableMarkup;
9use Drupal\display_builder\Attribute\Island;
10use Drupal\display_builder\DisplayBuilderHelpers;
11use Drupal\display_builder\InstanceInterface;
12use Drupal\display_builder\Island\IslandPluginBase;
13use Drupal\display_builder\Island\IslandReloadEventsTrait;
14use Drupal\display_builder\Island\IslandType;
15use Drupal\ui_patterns\Element\ComponentElementBuilder;
16use Symfony\Component\DependencyInjection\ContainerInterface;
17
18/**
19 * Preview island plugin implementation.
20 */
21#[Island(
22  id: 'preview',
23  enabled_by_default: TRUE,
24  label: new TranslatableMarkup('Preview'),
25  description: new TranslatableMarkup('Show a real time preview of the display.'),
26  type: IslandType::View,
27  default_region: 'main',
28  icon: 'binoculars',
29)]
30class PreviewPanel extends IslandPluginBase {
31
32  use IslandReloadEventsTrait;
33
34  /**
35   * The component element builder.
36   */
37  protected ComponentElementBuilder $componentElementBuilder;
38
39  /**
40   * The renderer.
41   */
42  protected RendererInterface $renderer;
43
44  /**
45   * {@inheritdoc}
46   */
47  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
48    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
49    $instance->componentElementBuilder = $container->get('ui_patterns.component_element_builder');
50    $instance->renderer = $container->get('renderer');
51
52    return $instance;
53  }
54
55  /**
56   * {@inheritdoc}
57   */
58  public static function keyboardShortcuts(): array {
59    return [
60      'key' => 'p',
61      'help' => t('Show the preview'),
62    ];
63  }
64
65  /**
66   * {@inheritdoc}
67   */
68  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
69    if (empty($data)) {
70      return [];
71    }
72
73    $this->alterPreviewPlaceholder($data);
74
75    $returned = [];
76
77    foreach ($data as $slot) {
78      $build = $this->componentElementBuilder->buildSource([], 'content', [], $slot, $this->configuration['contexts'] ?? []);
79      $build = $build['#slots']['content'][0] ?? [];
80
81      // Some sources (e.g. a comment field's "Add comment" form) throw once
82      // actually rendered rather than producing empty markup - @see
83      // RenderableBuilderTrait::isRenderEmptyOrFailing(). Unlike
84      // BuilderPanel, which already guards every source it builds the same
85      // way, nothing here previously checked this, so a throwing source
86      // shipped its #lazy_builder unresolved into the page, breaking Drupal
87      // core's own BigPipe processing of unrelated placeholders (e.g. the
88      // Navigation module's admin toolbar) later in the same request.
89      if ($this->isRenderEmptyOrFailing($this->renderer, $build)) {
90        $build = $this->buildPlaceholder($this->t('[Placeholder] No preview'));
91      }
92
93      $returned[] = $build;
94    }
95
96    return $returned;
97  }
98
99  /**
100   * Replace placeholder for preview.
101   *
102   * Some block source will not be created, create a simple placeholder to have
103   * a preview instead of nothing.
104   *
105   * @todo move as an issue to UI Patterns?
106   *
107   * @param array $data
108   *   The instance data to replace.
109   */
110  private function alterPreviewPlaceholder(array &$data): void {
111    $replacements = [
112      [
113        'search' => ['plugin_id' => 'system_messages_block'],
114        'new_value_title' => new TranslatableMarkup('[Placeholder] Block messages'),
115      ],
116      [
117        'search' => ['source_id' => 'page_title'],
118        'new_value_title' => new TranslatableMarkup('[Placeholder] Page title'),
119      ],
120      [
121        'search' => ['source_id' => 'main_page_content'],
122        'new_value_title' => new TranslatableMarkup('[Placeholder] Page content'),
123        'new_value_class' => 'db-preview-placeholder-lg',
124      ],
125      [
126        'search' => ['source_id' => 'view_attachment_before'],
127        'new_value_title' => new TranslatableMarkup('[Placeholder] View attachment before'),
128        'new_value_class' => 'db-preview-placeholder-md',
129      ],
130      [
131        'search' => ['source_id' => 'view_exposed'],
132        'new_value_title' => new TranslatableMarkup('[Placeholder] View exposed form'),
133        'new_value_class' => 'db-preview-placeholder-md',
134      ],
135      [
136        'search' => ['source_id' => 'view_header'],
137        'new_value_title' => new TranslatableMarkup('[Placeholder] View header'),
138        'new_value_class' => 'db-preview-placeholder-md',
139      ],
140      [
141        'search' => ['source_id' => 'view_rows'],
142        'new_value_title' => new TranslatableMarkup('[Placeholder] View rows'),
143        'new_value_class' => 'db-preview-placeholder-lg',
144      ],
145      [
146        'search' => ['source_id' => 'view_attachment_after'],
147        'new_value_title' => new TranslatableMarkup('[Placeholder] View attachment after'),
148        'new_value_class' => 'db-preview-placeholder-md',
149      ],
150      [
151        'search' => ['source_id' => 'view_pager'],
152        'new_value_title' => new TranslatableMarkup('[Placeholder] View pager'),
153      ],
154      [
155        'search' => ['source_id' => 'view_more'],
156        'new_value_title' => new TranslatableMarkup('[Placeholder] View more'),
157      ],
158      [
159        'search' => ['source_id' => 'view_footer'],
160        'new_value_title' => new TranslatableMarkup('[Placeholder] View footer'),
161        'new_value_class' => 'db-preview-placeholder-md',
162      ],
163      [
164        'search' => ['source_id' => 'view_feed_icons'],
165        'new_value_title' => new TranslatableMarkup('[Placeholder] View feed icons'),
166      ],
167    ];
168
169    DisplayBuilderHelpers::findAndReplaceInArray($data, $replacements);
170  }
171
172}