Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 5
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 / 22
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 5
240
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
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 / 15
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
 buildSingleComponent
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
72
 buildSingleBlock
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 4
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 Drupal\display_builder\SourceWithSlotsInterface;
13use Drupal\ui_patterns\SourceWithChoicesInterface;
14use Symfony\Component\DependencyInjection\ContainerInterface;
15
16/**
17 * Layers island plugin implementation.
18 */
19#[Island(
20  id: 'tree',
21  label: new TranslatableMarkup('Tree'),
22  description: new TranslatableMarkup('Hierarchical view of components and blocks.'),
23  type: IslandType::View,
24  default_region: 'main',
25  icon: 'bar-chart-steps',
26)]
27class TreePanel extends BuilderPanel {
28
29  /**
30   * Proxy for slot source operations.
31   */
32  protected SlotSourceProxy $slotSourceProxy;
33
34  /**
35   * {@inheritdoc}
36   */
37  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
38    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
39    $instance->slotSourceProxy = $container->get('display_builder.slot_sources_proxy');
40
41    return $instance;
42  }
43
44  /**
45   * {@inheritdoc}
46   */
47  public static function keyboardShortcuts(): array {
48    return [
49      'key' => 't',
50      'help' => t('Show the tree'),
51    ];
52  }
53
54  /**
55   * {@inheritdoc}
56   */
57  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
58    $builder_id = (string) $builder->id();
59
60    $build = [
61      '#type' => 'component',
62      '#component' => 'display_builder:panel_tree',
63      '#slots' => [
64        'items' => $this->digFromSlot($builder_id, $data),
65      ],
66      '#attributes' => [
67        // Required for JavaScript @see components/dropzone/dropzone.js.
68        'data-db-id' => $builder_id,
69        'data-node-title' => $this->t('Base container'),
70        'data-db-root' => TRUE,
71        // 'class' => ['db-dropzone--root', 'db-dropzone'],
72      ],
73    ];
74
75    return $build;
76  }
77
78  /**
79   * {@inheritdoc}
80   */
81  protected function buildSingleComponent(string $builder_id, string $instance_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
82    $component_id = $source->getPluginId();
83    $label = $source->label();
84
85    if ($source instanceof SourceWithChoicesInterface) {
86      $component_id = $source->getChoice($data['source']);
87      $label = $this->slotSourceProxy->getLabelWithSummary($data, [])['label'];
88    }
89    $instance_id = $instance_id ?: $data['node_id'];
90
91    if (!$instance_id || !$component_id) {
92      return NULL;
93    }
94
95    $slots = [];
96
97    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
98      $items = [
99        '#type' => 'component',
100        '#component' => 'display_builder:tree_item',
101        '#props' => [
102          'icon' => 'box-arrow-in-right',
103        ],
104        '#slots' => [
105          'title' => $definition['title'],
106        ],
107        // Slot is needed for contextual menu paste.
108        // @see assets/js/contextual_menu.js
109        '#attributes' => [
110          'data-slot-id' => $slot_id,
111          'data-slot-title' => $definition['title'],
112          'data-node-id' => $instance_id,
113          'data-node-title' => $label,
114          'data-menu-type' => 'slot',
115        ],
116      ];
117
118      if ($sources = $source->getSlotValue($slot_id)) {
119        $items['#slots']['children'] = $this->digFromSlot($builder_id, $sources);
120      }
121
122      $slots[] = $items;
123    }
124
125    // I f a single item, expand by default.
126    if (\count($slots) === 1) {
127      $slots[0]['#props']['expanded'] = TRUE;
128    }
129
130    return [
131      '#type' => 'component',
132      '#component' => 'display_builder:tree_item',
133      '#props' => [
134        'expanded' => TRUE,
135        'icon' => 'box',
136      ],
137      '#slots' => [
138        'title' => $label,
139        'children' => $slots,
140      ],
141      // Required for the context menu label.
142      // @see assets/js/contextual_menu.js
143      '#attributes' => [
144        'data-node-id' => $instance_id,
145        'data-node-title' => $label,
146        'data-slot-position' => $index,
147        'data-menu-type' => 'component',
148        // 'class' => ['db-dropzone', 'db-tree__component'],
149      ],
150    ];
151  }
152
153  /**
154   * {@inheritdoc}
155   */
156  protected function buildSingleBlock(string $builder_id, string $instance_id, array $data, int $index = 0): array {
157    $instance_id = $instance_id ?: $data['node_id'];
158    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
159
160    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
161      $label['summary'] = (string) $this->t('Field: @label', ['@label' => $label['label']]);
162    }
163
164    return [
165      '#type' => 'component',
166      '#component' => 'display_builder:tree_item',
167      '#props' => [
168        'icon' => 'view-list',
169      ],
170      '#slots' => [
171        'title' => $label['summary'],
172      ],
173      '#attributes' => [
174        'data-node-id' => $instance_id,
175        // This label is used for contextual menu.
176        // @see assets/js/contextual_menu.js
177        'data-node-title' => $label['summary'],
178        'data-slot-position' => $index,
179        'data-menu-type' => 'block',
180        // 'class' => ['db-dropzone', 'db-tree__block'],
181      ],
182    ];
183  }
184
185}