gridstack-8.x-2.5/src/Plugin/Layout/GridStackLayoutBase.php
src/Plugin/Layout/GridStackLayoutBase.php
<?php
namespace Drupal\gridstack\Plugin\Layout;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformStateInterface;
use Drupal\Core\Layout\LayoutDefault;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\gridstack\GridStackDefault;
use Drupal\gridstack\Entity\GridStack;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a GridStack base class for Layout plugins.
*/
abstract class GridStackLayoutBase extends LayoutDefault implements ContainerFactoryPluginInterface, PluginFormInterface {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The gridstack manager service.
*
* @var \Drupal\gridstack\GridStackManagerInterface
*/
protected $manager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = new static(
$configuration,
$plugin_id,
$plugin_definition
);
$instance->currentUser = $container->get('current_user');
$instance->manager = $container->get('gridstack.manager');
return $instance;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'regions' => [],
] + GridStackDefault::layoutSettings() + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
protected function attributeForm(array $settings = [], $context = 'settings') {
$elements['wrapper'] = [
'#type' => 'select',
'#options' => GridStackDefault::wrapperOptions(),
'#title' => $this->t('Wrapper'),
'#default_value' => isset($settings['wrapper']) ? trim($settings['wrapper']) : 'div',
];
$wrapper_classes = [];
$wrapper_class_value = isset($settings['wrapper_classes']) ? trim($settings['wrapper_classes']) : '';
if ($wrapper_class_value) {
$wrapper_classes = array_filter(array_map('trim', explode(" ", $wrapper_class_value)));
}
$elements['attributes'] = [
'#type' => 'textfield',
'#title' => $this->t('Attributes'),
'#description' => $this->t('Use comma: role|main,data-key|value'),
'#default_value' => isset($settings['attributes']) ? Xss::filter(trim($settings['attributes'])) : '',
];
$elements['wrapper_classes'] = [
'#type' => 'textfield',
'#title' => $this->t('Classes'),
'#description' => $this->t('Use space: bg-dark text-white'),
'#default_value' => $wrapper_class_value,
];
$elements['preset_classes'] = [];
if ($classes = GridStackLayoutTool::classes()) {
$desc = $context == 'settings' ? $this->t('Use the <b>Main container settings</b> here to affect all regions once if appropriate.') : $this->t('Use the <b>Main container settings</b> to affect all.');
$elements['preset_classes'] = [
'#type' => 'details',
'#open' => FALSE,
'#title' => $this->t('Preset classes'),
'#description' => $this->t('Merged with <b>Classes</b> above once saved.') . ' ' . $desc,
'#attributes' => ['class' => ['form-wrapper--preset-classes']],
];
$visibilities = ['hidden', 'visible', 'visibility'];
foreach ($classes as $key => $options) {
// Hiding the entire container is likely out of questions, skip.
if ($context == 'settings' && in_array($key, $visibilities)) {
continue;
}
$title = str_replace('_', ' ', $key);
$value = '';
// We don't store these, fecthing from wrapper_classes string instead.
if ($wrapper_classes) {
$values = array_values($options);
$values = array_combine($values, $values);
foreach ($wrapper_classes as $wrapper_class) {
if (isset($values[$wrapper_class])) {
$value = $values[$wrapper_class];
break;
}
}
}
$elements['preset_classes'][$key] = [
'#type' => 'radios',
'#title' => $this->t('@title', ['@title' => Unicode::ucfirst($title)]),
'#options' => ['' => $this->t('- None -')] + GridStackLayoutTool::options($options),
'#default_value' => $value,
];
}
}
return $elements;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
if ($settings = $form_state->getValue('settings')) {
foreach ($settings as $key => $value) {
$value = is_string($value) ? trim($value) : $value;
$this->configuration[$key] = is_array($value) ? array_filter($value) : $value;
}
}
unset($this->configuration['settings'], $this->configuration['preset_classes']);
$regions = [];
foreach ($form_state->getValue('regions') as $name => $region) {
foreach ($region as $key => $value) {
$value = is_string($value) ? trim($value) : $value;
$value = is_array($value) ? array_filter($value) : $value;
$regions[$name][$key] = $value;
if (empty($value) || $key == 'preset_classes') {
unset($regions[$name][$key]);
}
}
}
$this->configuration['regions'] = $regions;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::validateConfigurationForm($form, $form_state);
$gridnative = $form_state->getValue(['settings', 'gridnative']);
$form_state->setValue(['settings', 'gridnative'], (bool) $gridnative);
$preset_classes = $form_state->getValue(['settings', 'preset_classes']);
$wrapper_classes = $form_state->getValue(['settings', 'wrapper_classes']);
$merged = GridStackLayoutTool::mergedClasses($wrapper_classes, $preset_classes);
$form_state->setValue(['settings', 'wrapper_classes'], $merged);
foreach ($form_state->getValue('regions') as $name => $region) {
$wrapper_classes = isset($region['wrapper_classes']) ? $region['wrapper_classes'] : '';
$preset_classes = isset($region['preset_classes']) ? $region['preset_classes'] : [];
$merged = GridStackLayoutTool::mergedClasses($wrapper_classes, $preset_classes);
$form_state->setValue(['regions', $name, 'wrapper_classes'], $merged);
}
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
// This form may be loaded as a subform by Field Layout, Panels, etc.
// @see https://www.drupal.org/node/2536646
// @see https://www.drupal.org/node/2798261
// @see https://www.drupal.org/node/2774077
// @todo Remove when no more issues with it.
$form_state2 = $form_state instanceof SubformStateInterface ? $form_state->getCompleteFormState() : $form_state;
$form = parent::buildConfigurationForm($form, $form_state);
$access = $this->currentUser->hasPermission('administer gridstack');
$config = $this->getConfiguration();
$definition = $this->getPluginDefinition();
$regions = $definition->getRegions();
$name = $definition->get('optionset');
$optionset = GridStack::loadWithFallback($name);
$regions_all = $optionset->prepareRegions(FALSE);
$extras = $settings = [];
$framework = $optionset->getOption('use_framework');
/** @var \Drupal\field_ui\Form\EntityViewDisplayEditForm $entity_form */
$entity_form = $form_state2->getFormObject();
/* @var \Drupal\Core\Entity\Display\EntityDisplayInterface $display */
if (method_exists($entity_form, 'getEntity') && ($display = $entity_form->getEntity())) {
$extras = [
'bundle' => $display->getTargetBundle(),
'entity_type' => $display->getTargetEntityTypeId(),
'view_mode' => $display->getMode(),
];
}
if (isset($form['label'])) {
$name_nice = Unicode::ucfirst($name);
$form['label']['#attributes']['placeholder'] = $this->t('@name', ['@name' => $name_nice]);
$form['label']['#wrapper_attributes']['class'][] = 'is-gs-aside';
$form['label']['#description'] = $this->t('A region has direct contents. A container contains a single, or multiple regions.');
$default = isset($config['label']) ? $config['label'] : $name_nice;
$form['label']['#default_value'] = $form_state2->getValue('label', $default);
}
$form['regions'] = [
'#type' => 'container',
'#tree' => TRUE,
];
foreach ($regions_all as $region => $info) {
foreach (GridStackDefault::regionSettings() as $key => $value) {
$default = isset($config['regions'][$region][$key]) ? $config['regions'][$region][$key] : $value;
$default = $form_state2->getValue(['regions', $region, $key], $default);
$settings['regions'][$region][$key] = $default;
}
$prefix = !array_key_exists($region, $regions) ? 'Region container' : 'Region';
$form['regions'][$region] = [
'#type' => 'details',
'#title' => $this->t('@prefix: <em>@label</em>', ['@prefix' => $prefix, '@label' => $info['label']]),
'#open' => FALSE,
'#tree' => TRUE,
'#attributes' => ['data-gs-region' => $region],
];
$form['regions'][$region] += $this->attributeForm($settings['regions'][$region], 'regions');
}
foreach (GridStackDefault::layoutSettings() as $key => $value) {
$default = isset($config[$key]) ? $config[$key] : $value;
$settings[$key] = $form_state2->getValue(['settings', $key], $default);
}
$description = '';
if ($this->manager->getModuleHandler()->moduleExists('gridstack_ui') && $access) {
$description = $this->t('[<a href=":url" class="is-gs-edit-link">Edit @id</a>]', [
':url' => $optionset->toUrl('edit-form')->toString(),
'@id' => Xss::filter($optionset->label()),
]);
}
$form['settings'] = [
'#type' => 'details',
'#tree' => TRUE,
'#open' => TRUE,
'#weight' => 30,
'#title' => $this->t('Main container settings'),
'#description' => $description,
'#attributes' => ['class' => ['is-gs-main-settings']],
];
$form['settings']['skin'] = [
'#type' => 'select',
'#title' => $this->t('Skin'),
'#options' => $this->manager->skinManager()->getSkinOptions(),
'#empty_option' => $this->t('- None -'),
'#description' => $this->t('Leave empty to disable a skin.'),
'#default_value' => $settings['skin'],
];
$form['settings'] += $this->attributeForm($settings, 'settings');
$form['settings']['vm'] = [
'#type' => 'select',
'#title' => $this->t('Vertical margin'),
'#options' => GridStackDefault::breakpoints(),
'#empty_option' => $this->t('- None -'),
'#description' => $this->t('Useful to avoid overlapping regions due to backgrounds. Leave empty to disable.'),
'#default_value' => $settings['vm'],
'#access' => !empty($framework),
];
$form['settings']['gridnative'] = [
'#type' => 'checkbox',
'#title' => $this->t('Use native CSS Grid'),
'#description' => $this->t('<b>Experimental!</b> Check to replace any js-driven (gridstack, masonry, packery, isotope) layouts with native browser Grid layout. Check out <a href=":url">CSS Grid browser supports</a> relevant to your visitors. Uncheck if any issue.', [':url' => 'https://caniuse.com/#feat=css-grid']),
'#default_value' => $settings['gridnative'],
'#access' => empty($framework),
];
if ($uri = $optionset->getIconUri()) {
$image = [
'#theme' => 'image',
'#uri' => $uri,
'#alt' => $this->t('Thumbnail'),
];
$form['settings']['icon'] = [
'#theme' => 'container',
'#children' => $this->manager->getRenderer()->render($image),
'#attributes' => ['class' => ['form-wrapper--icon']],
'#weight' => 40,
];
}
$form['settings']['extras'] = [
'#type' => 'hidden',
'#value' => empty($extras) ? '' : Json::encode($extras),
];
$form['#attached']['library'][] = 'gridstack/admin_layout';
return $form;
}
}
