Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 89
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
HistoryButtons
0.00% covered (danger)
0.00%
0 / 82
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 12
756
0.00% covered (danger)
0.00%
0 / 1
 build
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 onAttachToRoot
0.00% covered (danger)
0.00%
0 / 1
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
 onAttachToSlot
0.00% covered (danger)
0.00%
0 / 1
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
 onMove
0.00% covered (danger)
0.00%
0 / 1
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
 onHistoryChange
0.00% covered (danger)
0.00%
0 / 1
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
 onUpdate
0.00% covered (danger)
0.00%
0 / 1
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
 onDelete
0.00% covered (danger)
0.00%
0 / 1
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
 hasButtons
0.00% covered (danger)
0.00%
0 / 17
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
 rebuild
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 buildUndoButton
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 buildRedoButton
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 buildClearButton
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
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\IslandPluginToolbarButtonConfigurationBase;
11use Drupal\display_builder\IslandType;
12
13/**
14 * History buttons island plugin implementation.
15 */
16#[Island(
17  id: 'history',
18  enabled_by_default: TRUE,
19  label: new TranslatableMarkup('History'),
20  description: new TranslatableMarkup('Undo and redo changes.'),
21  type: IslandType::Button,
22  default_region: 'end',
23)]
24class HistoryButtons extends IslandPluginToolbarButtonConfigurationBase {
25
26  /**
27   * {@inheritdoc}
28   */
29  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
30    $builder_id = (string) $builder->id();
31    $buttons = [
32      $this->isButtonEnabled('undo') ? $this->buildUndoButton($builder, $builder_id) : [],
33      $this->isButtonEnabled('redo') ? $this->buildRedoButton($builder, $builder_id) : [],
34      $this->isButtonEnabled('clear') ? $this->buildClearButton($builder, $builder_id) : [],
35    ];
36
37    if (empty(\array_filter($buttons))) {
38      return [];
39    }
40
41    return [
42      '#type' => 'component',
43      '#component' => 'display_builder:button_group',
44      '#slots' => [
45        'buttons' => $buttons,
46      ],
47    ];
48  }
49
50  /**
51   * {@inheritdoc}
52   */
53  public function onAttachToRoot(string $builder_id, string $instance_id): array {
54    return $this->rebuild($builder_id);
55  }
56
57  /**
58   * {@inheritdoc}
59   */
60  public function onAttachToSlot(string $builder_id, string $instance_id, string $parent_id): array {
61    return $this->rebuild($builder_id);
62  }
63
64  /**
65   * {@inheritdoc}
66   */
67  public function onMove(string $builder_id, string $instance_id): array {
68    return $this->rebuild($builder_id);
69  }
70
71  /**
72   * {@inheritdoc}
73   */
74  public function onHistoryChange(string $builder_id): array {
75    return $this->rebuild($builder_id);
76  }
77
78  /**
79   * {@inheritdoc}
80   */
81  public function onUpdate(string $builder_id, string $instance_id): array {
82    return $this->rebuild($builder_id);
83  }
84
85  /**
86   * {@inheritdoc}
87   */
88  public function onDelete(string $builder_id, string $parent_id): array {
89    return $this->rebuild($builder_id);
90  }
91
92  /**
93   * {@inheritdoc}
94   */
95  protected function hasButtons(): array {
96    return [
97      'undo' => [
98        'title' => $this->t('Undo'),
99        'description' => $this->t('Undo action, icon is always visible, label is number of undo.'),
100        'default' => 'icon_label',
101      ],
102      'redo' => [
103        'title' => $this->t('Redo'),
104        'description' => $this->t('Redo action, icon is always visible, label is number of redo.'),
105        'default' => 'icon_label',
106      ],
107      'clear' => [
108        'title' => $this->t('Clear'),
109        'description' => $this->t('A button to clear the logs history (past and future).'),
110        'default' => 'hidden',
111      ],
112    ];
113  }
114
115  /**
116   * Rebuilds the island with the given builder ID.
117   *
118   * @param string $builder_id
119   *   The ID of the builder.
120   *
121   * @return array
122   *   The rebuilt island.
123   */
124  private function rebuild(string $builder_id): array {
125    if (!$this->builder) {
126      // @todo pass \Drupal\display_builder\InstanceInterface object in
127      // parameters instead of loading again.
128      /** @var \Drupal\display_builder\InstanceInterface $builder */
129      $builder = $this->entityTypeManager->getStorage('display_builder_instance')->load($builder_id);
130      $this->builder = $builder;
131    }
132
133    return $this->addOutOfBand(
134      $this->build($this->builder),
135      '#' . $this->getHtmlId($builder_id),
136      'innerHTML'
137    );
138  }
139
140  /**
141   * Builds the undo button.
142   *
143   * @param \Drupal\display_builder\InstanceInterface $builder
144   *   The builder instance.
145   * @param string $builder_id
146   *   The builder ID.
147   *
148   * @return array
149   *   The undo button render array.
150   */
151  private function buildUndoButton(InstanceInterface $builder, string $builder_id): array {
152    $past = $builder->getCountPast();
153    $undo = $this->buildButton(
154      ($this->showLabel('undo') && $past) ? (string) $past : '',
155      'undo',
156      'arrow-counterclockwise',
157      $this->t('Undo (shortcut: u)'),
158      ['u' => $this->t('Undo last change')]
159    );
160
161    if (empty($past)) {
162      $undo['#attributes']['disabled'] = 'disabled';
163    }
164
165    return $this->htmxEvents->onUndo($undo, $builder_id);
166  }
167
168  /**
169   * Builds the redo button.
170   *
171   * @param \Drupal\display_builder\InstanceInterface $builder
172   *   The builder instance.
173   * @param string $builder_id
174   *   The builder ID.
175   *
176   * @return array
177   *   The redo button render array.
178   */
179  private function buildRedoButton(InstanceInterface $builder, string $builder_id): array {
180    $future = $builder->getCountFuture();
181    $redo = $this->buildButton(
182      ($this->showLabel('redo') && $future) ? (string) $future : '',
183      'redo',
184      'arrow-clockwise',
185      $this->t('Redo (shortcut: r)'),
186      ['r' => $this->t('Redo last undone change')]
187    );
188
189    if (empty($future)) {
190      $redo['#attributes']['disabled'] = 'disabled';
191    }
192
193    return $this->htmxEvents->onRedo($redo, $builder_id);
194  }
195
196  /**
197   * Builds the clear button.
198   *
199   * @param \Drupal\display_builder\InstanceInterface $builder
200   *   The builder instance.
201   * @param string $builder_id
202   *   The builder ID.
203   *
204   * @return array
205   *   The clear button render array.
206   */
207  private function buildClearButton(InstanceInterface $builder, string $builder_id): array {
208    $past = $builder->getCountPast();
209    $future = $builder->getCountFuture();
210    $clear = $this->buildButton(
211      $this->showLabel('clear') ? $this->t('Clear') : '',
212      'clear',
213      $this->showIcon('clear') ? 'clock-history' : '',
214      $this->t('Clear history (shortcut: Shift+C)'),
215      ['C' => $this->t('Clear all changes history (Shift+C)')]
216    );
217    $clear['#props']['variant'] = 'warning';
218    $clear['#attributes']['outline'] = TRUE;
219
220    if (empty($past) && empty($future)) {
221      $clear['#attributes']['class'] = ['hidden'];
222    }
223
224    return $this->htmxEvents->onClear($clear, $builder_id);
225  }
226
227}