Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 102
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
TreePanel
0.00% covered (danger)
0.00%
0 / 95
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 4
132
0.00% covered (danger)
0.00%
0 / 1
 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 / 19
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 / 53
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 buildSingleBlock
0.00% covered (danger)
0.00%
0 / 19
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\Island\IslandType;
11use Drupal\display_builder\SourceWithSlotsInterface;
12
13/**
14 * Navigator island plugin implementation.
15 */
16#[Island(
17  id: 'tree',
18  label: new TranslatableMarkup('Navigator'),
19  description: new TranslatableMarkup('Hierarchical schematic view of components and blocks.'),
20  type: IslandType::View,
21  default_region: 'sidebar',
22  icon: 'bar-chart-steps',
23)]
24class TreePanel extends BuilderPanel {
25
26  /**
27   * {@inheritdoc}
28   */
29  public static function keyboardShortcuts(): array {
30    return [
31      'key' => 'n',
32      'help' => t('Show the navigator'),
33    ];
34  }
35
36  /**
37   * {@inheritdoc}
38   */
39  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
40    $builder_id = (string) $builder->id();
41
42    $build = [
43      '#type' => 'component',
44      '#component' => 'display_builder:panel_tree',
45      '#props' => [
46        'collapse_label' => (string) $this->t('Collapse all'),
47        'expand_label' => (string) $this->t('Expand all'),
48      ],
49      '#slots' => [
50        'items' => $this->digFromSlot($builder, $data),
51      ],
52      '#attributes' => [
53        // Required for JavaScript @see components/dropzone/dropzone.js.
54        'data-db-id' => $builder_id,
55        'data-node-title' => $this->t('Base container'),
56        'data-db-root' => TRUE,
57        // Shown by panel_tree.css when the dropzone is actually empty.
58        'data-empty-hint' => $this->t('Empty'),
59      ],
60    ];
61
62    return $this->htmxEvents->onRootDrop($build, $builder_id, $this->getPluginID());
63  }
64
65  /**
66   * {@inheritdoc}
67   */
68  protected function buildSingleComponent(InstanceInterface $instance, string $node_id, SourceWithSlotsInterface $source, array $data, int $index = 0): ?array {
69    $info = $this->resolveComponentInfo($source, $data, $node_id);
70
71    if ($info === NULL) {
72      return NULL;
73    }
74
75    ['label' => $label, 'instance_id' => $node_id] = $info;
76
77    $builder_id = (string) $instance->id();
78    $slots = [];
79
80    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
81      $dropzone = [
82        '#type' => 'component',
83        '#component' => 'display_builder:dropzone',
84        '#attributes' => [
85          // Required for JavaScript @see components/dropzone/dropzone.js.
86          'data-db-id' => $builder_id,
87          'data-testid' => 'dropzone_' . $slot_id,
88        ],
89      ];
90
91      if ($sources = $source->getSlotValue($slot_id)) {
92        $dropzone['#slots']['content'] = $this->digFromSlot($instance, $sources);
93      }
94      else {
95        // Shown by panel_tree.css when the dropzone is actually empty.
96        $dropzone['#attributes']['data-empty-hint'] = $this->t('Empty');
97      }
98      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $node_id, $slot_id);
99
100      $slots[] = [
101        '#type' => 'component',
102        '#component' => 'display_builder:tree_node',
103        '#props' => [
104          'icon' => 'box-arrow-in-right',
105        ],
106        '#slots' => [
107          'title' => $definition['title'],
108          'children' => [$dropzone],
109        ],
110        '#attributes' => \array_merge(
111          ['data-menu-type' => 'slot'],
112          $this->buildSlotAttributes($slot_id, $definition['title'], $node_id, $label)
113        ),
114      ];
115    }
116
117    // If a single slot, expand it by default.
118    if (\count($slots) === 1) {
119      $slots[0]['#props']['expanded'] = TRUE;
120    }
121
122    $build = [
123      '#type' => 'component',
124      '#component' => 'display_builder:tree_node',
125      '#props' => [
126        'expanded' => TRUE,
127        'icon' => 'box',
128      ],
129      '#slots' => [
130        'title' => $label,
131        'children' => $slots,
132      ],
133      '#attributes' => \array_merge(
134        ['data-node-id' => $node_id, 'data-menu-type' => 'component'],
135        $this->buildNodeAttributes($label, $index)
136      ),
137    ];
138
139    return $this->htmxEvents->onInstanceClick($build, $builder_id, $node_id, $source->label(), $index);
140  }
141
142  /**
143   * {@inheritdoc}
144   */
145  protected function buildSingleBlock(InstanceInterface $instance, string $node_id, array $data, int $index = 0): array {
146    $node_id = $node_id ?: $data['node_id'];
147    $label = $this->slotSourceProxy->getLabelWithSummary($data, $this->configuration['contexts'] ?? []);
148
149    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
150      $label['summary'] = (string) $this->t('Field: @label', ['@label' => $label['label']]);
151    }
152
153    $build = [
154      '#type' => 'component',
155      '#component' => 'display_builder:tree_node',
156      '#props' => [
157        'icon' => 'view-list',
158      ],
159      '#slots' => [
160        'title' => $label['summary'],
161      ],
162      '#attributes' => \array_merge(
163        ['data-node-id' => $node_id, 'data-menu-type' => 'block'],
164        $this->buildNodeAttributes($label['summary'], $index, $data['source_id'] ?? NULL)
165      ),
166    ];
167
168    return $this->htmxEvents->onInstanceClick($build, (string) $instance->id(), $node_id, $label['summary'], $index);
169  }
170
171}