Skip to content
Snippets Groups Projects
Commit 679242a8 authored by Mikael Meulle's avatar Mikael Meulle Committed by christian.wiedemann
Browse files

Issue #3477625 by christian.wiedemann, just_like_good_vibes: UI Patterns UI

parent 2ac2823c
No related branches found
No related tags found
1 merge request!359Issue #3477625 by christian.wiedemann, just_like_good_vibes: UI Patterns UI
Pipeline #460232 passed
Showing
with 2127 additions and 0 deletions
ui_patterns_ui.component_display.*.*.*:
label: Component display
type: config_entity
mapping:
id:
type: string
label: ID
label:
type: label
label: Label
status:
type: integer
label: Status
uuid:
type: string
component_id:
type: string
label: Component Id
form_mode_name:
type: string
label: Form Mode Name
content:
type: sequence
label: 'Slot or prop Configuration'
sequence:
type: ui_patterns_ui.component_display
hidden:
type: sequence
label: 'Component display setting'
sequence:
type: boolean
label: 'Value'
ui_patterns_ui.component_display:
type: ui_patterns_widget_settings
mapping:
source_id:
type: string
label: 'source'
source:
label: 'source'
type: ui_patterns_source.[%parent.source_id]
parent:
type: string
label: 'Parent'
widget_settings:
type: ui_patterns_ui.widget_settings.[%parent.type]
label: 'Component Display'
weight:
type: string
label: 'Weight'
region:
type: string
label: 'Region'
third_party_settings:
type: sequence
label: 'List of third party settings'
sequence:
type: mixed
ui_patterns_widget_settings:
type: mapping
label: 'Widget settings'
mapping:
type:
type: string
label: 'Format type machine name'
label:
type: string
label: 'Label setting machine name'
widget_settings:
type: ui_patterns_ui.widget_settings.[%parent.type]
label: 'Widget Settings'
# Default schema for entity display field with undefined type.
ui_patterns_ui.widget_settings.*:
type: mapping
mapping:
title:
type: string
label: 'Title'
required:
type: integer
label: 'Widget is required'
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_ui;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Provides an interface defining a component display entity type.
*/
interface ComponentFormDisplayInterface extends ConfigEntityInterface {
/**
* Gets the highest weight of the display options in the display.
*
* @return int|null
* The highest weight of the display options in the display, or NULL if the
* display is empty.
*/
public function getHighestWeight(): int|null;
/**
* Gets the display options for all components.
*
* @return array
* The array of display options, keyed by component name.
*/
public function getPropSlotOptions(): array;
/**
* Gets the display options set for a component.
*
* @param string $name
* The name of the component.
*
* @return array|null
* The display options for the component, or NULL if the component is not
* displayed.
*/
public function getPropSlotOption(string $name);
/**
* Sets the display options for a prop or slot.
*
* @param string $name
* The name of the prop or slot.
* @param array $options
* The display options.
*
* @return $this
*/
public function setPropSlotOptions(string $name, array $options = []);
/**
* Sets a prop or slot to be hidden.
*
* @param string $name
* The name of the prop or slot.
*
* @return $this
*/
public function removePropSlotOption(string $name);
/**
* Returns the form mode name.
*
* @return ?string
* The form mode name.
*/
public function getFormModeName(): ?string;
/**
* Returns the sdc component id.
*
* @return string
* The sdc component id.
*/
public function getComponentId():string;
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_ui\Controller;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Plugin\Component;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Theme\ComponentPluginManager;
use Drupal\Core\Url;
use Drupal\ui_patterns_ui\Entity\ComponentFormDisplay;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Provides a listing of component displays.
*/
final class ComponentController extends ControllerBase {
/**
* Redirects to the default route of a specified component.
*/
public function forward(string $component_id):RedirectResponse {
$component_plugin_manager = self::getComponentPluginManager();
$component = $component_plugin_manager->find($component_id);
return (new TrustedRedirectResponse($this->getDefaultRoute($component)
->toString()))
->addCacheableDependency((new CacheableMetadata())->setCacheMaxAge(0));
}
/**
* Renders a list of components in a table format.
*/
public function render(): array {
$build['table'] = [
'#type' => 'table',
'#header' => $this->buildHeader(),
'#title' => $this->t('Component List'),
'#rows' => [],
'#empty' => $this->t('There are no @label yet.', ['@label' => '']),
];
foreach ($this->load() as $component) {
if ($row = $this->buildComponentRow($component)) {
$build['table']['#rows'][$component->getPluginId()] = $row;
}
}
$build['pager'] = [
'#type' => 'pager',
];
return $build;
}
/**
* The component plugin manager.
*/
public static function getComponentPluginManager(): ComponentPluginManager {
return \Drupal::service('plugin.manager.sdc');
}
/**
* Load all components, sorted by provider and label.
*/
public function load(): array {
$component_plugin_manager = self::getComponentPluginManager();
$definitions = $component_plugin_manager->getDefinitions();
uasort($definitions, function ($a, $b) {
$comp = strnatcasecmp((string) ($a['provider'] ?? ''), (string) ($b['provider'] ?? ''));
return ($comp === 0) ? strnatcasecmp((string) ($a['label'] ?? ''), (string) ($b['label'] ?? '')) : $comp;
});
$plugin_ids = array_keys($definitions);
// @phpstan-ignore-next-line
return array_values(array_filter(array_map(
[$component_plugin_manager, 'createInstance'],
$plugin_ids
)));
}
/**
* Builds the header row for the component listing.
*
* @return array
* A render array structure of header strings.
*/
public function buildHeader(): array {
$header['label'] = $this->t('Label');
$header['provider'] = $this->t('Provider');
$header['operations'] = $this->t('Operations');
return $header;
}
/**
* Build one component row.
*/
public function buildComponentRow(Component $component): array {
$definition = $component->getPluginDefinition();
$row['label'] = $component->metadata->name;
$row['provider'] = is_array($definition) ? $definition['provider'] : '';
$row['operations']['data'] = [
'#type' => 'operations',
'#links' => $this->getComponentOperations($component),
];
return $row;
}
/**
* Retrieves the default route for a given component.
*
* This function determines the appropriate URL for the default form display
* of a component. If a default display exists, it returns its URL; otherwise,
* it constructs a route URL for adding a new form display.
*
* @param \Drupal\Core\Plugin\Component $component
* The component for which the default route is being retrieved.
*
* @return \Drupal\Core\Url
* The URL object representing the default route for the component.
*/
private function getDefaultRoute(Component $component) {
$default_display = ComponentFormDisplay::loadDefault($component->getPluginId());
if ($default_display !== NULL) {
return $default_display->toUrl();
}
else {
$route = 'entity.component_form_display.' . $component->getPluginId() . '.add_form';
return Url::fromRoute($route, [
'component_id' => $component->getPluginId(),
]);
}
}
/**
* Returns component operations.
*/
protected function getComponentOperations(Component $component): array {
$operations['configure'] = [
'title' => $this->t('Manage form display'),
'url' => $this->getDefaultRoute($component),
'attributes' => [],
'weight' => 50,
];
return $operations;
}
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_ui\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\FormElementBase;
use Drupal\ui_patterns\SourcePluginBase;
use Drupal\ui_patterns\SourcePluginManager;
use Drupal\ui_patterns_ui\Entity\ComponentFormDisplay;
/**
* Component display form element.
*
* @FormElement("uip_display_form")
*/
class UiPComponentFormDisplayForm extends FormElementBase {
/**
* Returns the source manager.
*/
public static function getSourceManager(): SourcePluginManager {
return \Drupal::service('plugin.manager.ui_patterns_source');
}
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#input' => TRUE,
'#multiple' => FALSE,
'#default_value' => NULL,
'#source_contexts' => [],
'#tag_filter' => [],
'#process' => [
[$class, 'buildForm'],
],
'#after_build' => [
[$class, 'afterBuild'],
],
'#theme_wrappers' => ['form_element'],
];
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
if (is_array($input)) {
$display_id = $element['#display_id'];
$display = ComponentFormDisplay::load($display_id);
$props = [];
$slots = [];
foreach ($input as $key => $source) {
if ($display->isSlot($key)) {
$slots[$key]['sources'][] = $source;
}
else {
$props[$key] = $source;
}
}
$output = [
'component_id' => NULL,
'display_id' => $element['#display_id'],
'variant_id' => NULL,
'props' => $props,
'slots' => $slots,
];
$element['#default_value'] = $output;
return $output;
}
else {
return [
'component_id' => NULL,
'variant_id' => NULL,
'props' => [],
'slots' => [],
];
}
}
/**
* {@inheritdoc}
*/
public static function afterBuild(array $element, FormStateInterface $form_state) : array {
if ($form_state->isProcessingInput()) {
if (isset($element['#value'])) {
$form_state->setValueForElement($element, $element['#value']);
}
}
return $element;
}
/**
* {@inheritdoc}
*/
public static function buildForm(array $element, FormStateInterface $form_state): array {
$display_id = $element['#display_id'] ?? '';
$display = $display_id ? ComponentFormDisplay::load($display_id) : NULL;
if (!$display) {
return $element;
}
$element['#display'] = $display;
$slot_prop_options = $display->getPropSlotOptions();
foreach ($slot_prop_options as $prop_slot_id => $slot_prop_option) {
if ($slot_prop_option['region'] !== 'content') {
continue;
}
$source_id = $slot_prop_option['source_id'] ?? NULL;
if ($source_id === NULL) {
continue;
}
$source_plugin_manager = self::getSourceManager();
$prop_definition = $display->getPropDefinition($prop_slot_id);
$configuration = $element['#default_value']['props'][$prop_slot_id] ?? $element['#default_value']['slots'][$prop_slot_id]['sources'][0] ?? [];
$source_contexts = [];
$form_array_parents = $element['#array_parents'];
$configuration['widget_settings'] = $slot_prop_option['widget_settings'] ?? [];
$configuration['widget_settings']['title_display'] = 'before';
$source_configuration = SourcePluginBase::buildConfiguration($prop_slot_id, $prop_definition, $configuration, $source_contexts, $form_array_parents);
/** @var \Drupal\ui_patterns\SourcePluginBase $source */
$source = $source_plugin_manager->createInstance($source_id, $source_configuration);
$form = $source->settingsForm([], $form_state);
$element[$prop_slot_id]['#type'] = 'container';
$element[$prop_slot_id]['source'] = $form;
$element[$prop_slot_id]['source_id'] = ['#type' => 'hidden', '#default_value' => $source_id];
}
return $element;
}
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_ui\Element;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\ui_patterns\Element\ComponentForm;
/**
* Provides a Component form builder element.
*
* @FormElement("uip_displays_form")
*/
class UiPComponentFormDisplaysForm extends ComponentForm {
/**
* Returns the entity type manager.
*/
public static function getEntityTypeManager(): EntityTypeManagerInterface {
return \Drupal::entityTypeManager();
}
/**
* Get form displays displays for a component.
*
* @param string $component_id
* The component id.
*
* @return \Drupal\ui_patterns_ui\Entity\ComponentFormDisplay[]
* The component form displays.
*/
protected static function getComponentDisplays(string $component_id) : array {
$storage = self::getEntityTypeManager()->getStorage('component_form_display');
/** @var \Drupal\ui_patterns_ui\Entity\ComponentFormDisplay[] $matched_displays */
$matched_displays = $storage->loadByProperties([
'component_id' => $component_id,
'status' => TRUE,
]);
return $matched_displays;
}
/**
* Build the display selector.
*/
protected static function buildSelectComponentDisplays(array &$element, array $matched_displays, string $wrapper_id, ?string $selected_display_id = NULL) : void {
$options = [];
foreach ($matched_displays as $matched_display) {
$options[$matched_display->id()] = $matched_display->label();
}
if (\Drupal::currentUser()->hasPermission('access ui patterns component form')) {
$options['_component_form'] = t('Component form');
}
$element['display_id'] = [
'#type' => 'select',
'#options' => $options,
'#attributes' => ['class' => ['uip-display-select']],
'#default_value' => $selected_display_id ?? array_keys($options)[0],
'#ajax' => [
'callback' => [static::class, 'changeSelectorFormChangeAjax'],
'wrapper' => $wrapper_id,
'effect' => 'fade',
],
'#executes_submit_callback' => FALSE,
'#access' => count($options) > 1,
];
}
/**
* Build the display form.
*/
protected static function buildComponentFormDisplay(array &$element, FormStateInterface $form_state, string $component_id, array $matched_displays) : void {
$all_form_state_values = $form_state->getValues();
$form_state_values = &NestedArray::getValue($all_form_state_values, $element['#parents'] ?? []);
$all_input_values = $form_state->getUserInput();
$input_values = &NestedArray::getValue($all_input_values, $element['#parents'] ?? []);
$display_id = $element['#default_value']['display_id'] ?? NULL;
// If there is already an existing config use _component_form
// to display this config.
if ($display_id === NULL && (
count($element['#default_value']['props']) !== 0 ||
count($element['#default_value']['slots']) !== 0)) {
$display_id = '_component_form';
}
if (isset($form_state_values['display_id'])) {
$display_id = $form_state_values['display_id'];
}
elseif (isset($input_values['display_id'])) {
$display_id = $input_values['display_id'];
}
$wrapper_id = static::getElementId($element, 'display');
static::buildSelectComponentDisplays($element, $matched_displays, $wrapper_id, $display_id);
$display_id = $element['display_id']['#default_value'];
$element["display"] = [
'#type' => 'container',
'#attributes' => ['id' => $wrapper_id],
'component_id' => [
'#type' => 'hidden',
'#value' => $component_id,
],
'display_id' => [
'#type' => 'hidden',
'#value' => $display_id,
],
];
if ($display_id === '_component_form') {
$element["display"]['value'] = [
'#type' => 'component_form',
'#allow_override' => TRUE,
'#component_id' => $component_id,
'#component_required' => $element['#component_required'] ?? FALSE,
'#tag_filter' => $element['#tag_filter'] ?? [],
'#ajax_url' => $element['#ajax_url'] ?? NULL,
'#source_contexts' => $element['#source_contexts'] ?? [],
'#render_slots' => $element['#render_slots'] ?? TRUE,
'#render_props' => $element['#render_props'] ?? TRUE,
'#default_value' => $element['#default_value'],
];
}
elseif ($display_id) {
$element["display"]['value'] = [
'#type' => 'uip_display_form',
'#display_id' => $display_id,
'#component_id' => $component_id,
'#default_value' => $element['#default_value'],
];
}
}
/**
* {@inheritdoc}
*/
public static function buildForm(array &$element, FormStateInterface $form_state) : array {
$element = ComponentForm::buildForm($element, $form_state);
// Retrieve component id.
$component_id = $element['#component_id'] ?? $element['#default_value']['component_id'] ?? NULL;
$matched_displays = ($component_id !== NULL) ? static::getComponentDisplays($component_id) : [];
if (count($matched_displays) === 0) {
return $element;
}
// Hide elements from component form.
$element["variant_id"]['#access'] = FALSE;
$element["props"]['#access'] = FALSE;
$element["slots"]['#access'] = FALSE;
$element['#component_validation'] = FALSE;
$element["#after_build"] = [
[static::class, 'afterBuild'],
];
// Display ID.
static::buildComponentFormDisplay($element, $form_state, $component_id, $matched_displays);
return $element;
}
/**
* Ajax callback for display selector change.
*/
public static function changeSelectorFormChangeAjax(
array $form,
FormStateInterface $form_state,
) : array {
$parents = $form_state->getTriggeringElement()['#array_parents'];
$parents = array_merge(array_slice($parents, 0, -1), ['display']);
$sub_form = &NestedArray::getValue($form, $parents);
return $sub_form ?? [];
}
/**
* {@inheritdoc}
*/
public static function afterBuild(array $element, FormStateInterface $form_state) : array {
if ($form_state->isProcessingInput()) {
if (isset($element['display']['value']['#value'])) {
$element['#value'] = $element['display']['value']['#value'];
if (isset($element['display']['display_id'])
&& $element['display']['display_id']['#value'] !== '_component_form') {
$element['#value']['display_id'] = $element['display']['display_id']['#value'];
}
$element['#value']['component_id'] = $element['display']['component_id']['#value'];
$form_state->setValueForElement($element, $element['#value']);
}
}
return $element;
}
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_ui\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Theme\ComponentPluginManager;
use Drupal\Core\Url;
use Drupal\ui_patterns\Plugin\UiPatterns\PropType\SlotPropType;
use Drupal\ui_patterns\PropTypePluginBase;
use Drupal\ui_patterns\SourcePluginBase;
use Drupal\ui_patterns\SourcePluginManager;
use Drupal\ui_patterns_ui\ComponentFormDisplayInterface;
/**
* Defines the component display entity type.
*
* @ConfigEntityType(
* id = "component_form_display",
* label = @Translation("Component form display"),
* label_collection = @Translation("Component form displays"),
* label_singular = @Translation("Component form display"),
* label_plural = @Translation("Component form displays"),
* label_count = @PluralTranslation(
* singular = "@count component display",
* plural = "@count component displays",
* ),
* config_prefix = "component_display",
* admin_permission = "administer component_display",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid",
* },
* handlers = {
* "form" = {
* "add" = "Drupal\ui_patterns_ui\Form\ComponentFormDisplayForm",
* "edit" = "Drupal\ui_patterns_ui\Form\ComponentFormDisplayForm",
* "delete" = "Drupal\ui_patterns_ui\Form\ComponentFormDisplayDeleteForm",
* },
* },
* links = {
* "collection" = "/admin/structure/component/{component_type}",
* "delete-form" = "/admin/structure/component/{component_type}/form-display/{form_mode_name}/delete",
* },
*
* config_export = {
* "id",
* "label",
* "component_id",
* "form_mode_name",
* "content",
* "hidden",
* },
* )
*/
final class ComponentFormDisplay extends ConfigEntityBase implements ComponentFormDisplayInterface {
/**
* The display ID.
*/
protected string $id;
/**
* The display label.
*/
protected string $label;
/**
* The sdc component id.
*/
protected string $component_id;
/**
* The form mode name.
*/
protected ?string $form_mode_name = NULL;
/**
* The plugin objects used for this display, keyed by prop name.
*
* @var array
*/
protected $plugins = [];
/**
* List of slot or props display options, keyed by name.
*
* @var array
*/
protected $content = [];
/**
* List of slots or props that are set to be hidden.
*
* @var array
*/
protected $hidden = [];
/**
* {@inheritdoc}
*/
public function id() {
return str_replace(':', '.', $this->getComponentId()) . '.' . $this->getFormModeName();
}
/**
* Returns the component plugin manager.
*/
public static function getComponentPluginManager(): ComponentPluginManager {
return \Drupal::service('plugin.manager.sdc');
}
/**
* Returns the source plugin manager.
*/
public static function getSourcePluginManager(): SourcePluginManager {
return \Drupal::service('plugin.manager.ui_patterns_source');
}
/**
* {@inheritdoc}
*/
public function getComponentId():string {
return $this->component_id;
}
/**
* Checks if the prop_id is a slot or prop.
*/
public function isSlot(string $prop_id): bool {
return $this->getPropType($prop_id) instanceof SlotPropType;
}
/**
* Returns the prop definition.
*/
public function getPropDefinition(string $prop_id): array | NULL {
$component_definition = self::getComponentPluginManager()->getDefinition($this->component_id);
if (isset($component_definition['props']['properties'][$prop_id])) {
return $component_definition['props']['properties'][$prop_id];
}
if (isset($component_definition['slots'][$prop_id])) {
return $component_definition['slots'][$prop_id];
}
return NULL;
}
/**
* Returns the prop type.
*/
protected function getPropType(string $prop_id): ?PropTypePluginBase {
$prop_definition = $this->getPropDefinition($prop_id);
if (isset($prop_definition['ui_patterns']['type_definition'])) {
return $prop_definition['ui_patterns']['type_definition'];
}
return NULL;
}
/**
* Returns the source configuration.
*/
protected function getSourceConfiguration(string $prop_id): array {
$prop_definition = $this->getPropDefinition($prop_id);
$display_options = $this->getPropSlotOption($prop_id);
$settings = [];
$settings['widget_settings'] = $display_options['widget_settings'] ?? [];
$settings['widget_settings']['title_display'] = 'before';
$configuration = SourcePluginBase::buildConfiguration($prop_id, $prop_definition, $settings, [], []);
$configuration['settings'] = $display_options['source'] ?? [];
return $configuration;
}
/**
* Sets the form mode name.
*/
public function setFormModeName(string $form_mode_name):void {
$this->form_mode_name = $form_mode_name;
}
/**
* {@inheritdoc}
*/
public function getFormModeName(): ?string {
return $this->form_mode_name;
}
/**
* {@inheritdoc}
*/
protected function urlRouteParameters($rel) {
$params = parent::urlRouteParameters($rel);
$params['component_id'] = $this->getComponentId();
$params['form_mode_name'] = $this->getFormModeName();
return $params;
}
/**
* Returns source plugins.
*/
public function getSourcePlugins(string $prop_id): array {
$component = $this->getPropSlotOption($prop_id);
$sources = self::getSourcePluginManager()->getDefinitionsForPropType($this->getPropType($prop_id)->getPluginId());
return self::getSourcePluginManager()->createInstances(array_keys($sources), ['widget_settings' => $component['widget_settings'] ?? []]);
}
/**
* Returns the default source plugin.
*/
public function getDefaultSourcePlugin(string $prop_id): ?SourcePluginBase {
$source_id = self::getSourcePluginManager()->getPropTypeDefault($this->getPropType($prop_id)->getPluginId());
return $source_id ? $this->getSourcePlugin($prop_id, $source_id) : NULL;
}
/**
* Returns a source plugin for prop and source id.
*/
public function getSourcePlugin(string $prop_id, string $source_id): ?SourcePluginBase {
/** @var \Drupal\ui_patterns\SourcePluginBase $source */
$source = self::getSourcePluginManager()->createInstance($source_id, $this->getSourceConfiguration($prop_id));
return $source;
}
/**
* Returns the selected source plugin for prop id.
*/
public function getSelectedSourcePlugin(string $prop_id): ?SourcePluginBase {
$display_options = $this->getPropSlotOption($prop_id);
$selected_source_id = !empty($display_options['source_id']) ? $display_options['source_id'] : NULL;
$plugin = NULL;
if ($selected_source_id === NULL) {
$plugin = $this->getDefaultSourcePlugin($prop_id);
}
elseif ($selected_source_id !== NULL) {
$plugin = $this->getSourcePlugin($prop_id, $selected_source_id);
}
return $plugin;
}
/**
* {@inheritdoc}
*/
public function getPropSlotOptions(): array {
$content = $this->content;
uasort($content, function ($a, $b) {
return $a['weight'] <=> $b['weight'];
});
return $content;
}
/**
* {@inheritdoc}
*/
public function getPropSlotOption($name) {
return $this->content[$name] ?? NULL;
}
/**
* {@inheritdoc}
*/
public function getHighestWeight(): ?int {
$weights = [];
// Collect weights for the components in the display.
foreach ($this->content as $options) {
if (isset($options['weight'])) {
$weights[] = $options['weight'];
}
}
return $weights ? max($weights) : NULL;
}
/**
* {@inheritdoc}
*/
public function setPropSlotOptions($name, array $options = []) {
// If no weight specified, make sure the field sinks at the bottom.
if (!isset($options['weight'])) {
$max = $this->getHighestWeight();
$options['weight'] = isset($max) ? $max + 1 : 0;
}
// Ensure we always have an empty settings and array.
$options += ['widget_settings' => [], 'third_party_settings' => []];
$this->content[$name] = $options;
unset($this->hidden[$name]);
unset($this->plugins[$name]);
return $this;
}
/**
* {@inheritdoc}
*/
public function removePropSlotOption($name) {
$this->hidden[$name] = TRUE;
unset($this->content[$name]);
unset($this->plugins[$name]);
return $this;
}
/**
* {@inheritdoc}
*/
public function toUrl($rel = NULL, array $options = []) {
// Unless language was already provided, avoid setting an explicit language.
$options += ['language' => NULL];
if ($rel === 'edit-form' || $rel === NULL) {
return Url::fromRoute('entity.component_form_display.' . $this->getComponentId() . '.edit_form', ['form_mode_name' => $this->getFormModeName()]);
}
if ($rel === 'collection') {
return Url::fromRoute('entity.component_form_display.' . $this->getComponentId());
}
return parent::toUrl($rel, $options);
}
/**
* Load form display by form mode.
*/
public static function loadByFormMode(string $component_id, mixed $form_mode): ?ComponentFormDisplayInterface {
if (is_array($form_mode)) {
// Strange behavior for default value form modes.
// @todo Debug it.
return NULL;
}
/** @var \Drupal\ui_patterns_ui\Entity\ComponentFormDisplay[] $items */
$items = \Drupal::entityTypeManager()->getStorage('component_form_display')
->loadByProperties(['component_id' => $component_id, 'form_mode_name' => $form_mode]);
return count($items) !== 0 ? current($items) : NULL;
}
/**
* Load the default display.
*/
public static function loadDefault(string $component_id): ?ComponentFormDisplayInterface {
/** @var \Drupal\ui_patterns_ui\Entity\ComponentFormDisplay[] $items */
$items = \Drupal::entityTypeManager()->getStorage('component_form_display')
->loadByProperties(['component_id' => $component_id]);
return count($items) !== 0 ? current($items) : NULL;
}
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_ui\Form;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\ui_patterns_ui\Entity\ComponentFormDisplay;
/**
* Component display form.
*/
final class ComponentFormDisplayDeleteForm extends EntityDeleteForm {
/**
* {@inheritdoc}
*/
public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
$component_id = $route_match->getParameter('component_id');
$form_mode = $route_match->getParameter('form_mode_name');
return ComponentFormDisplay::loadByFormMode($component_id, $form_mode);
}
}
This diff is collapsed.
<?php
namespace Drupal\ui_patterns_ui\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Theme\ComponentPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides local action definitions for all component forms.
*/
class UiPatternsUiLocalAction extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* Constructs a UiPatternsUiLocalAction object.
*/
public function __construct(protected RouteProviderInterface $routeProvider, protected ComponentPluginManager $componentPluginManager, protected EntityTypeManagerInterface $entityTypeManager) {
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('router.route_provider'),
$container->get('plugin.manager.sdc'),
$container->get('entity_type.manager'),
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives = [];
$components = $this->componentPluginManager->getAllComponents();
foreach ($components as $component) {
$this->derivatives["component_form_display.{$component->getPluginId()}"] = [
'route_name' => "entity.component_form_display.{$component->getPluginId()}.add_form",
'title' => $this->t('Add form display'),
'appears_on' => ["entity.component_form_display.{$component->getPluginId()}.edit_form"],
];
}
foreach ($this->derivatives as &$entry) {
$entry += $base_plugin_definition;
}
return $this->derivatives;
}
}
<?php
namespace Drupal\ui_patterns_ui\Plugin\Derivative;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Theme\ComponentPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides local task definitions for all component forms.
*/
class UiPatternsUiLocalTask extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
public function __construct(protected RouteProviderInterface $routeProvider, protected ComponentPluginManager $componentPluginManager, protected EntityTypeManagerInterface $entityTypeManager, TranslationInterface $stringTranslation) {
$this->setStringTranslation($stringTranslation);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('router.route_provider'),
$container->get('plugin.manager.sdc'),
$container->get('entity_type.manager'),
$container->get('string_translation')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives = [];
/** @var \Drupal\ui_patterns_ui\Entity\ComponentFormDisplay[] $displays */
$displays = $this->entityTypeManager->getStorage('component_form_display')->loadMultiple();
$display_weight = 0;
$components = $this->componentPluginManager->getAllComponents();
foreach ($components as $component) {
$this->derivatives["component_form_display.{$component->getPluginId()}.base"] = [
'route_name' => "entity.component_form_display.{$component->getPluginId()}",
'base_route' => "entity.component_form_display.{$component->getPluginId()}",
'weight' => 10,
'title' => $this->t('Overview'),
];
}
foreach ($displays as $display) {
$this->derivatives["component_form_display.{$display->id()}"] = [
'route_name' => "entity.component_form_display.{$display->getComponentId()}.edit_form",
'base_route' => "entity.component_form_display.{$display->getComponentId()}.edit_form",
'title' => $display->label(),
'weight' => $display_weight,
'cache_tags' => $display->getEntityType()->getListCacheTags(),
'parent_id' => "ui_patterns_ui.form_displays:component_form_display.{$display->getComponentId()}.base",
'route_parameters' => [
'component_id' => $display->getComponentId(),
'form_mode_name' => $display->getFormModeName(),
],
];
$display_weight += 10;
}
foreach ($this->derivatives as &$entry) {
$entry += $base_plugin_definition;
}
return $this->derivatives;
}
}
<?php
namespace Drupal\ui_patterns_ui\Routing;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Theme\ComponentPluginManager;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Subscriber for Component form routes.
*/
class RouteSubscriber extends RouteSubscriberBase {
use StringTranslationTrait;
/**
* Constructs a RouteSubscriber object.
*/
public function __construct(
protected EntityTypeManagerInterface $entityTypeManager,
protected ComponentPluginManager $componentPluginManager,
) {
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection): void {
$components = $this->componentPluginManager->getAllComponents();
$defaults = [];
$path = '/admin/structure/component/';
foreach ($components as $component) {
$route = new Route(
$path . $component->getPluginId() . '/form-display',
[
'_controller' => '\Drupal\ui_patterns_ui\Controller\ComponentController::forward',
'component_id' => $component->getPluginId(),
] + $defaults,
[
'_permission' => 'administer component form display',
]
);
$collection->add("entity.component_form_display.{$component->getPluginId()}", $route);
$route = new Route(
$path . $component->getPluginId() . '/form-display/{form_mode_name}',
[
'_entity_form' => 'component_form_display.edit',
'_title' => $component->metadata->name,
'component_id' => $component->getPluginId(),
] + $defaults,
[
'_permission' => 'administer component form display',
]
);
$collection->add("entity.component_form_display.{$component->getPluginId()}.edit_form", $route);
$route = new Route(
$path . $component->getPluginId() . '/form-display/add',
[
'_entity_form' => 'component_form_display.add',
'_title' => 'Add form display',
'component_id' => $component->getPluginId(),
] + $defaults,
[
'_permission' => 'administer component',
]
);
$collection->add("entity.component_form_display.{$component->getPluginId()}.add_form", $route);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events = parent::getSubscribedEvents();
$events[RoutingEvents::ALTER] = ['onAlterRoutes', -100];
return $events;
}
}
---
display_test_1:
component:
component_id: ui_patterns_test:test-component
display_id: ui_patterns_test.test-component.test
output:
props:
string:
value: 'demo'
assertSession:
elementExists:
- [ 'css', '.ui-patterns-test-component' ]
langcode: en
status: true
third_party_settings:
layout_builder:
enabled: true
allow_custom: false
sections:
-
layout_id: 'ui_patterns:ui_patterns_test:test_component'
layout_settings:
label: TestDemo
context_mapping: { }
ui_patterns:
component_id: null
variant_id: null
slots: { }
props: { }
components: { }
third_party_settings: { }
id: node.page.full
targetEntityType: node
bundle: page
mode: full
hidden:
layout_builder__layout: true
field_text_1: true
title: true
body: true
links: true
langcode: en
status: 1
dependencies: { }
id: ui_patterns_test.test-component.test
label: Test
component_id: 'ui_patterns_test:test-component'
form_mode_name: test
content:
string:
source_id: textfield
weight: '1'
region: configure
parent: ''
third_party_settings: { }
source:
value: demo
string_plain:
source_id: textfield
weight: '0'
region: content
parent: ''
third_party_settings: { }
widget_settings:
title: 'UIP String PLAIN'
required: 0
hidden:
attributes: true
integer: true
number: true
url: true
identifier: true
boolean: true
links: true
enum_integer: true
enum_string: true
enum_list: true
enum_list_multiple: true
enum_set: true
list_string: true
list_integer: true
list_mixed: true
attributes_implicit: true
attributes_ui_patterns: true
attributes_class: true
attributes_with_default: true
variant: true
slot: true
<?php
namespace Drupal\Tests\ui_patterns_ui\Functional;
use Drupal\Tests\ui_patterns\Functional\UiPatternsFunctionalTestBase;
use Drupal\Tests\ui_patterns\Traits\TestDataTrait;
/**
* Test components rendering as layouts.
*
* @group ui_patterns_layouts
*/
class LayoutBuilderRenderTest extends UiPatternsFunctionalTestBase {
use TestDataTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'ui_patterns',
'ui_patterns_test',
'ui_patterns_layouts',
'ui_patterns_ui',
'field_ui',
'layout_builder',
'block',
];
/**
* Test the form and the existence of the.
*/
public function testDisplayExistsForm(): void {
$assert_session = $this->assertSession();
$config_import = $this->loadConfigFixture(__DIR__ . '/../../fixtures/config/core.entity_view_display.node.page.full.yml');
$ui_patterns_config = &$config_import['third_party_settings']['layout_builder']['sections'][0]['layout_settings']['ui_patterns'];
$test_data = $this->loadTestDataFixture(__DIR__ . "/../../fixtures/TestDataSet.yml");
$test_set = $test_data->getTestSet('display_test_1');
$this->createTestContentContentType();
$ui_patterns_config = $this->buildUiPatternsConfig($test_set);
$this->importConfigFixture(
'core.entity_view_display.node.page.full',
$config_import
);
$display_config_import = $this->loadConfigFixture(__DIR__ . '/../../fixtures/config/ui_patterns_ui.component_display.ui_patterns_test.test-component.test.yml');
$this->importConfigFixture(
'ui_patterns_ui.component_display.ui_patterns_test.test-component.test',
$display_config_import
);
$this->drupalGet('admin/structure/types/manage/page/display/full/layout');
$assert_session->statusCodeEquals(200);
$this->click('.layout-builder__link--configure');
$assert_session->elementExists('css', '.uip-display-select');
}
/**
* Tests preview and output of props.
*/
public function testRender(): void {
$assert_session = $this->assertSession();
$config_import = $this->loadConfigFixture(__DIR__ . '/../../fixtures/config/core.entity_view_display.node.page.full.yml');
$display_config_import = $this->loadConfigFixture(__DIR__ . '/../../fixtures/config/ui_patterns_ui.component_display.ui_patterns_test.test-component.test.yml');
$this->importConfigFixture(
'ui_patterns_ui.component_display.ui_patterns_test.test-component.test',
$display_config_import
);
$ui_patterns_config = &$config_import['third_party_settings']['layout_builder']['sections'][0]['layout_settings']['ui_patterns'];
$test_data = $this->loadTestDataFixture(__DIR__ . "/../../fixtures/TestDataSet.yml");
$tests = [
$test_data->getTestSet('display_test_1'),
];
foreach ($tests as $test_set) {
$ui_patterns_config = $this->buildUiPatternsConfig($test_set);
$config_import['third_party_settings']['layout_builder']['sections'][0]['layout_id'] = 'ui_patterns:' . str_replace('-', '_', $test_set['component']['component_id']);
$this->importConfigFixture(
'core.entity_view_display.node.page.full',
$config_import
);
$node = $this->createTestContentNode('page', $test_set['entity'] ?? []);
$this->drupalGet('admin/structure/types/manage/page/display/full/layout');
$assert_session->statusCodeEquals(200);
$component_id = str_replace('_', '-', explode(':', $test_set['component']['component_id'])[1]);
$assert_session->elementExists('css', '.ui-patterns-' . $component_id);
$this->drupalGet('node/' . $node->id());
$assert_session->statusCodeEquals(200);
$assert_session->elementExists('css', '.ui-patterns-' . $component_id);
$this->validateRenderedComponent($test_set);
$node->delete();
}
}
}
<?php
/**
* @file
* API file.
*/
use Drupal\ui_patterns_ui\Entity\ComponentFormDisplay;
/**
* Alter group form row.
*
* @param array $row
* The group row.
* @param \Drupal\ui_patterns_ui\Entity\ComponentFormDisplay $display
* The component form display.
* @param array $group
* The group for this row.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
function hook_component_form_display_group_row_alter(array &$row, ComponentFormDisplay $display, array $group) {
$row['human_name']['#markup'] = $group['label'];
}
/**
* Groups for display.
*
* @param \Drupal\ui_patterns_ui\Entity\ComponentFormDisplay $display
* The component form display.
*/
function hook_component_form_display_groups(ComponentFormDisplay $display) {
return $display->getThirdPartySettings('my_module');
}
name: "UI Patterns UI"
type: module
description: "Manage display and form settings of components."
core_version_requirement: ^10.3 || ^11
package: "User interface (experimental)"
lifecycle: experimental
dependencies:
- ui_patterns:ui_patterns
ui_patterns_ui.add_manage_display:
class: \Drupal\Core\Menu\LocalActionDefault
deriver: \Drupal\ui_patterns_ui\Plugin\Derivative\UiPatternsUiLocalAction
ui_patterns_ui.component.overview:
title: Component
parent: system.admin_structure
description: 'Manage form settings of components'
route_name: component.collection
ui_patterns_ui.form_displays:
class: \Drupal\Core\Menu\LocalTaskDefault
deriver: \Drupal\ui_patterns_ui\Plugin\Derivative\UiPatternsUiLocalTask
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment