Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
95.60% covered (success)
95.60%
174 / 182
96.67% covered (success)
96.67%
29 / 30
68.97% covered (warning)
68.97%
20 / 29
94.44% covered (success)
94.44%
17 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
HtmxEvents
95.60% covered (success)
95.60%
174 / 182
96.67% covered (success)
96.67%
29 / 30
68.97% covered (warning)
68.97%
20 / 29
94.44% covered (success)
94.44%
17 / 18
41.22
0.00% covered (danger)
0.00%
0 / 1
 onClickDelete
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onClickSavePreset
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onClickPaste
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onClickDuplicate
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onClickPasteStyles
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onClickDeleteStyles
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onRootDrop
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onSlotDrop
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onInstanceClick
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
5 / 5
25.00% covered (danger)
25.00%
1 / 4
100.00% covered (success)
100.00%
1 / 1
6.80
 onInstanceFormChange
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 onInstanceUpdateButtonClick
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
7 / 7
37.50% covered (danger)
37.50%
3 / 8
100.00% covered (success)
100.00%
1 / 1
7.91
 onThirdPartyFormChange
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onUndo
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onRedo
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onReset
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onRevert
0.00% covered (danger)
0.00%
0 / 8
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
 onClear
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onPublish
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder;
6
7use Drupal\Component\Render\MarkupInterface;
8use Drupal\Core\Url;
9
10/**
11 * The HTMX Events class.
12 */
13class HtmxEvents {
14
15  public const HTMX_REQUEST = 'click consume';
16
17  /**
18   * Delete on click.
19   *
20   * The node ID isn't known until the contextual menu resolves what was
21   * right-clicked, so it's supplied at request time via 'hx-vals'
22   * (@see components/contextual_menu/contextual_menu.js) rather than baked into
23   * the URL here.
24   *
25   * @param array $build
26   *   The render array.
27   * @param string $builder_id
28   *   The instance entity ID.
29   *
30   * @return array
31   *   The render array.
32   */
33  public function onClickDelete(array $build, string $builder_id): array {
34    $url = new Url(
35      'display_builder.api_delete',
36      [
37        'display_builder_instance' => $builder_id,
38      ]
39    );
40
41    $htmx = DisplayBuilderHtmx::request('post', $url, self::HTMX_REQUEST);
42    $htmx->on('click', \sprintf('Drupal.displayBuilder.handleSecondDrawer(%s, this, event, "close")', $builder_id));
43    $htmx->applyTo($build);
44
45    return $build;
46  }
47
48  /**
49   * Save as preset on click.
50   *
51   * The node ID isn't known until the contextual menu resolves what was
52   * right-clicked, so it's supplied at request time via 'hx-vals'
53   * (@see components/contextual_menu/contextual_menu.js) rather than baked into
54   * the URL here.
55   *
56   * @param array $build
57   *   The render array.
58   * @param string $builder_id
59   *   The instance entity ID.
60   * @param string|\Drupal\Component\Render\MarkupInterface $prompt
61   *   The prompt before save.
62   *
63   * @return array
64   *   The render array.
65   */
66  public function onClickSavePreset(array $build, string $builder_id, MarkupInterface|string $prompt): array {
67    $url = new Url(
68      'display_builder.api_save_preset',
69      [
70        'display_builder_instance' => $builder_id,
71      ]
72    );
73
74    $htmx = DisplayBuilderHtmx::request('post', $url, self::HTMX_REQUEST);
75    $htmx->prompt((string) $prompt);
76    $htmx->applyTo($build);
77
78    return $build;
79  }
80
81  /**
82   * Paste on click.
83   *
84   * The node/parent/slot IDs aren't known until the contextual menu
85   * resolves what was right-clicked and what was previously copied, so
86   * they're supplied at request time via 'hx-vals'
87   * (@see components/contextual_menu/contextual_menu.js) rather than baked into
88   * the URL here.
89   *
90   * @param array $build
91   *   The render array.
92   * @param string $builder_id
93   *   The instance entity ID.
94   *
95   * @return array
96   *   The render array.
97   */
98  public function onClickPaste(array $build, string $builder_id): array {
99    $url = new Url(
100      'display_builder.api_paste',
101      [
102        'display_builder_instance' => $builder_id,
103      ]
104    );
105
106    DisplayBuilderHtmx::request('post', $url, self::HTMX_REQUEST)->applyTo($build);
107
108    return $build;
109  }
110
111  /**
112   * Duplicate on placeholder click.
113   *
114   * The node/parent/slot IDs aren't known until the contextual menu
115   * resolves what was right-clicked, so they're supplied at request time
116   * via 'hx-vals' (@see components/contextual_menu/contextual_menu.js) rather
117   * than baked into the URL here.
118   *
119   * @param array $build
120   *   The render array.
121   * @param string $builder_id
122   *   The instance entity ID.
123   *
124   * @return array
125   *   The render array.
126   */
127  public function onClickDuplicate(array $build, string $builder_id): array {
128    $url = new Url(
129      'display_builder.api_duplicate',
130      [
131        'display_builder_instance' => $builder_id,
132      ]
133    );
134
135    DisplayBuilderHtmx::request('post', $url, self::HTMX_REQUEST)->applyTo($build);
136
137    return $build;
138  }
139
140  /**
141   * Paste or merge styles on click.
142   *
143   * Used by both the "Paste styles" and "Merge styles" menu items - they
144   * hit the same endpoint and only differ in the 'mode' request value,
145   * which the contextual menu sets based on which item was clicked
146   * (@see components/contextual_menu/contextual_menu.js), same as the
147   * target/source node IDs.
148   *
149   * @param array $build
150   *   The render array.
151   * @param string $builder_id
152   *   The instance entity ID.
153   *
154   * @return array
155   *   The render array.
156   */
157  public function onClickPasteStyles(array $build, string $builder_id): array {
158    $url = new Url(
159      'display_builder.api_paste_styles',
160      [
161        'display_builder_instance' => $builder_id,
162      ]
163    );
164
165    DisplayBuilderHtmx::request('post', $url, self::HTMX_REQUEST)->applyTo($build);
166
167    return $build;
168  }
169
170  /**
171   * Delete styles on click.
172   *
173   * The node ID isn't known until the contextual menu resolves what was
174   * right-clicked, so it's supplied at request time via 'hx-vals'
175   * (@see components/contextual_menu/contextual_menu.js) rather than baked into
176   * the URL here.
177   *
178   * @param array $build
179   *   The render array.
180   * @param string $builder_id
181   *   The instance entity ID.
182   *
183   * @return array
184   *   The render array.
185   */
186  public function onClickDeleteStyles(array $build, string $builder_id): array {
187    $url = new Url(
188      'display_builder.api_delete_styles',
189      [
190        'display_builder_instance' => $builder_id,
191      ]
192    );
193
194    DisplayBuilderHtmx::request('post', $url, self::HTMX_REQUEST)->applyTo($build);
195
196    return $build;
197  }
198
199  /**
200   * Drop a component_id, a block_id, or an node_id, to the root dropzone.
201   *
202   * @param array $build
203   *   The render array.
204   * @param string $builder_id
205   *   The instance entity ID.
206   * @param string $island_id
207   *   The island initiating the event.
208   *
209   * @return array
210   *   The render array.
211   */
212  public function onRootDrop(array $build, string $builder_id, string $island_id): array {
213    $url = new Url(
214      'display_builder.api_root_attach',
215      [
216        'display_builder_instance' => $builder_id,
217        'from' => $island_id,
218      ]
219    );
220
221    $htmx = DisplayBuilderHtmx::request('post', $url, 'dragend consume');
222    $htmx->on('dragend', \sprintf('Drupal.displayBuilder.handleSecondDrawer(%s, this, event, "dragend")', $builder_id));
223    $htmx->applyTo($build);
224
225    return $build;
226  }
227
228  /**
229   * Drop a component_id, a block_id, or an node_id, to a component slot.
230   *
231   * @param array $build
232   *   The render array.
233   * @param string $builder_id
234   *   The instance entity ID.
235   * @param string $island_id
236   *   The island initiating the event.
237   * @param string $node_id
238   *   The node id of the source.
239   * @param string $slot
240   *   The slot.
241   *
242   * @return array
243   *   The render array.
244   */
245  public function onSlotDrop(array $build, string $builder_id, string $island_id, string $node_id, string $slot): array {
246    $url = new Url(
247      'display_builder.api_slot_attach',
248      [
249        'display_builder_instance' => $builder_id,
250        'node_id' => $node_id,
251        'slot' => $slot,
252        'from' => $island_id,
253      ]
254    );
255
256    $htmx = DisplayBuilderHtmx::request('post', $url, 'dragend consume');
257    $htmx->on('dragend', \sprintf('Drupal.displayBuilder.handleSecondDrawer(%s, this, event, "dragend")', $builder_id));
258    $htmx->applyTo($build);
259
260    return $build;
261  }
262
263  /**
264   * When a component or block is clicked.
265   *
266   * @param array $build
267   *   The render array.
268   * @param string $builder_id
269   *   The instance entity ID.
270   * @param string $node_id
271   *   The node id of the source.
272   * @param string $title
273   *   The instance title.
274   * @param int $index
275   *   The instance index.
276   *
277   * @return array
278   *   The render array.
279   */
280  public function onInstanceClick(array $build, string $builder_id, string $node_id, string $title, int $index): array {
281    $url = new Url(
282      'display_builder.api_get',
283      [
284        'display_builder_instance' => $builder_id,
285        'node_id' => $node_id,
286      ]
287    );
288
289    $htmx = DisplayBuilderHtmx::request('get', $url, self::HTMX_REQUEST);
290    $htmx->vals(['node_id' => $node_id]);
291    $htmx->on('click', \sprintf('Drupal.displayBuilder.handleSecondDrawer(%s, this, event, "click")', $builder_id));
292    $htmx->applyTo($build);
293
294    $attributes = [
295      'tabindex' => '0',
296      'data-node-id' => $node_id,
297    ];
298
299    // If not set before we add information for contextual menu or drawer label.
300    if (!isset($build['#attributes']['data-node-title'])) {
301      // Only for icon case, remove suffix without loading label.
302      $attributes['data-node-title'] = \ucfirst(\trim(\str_replace(['renderable', '_'], ['', ' '], $title)));
303    }
304
305    if (!isset($build['#attributes']['data-slot-position'])) {
306      $attributes['data-slot-position'] = $index;
307    }
308
309    $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $attributes);
310
311    return $build;
312  }
313
314  /**
315   * When a value is changed in an instance island form.
316   *
317   * @param array $build
318   *   The render array.
319   * @param string $builder_id
320   *   The instance entity ID.
321   * @param string $island_id
322   *   The island initiating the event.
323   * @param string $node_id
324   *   The node id of the source.
325   *
326   * @return array
327   *   The render array.
328   */
329  public function onInstanceFormChange(array $build, string $builder_id, string $island_id, string $node_id): array {
330    if (!isset($build['source'])) {
331      return $build;
332    }
333
334    $url = new Url(
335      'display_builder.api_update',
336      [
337        'display_builder_instance' => $builder_id,
338        'node_id' => $node_id,
339        'from' => $island_id,
340      ]
341    );
342
343    $htmx = DisplayBuilderHtmx::request('put', $url, 'change consume');
344
345    $htmx->applyTo($build['source']);
346
347    return $build;
348  }
349
350  /**
351   * When the update button is clicked in an instance island form.
352   *
353   * @param array $build
354   *   The render array.
355   * @param string $builder_id
356   *   The instance entity ID.
357   * @param string $island_id
358   *   The island initiating the event.
359   * @param string $node_id
360   *   The node id of the source.
361   *
362   * @return array
363   *   The render array.
364   */
365  public function onInstanceUpdateButtonClick(array $build, string $builder_id, string $island_id, string $node_id): array {
366    if (!isset($build['update']) || !isset($build['source']) || !isset($build['source']['#id'])) {
367      return $build;
368    }
369    $url = new Url(
370      'display_builder.api_update',
371      [
372        'display_builder_instance' => $builder_id,
373        'node_id' => $node_id,
374        'from' => $island_id,
375      ]
376    );
377
378    $htmx = DisplayBuilderHtmx::request('put', $url, self::HTMX_REQUEST);
379    $htmx->include('#' . $build['source']['#id']);
380
381    $htmx->applyTo($build['update']);
382
383    return $build;
384  }
385
386  /**
387   * When a value is changed in a third party island.
388   *
389   * @param array $build
390   *   The render array.
391   * @param string $builder_id
392   *   The instance entity ID.
393   * @param string $node_id
394   *   The node id of the source.
395   * @param string $island_id
396   *   The island id.
397   *
398   * @return array
399   *   The render array.
400   */
401  public function onThirdPartyFormChange(array $build, string $builder_id, string $node_id, string $island_id): array {
402    $url = new Url(
403      'display_builder.api_third_party_settings_update',
404      [
405        'display_builder_instance' => $builder_id,
406        'node_id' => $node_id,
407        'island_id' => $island_id,
408      ]
409    );
410
411    DisplayBuilderHtmx::request('put', $url, 'change')->applyTo($build);
412
413    return $build;
414  }
415
416  /**
417   * When the undo button is clicked.
418   *
419   * @param array $build
420   *   The render array.
421   * @param string $builder_id
422   *   The instance entity ID.
423   *
424   * @return array
425   *   The render array.
426   */
427  public function onUndo(array $build, string $builder_id): array {
428    $url = new Url(
429      'display_builder.api_undo',
430      [
431        'display_builder_instance' => $builder_id,
432      ]
433    );
434
435    DisplayBuilderHtmx::request('post', $url, self::HTMX_REQUEST)->applyTo($build);
436
437    return $build;
438  }
439
440  /**
441   * When the undo button is clicked.
442   *
443   * @param array $build
444   *   The render array.
445   * @param string $builder_id
446   *   The instance entity ID.
447   *
448   * @return array
449   *   The render array.
450   */
451  public function onRedo(array $build, string $builder_id): array {
452    $url = new Url(
453      'display_builder.api_redo',
454      [
455        'display_builder_instance' => $builder_id,
456      ]
457    );
458
459    DisplayBuilderHtmx::request('post', $url, self::HTMX_REQUEST)->applyTo($build);
460
461    return $build;
462  }
463
464  /**
465   * When the restore button is clicked.
466   *
467   * @param array $build
468   *   The render array.
469   * @param string $builder_id
470   *   The instance entity ID.
471   *
472   * @return array
473   *   The render array.
474   */
475  public function onReset(array $build, string $builder_id): array {
476    $url = new Url(
477      'display_builder.api_restore',
478      [
479        'display_builder_instance' => $builder_id,
480      ]
481    );
482
483    DisplayBuilderHtmx::request('post', $url, self::HTMX_REQUEST)->applyTo($build);
484
485    return $build;
486  }
487
488  /**
489   * When the revert button is clicked.
490   *
491   * @param array $build
492   *   The render array.
493   * @param string $builder_id
494   *   The instance entity ID.
495   *
496   * @return array
497   *   The render array.
498   */
499  public function onRevert(array $build, string $builder_id): array {
500    $url = new Url(
501      'display_builder.api_revert',
502      [
503        'display_builder_instance' => $builder_id,
504      ]
505    );
506
507    DisplayBuilderHtmx::request('post', $url, self::HTMX_REQUEST)->applyTo($build);
508
509    return $build;
510  }
511
512  /**
513   * When the history clear button is clicked.
514   *
515   * @param array $build
516   *   The render array.
517   * @param string $builder_id
518   *   The instance entity ID.
519   *
520   * @return array
521   *   The render array.
522   */
523  public function onClear(array $build, string $builder_id): array {
524    $url = new Url(
525      'display_builder.api_clear',
526      [
527        'display_builder_instance' => $builder_id,
528      ]
529    );
530
531    DisplayBuilderHtmx::request('post', $url, self::HTMX_REQUEST)->applyTo($build);
532
533    return $build;
534  }
535
536  /**
537   * When the save button is clicked.
538   *
539   * @param array $build
540   *   The render array.
541   * @param string $builder_id
542   *   The instance entity ID.
543   *
544   * @return array
545   *   The render array.
546   */
547  public function onPublish(array $build, string $builder_id): array {
548    $url = new Url(
549      'display_builder.api_publish',
550      [
551        'display_builder_instance' => $builder_id,
552      ]
553    );
554
555    DisplayBuilderHtmx::request('post', $url, self::HTMX_REQUEST)->applyTo($build);
556
557    return $build;
558  }
559
560}