Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 133
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
LayersPanel
0.00% covered (danger)
0.00%
0 / 126
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 7
756
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 / 7
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 buildSingleComponent
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
56
 buildSingleBlock
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 addThirdPartySettingsSummary
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 addComponentSettingsSummary
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder\Plugin\display_builder\Island;
6
7use Drupal\Core\StringTranslation\TranslatableMarkup;
8use Drupal\display_builder\Attribute\Island;
9use Drupal\display_builder\InstanceInterface;
10use Drupal\display_builder\IslandPluginManagerInterface;
11use Drupal\display_builder\IslandType;
12use Drupal\display_builder\SlotSourceProxy;
13use Drupal\display_builder\SourceWithSlotsInterface;
14use Drupal\display_builder\ThirdPartySettingsInterface;
15use Drupal\ui_patterns\SourceWithChoicesInterface;
16use Symfony\Component\DependencyInjection\ContainerInterface;
17
18/**
19 * Layers island plugin implementation.
20 */
21#[Island(
22  id: 'layers',
23  label: new TranslatableMarkup('Layers'),
24  description: new TranslatableMarkup('Manage hierarchical layer view of elements without preview.'),
25  type: IslandType::View,
26  default_region: 'main',
27  icon: 'layers',
28)]
29class LayersPanel extends BuilderPanel {
30
31  /**
32   * Proxy for slot source operations.
33   */
34  protected SlotSourceProxy $slotSourceProxy;
35
36  /**
37   * Island plugins manager.
38   */
39  protected IslandPluginManagerInterface $islandManager;
40
41  /**
42   * {@inheritdoc}
43   */
44  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
45    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
46    $instance->slotSourceProxy = $container->get('display_builder.slot_sources_proxy');
47    $instance->islandManager = $container->get('plugin.manager.db_island');
48
49    return $instance;
50  }
51
52  /**
53   * {@inheritdoc}
54   */
55  public static function keyboardShortcuts(): array {
56    return [
57      'key' => 'y',
58      'help' => t('Show the layer'),
59    ];
60  }
61
62  /**
63   * {@inheritdoc}
64   */
65  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
66    $build = parent::build($builder, $data, $options);
67
68    if (empty($build['#slots']['content'] ?? [])) {
69      // Load en empty component to have any assets with it.
70      $build['#slots']['content'] = [
71        '#type' => 'component',
72        '#component' => 'display_builder:layer',
73      ];
74    }
75
76    return $build;
77  }
78
79  /**
80   * {@inheritdoc}
81   */
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
87      $component_id = $source->getChoice($data['source']);
88      $label = $this->slotSourceProxy->getLabelWithSummary($data, [])['label'];
89    }
90
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
94      $params = [
95        '@instance_id' => $instance_id ?? 'NULL',
96        '@component_id' => $component_id,
97      ];
98      $this->logger->error('[LayersPanel::buildSingleComponent] missing component ID: @component_id or instance ID: @instance_id. <pre>' . \print_r($data, TRUE) . '</pre>', $params);
99
100      return NULL;
101    }
102
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
156
157  /**
158   * {@inheritdoc}
159   */
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
164      $label['summary'] = (string) $this->t('Field: @label', ['@label' => $label['label']]);
165    }
166
167    $build = [
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
178      $this->logger->error('[LayersPanel::buildSingleBlock] missing instance ID. <pre>' . \print_r($data, TRUE) . '</pre>');
179
180      return $build;
181    }
182
183    $build = $this->addThirdPartySettingsSummary($data, $build);
184
185    // This label is used for contextual menu.
186    // @see assets/js/contextual_menu.js
187    $build['#attributes']['data-node-title'] = $label['summary'];
188    $build['#attributes']['data-slot-position'] = $index;
189    $build['#attributes']['data-instance-id'] = $instance_id;
190
191    // Add data-node-type for easier identification of block types in JS, CSS or
192    // tests.
193    if (isset($data['source_id'])) {
194      $build['#attributes']['data-node-type'] = $data['source_id'];
195    }
196
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
198  }
199
200  /**
201   * Add third party settings summary to layer's info slot.
202   *
203   * @param array $data
204   *   The node data.
205   * @param array $build
206   *   The layer component renderable array.
207   *
208   * @return array
209   *   The layer component renderable array.
210   */
211  private function addThirdPartySettingsSummary(array $data, array $build): array {
212    if (!isset($data['third_party_settings'])) {
213      return $build;
214    }
215
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
223        continue;
224      }
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
228        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
229      }
230    }
231
232    return $build;
233  }
234
235  /**
236   * Add config settings summary to layer's info slot.
237   *
238   * @param \Drupal\display_builder\SourceWithSlotsInterface $source
239   *   The source plugin.
240   * @param array $build
241   *   The layer component renderable array.
242   *
243   * @return array
244   *   The layer component renderable array.
245   */
246  private function addComponentSettingsSummary(SourceWithSlotsInterface $source, array $build): array {
247    $items = [];
248
249    foreach ($source->settingsSummary() as $item) {
250      if ($item !== NULL) {
251        $items[] = [
252          '#type' => 'html_tag',
253          '#tag' => 'li',
254          '#value' => $item,
255        ];
256      }
257    }
258
259    if (empty($items)) {
260      return $build;
261    }
262
263    $summary = [
264      [
265        '#type' => 'html_tag',
266        '#tag' => 'em',
267        '#value' => new TranslatableMarkup('Config'),
268      ],
269      [
270        '#type' => 'html_tag',
271        '#tag' => 'ul',
272        '#attributes' => [
273          'class' => ['summary'],
274        ],
275        0 => $items,
276      ],
277    ];
278
279    $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
280
281    return $build;
282  }
283
284}

