Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 162 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
ViewportSwitcher | |
0.00% |
0 / 157 |
|
0.00% |
0 / 9 |
756 | |
0.00% |
0 / 1 |
create | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
defaultConfiguration | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
buildConfigurationForm | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
2 | |||
configurationSummary | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
build | |
0.00% |
0 / 82 |
|
0.00% |
0 / 1 |
56 | |||
getDefinitions | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
getMaxWidthValueFromMediaQuery | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
getProvidersOptions | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
getProviders | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace Drupal\display_builder\Plugin\display_builder\Island; |
6 | |
7 | use Drupal\breakpoint\BreakpointManager; |
8 | use Drupal\Core\Extension\ModuleExtensionList; |
9 | use Drupal\Core\Extension\ThemeExtensionList; |
10 | use Drupal\Core\Form\FormStateInterface; |
11 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
12 | use Drupal\display_builder\Attribute\Island; |
13 | use Drupal\display_builder\InstanceInterface; |
14 | use Drupal\display_builder\IslandConfigurationFormInterface; |
15 | use Drupal\display_builder\IslandConfigurationFormTrait; |
16 | use Drupal\display_builder\IslandPluginBase; |
17 | use Drupal\display_builder\IslandType; |
18 | use Symfony\Component\DependencyInjection\ContainerInterface; |
19 | |
20 | /** |
21 | * Island plugin implementation. |
22 | */ |
23 | #[Island( |
24 | id: 'viewport', |
25 | label: new TranslatableMarkup('Viewport switcher'), |
26 | description: new TranslatableMarkup('Change main region width according to breakpoints.'), |
27 | type: IslandType::Button, |
28 | )] |
29 | class ViewportSwitcher extends IslandPluginBase implements IslandConfigurationFormInterface { |
30 | |
31 | use IslandConfigurationFormTrait; |
32 | |
33 | private const HIDE_PROVIDER = ['toolbar']; |
34 | |
35 | /** |
36 | * The module list extension service. |
37 | */ |
38 | protected ThemeExtensionList $themeList; |
39 | |
40 | /** |
41 | * The module list extension service. |
42 | */ |
43 | protected ModuleExtensionList $moduleList; |
44 | |
45 | /** |
46 | * The breakpoint manager. |
47 | */ |
48 | protected BreakpointManager $breakpointManager; |
49 | |
50 | /** |
51 | * {@inheritdoc} |
52 | */ |
53 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { |
54 | $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); |
55 | $instance->themeList = $container->get('extension.list.theme'); |
56 | $instance->moduleList = $container->get('extension.list.module'); |
57 | $instance->breakpointManager = $container->get('breakpoint.manager'); |
58 | |
59 | return $instance; |
60 | } |
61 | |
62 | /** |
63 | * {@inheritdoc} |
64 | */ |
65 | public function defaultConfiguration(): array { |
66 | return [ |
67 | 'exclude' => [], |
68 | 'format' => 'default', |
69 | ]; |
70 | } |
71 | |
72 | /** |
73 | * {@inheritdoc} |
74 | */ |
75 | public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { |
76 | $configuration = $this->getConfiguration(); |
77 | |
78 | $form['format'] = [ |
79 | '#type' => 'select', |
80 | '#title' => $this->t('Selector format'), |
81 | '#description' => $this->t('Choose the appearance of the selector. Normal select or a compact dropdown menu.'), |
82 | '#options' => [ |
83 | 'default' => $this->t('Default'), |
84 | 'compact' => $this->t('Compact'), |
85 | ], |
86 | '#default_value' => $configuration['format'], |
87 | ]; |
88 | |
89 | $form['exclude'] = [ |
90 | '#type' => 'checkboxes', |
91 | '#title' => $this->t('Exclude providers'), |
92 | '#options' => $this->getProvidersOptions($this->t('breakpoint'), $this->t('breakpoints')), |
93 | '#default_value' => $configuration['exclude'], |
94 | ]; |
95 | |
96 | return $form; |
97 | } |
98 | |
99 | /** |
100 | * {@inheritdoc} |
101 | */ |
102 | public function configurationSummary(): array { |
103 | $configuration = $this->getConfiguration(); |
104 | $summary = []; |
105 | |
106 | $exclude = \array_filter($configuration['exclude'] ?? []); |
107 | |
108 | $summary[] = $this->t('Excluded providers: @exclude', [ |
109 | '@exclude' => ($exclude = \array_filter($configuration['exclude'] ?? [])) ? \implode(', ', $exclude) : $this->t('None'), |
110 | ]); |
111 | |
112 | $summary[] = $this->t('Format: @format', [ |
113 | '@format' => $configuration['format'] ?? 'default', |
114 | ]); |
115 | |
116 | return $summary; |
117 | } |
118 | |
119 | /** |
120 | * {@inheritdoc} |
121 | */ |
122 | public function build(InstanceInterface $builder, array $data = [], array $options = []): array { |
123 | $configuration = $this->getConfiguration(); |
124 | $definitions = $this->getDefinitions(); |
125 | |
126 | $groups = []; |
127 | |
128 | foreach ($definitions as $definition) { |
129 | if (!isset($definition['group'])) { |
130 | continue; |
131 | } |
132 | $groups[$definition['group']] = $definition['group']; |
133 | } |
134 | |
135 | $options = $data = []; |
136 | $items = [ |
137 | [ |
138 | 'title' => $this->t('Fluid'), |
139 | 'class' => 'active', |
140 | ], |
141 | [ |
142 | 'divider' => TRUE, |
143 | ], |
144 | ]; |
145 | |
146 | foreach ($groups as $group_id => $label) { |
147 | $points = $this->breakpointManager->getBreakpointsByGroup($group_id); |
148 | |
149 | foreach ($points as $point_id => $point) { |
150 | $query = $point->getMediaQuery(); |
151 | $width = $this->getMaxWidthValueFromMediaQuery($query); |
152 | |
153 | if (!$width) { |
154 | continue; |
155 | } |
156 | $point_label = $point->getLabel(); |
157 | $options[$label][$point_id] = $point_label; |
158 | $items[] = [ |
159 | 'title' => $point_label, |
160 | 'value' => $point_id, |
161 | ]; |
162 | $data[$point_id] = $width; |
163 | } |
164 | } |
165 | |
166 | $select = [ |
167 | '#type' => 'component', |
168 | '#component' => 'display_builder:select', |
169 | '#attached' => [ |
170 | 'library' => ['display_builder/viewport_switcher'], |
171 | ], |
172 | '#props' => [ |
173 | 'options' => $options, |
174 | 'icon' => 'window', |
175 | 'empty_option' => $this->t('Fluid'), |
176 | ], |
177 | '#attributes' => [ |
178 | 'style' => 'display: inline-block;', |
179 | 'data-points' => \json_encode($data), |
180 | 'data-island-action' => 'viewport', |
181 | ], |
182 | ]; |
183 | |
184 | if ($configuration['format'] !== 'compact') { |
185 | return $select; |
186 | } |
187 | |
188 | unset($select['#attributes']['style']); |
189 | $select['#props']['icon'] = NULL; |
190 | |
191 | $button = $this->buildButton('', NULL, 'display', $this->t('Switch viewport of this display')); |
192 | $button['#attributes']['class'] = ['switch-viewport-btn']; |
193 | |
194 | return [ |
195 | '#type' => 'component', |
196 | '#component' => 'display_builder:dropdown', |
197 | '#slots' => [ |
198 | 'button' => $button, |
199 | 'content' => [ |
200 | '#type' => 'component', |
201 | '#component' => 'display_builder:menu', |
202 | '#props' => [ |
203 | 'items' => $items, |
204 | ], |
205 | '#attributes' => [ |
206 | 'class' => ['viewport-menu', 'db-background'], |
207 | 'data-points' => \json_encode($data), |
208 | ], |
209 | ], |
210 | ], |
211 | '#props' => [ |
212 | 'tooltip' => $this->t('Switch viewport'), |
213 | ], |
214 | '#attributes' => [ |
215 | 'class' => ['switch-viewport'], |
216 | 'data-island-action' => 'viewport', |
217 | ], |
218 | '#attached' => [ |
219 | 'library' => ['display_builder/viewport_switcher'], |
220 | ], |
221 | ]; |
222 | } |
223 | |
224 | /** |
225 | * Get the definitions list which is used by a few methods. |
226 | * |
227 | * @return array |
228 | * Breakpoints plugin definitions as associative arrays. |
229 | */ |
230 | public function getDefinitions(): array { |
231 | $definitions = $this->breakpointManager->getDefinitions(); |
232 | |
233 | foreach ($definitions as $definition_id => $definition) { |
234 | if (isset($definition['provider']) && \in_array($definition['provider'], self::HIDE_PROVIDER, TRUE)) { |
235 | unset($definitions[$definition_id]); |
236 | } |
237 | |
238 | // Exclude definitions with not supported media queries. |
239 | if (!$this->getMaxWidthValueFromMediaQuery($definition['mediaQuery'])) { |
240 | unset($definitions[$definition_id]); |
241 | } |
242 | } |
243 | |
244 | return $definitions; |
245 | } |
246 | |
247 | /** |
248 | * Get width and max width from media query. |
249 | * |
250 | * @param string $query |
251 | * The media query from the breakpoint definition. |
252 | * |
253 | * @return ?string |
254 | * The width with its unit (120px, 13em, 100vh...). Null is no width found. |
255 | */ |
256 | protected function getMaxWidthValueFromMediaQuery(string $query): ?string { |
257 | if (\str_contains($query, 'not ')) { |
258 | // Queries with negated expression(s) are not supported. |
259 | return NULL; |
260 | } |
261 | // Look for max-width: 1250px or max-width: 1250 px. |
262 | \preg_match('/max-width:\s*([0-9]+)\s*([A-Za-z]+)/', $query, $matches); |
263 | |
264 | if (\count($matches) > 2) { |
265 | return $matches[1] . $matches[2]; |
266 | } |
267 | // Looking for width <= 1250px or <= 1250 px. |
268 | \preg_match('/width\s*<=\s*([0-9]+)\s*([A-Za-z]+)/', $query, $matches); |
269 | |
270 | if (\count($matches) > 1) { |
271 | return $matches[1]; |
272 | } |
273 | |
274 | // @todo Currently only supports queries with max-width or width <= using |
275 | // px, em, vh units. |
276 | // Does not support min-width, percentage units, or complex/combined media |
277 | // queries. |
278 | return NULL; |
279 | } |
280 | |
281 | /** |
282 | * Get providers options for select input. |
283 | * |
284 | * @param string|TranslatableMarkup $singular |
285 | * Singular label of the plugins. |
286 | * @param string|TranslatableMarkup $plural |
287 | * Plural label of the plugins. |
288 | * |
289 | * @return array |
290 | * An associative array with extension ID as key and extension description |
291 | * as value. |
292 | */ |
293 | protected function getProvidersOptions(string|TranslatableMarkup $singular = 'definition', string|TranslatableMarkup $plural = 'definitions'): array { |
294 | $options = []; |
295 | |
296 | foreach ($this->getProviders($this->getDefinitions()) as $provider_id => $provider) { |
297 | $params = [ |
298 | '@name' => $provider['name'], |
299 | '@type' => $provider['type'], |
300 | '@count' => $provider['count'], |
301 | '@singular' => $singular, |
302 | '@plural' => $plural, |
303 | ]; |
304 | $options[$provider_id] = $this->formatPlural($provider['count'], '@name (@type, @count @singular)', '@name (@type, @count @plural)', $params); |
305 | } |
306 | |
307 | return $options; |
308 | } |
309 | |
310 | /** |
311 | * Get all providers. |
312 | * |
313 | * @param array $definitions |
314 | * Plugin definitions. |
315 | * |
316 | * @return array |
317 | * Drupal extension definitions, keyed by extension ID |
318 | */ |
319 | protected function getProviders(array $definitions): array { |
320 | $themes = $this->themeList->getAllInstalledInfo(); |
321 | $modules = $this->moduleList->getAllInstalledInfo(); |
322 | $providers = []; |
323 | |
324 | foreach ($definitions as $definition) { |
325 | $provider_id = $definition['provider']; |
326 | $provider = $themes[$provider_id] ?? $modules[$provider_id] ?? NULL; |
327 | |
328 | if (!$provider) { |
329 | continue; |
330 | } |
331 | $provider['count'] = isset($providers[$provider_id]) ? ($providers[$provider_id]['count']) + 1 : 1; |
332 | $providers[$provider_id] = $provider; |
333 | } |
334 | |
335 | return $providers; |
336 | } |
337 | |
338 | } |