Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 88
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
LayersPanel
0.00% covered (danger)
0.00%
0 / 82
0.00% covered (danger)
0.00%
0 / 6
342
0.00% covered (danger)
0.00%
0 / 1
 create
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 keyboardShortcuts
0.00% covered (danger)
0.00%
0 / 3
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 / 1
6
 buildSingleComponent
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
72
 buildSingleBlock
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 getComponentVariantLabel
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\IslandType;
11use Drupal\display_builder\SlotSourceProxy;
12use Symfony\Component\DependencyInjection\ContainerInterface;
13
14/**
15 * Layers island plugin implementation.
16 */
17#[Island(
18  id: 'layers',
19  label: new TranslatableMarkup('Layers'),
20  description: new TranslatableMarkup('Manageable hierarchical layer view of elements.'),
21  type: IslandType::View,
22  icon: 'layers',
23)]
24class LayersPanel extends BuilderPanel {
25
26  /**
27   * Proxy for slot source operations.
28   */
29  protected SlotSourceProxy $slotSourceProxy;
30
31  /**
32   * {@inheritdoc}
33   */
34  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
35    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
36    $instance->slotSourceProxy = $container->get('display_builder.slot_sources_proxy');
37
38    return $instance;
39  }
40
41  /**
42   * {@inheritdoc}
43   */
44  public static function keyboardShortcuts(): array {
45    return [
46      'y' => t('Show the layer'),
47    ];
48  }
49
50  /**
51   * {@inheritdoc}
52   */
53  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
54    $build = parent::build($builder, $data, $options);
55
56    if (empty($build['#slots']['content'] ?? [])) {
57      // Load en empty component to have any assets with it.
58      $build['#slots']['content'] = [
59        '#type' => 'component',
60        '#component' => 'display_builder:layer',
61      ];
62    }
63
64    return $build;
65  }
66
67  /**
68   * {@inheritdoc}
69   */
70  public function buildSingleComponent(string $builder_id, string $instance_id, array $data, int $index = 0): array {
71    $component_id = $data['source']['component']['component_id'] ?? NULL;
72    $instance_id = $instance_id ?: $data['_node_id'];
73
74    if (!$instance_id && !$component_id) {
75      return [];
76    }
77
78    $component = $this->sdcManager->getDefinition($component_id);
79
80    if (!$component) {
81      return [];
82    }
83
84    $slots = [];
85
86    foreach ($component['slots'] ?? [] as $slot_id => $definition) {
87      $dropzone = [
88        '#type' => 'component',
89        '#component' => 'display_builder:dropzone',
90        '#props' => [
91          'title' => $definition['title'],
92          'variant' => 'highlighted',
93        ],
94        '#attributes' => [
95          // Required for JavaScript @see components/dropzone/dropzone.js.
96          'data-db-id' => $builder_id,
97          // Slot is needed for contextual menu paste.
98          // @see assets/js/contextual_menu.js
99          'data-slot-id' => $slot_id,
100          'data-slot-title' => $definition['title'],
101          'data-node-title' => $component['label'],
102        ],
103      ];
104
105      if (isset($data['source']['component']['slots'][$slot_id]['sources'])) {
106        $sources = $data['source']['component']['slots'][$slot_id]['sources'];
107        $dropzone['#slots']['content'] = $this->digFromSlot($builder_id, $sources);
108      }
109      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $instance_id, $slot_id);
110      $slots[] = [
111        [
112          '#plain_text' => $definition['title'],
113        ],
114        $dropzone,
115      ];
116    }
117    $name = $component['name'];
118    $variant = $this->getComponentVariantLabel($data, $component);
119
120    if ($variant) {
121      $name .= ' - ' . $variant;
122    }
123
124    $build = [
125      '#type' => 'component',
126      '#component' => 'display_builder:layer',
127      '#slots' => [
128        'title' => $name,
129        'children' => $slots,
130      ],
131      // Required for the context menu label.
132      // @see assets/js/contextual_menu.js
133      '#attributes' => [
134        'data-node-title' => $name,
135      ],
136    ];
137
138    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, (string) $component['label'], $index);
139  }
140
141  /**
142   * {@inheritdoc}
143   */
144  public function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
145    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
146    $build = [
147      '#type' => 'component',
148      '#component' => 'display_builder:layer',
149      '#slots' => [
150        'title' => $label['summary'],
151      ],
152    ];
153    $instance_id = $instance_id ?: $data['_node_id'];
154
155    // This label is used for contextual menu.
156    // @see assets/js/contextual_menu.js
157    $build['#attributes']['data-node-title'] = $label['summary'];
158    $build['#attributes']['data-slot-position'] = $index;
159
160    return $this->htmxEvents->onInstanceClick($build, $builder_id, $instance_id, $label['label'], $index);
161  }
162
163  /**
164   * Get the label for a component variant.
165   *
166   * @param array $data
167   *   The component data array.
168   * @param array $definition
169   *   The component definition array.
170   *
171   * @return string
172   *   The variant label or empty string if no variant is set.
173   */
174  private function getComponentVariantLabel(array $data, array $definition): string {
175    if (!isset($data['source']['component']['variant_id'])) {
176      return '';
177    }
178
179    if ($data['source']['component']['variant_id']['source_id'] !== 'select') {
180      return '';
181    }
182    $variant_id = $data['source']['component']['variant_id']['source']['value'] ?? '';
183
184    if (empty($variant_id)) {
185      return '';
186    }
187
188    return $definition['variants'][$variant_id]['title'] ?? '';
189  }
190
191}