Paths

Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

LayersPanel->addComponentSettingsSummary
246  private function addComponentSettingsSummary(SourceWithSlotsInterface $source, array $build): array {
247    $items = [];
248
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
 
250      if ($item !== NULL) {
 
249    foreach ($source->settingsSummary() as $item) {
250      if ($item !== NULL) {
251        $items[] = [
252          '#type' => 'html_tag',
 
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
250      if ($item !== NULL) {
251        $items[] = [
252          '#type' => 'html_tag',
253          '#tag' => 'li',
254          '#value' => $item,
255        ];
256      }
257    }
258
259    if (empty($items)) {
 
260      return $build;
246  private function addComponentSettingsSummary(SourceWithSlotsInterface $source, array $build): array {
247    $items = [];
248
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
 
250      if ($item !== NULL) {
 
249    foreach ($source->settingsSummary() as $item) {
250      if ($item !== NULL) {
251        $items[] = [
252          '#type' => 'html_tag',
 
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
250      if ($item !== NULL) {
251        $items[] = [
252          '#type' => 'html_tag',
253          '#tag' => 'li',
254          '#value' => $item,
255        ];
256      }
257    }
258
259    if (empty($items)) {
 
265        '#type' => 'html_tag',
266        '#tag' => 'em',
267        '#value' => new TranslatableMarkup('Config'),
268      ],
269      [
270        '#type' => 'html_tag',
271        '#tag' => 'ul',
272        '#attributes' => [
273          'class' => ['summary'],
274        ],
275        0 => $items,
276      ],
277    ];
278
279    $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
280
281    return $build;
282  }
246  private function addComponentSettingsSummary(SourceWithSlotsInterface $source, array $build): array {
247    $items = [];
248
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
 
250      if ($item !== NULL) {
 
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
250      if ($item !== NULL) {
251        $items[] = [
252          '#type' => 'html_tag',
253          '#tag' => 'li',
254          '#value' => $item,
255        ];
256      }
257    }
258
259    if (empty($items)) {
 
260      return $build;
246  private function addComponentSettingsSummary(SourceWithSlotsInterface $source, array $build): array {
247    $items = [];
248
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
 
250      if ($item !== NULL) {
 
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
250      if ($item !== NULL) {
251        $items[] = [
252          '#type' => 'html_tag',
253          '#tag' => 'li',
254          '#value' => $item,
255        ];
256      }
257    }
258
259    if (empty($items)) {
 
265        '#type' => 'html_tag',
266        '#tag' => 'em',
267        '#value' => new TranslatableMarkup('Config'),
268      ],
269      [
270        '#type' => 'html_tag',
271        '#tag' => 'ul',
272        '#attributes' => [
273          'class' => ['summary'],
274        ],
275        0 => $items,
276      ],
277    ];
278
279    $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
280
281    return $build;
282  }
246  private function addComponentSettingsSummary(SourceWithSlotsInterface $source, array $build): array {
247    $items = [];
248
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
250      if ($item !== NULL) {
251        $items[] = [
252          '#type' => 'html_tag',
253          '#tag' => 'li',
254          '#value' => $item,
255        ];
256      }
257    }
258
259    if (empty($items)) {
 
260      return $build;
246  private function addComponentSettingsSummary(SourceWithSlotsInterface $source, array $build): array {
247    $items = [];
248
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
250      if ($item !== NULL) {
251        $items[] = [
252          '#type' => 'html_tag',
253          '#tag' => 'li',
254          '#value' => $item,
255        ];
256      }
257    }
258
259    if (empty($items)) {
 
265        '#type' => 'html_tag',
266        '#tag' => 'em',
267        '#value' => new TranslatableMarkup('Config'),
268      ],
269      [
270        '#type' => 'html_tag',
271        '#tag' => 'ul',
272        '#attributes' => [
273          'class' => ['summary'],
274        ],
275        0 => $items,
276      ],
277    ];
278
279    $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
280
281    return $build;
282  }
246  private function addComponentSettingsSummary(SourceWithSlotsInterface $source, array $build): array {
247    $items = [];
248
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
250      if ($item !== NULL) {
251        $items[] = [
252          '#type' => 'html_tag',
253          '#tag' => 'li',
254          '#value' => $item,
255        ];
256      }
257    }
258
259    if (empty($items)) {
 
260      return $build;
246  private function addComponentSettingsSummary(SourceWithSlotsInterface $source, array $build): array {
247    $items = [];
248
249    foreach ($source->settingsSummary() as $item) {
 
249    foreach ($source->settingsSummary() as $item) {
250      if ($item !== NULL) {
251        $items[] = [
252          '#type' => 'html_tag',
253          '#tag' => 'li',
254          '#value' => $item,
255        ];
256      }
257    }
258
259    if (empty($items)) {
 
265        '#type' => 'html_tag',
266        '#tag' => 'em',
267        '#value' => new TranslatableMarkup('Config'),
268      ],
269      [
270        '#type' => 'html_tag',
271        '#tag' => 'ul',
272        '#attributes' => [
273          'class' => ['summary'],
274        ],
275        0 => $items,
276      ],
277    ];
278
279    $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
280
281    return $build;
282  }
LayersPanel->addThirdPartySettingsSummary
211  private function addThirdPartySettingsSummary(array $data, array $build): array {
212    if (!isset($data['third_party_settings'])) {
 
213      return $build;
211  private function addThirdPartySettingsSummary(array $data, array $build): array {
212    if (!isset($data['third_party_settings'])) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
 
223        continue;
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
223        continue;
224      }
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
228        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
229      }
230    }
231
232    return $build;
233  }
211  private function addThirdPartySettingsSummary(array $data, array $build): array {
212    if (!isset($data['third_party_settings'])) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
 
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
 
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
 
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
223        continue;
224      }
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
228        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
223        continue;
224      }
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
228        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
229      }
230    }
231
232    return $build;
233  }
211  private function addThirdPartySettingsSummary(array $data, array $build): array {
212    if (!isset($data['third_party_settings'])) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
 
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
 
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
 
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
223        continue;
224      }
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
228        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
229      }
230    }
231
232    return $build;
233  }
211  private function addThirdPartySettingsSummary(array $data, array $build): array {
212    if (!isset($data['third_party_settings'])) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
 
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
 
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
223        continue;
224      }
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
228        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
223        continue;
224      }
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
228        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
229      }
230    }
231
232    return $build;
233  }
211  private function addThirdPartySettingsSummary(array $data, array $build): array {
212    if (!isset($data['third_party_settings'])) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
 
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
 
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
223        continue;
224      }
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
228        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
229      }
230    }
231
232    return $build;
233  }
211  private function addThirdPartySettingsSummary(array $data, array $build): array {
212    if (!isset($data['third_party_settings'])) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
223        continue;
224      }
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
228        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
229      }
230    }
231
232    return $build;
233  }
211  private function addThirdPartySettingsSummary(array $data, array $build): array {
212    if (!isset($data['third_party_settings'])) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
 
216    foreach ($data['third_party_settings'] as $provider => $settings) {
217      // In Display Builder, third_party_settings providers can be:
218      // - an island plugin ID (our 'normal' way)
219      // - a Drupal module name (the Drupal way, found in displays imported and
220      // converted, not leveraged by us for now but we may do it later).
221      // So, let's check the plugin ID exists before running logic.
222      if (!$this->islandManager->hasDefinition($provider)) {
223        continue;
224      }
225      $island = $this->islandManager->createInstance($provider, $settings);
226
227      if ($island instanceof ThirdPartySettingsInterface && $summary = $island->getSummary()) {
228        $build['#slots']['info'] = \array_merge($build['#slots']['info'] ?? [], $summary);
229      }
230    }
231
232    return $build;
233  }
LayersPanel->build
65  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
66    $build = parent::build($builder, $data, $options);
67
68    if (empty($build['#slots']['content'] ?? [])) {
 
70      $build['#slots']['content'] = [
71        '#type' => 'component',
72        '#component' => 'display_builder:layer',
73      ];
74    }
75
76    return $build;
 
