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}

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.

TreePanel->build
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  }
TreePanel->buildSingleBlock
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') {
 
149    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
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',
 
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  }
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') {
 
149    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
149    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
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  }
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') {
 
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',
 
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  }
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') {
 
149    if (isset($data['source_id']) && $data['source_id'] === 'entity_field') {
 
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  }
TreePanel->buildSingleComponent
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;
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) {
 
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) {
 
80    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
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)) {
 
91      if ($sources = $source->getSlotValue($slot_id)) {
92        $dropzone['#slots']['content'] = $this->digFromSlot($instance, $sources);
 
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);
 
80    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
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',
 
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  }
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) {
 
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) {
 
80    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
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)) {
 
91      if ($sources = $source->getSlotValue($slot_id)) {
92        $dropzone['#slots']['content'] = $this->digFromSlot($instance, $sources);
 
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);
 
80    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
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) {
 
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  }
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) {
 
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) {
 
80    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
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)) {
 
96        $dropzone['#attributes']['data-empty-hint'] = $this->t('Empty');
97      }
98      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $node_id, $slot_id);
 
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);
 
80    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
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',
 
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  }
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) {
 
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) {
 
80    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
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)) {
 
96        $dropzone['#attributes']['data-empty-hint'] = $this->t('Empty');
97      }
98      $dropzone = $this->htmxEvents->onSlotDrop($dropzone, $builder_id, $this->getPluginID(), $node_id, $slot_id);
 
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);
 
80    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
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) {
 
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  }
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) {
 
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) {
 
80    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
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',
 
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  }
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) {
 
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) {
 
80    foreach ($source->getSlotDefinitions() as $slot_id => $definition) {
 
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) {
 
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  }
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) {
 
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) {
 
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',
 
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  }
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) {
 
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) {
 
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) {
 
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  }
TreePanel->keyboardShortcuts
31      'key' => 'n',
32      'help' => t('Show the navigator'),
33    ];
34  }