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