76    return $build;
77  }
65  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
66    $build = parent::build($builder, $data, $options);
67
68    if (empty($build['#slots']['content'] ?? [])) {
 
76    return $build;
77  }
LayersPanel->buildSingleBlock
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
164      $label['summary'] = (string) $this->t('Field: @label', ['@label' => $label['label']]);
165    }
166
167    $build = [
168      '#type' => 'component',
 
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
 
178      $this->logger->error('[LayersPanel::buildSingleBlock] missing instance ID. <pre>' . \print_r($data, TRUE) . '</pre>');
179
180      return $build;
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
164      $label['summary'] = (string) $this->t('Field: @label', ['@label' => $label['label']]);
165    }
166
167    $build = [
168      '#type' => 'component',
 
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
 
183    $build = $this->addThirdPartySettingsSummary($data, $build);
184
185    // This label is used for contextual menu.
186    // @see assets/js/contextual_menu.js
187    $build['#attributes']['data-node-title'] = $label['summary'];
188    $build['#attributes']['data-slot-position'] = $index;
189    $build['#attributes']['data-instance-id'] = $instance_id;
190
191    // Add data-node-type for easier identification of block types in JS, CSS or
192    // tests.
193    if (isset($data['source_id'])) {
 
194      $build['#attributes']['data-node-type'] = $data['source_id'];
195    }
196
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
 
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
198  }
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
164      $label['summary'] = (string) $this->t('Field: @label', ['@label' => $label['label']]);
165    }
166
167    $build = [
168      '#type' => 'component',
 
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
 
183    $build = $this->addThirdPartySettingsSummary($data, $build);
184
185    // This label is used for contextual menu.
186    // @see assets/js/contextual_menu.js
187    $build['#attributes']['data-node-title'] = $label['summary'];
188    $build['#attributes']['data-slot-position'] = $index;
189    $build['#attributes']['data-instance-id'] = $instance_id;
190
191    // Add data-node-type for easier identification of block types in JS, CSS or
192    // tests.
193    if (isset($data['source_id'])) {
 
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
198  }
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
 
178      $this->logger->error('[LayersPanel::buildSingleBlock] missing instance ID. <pre>' . \print_r($data, TRUE) . '</pre>');
179
180      return $build;
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
 
183    $build = $this->addThirdPartySettingsSummary($data, $build);
184
185    // This label is used for contextual menu.
186    // @see assets/js/contextual_menu.js
187    $build['#attributes']['data-node-title'] = $label['summary'];
188    $build['#attributes']['data-slot-position'] = $index;
189    $build['#attributes']['data-instance-id'] = $instance_id;
190
191    // Add data-node-type for easier identification of block types in JS, CSS or
192    // tests.
193    if (isset($data['source_id'])) {
 
194      $build['#attributes']['data-node-type'] = $data['source_id'];
195    }
196
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
 
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
198  }
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
 
