toolshed-8.x-1.x-dev/src/Form/ThemeBreakpointSettingsForm.php
src/Form/ThemeBreakpointSettingsForm.php
<?php
namespace Drupal\toolshed\Form;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Utility\Html;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\breakpoint\BreakpointManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Create configuration form for Toolshed JS events.
*/
class ThemeBreakpointSettingsForm extends ConfigFormBase implements ContainerInjectionInterface {
/**
* Service to fetch and manage breakpoints for various themes.
*
* @var \Drupal\breakpoint\BreakpointManagerInterface&\Drupal\Component\Plugin\PluginManagerInterface
*/
protected BreakpointManagerInterface&PluginManagerInterface $bpsManager;
/**
* Service handler for managing and loading themes.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected ThemeHandlerInterface $themeHandler;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container): static {
$instance = parent::create($container)
->setThemeHandler($container->get('theme_handler'))
->setBreakpointManager($container->get('breakpoint.manager'));
return $instance;
}
/**
* Set the form theme handler service.
*
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme extension handler.
*
* @return self
* Returns self for method chaining.
*/
public function setThemeHandler(ThemeHandlerInterface $theme_handler): self {
$this->themeHandler = $theme_handler;
return $this;
}
/**
* Set the config form breakpoint manager service.
*
* @param \Drupal\breakpoint\BreakpointManagerInterface&\Drupal\Component\Plugin\PluginManagerInterface $breakpoint_manager
* The breakpoint manager.
*
* @return self
* Returns self for method chaining.
*/
public function setBreakpointManager(BreakpointManagerInterface&PluginManagerInterface $breakpoint_manager): self {
$this->bpsManager = $breakpoint_manager;
return $this;
}
/**
* Get the title to use as the form or page title.
*
* @param string $theme
* Name of the theme to use the title of.
*
* @return \Stringable|string
* Translatable markup of the form title.
*/
public static function getFormTitle($theme = NULL): \Stringable|string {
$themeHandler = \Drupal::service('theme_handler');
$theme = $theme ?: $themeHandler->getDefault();
return t('@theme_name Breakpoints', [
'@theme_name' => $themeHandler->getName($theme),
]);
}
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'toolshed_theme_breakpoint_settings_form';
}
/**
* {@inheritdoc}
*/
public function getEditableConfigNames(): array {
$configNames = [];
foreach ($this->themeHandler->listInfo() as $info) {
$configNames[] = 'toolshed.breakpoints.' . $info->getName();
}
return $configNames;
}
/**
* Get the available JS Events which are triggered for this media query.
*
* The available set of events is limited so that JS event handlers can have
* a consistent set of event names to watch for. These event names should
* be reviewed to ensure that they make sense to other developers.
*
* @return array
* An array that can be used as the select form elements '#options' value.
*/
public function getAvailableEvents(): array {
return [
'' => $this->t('- No alias -'),
'screenSm' => $this->t('onScreenSm'),
'screenMd' => $this->t('onScreenMd'),
'screenLg' => $this->t('onScreenLg'),
];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $theme = NULL): array {
if (!isset($theme) || !is_string($theme)) {
$theme = $this->themeHandler->getDefault();
}
$form_state->set('theme', $theme);
// List the breakpoints settings for the themes.
$form['breakpoints'] = [
'#type' => 'table',
'#empty' => $this->t('There are currently no defined breakpoints for this theme. Define them in the *.breakpoints.yml file for this theme.'),
'#header' => [
$this->t('Breakpoint'),
$this->t('Media query'),
$this->t('Inverted'),
$this->t('Alias'),
$this->t('Sort order'),
$this->t('Actions'),
],
'#attributes' => ['id' => 'breakpoints-manage-table'],
'#tabledrag' => [
'options' => [
'action' => 'order',
'group' => 'bp-sort-weight',
'relationship' => 'sibling',
],
],
];
// Get existing configurations if they exist.
$configs = $this->config("toolshed.breakpoints.{$theme}")->get('settings') ?: [];
// Fetch the correct breakpoints for the current theme.
$bps = array_filter($this->bpsManager->getDefinitions(), function ($bp) use ($theme) {
return isset($bp['provider']) && ($bp['provider'] === $theme);
});
// Iterate through the currently configured items.
foreach ($configs as $bpConfig) {
if (!isset($bps[$bpConfig['name']])) {
continue;
}
$bpId = $bpConfig['name'];
$form['breakpoints'][$bpId] = [
'#attributes' => [
'id' => Html::cleanCssIdentifier("bp-{$bpId}"),
'class' => ['draggable'],
],
'breakpoint' => ['#plain_text' => $bps[$bpId]['label']],
'mediaQuery' => ['#plain_text' => $bps[$bpId]['mediaQuery']],
'inverted' => [
'#type' => 'checkbox',
'#default_value' => $bpConfig['inverted'],
],
'event' => [
'#type' => 'select',
'#options' => $this->getAvailableEvents(),
'#default_value' => $bpConfig['event'],
],
'weight' => [
'#type' => 'number',
'#value' => $bpConfig['weight'],
'#attributes' => ['class' => ['bp-sort-weight']],
],
'actions' => [
'#type' => 'submit',
'#bp_key' => $bpId,
'#name' => 'delete_' . Html::cleanCssIdentifier($bpId),
'#value' => $this->t('Remove'),
'#attributes' => [
'class' => ['button', 'button--cancel'],
],
'#submit' => [
[$this, 'submitRemoveBreakpoint'],
[$this, 'submitForm'],
],
],
];
}
$bpDiff = array_diff_key($bps, $form['breakpoints']);
if (!empty($bpDiff)) {
foreach ($bpDiff as $bp => $bpInfo) {
$bpOpts[$bp] = $bpInfo['label'] . ' -- ' . $bpInfo['mediaQuery'];
}
$form['breakpoints']['__add_breakpoint'] = [
'#attributes' => [
'id' => "add-new-breapoint",
'class' => ['draggable'],
],
'breakpoint' => [
'#type' => 'select',
'#field_prefix' => $this->t('<strong>Add:</strong>'),
'#options' => $bpOpts,
'#wrapper_attributes' => ['colspan' => 2],
],
'inverted' => [
'#type' => 'checkbox',
'#default_value' => FALSE,
],
'event' => [
'#type' => 'select',
'#options' => $this->getAvailableEvents(),
],
'weight' => [
'#type' => 'number',
'#value' => 99,
'#attributes' => ['class' => ['bp-sort-weight']],
],
'actions' => [
'#type' => 'submit',
'#value' => $this->t('Add'),
'#submit' => [
[$this, 'submitAddBreakpoint'],
[$this, 'submitForm'],
],
],
];
}
$form['actions'] = [
'#type' => 'actions',
'submit' => [
'#type' => 'submit',
'#value' => $this->t('Save'),
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
$theme = $form_state->get('theme');
$values = $form_state->getValue('breakpoints');
// This table row is only for adding new breakpoints, and shouldn't be
// removed before cleaning the breakpoints for this theme configuration.
unset($values['__add_breakpoint']);
$breakpoints = [];
foreach ($values as $bp => $bpVal) {
$breakpoints[] = [
'name' => $bp,
'event' => $bpVal['event'],
'inverted' => $bpVal['inverted'],
'weight' => $bpVal['weight'],
];
}
// Save the changes to the theme setup.
$this->config("toolshed.breakpoints.{$theme}")
->set('settings', $breakpoints)
->save();
}
/**
* Form submit callback to add a breakpoint into the settings.
*
* @param array $form
* Reference to the complete form structure.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state and submission values for the form.
*/
public function submitAddBreakpoint(array &$form, FormStateInterface $form_state): void {
$values = $form_state->getValue('breakpoints');
$addValues = $values['__add_breakpoint'];
$values[$addValues['breakpoint']] = [
'name' => $addValues['breakpoint'],
'event' => $addValues['event'],
'inverted' => $addValues['inverted'],
'weight' => $addValues['weight'],
];
$form_state->setValue('breakpoints', $values);
}
/**
* Form submit callback to remove a breakpoint from the settings.
*
* @param array $form
* Reference to the complete form structure.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state and submission values for the form.
*/
public function submitRemoveBreakpoint(array &$form, FormStateInterface $form_state): void {
$element = $form_state->getTriggeringElement();
$values = $form_state->getValue('breakpoints');
if (!empty($element['#bp_key']) && isset($values[$element['#bp_key']])) {
unset($values[$element['#bp_key']]);
$form_state->setValue('breakpoints', $values);
}
}
}
