Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 100
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
TreePanel
0.00% covered (danger)
0.00%
0 / 94
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 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 buildSingleComponent
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 1
90
 buildSingleBlock
0.00% covered (danger)
0.00%
0 / 18
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: 'tree',
19  label: new TranslatableMarkup('Tree'),
20  description: new TranslatableMarkup('Manageable hierarchical tree view of elements.'),
21  type: IslandType::View,
22  icon: 'bar-chart-steps',
23)]
24class TreePanel 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      't' => t('Show the tree'),
47    ];
48  }
49
50  /**
51   * {@inheritdoc}
52   */
53  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
54    $builder_id = (string) $builder->id();
55
56    return [
57      '#type' => 'component',
58      '#component' => 'display_builder:panel_tree',
59      '#slots' => [
60        'items' => $this->digFromSlot($builder_id, $data),
61      ],
62    ];
63  }
64
65  /**
66   * {@inheritdoc}
67   */
68  public function buildSingleComponent(string $builder_id, string $instance_id, array $data, int $index = 0): array {
69    $component_id = $data['source']['component']['component_id'] ?? NULL;
70    $instance_id = $instance_id ?: $data['_node_id'];
71
72    if (!$instance_id && !$component_id) {
73      return [];
74    }
75
76    $component = $this->sdcManager->getDefinition($component_id);
77
78    if (!$component) {
79      return [];
80    }
81
82    $slots = [];
83
84    foreach ($component['slots'] ?? [] as $slot_id => $definition) {
85      $items = [
86        '#type' => 'component',
87        '#component' => 'display_builder:tree_item',
88        '#props' => [
89          'icon' => 'box-arrow-in-right',
90        ],
91        '#slots' => [
92          'title' => $definition['title'],
93        ],
94        // Slot is needed for contextual menu paste.
95        // @see assets/js/contextual_menu.js
96        '#attributes' => [
97          'data-slot-id' => $slot_id,
98          'data-slot-title' => $definition['title'],
99          'data-node-id' => $instance_id,
100          'data-node-title' => $component['label'],
101          'data-menu-type' => 'slot',
102        ],
103      ];
104
105      if (isset($data['source']['component']['slots'][$slot_id]['sources'])) {
106        $sources = $data['source']['component']['slots'][$slot_id]['sources'];
107        $items['#slots']['children'] = $this->digFromSlot($builder_id, $sources);
108      }
109
110      $slots[] = $items;
111    }
112
113    // I f a single item, expand by default.
114    if (\count($slots) === 1) {
115      $slots[0]['#props']['expanded'] = TRUE;
116    }
117
118    $name = $component['name'];
119    $variant = $this->getComponentVariantLabel($data, $component);
120
121    if ($variant) {
122      $name .= ' - ' . $variant;
123    }
124
125    return [
126      '#type' => 'component',
127      '#component' => 'display_builder:tree_item',
128      '#props' => [
129        'expanded' => TRUE,
130        'icon' => 'box',
131      ],
132      '#slots' => [
133        'title' => $name,
134        'children' => $slots,
135      ],
136      // Required for the context menu label.
137      // @see assets/js/contextual_menu.js
138      '#attributes' => [
139        'data-node-id' => $instance_id,
140        'data-node-title' => $name,
141        'data-slot-position' => $index,
142        'data-menu-type' => 'component',
143      ],
144    ];
145  }
146
147  /**
148   * {@inheritdoc}
149   */
150  public function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
151    $instance_id = $instance_id ?: $data['_node_id'];
152    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
153
154    return [
155      '#type' => 'component',
156      '#component' => 'display_builder:tree_item',
157      '#props' => [
158        'icon' => 'view-list',
159      ],
160      '#slots' => [
161        'title' => $label['summary'],
162      ],
163      '#attributes' => [
164        'data-node-id' => $instance_id,
165        // This label is used for contextual menu.
166        // @see assets/js/contextual_menu.js
167        'data-node-title' => $label['label'],
168        'data-slot-position' => $index,
169        'data-menu-type' => 'block',
170      ],
171    ];
172  }
173
174  /**
175   * Get the label for a component variant.
176   *
177   * @param array $data
178   *   The component data array.
179   * @param array $definition
180   *   The component definition array.
181   *
182   * @return string
183   *   The variant label or empty string if no variant is set.
184   */
185  private function getComponentVariantLabel(array $data, array $definition): string {
186    if (!isset($data['source']['component']['variant_id'])) {
187      return '';
188    }
189
190    if ($data['source']['component']['variant_id']['source_id'] !== 'select') {
191      return '';
192    }
193    $variant_id = $data['source']['component']['variant_id']['source']['value'] ?? '';
194
195    if (empty($variant_id)) {
196      return '';
197    }
198
199    return $definition['variants'][$variant_id]['title'] ?? '';
200  }
201
202}