183    $build = $this->addThirdPartySettingsSummary($data, $build);
184
185    // This label is used for contextual menu.
186    // @see assets/js/contextual_menu.js
187    $build['#attributes']['data-node-title'] = $label['summary'];
188    $build['#attributes']['data-slot-position'] = $index;
189    $build['#attributes']['data-instance-id'] = $instance_id;
190
191    // Add data-node-type for easier identification of block types in JS, CSS or
192    // tests.
193    if (isset($data['source_id'])) {
 
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
198  }
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
164      $label['summary'] = (string) $this->t('Field: @label', ['@label' => $label['label']]);
165    }
166
167    $build = [
168      '#type' => 'component',
 
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
 
178      $this->logger->error('[LayersPanel::buildSingleBlock] missing instance ID. <pre>' . \print_r($data, TRUE) . '</pre>');
179
180      return $build;
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
164      $label['summary'] = (string) $this->t('Field: @label', ['@label' => $label['label']]);
165    }
166
167    $build = [
168      '#type' => 'component',
 
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
 
183    $build = $this->addThirdPartySettingsSummary($data, $build);
184
185    // This label is used for contextual menu.
186    // @see assets/js/contextual_menu.js
187    $build['#attributes']['data-node-title'] = $label['summary'];
188    $build['#attributes']['data-slot-position'] = $index;
189    $build['#attributes']['data-instance-id'] = $instance_id;
190
191    // Add data-node-type for easier identification of block types in JS, CSS or
192    // tests.
193    if (isset($data['source_id'])) {
 
194      $build['#attributes']['data-node-type'] = $data['source_id'];
195    }
196
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
 
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
198  }
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
164      $label['summary'] = (string) $this->t('Field: @label', ['@label' => $label['label']]);
165    }
166
167    $build = [
168      '#type' => 'component',
 
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
 
183    $build = $this->addThirdPartySettingsSummary($data, $build);
184
185    // This label is used for contextual menu.
186    // @see assets/js/contextual_menu.js
187    $build['#attributes']['data-node-title'] = $label['summary'];
188    $build['#attributes']['data-slot-position'] = $index;
189    $build['#attributes']['data-instance-id'] = $instance_id;
190
191    // Add data-node-type for easier identification of block types in JS, CSS or
192    // tests.
193    if (isset($data['source_id'])) {
 
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
198  }
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
 
178      $this->logger->error('[LayersPanel::buildSingleBlock] missing instance ID. <pre>' . \print_r($data, TRUE) . '</pre>');
179
180      return $build;
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
 
183    $build = $this->addThirdPartySettingsSummary($data, $build);
184
185    // This label is used for contextual menu.
186    // @see assets/js/contextual_menu.js
187    $build['#attributes']['data-node-title'] = $label['summary'];
188    $build['#attributes']['data-slot-position'] = $index;
189    $build['#attributes']['data-instance-id'] = $instance_id;
190
191    // Add data-node-type for easier identification of block types in JS, CSS or
192    // tests.
193    if (isset($data['source_id'])) {
 
194      $build['#attributes']['data-node-type'] = $data['source_id'];
195    }
196
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
 
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
198  }
160  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
161    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
162
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
163    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
168      '#type' => 'component',
169      '#component' => 'display_builder:layer',
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173    ];
174
175    $instance_id = $instance_id ?: $data['node_id'] ?? NULL;
176
177    if (!$instance_id) {
 
183    $build = $this->addThirdPartySettingsSummary($data, $build);
184
185    // This label is used for contextual menu.
186    // @see assets/js/contextual_menu.js
187    $build['#attributes']['data-node-title'] = $label['summary'];
188    $build['#attributes']['data-slot-position'] = $index;
189    $build['#attributes']['data-instance-id'] = $instance_id;
190
191    // Add data-node-type for easier identification of block types in JS, CSS or
192    // tests.
193    if (isset($data['source_id'])) {
 
197    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['summary'], $index);
198  }
LayersPanel->buildSingleComponent
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
87      $component_id = $source->getChoice($data['source']);
88      $label = $this->slotSourceProxy->getLabelWithSummary($data, [])['label'];
89    }
90
91    $instance_id = $instance_id ?: $data['node_id'];
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
95        '@instance_id' => $instance_id ?? 'NULL',
96        '@component_id' => $component_id,
97      ];
98      $this->logger->error('[LayersPanel::buildSingleComponent] missing component ID: @component_id or instance ID: @instance_id. <pre>' . \print_r($data, TRUE) . '</pre>', $params);
99
100      return NULL;
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
87      $component_id = $source->getChoice($data['source']);
88      $label = $this->slotSourceProxy->getLabelWithSummary($data, [])['label'];
89    }
90
91    $instance_id = $instance_id ?: $data['node_id'];
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
 
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
87      $component_id = $source->getChoice($data['source']);
88      $label = $this->slotSourceProxy->getLabelWithSummary($data, [])['label'];
89    }
90
91    $instance_id = $instance_id ?: $data['node_id'];
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
87      $component_id = $source->getChoice($data['source']);
88      $label = $this->slotSourceProxy->getLabelWithSummary($data, [])['label'];
89    }
90
91    $instance_id = $instance_id ?: $data['node_id'];
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
87      $component_id = $source->getChoice($data['source']);
88      $label = $this->slotSourceProxy->getLabelWithSummary($data, [])['label'];
89    }
90
91    $instance_id = $instance_id ?: $data['node_id'];
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
87      $component_id = $source->getChoice($data['source']);
88      $label = $this->slotSourceProxy->getLabelWithSummary($data, [])['label'];
89    }
90
91    $instance_id = $instance_id ?: $data['node_id'];
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
95        '@instance_id' => $instance_id ?? 'NULL',
96        '@component_id' => $component_id,
97      ];
98      $this->logger->error('[LayersPanel::buildSingleComponent] missing component ID: @component_id or instance ID: @instance_id. <pre>' . \print_r($data, TRUE) . '</pre>', $params);
99
100      return NULL;
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
87      $component_id = $source->getChoice($data['source']);
88      $label = $this->slotSourceProxy->getLabelWithSummary($data, [])['label'];
89    }
90
91    $instance_id = $instance_id ?: $data['node_id'];
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
 
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
87      $component_id = $source->getChoice($data['source']);
88      $label = $this->slotSourceProxy->getLabelWithSummary($data, [])['label'];
89    }
90
91    $instance_id = $instance_id ?: $data['node_id'];
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
87      $component_id = $source->getChoice($data['source']);
88      $label = $this->slotSourceProxy->getLabelWithSummary($data, [])['label'];
89    }
90
91    $instance_id = $instance_id ?: $data['node_id'];
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
87      $component_id = $source->getChoice($data['source']);
88      $label = $this->slotSourceProxy->getLabelWithSummary($data, [])['label'];
89    }
90
91    $instance_id = $instance_id ?: $data['node_id'];
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
95        '@instance_id' => $instance_id ?? 'NULL',
96        '@component_id' => $component_id,
97      ];
98      $this->logger->error('[LayersPanel::buildSingleComponent] missing component ID: @component_id or instance ID: @instance_id. <pre>' . \print_r($data, TRUE) . '</pre>', $params);
99
100      return NULL;
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
 
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
95        '@instance_id' => $instance_id ?? 'NULL',
96        '@component_id' => $component_id,
97      ];
98      $this->logger->error('[LayersPanel::buildSingleComponent] missing component ID: @component_id or instance ID: @instance_id. <pre>' . \print_r($data, TRUE) . '</pre>', $params);
99
100      return NULL;
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
 
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
82  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
83    $component_id = $source->getPluginID();
84    $label = $source->label();
85
86    if ($source instanceof SourceWithChoicesInterface) {
 
91    $instance_id = $instance_id ?: $data['node_id'];
92
93    if (!$instance_id || !$component_id) {
 
93    if (!$instance_id || !$component_id) {
 
103    $slots = [];
104
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
105    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
106      $dropzone = [
107        '#type' => 'component',
108        '#component' => 'display_builder:dropzone',
109        '#props' => [
110          'title' => $definition['title'],
111          'variant' => 'highlighted',
112        ],
113        '#attributes' => [
114          // Required for JavaScript @see components/dropzone/dropzone.js.
115          'data-db-id' => $builder_id,
116          // Slot is needed for contextual menu paste.
117          // @see assets/js/contextual_menu.js
118          'data-slot-id' => $slot_id,
119          'data-slot-title' => $definition['title'],
120          'data-node-title' => $label,
121          'data-instance-id' => $instance_id . '_' . $slot_id,
122        ],
123      ];
124
125      if ($sources = $source->getSlotValue($slot_id)) {
126        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
127      }
128      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
129      $slots[] = [
130        [
131          '#plain_text' => $definition['title'],
132        ],
133        $dropzone,
134      ];
135    }
136
137    $build = [
138      '#type' => 'component',
139      '#component' => 'display_builder:layer',
140      '#slots' => [
141        'title' => $label,
142        'children' => $slots,
143      ],
144      // Required for the context menu label.
145      // @see assets/js/contextual_menu.js
146      '#attributes' => [
147        'data-node-title' => $label,
148        'data-instance-id' => $instance_id,
149      ],
150    ];
151    $build = $this->addThirdPartySettingsSummary($data, $build);
152    $build = $this->addComponentSettingsSummary($source, $build);
153
154    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $source->label(), $index);
155  }
LayersPanel->create
44  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
45    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
46    $instance->slotSourceProxy = $container->get('display_builder.slot_sources_proxy');
47    $instance->islandManager = $container->get('plugin.manager.db_island');
48
49    return $instance;
50  }
LayersPanel->keyboardShortcuts
57      'key' => 'y',
58      'help' => t('Show the layer'),
59    ];
60  }