Skip to content

Instantly share code, notes, and snippets.

@lauriii
Last active April 21, 2023 15:51
Show Gist options
  • Save lauriii/666c25732c6cfbc80eaf4545cd4e8cb6 to your computer and use it in GitHub Desktop.
Save lauriii/666c25732c6cfbc80eaf4545cd4e8cb6 to your computer and use it in GitHub Desktop.
diff --git a/core/lib/Drupal/Core/Field/Annotation/FieldType.php b/core/lib/Drupal/Core/Field/Annotation/FieldType.php
index 8bf54cb585..ae313c2549 100644
--- a/core/lib/Drupal/Core/Field/Annotation/FieldType.php
+++ b/core/lib/Drupal/Core/Field/Annotation/FieldType.php
@@ -100,4 +100,6 @@ class FieldType extends DataType {
*/
public $cardinality;
+ public $group_display = FALSE;
+
}
diff --git a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
index 520ff8a709..c1aa1b1a9c 100644
--- a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
+++ b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
@@ -108,7 +108,9 @@ public static function createFromItemType($item_type) {
* {@inheritdoc}
*/
public function getName() {
- return $this->definition['field_name'];
+ // @todo figure out how to get rid of this. This is just to avoid notices
+ // from field_name not existing when trying to initialize default value.
+ return isset($this->definition['field_name']) ? $this->definition['field_name'] : null;
}
/**
diff --git a/core/lib/Drupal/Core/Field/FieldItemBase.php b/core/lib/Drupal/Core/Field/FieldItemBase.php
index 399b506ede..b209969739 100644
--- a/core/lib/Drupal/Core/Field/FieldItemBase.php
+++ b/core/lib/Drupal/Core/Field/FieldItemBase.php
@@ -33,6 +33,22 @@ public static function defaultFieldSettings() {
return [];
}
+ /**
+ * {@inheritdoc}
+ */
+ public static function storageSettingsSummary(FieldStorageDefinitionInterface $field_definition): array {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function fieldSettingsSummary(FieldDefinitionInterface $field_definition): array {
+ $definition = \Drupal::service('plugin.manager.field.field_type')
+ ->getDefinition($field_definition->getType())['label'];
+ return [['#markup' => $definition, '#weight' => -10]];
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php
index d6ef1557e6..3ec74e6330 100644
--- a/core/lib/Drupal/Core/Field/FieldItemInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php
@@ -262,6 +262,28 @@ public static function defaultStorageSettings();
*/
public static function defaultFieldSettings();
+ /**
+ * Returns a short summary of the field's storage-level settings.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $field_definition
+ * The field storage definition.
+ *
+ * @return array
+ * A renderable array summarizing storage-level settings.
+ */
+ public static function storageSettingsSummary(FieldStorageDefinitionInterface $field_definition): array;
+
+ /**
+ * Returns a short summary of the field's field-level settings.
+ *
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ * The field entity.
+ *
+ * @return array
+ * A renderable array summarizing the field-level settings.
+ */
+ public static function fieldSettingsSummary(FieldDefinitionInterface $field_definition): array;
+
/**
* Returns a settings array that can be stored as a configuration value.
*
diff --git a/core/lib/Drupal/Core/Field/FieldTypePluginManager.php b/core/lib/Drupal/Core/Field/FieldTypePluginManager.php
index f14e2e3f21..f7cb853c89 100644
--- a/core/lib/Drupal/Core/Field/FieldTypePluginManager.php
+++ b/core/lib/Drupal/Core/Field/FieldTypePluginManager.php
@@ -121,6 +121,30 @@ public function getDefaultFieldSettings($type) {
return [];
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageSettingsSummary(FieldStorageDefinitionInterface $field_definition) {
+ $plugin_definition = $this->getDefinition($field_definition->getType(), FALSE);
+ if (!empty($plugin_definition['class'])) {
+ $plugin_class = DefaultFactory::getPluginClass($field_definition->getType(), $plugin_definition);
+ return $plugin_class::storageSettingsSummary($field_definition);
+ }
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFieldSettingsSummary(FieldDefinitionInterface $field_definition) {
+ $plugin_definition = $this->getDefinition($field_definition->getType(), FALSE);
+ if (!empty($plugin_definition['class'])) {
+ $plugin_class = DefaultFactory::getPluginClass($field_definition->getType(), $plugin_definition);
+ return $plugin_class::fieldSettingsSummary($field_definition);
+ }
+ return [];
+ }
+
/**
* {@inheritdoc}
*/
@@ -136,9 +160,16 @@ public function getUiDefinitions() {
foreach ($definitions as $id => $definition) {
if (is_subclass_of($definition['class'], '\Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface')) {
foreach ($this->getPreconfiguredOptions($definition['id']) as $key => $option) {
- $definitions['field_ui:' . $id . ':' . $key] = [
- 'label' => $option['label'],
- ] + $definition;
+ // Media has overridden fields so merge the entity_reference definition with the override.
+ if ($key === 'media') {
+ $definitions['field_ui:' . $id . ':' . $key] = [
+ 'label' => $option['label'],
+ ] + array_merge($definition, $option);
+ } else {
+ $definitions['field_ui:' . $id . ':' . $key] = [
+ 'label' => $option['label'],
+ ] + $definition;
+ }
if (isset($option['category'])) {
$definitions['field_ui:' . $id . ':' . $key]['category'] = $option['category'];
diff --git a/core/lib/Drupal/Core/Field/FieldTypePluginManagerInterface.php b/core/lib/Drupal/Core/Field/FieldTypePluginManagerInterface.php
index dd5e9390ae..f39d76b0bf 100644
--- a/core/lib/Drupal/Core/Field/FieldTypePluginManagerInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldTypePluginManagerInterface.php
@@ -76,6 +76,32 @@ public function getDefaultFieldSettings($type);
*/
public function getDefaultStorageSettings($type);
+ /**
+ * Returns the summary of storage-level settings for a field type.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage
+ * The field storage definition.
+ *
+ * @return array
+ * A renderable array for the field's storage-level settings summary, as
+ * provided by the plugin definition, or an empty array if type or summary
+ * is undefined.
+ */
+ public function getStorageSettingsSummary(FieldStorageDefinitionInterface $field_storage);
+
+ /**
+ * Returns the summary of field-level settings for a field type.
+ *
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ * The field entity.
+ *
+ * @return array
+ * A renderable array for The field's field-level settings summary, as
+ * provided by the plugin definition, or an empty array if type or summary
+ * is undefined.
+ */
+ public function getFieldSettingsSummary(FieldDefinitionInterface $field_definition);
+
/**
* Gets the definition of all field types that can be added via UI.
*
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php
index 15b7b65545..96f203214e 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php
@@ -16,8 +16,10 @@
* label = @Translation("Number (decimal)"),
* description = @Translation("This field stores a number in the database in a fixed decimal format."),
* category = @Translation("Number"),
+ * group = @Translation("Number"),
* default_widget = "number",
- * default_formatter = "number_decimal"
+ * default_formatter = "number_decimal",
+ * group_display = TRUE
* )
*/
class DecimalItem extends NumericItemBase {
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
index 43cd540bb2..e983d00090 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
@@ -2,6 +2,7 @@
namespace Drupal\Core\Field\Plugin\Field\FieldType;
+use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\ContentEntityStorageInterface;
@@ -39,6 +40,8 @@
* default_widget = "entity_reference_autocomplete",
* default_formatter = "entity_reference_label",
* list_class = "\Drupal\Core\Field\EntityReferenceFieldItemList",
+ * group_display = TRUE,
+ * group = @Translation("Reference"),
* )
*/
class EntityReferenceItem extends FieldItemBase implements OptionsProviderInterface, PreconfiguredFieldUiOptionsInterface {
@@ -110,6 +113,49 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
return $properties;
}
+ /**
+ * {@inheritdoc}
+ */
+ public static function storageSettingsSummary(FieldStorageDefinitionInterface $field_definition): array {
+ $summary = parent::storageSettingsSummary($field_definition);
+ $target_type = $field_definition->getSetting('target_type');
+ $target_type_info = \Drupal::entityTypeManager()->getDefinition($target_type);
+ if (!empty($target_type_info)) {
+ $summary[] = t('Reference type: @entity_type', [
+ '@entity_type' => $target_type_info->getLabel(),
+ ]);
+ }
+ return $summary;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function fieldSettingsSummary(FieldDefinitionInterface $field_definition): array {
+ $summary = parent::fieldSettingsSummary($field_definition);
+ $target_type = $field_definition->getFieldStorageDefinition()->getSetting('target_type');
+ $handler_settings = $field_definition->getSetting('handler_settings');
+
+ if (!isset($handler_settings['target_bundles'])) {
+ return $summary;
+ }
+
+ /** @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_bundle_information */
+ $entity_bundle_information = \Drupal::service('entity_type.bundle.info');
+ $bundle_info = $entity_bundle_information->getBundleInfo($target_type);
+ $bundles = array_map(fn($bundle) => $bundle_info[$bundle]['label'], $handler_settings['target_bundles']);
+ $bundle_label = \Drupal::entityTypeManager()->getDefinition($target_type)->getBundleLabel();
+
+ if (!empty($bundles)) {
+ $summary[] = new FormattableMarkup('@bundle: @entity_type', [
+ '@bundle' => $bundle_label ?: new TranslatableMarkup('Bundle'),
+ '@entity_type' => implode(', ', $bundles),
+ ]);
+ }
+
+ return $summary;
+ }
+
/**
* {@inheritdoc}
*/
@@ -397,6 +443,11 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
* {@inheritdoc}
*/
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
+ // @todo this should become visible with AJAX.
+ if (!method_exists($form_state->getFormObject(), 'getEntity')) {
+ return [];
+ }
+
$field = $form_state->getFormObject()->getEntity();
// Get all selection plugins for this entity type.
@@ -419,7 +470,6 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
'#type' => 'container',
'#process' => [[static::class, 'fieldSettingsAjaxProcess']],
'#element_validate' => [[static::class, 'fieldSettingsFormValidate']],
-
];
$form['handler'] = [
'#type' => 'details',
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/FloatItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/FloatItem.php
index 739a62c76e..b0f91543ea 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/FloatItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/FloatItem.php
@@ -15,8 +15,10 @@
* label = @Translation("Number (float)"),
* description = @Translation("This field stores a number in the database in a floating point format."),
* category = @Translation("Number"),
+ * group = @Translation("Number"),
* default_widget = "number",
- * default_formatter = "number_decimal"
+ * default_formatter = "number_decimal",
+ * group_display = TRUE
* )
*/
class FloatItem extends NumericItemBase {
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php
index 1ce7a4463f..d2f9a7874d 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php
@@ -15,8 +15,10 @@
* label = @Translation("Number (integer)"),
* description = @Translation("This field stores a number in the database as an integer."),
* category = @Translation("Number"),
+ * group = @Translation("Number"),
* default_widget = "number",
- * default_formatter = "number_integer"
+ * default_formatter = "number_integer",
+ * group_display = TRUE
* )
*/
class IntegerItem extends NumericItemBase {
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItem.php
index 172f904b26..1b6d580085 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItem.php
@@ -16,7 +16,9 @@
* description = @Translation("A field containing a plain string value."),
* category = @Translation("Text"),
* default_widget = "string_textfield",
- * default_formatter = "string"
+ * default_formatter = "string",
+ * group_display = TRUE,
+ * group = @Translation("Plain text"),
* )
*/
class StringItem extends StringItemBase {
@@ -90,6 +92,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
'#description' => $this->t('The maximum length of the field in characters.'),
'#min' => 1,
'#disabled' => $has_data,
+ '#group' => 'advanced',
];
return $element;
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringLongItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringLongItem.php
index 93c6b27bcb..84b6a44fa1 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringLongItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringLongItem.php
@@ -16,6 +16,8 @@
* category = @Translation("Text"),
* default_widget = "string_textarea",
* default_formatter = "basic_string",
+ * group_display = TRUE,
+ * group = @Translation("Plain text"),
* )
*/
class StringLongItem extends StringItemBase {
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/TimestampItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/TimestampItem.php
index 5706ed72f8..4bd285b101 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/TimestampItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/TimestampItem.php
@@ -13,6 +13,7 @@
* @FieldType(
* id = "timestamp",
* label = @Translation("Timestamp"),
+ * group = @Translation("Date and time"),
* description = @Translation("An entity field containing a UNIX timestamp value."),
* default_widget = "datetime_timestamp",
* default_formatter = "timestamp",
@@ -25,7 +26,8 @@
* }
* }
* }
- * }
+ * },
+ * group_display = TRUE,
* )
*/
class TimestampItem extends FieldItemBase {
diff --git a/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php b/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php
index 2d6ed8bb75..245f7d609c 100644
--- a/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php
+++ b/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php
@@ -107,9 +107,23 @@ public function getGroupedDefinitions(array $definitions = NULL, $label_key = 'l
$definitions = $this->getSortedDefinitions($definitions ?? $this->getDefinitions(), $label_key);
$grouped_definitions = [];
foreach ($definitions as $id => $definition) {
- $grouped_definitions[(string) $definition['category']][$id] = $definition;
- }
+ $grouped_definitions[(string)$definition['category']][$id] = $definition;
+ }
return $grouped_definitions;
}
+ // @todo: Maybe rename this
+ public function getGroupDefinitions(array $definitions = NULL, $label_key = 'label') {
+ /** @var \Drupal\Core\Plugin\CategorizingPluginManagerTrait|\Drupal\Component\Plugin\PluginManagerInterface $this */
+ $definitions = $this->getSortedDefinitions($definitions ?? $this->getDefinitions(), $label_key);
+ $grouped_definitions = [];
+ foreach ($definitions as $id => $definition) {
+ if (isset($definition['group'])) {
+ $grouped_definitions[(string) $definition['group']][$id] = $definition;
+ } else {
+ $grouped_definitions['Other'][$id] = $definition;
+ }
+ }
+ return $grouped_definitions;
+ }
}
diff --git a/core/misc/dialog/dialog.ajax.js b/core/misc/dialog/dialog.ajax.js
index dd62949273..708d2b4b7e 100644
--- a/core/misc/dialog/dialog.ajax.js
+++ b/core/misc/dialog/dialog.ajax.js
@@ -48,6 +48,19 @@
originalClose.apply(settings.dialog, [event, ...args]);
$(event.target).remove();
};
+ const $table = $('#field-overview');
+ $(once('manage-fields-field-type', $table.find('a.use-ajax'))).on(
+ 'keypress',
+ (e) => {
+ // The AJAX link has the button role, so we need to make sure the link
+ // is also triggered when pressing the spacebar.
+ if (e.which === 32) {
+ e.preventDefault();
+ e.stopPropagation();
+ $(e.currentTarget).trigger('click');
+ }
+ },
+ );
},
/**
@@ -62,7 +75,7 @@
prepareDialogButtons($dialog) {
const buttons = [];
const $buttons = $dialog.find(
- '.form-actions input[type=submit], .form-actions a.button',
+ '.form-actions input[type=submit], .form-actions a.button, .form-actions a.button, .form-actions .action-link',
);
$buttons.each(function () {
const $originalButton = $(this).css({ display: 'none' });
diff --git a/core/modules/config_translation/config_translation.module b/core/modules/config_translation/config_translation.module
index b984783a9d..d275ebbc3c 100644
--- a/core/modules/config_translation/config_translation.module
+++ b/core/modules/config_translation/config_translation.module
@@ -5,6 +5,7 @@
* Configuration Translation module.
*/
+use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\EntityInterface;
@@ -169,6 +170,14 @@ function config_translation_entity_operation(EntityInterface $entity) {
'title' => t('Translate'),
'weight' => 50,
'url' => $entity->toUrl($link_template),
+ 'attributes' => [
+ 'class' => ['use-ajax'],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => Json::encode([
+ 'width' => '85vw',
+ ]),
+ 'role' => 'button',
+ ]
];
}
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 23f3a8eae1..aa7e355453 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -494,6 +494,7 @@ function content_translation_form_field_config_edit_form_alter(array &$form, For
'#weight' => -1,
'#disabled' => !$bundle_is_translatable,
'#access' => $field->getFieldStorageDefinition()->isTranslatable(),
+ '#group' => 'basic',
];
// Provide helpful pointers for administrators.
diff --git a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php
index 5e17728b99..d00ed523ca 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php
@@ -15,11 +15,13 @@
* @FieldType(
* id = "datetime",
* label = @Translation("Date"),
+ * group = @Translation("Date and time"),
* description = @Translation("Create and store date values."),
* default_widget = "datetime_default",
* default_formatter = "datetime_default",
* list_class = "\Drupal\datetime\Plugin\Field\FieldType\DateTimeFieldItemList",
- * constraints = {"DateTimeFormat" = {}}
+ * constraints = {"DateTimeFormat" = {}},
+ * group_display = TRUE,
* )
*/
class DateTimeItem extends FieldItemBase implements DateTimeItemInterface {
diff --git a/core/modules/field_ui/css/field_ui.admin.css b/core/modules/field_ui/css/field_ui.admin.css
index f1716491c4..4124846b53 100644
--- a/core/modules/field_ui/css/field_ui.admin.css
+++ b/core/modules/field_ui/css/field_ui.admin.css
@@ -10,6 +10,17 @@
.field-ui-overview .region-message td {
font-style: italic;
}
+.field-settings-summary-cell > .item-list > ul > li,
+.storage-settings-summary-cell > .item-list > ul > li {
+ margin: 0;
+ list-style-type: none;
+}
+.field-settings-summary-cell > .item-list > ul > li {
+ font-size: 0.9em;
+}
+.field-settings-summary-cell > .item-list > ul > li:first-child {
+ font-size: 1em;
+}
/* 'Manage form display' and 'Manage display' overview */
.field-ui-overview .field-plugin-summary-cell {
@@ -51,3 +62,4 @@
.field-ui-overview .field-plugin-settings-edit-form .plugin-name {
font-weight: bold;
}
+
diff --git a/core/modules/field_ui/css/field_ui.css b/core/modules/field_ui/css/field_ui.css
new file mode 100644
index 0000000000..16aefd061b
--- /dev/null
+++ b/core/modules/field_ui/css/field_ui.css
@@ -0,0 +1,13 @@
+.region-content {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
+}
+#block-claro-content {
+ width: 100%;
+}
+
+.block-field-ui-link .button {
+ padding: 6px 8px;
+}
diff --git a/core/modules/field_ui/css/field_ui_manage_fields.css b/core/modules/field_ui/css/field_ui_manage_fields.css
new file mode 100644
index 0000000000..84535828a7
--- /dev/null
+++ b/core/modules/field_ui/css/field_ui_manage_fields.css
@@ -0,0 +1,105 @@
+:root {
+ --shadow-z1: 0px 2px 4px rgba(0, 0, 0, 0.1);
+ --shadow-z2: 0px 4px 10px rgba(0, 0, 0, 0.1);
+}
+
+#manage-fields-container {
+ display: flex;
+}
+
+#manage-fields-table {
+ width: 70%;
+}
+
+#manage-fields-add-field {
+ width: 28%;
+ margin-inline-start: 2%;
+ padding-inline: 1rem;
+ padding-block-end: 1rem;
+ border: 1px solid var(--color-gray-200);
+ box-shadow: var(--shadow-z1);
+}
+
+#manage-fields-add-field .field-option {
+ display: flex;
+ color: unset;
+ /*width: 100%;*/
+ margin-top: 0.75rem;
+ padding: 0.5rem;
+ border: 1px solid var(--color-gray-200);
+ text-align: start;
+ background: #fff;
+ cursor: pointer;
+ text-decoration: none;
+}
+
+#manage-fields-add-field .field-option:not(:focus), .field-option-card:not(:focus) {
+ box-shadow: var(--shadow-z2);
+}
+
+#manage-fields-add-field .field-option:hover, .field-option-card:hover {
+ background: #f5f4f4;
+}
+
+#manage-fields-add-field button:hover .field-option__thumb {
+ border-color: #000;
+}
+
+#manage-fields-add-field .field-option__description {
+ color: var(--color-gray-700);
+ font-size: var(--font-size-xs);
+}
+
+.field-option__thumb {
+ background: var(--color-gray-050);
+ position: relative;
+ width: 3.1rem;
+ min-width: 3.1rem;
+ height: 3.1rem;
+}
+.field-option__thumb > img {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.field-option__words {
+ padding-inline-start: 0.5rem;
+}
+
+#manage-fields-table > table {
+ margin-top: 0;
+}
+
+@media (min-width: 42em) {
+ #select-field-option {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+.field-option-card {
+ border: 1px solid var(--color-gray-200);
+ padding: 1em !important;
+ display: block;
+}
+
+/* override */
+#select-field-option > .form-type--boolean {
+ margin-left: 0 !important;
+ margin-right: 13px !important;
+ margin-top: 0 !important;
+}
+
+.field-option-radio {
+ border: 1px solid var(--color-gray-200);
+ border-radius: 10px;
+ cursor: pointer;
+ margin-left: 0px !important;
+ margin-right: 0.3em !important;
+}
+
+.field-option-card > .form-item__label.option {
+ font-weight: bold !important;
+}
diff --git a/core/modules/field_ui/field_ui.js b/core/modules/field_ui/field_ui.js
index 10f5581e50..4a0985be72 100644
--- a/core/modules/field_ui/field_ui.js
+++ b/core/modules/field_ui/field_ui.js
@@ -3,7 +3,7 @@
* Attaches the behaviors for the Field UI module.
*/
-(function ($, Drupal, drupalSettings) {
+(function ($, Drupal, drupalSettings, once) {
/**
* @type {Drupal~behavior}
*
@@ -387,4 +387,26 @@
return refreshRows;
},
};
-})(jQuery, Drupal, drupalSettings);
+
+ Drupal.behaviors.fieldUiAddForm = {
+ attach(context) {
+ once('fieldUiTabs', '#field-ui-tabs', context).forEach((tabs) => {
+ const form = tabs.parentElement;
+ form.querySelector('#advanced').setAttribute('style', 'display: none');
+ tabs.querySelectorAll('a').forEach((tab) => {
+ tab.addEventListener('click', (e) => {
+ e.preventDefault();
+ const url = new URL(e.target.href);
+ tabs.querySelectorAll('a').forEach(element => element.classList.remove('is-active'));
+ form.querySelector('#basic').setAttribute('style', 'display: none');
+ form.querySelector('#advanced').setAttribute('style', 'display: none');
+
+ e.target.classList.add('is-active');
+ form.querySelector(url.hash).setAttribute('style', 'display: revert');
+ });
+ });
+ });
+ }
+ };
+
+})(jQuery, Drupal, drupalSettings, once);
diff --git a/core/modules/field_ui/field_ui.libraries.yml b/core/modules/field_ui/field_ui.libraries.yml
index 8db10f6e91..d9c856696b 100644
--- a/core/modules/field_ui/field_ui.libraries.yml
+++ b/core/modules/field_ui/field_ui.libraries.yml
@@ -10,3 +10,14 @@ drupal.field_ui:
- core/drupal
- core/drupalSettings
- core/once
+
+drupal.field_ui.manage_fields:
+ version: VERSION
+ css:
+ theme:
+ css/field_ui_manage_fields.css: {}
+
+drupal.field_ui_css:
+ css:
+ theme:
+ css/field_ui.css: {}
diff --git a/core/modules/field_ui/field_ui.links.action.yml b/core/modules/field_ui/field_ui.links.action.yml
index 2f44c3c82b..b6fe6c3f77 100644
--- a/core/modules/field_ui/field_ui.links.action.yml
+++ b/core/modules/field_ui/field_ui.links.action.yml
@@ -2,6 +2,7 @@ field_ui.entity_view_mode_add:
route_name: field_ui.entity_view_mode_add
title: 'Add view mode'
weight: 1
+ class: \Drupal\field_ui\AddViewModeLocalAction
appears_on:
- entity.entity_view_mode.collection
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index 5f1fe78eb3..80ec0e1e05 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -63,6 +63,11 @@ function field_ui_theme() {
'empty' => '',
],
],
+ 'field_ui_tabs' => [
+ 'variables' => [
+ 'items' => [],
+ ],
+ ],
];
}
@@ -248,3 +253,12 @@ function field_ui_form_field_ui_field_storage_add_form_alter(array &$form) {
unset($form['add']['new_storage_type']['#options'][$optgroup]['entity_reference']);
$form['add']['new_storage_type']['#options'][$optgroup]['entity_reference'] = t('Other…');
}
+
+function field_ui_preprocess_field_ui_tabs(&$variables) {
+ $variables['wrapper_attributes'] = new \Drupal\Core\Template\Attribute();
+ foreach ($variables['items'] as &$item) {
+ if (!isset($item['attributes'])) {
+ $item['attributes'] = new \Drupal\Core\Template\Attribute();
+ }
+ }
+}
diff --git a/core/modules/field_ui/icons/boolean.svg b/core/modules/field_ui/icons/boolean.svg
new file mode 100644
index 0000000000..27a7a1df53
--- /dev/null
+++ b/core/modules/field_ui/icons/boolean.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M8 7a5 5 0 1 0 0 10h8a5 5 0 0 0 0-10H8zm0-2h8a7 7 0 0 1 0 14H8A7 7 0 0 1 8 5zm0 10a3 3 0 1 1 0-6 3 3 0 0 1 0 6z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/comment.svg b/core/modules/field_ui/icons/comment.svg
new file mode 100644
index 0000000000..f0a883707c
--- /dev/null
+++ b/core/modules/field_ui/icons/comment.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M14 22.5L11.2 19H6a1 1 0 0 1-1-1V7.103a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1V18a1 1 0 0 1-1 1h-5.2L14 22.5zm1.839-5.5H21V8.103H7V17H12.161L14 19.298 15.839 17zM2 2h17v2H3v11H1V3a1 1 0 0 1 1-1z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/daterange.svg b/core/modules/field_ui/icons/daterange.svg
new file mode 100644
index 0000000000..c862ce8031
--- /dev/null
+++ b/core/modules/field_ui/icons/daterange.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path fill="none" d="M0 0h24v24H0z"/><path d="M17 3h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h4V1h2v2h6V1h2v2zm3 8H4v8h16v-8zm-5-6H9v2H7V5H4v4h16V5h-3v2h-2V5zm-9 8h2v2H6v-2zm5 0h2v2h-2v-2zm5 0h2v2h-2v-2z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/datetime.svg b/core/modules/field_ui/icons/datetime.svg
new file mode 100644
index 0000000000..ceae3ea178
--- /dev/null
+++ b/core/modules/field_ui/icons/datetime.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M17 3h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h4V1h2v2h6V1h2v2zm3 6V5h-3v2h-2V5H9v2H7V5H4v4h16zm0 2H4v8h16v-8zM6 13h5v4H6v-4z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/email.svg b/core/modules/field_ui/icons/email.svg
new file mode 100644
index 0000000000..a0cf4a3678
--- /dev/null
+++ b/core/modules/field_ui/icons/email.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M20 12a8 8 0 1 0-3.562 6.657l1.11 1.664A9.953 9.953 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10v1.5a3.5 3.5 0 0 1-6.396 1.966A5 5 0 1 1 15 8H17v5.5a1.5 1.5 0 0 0 3 0V12zm-8-3a3 3 0 1 0 0 6 3 3 0 0 0 0-6z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/entity_reference.svg b/core/modules/field_ui/icons/entity_reference.svg
new file mode 100644
index 0000000000..aae6f2189a
--- /dev/null
+++ b/core/modules/field_ui/icons/entity_reference.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0H24V24H0z"/><path d="M15 3c.552 0 1 .448 1 1v4c0 .552-.448 1-1 1h-2v2h4c.552 0 1 .448 1 1v3h2c.552 0 1 .448 1 1v4c0 .552-.448 1-1 1h-6c-.552 0-1-.448-1-1v-4c0-.552.448-1 1-1h2v-2H8v2h2c.552 0 1 .448 1 1v4c0 .552-.448 1-1 1H4c-.552 0-1-.448-1-1v-4c0-.552.448-1 1-1h2v-3c0-.552.448-1 1-1h4V9H9c-.552 0-1-.448-1-1V4c0-.552.448-1 1-1h6zM9 17H5v2h4v-2zm10 0h-4v2h4v-2zM14 5h-4v2h4V5z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/fallback.svg b/core/modules/field_ui/icons/fallback.svg
new file mode 100644
index 0000000000..f7a7e6cf4e
--- /dev/null
+++ b/core/modules/field_ui/icons/fallback.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path fill="none" d="M0 0h24v24H0z"/><path d="M21 15v3h3v2h-3v3h-2v-3h-3v-2h3v-3h2zm.008-12c.548 0 .992.445.992.993V13h-2V5H4v13.999L14 9l3 3v2.829l-3-3L6.827 19H14v2H2.992A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h18.016zM8 7a2 2 0 1 1 0 4 2 2 0 0 1 0-4z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/file.svg b/core/modules/field_ui/icons/file.svg
new file mode 100644
index 0000000000..e5e01f8741
--- /dev/null
+++ b/core/modules/field_ui/icons/file.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M21 8v12.993A1 1 0 0 1 20.007 22H3.993A.993.993 0 0 1 3 21.008V2.992C3 2.455 3.449 2 4.002 2h10.995L21 8zm-2 1h-5V4H5v16h14V9zM8 7h3v2H8V7zm0 4h8v2H8v-2zm0 4h8v2H8v-2z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/formatted_text.svg b/core/modules/field_ui/icons/formatted_text.svg
new file mode 100644
index 0000000000..3f92edf8d4
--- /dev/null
+++ b/core/modules/field_ui/icons/formatted_text.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M180 936q-24.75 0-42.375-17.625T120 876V276q0-24.75 17.625-42.375T180 216h600q24.75 0 42.375 17.625T840 276v600q0 24.75-17.625 42.375T780 936H180Zm0-60h600V356H180v520Zm100-310v-60h390v60H280Zm0 160v-60h230v60H280Z" fill="#55565b" /></svg>
diff --git a/core/modules/field_ui/icons/image.svg b/core/modules/field_ui/icons/image.svg
new file mode 100644
index 0000000000..6f6bd03bfe
--- /dev/null
+++ b/core/modules/field_ui/icons/image.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M4.828 21l-.02.02-.021-.02H2.992A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993H4.828zM20 15V5H4v14L14 9l6 6zm0 2.828l-6-6L6.828 19H20v-1.172zM8 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/integer.svg b/core/modules/field_ui/icons/integer.svg
new file mode 100644
index 0000000000..1bb860ec6d
--- /dev/null
+++ b/core/modules/field_ui/icons/integer.svg
@@ -0,0 +1,12 @@
+<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1_5)">
+<path d="M93.8663 21V24.2553L82.1125 39.0451C84.7715 39.3972 87.289 40.5089 89.3935 42.2603C91.4979 44.0116 93.1095 46.3361 94.0545 48.9832C94.9995 51.6302 95.2419 54.4994 94.7557 57.2812C94.2695 60.063 93.0731 62.6519 91.2955 64.7687C89.5179 66.8856 87.2265 68.45 84.6686 69.2934C82.1106 70.1367 79.383 70.227 76.7801 69.5544C74.1771 68.8818 71.7974 67.4719 69.8977 65.4767C67.998 63.4816 66.6503 60.9768 66 58.2326L70.3927 57.071C71.0007 59.6131 72.4671 61.8333 74.5165 63.3148C76.5658 64.7962 79.057 65.4368 81.5222 65.1163C83.9874 64.7959 86.2569 63.5364 87.9045 61.5744C89.5521 59.6124 90.4643 57.0829 90.4699 54.4611C90.4704 52.6292 90.0277 50.8275 89.1836 49.2268C88.3395 47.6262 87.1221 46.2797 85.6468 45.3149C84.1715 44.3501 82.4872 43.7991 80.7536 43.7141C79.02 43.6291 77.2946 44.0129 75.7409 44.8291L75.2246 45.1207L73.6623 42.2358L86.7452 25.7802H67.8273V21H93.8663Z" fill="#55565b" stroke="#55565b" stroke-width="2"/>
+<path d="M54.3333 34.0484C54.3333 31.5644 53.35 29.1821 51.5997 27.4257C49.8493 25.6692 47.4754 24.6824 45 24.6824C42.5246 24.6824 40.1507 25.6692 38.4003 27.4257C36.65 29.1821 35.6667 31.5644 35.6667 34.0484H31C31.0001 31.8003 31.5378 29.585 32.568 27.5887C33.5981 25.5924 35.0907 23.8733 36.9202 22.5758C38.7497 21.2784 40.8628 20.4405 43.082 20.1325C45.3011 19.8245 47.5616 20.0554 49.6733 20.8058C51.7851 21.5563 53.6866 22.8043 55.218 24.4451C56.7495 26.0859 57.8662 28.0716 58.4743 30.2353C59.0825 32.399 59.1643 34.6775 58.7129 36.8795C58.2615 39.0816 57.2901 41.1427 55.8803 42.8899L38.8003 63.3147L59 63.317V68H31V65.3611L52.1493 40.0684C53.563 38.3836 54.3367 36.2509 54.3333 34.0484Z" fill="#55565b" stroke="#55565b" stroke-width="2"/>
+<path d="M17 21V68H12.3846V26.0531L2 28.818V24.0699L13.5385 21H17Z" fill="#55565b" stroke="#55565b" stroke-width="2"/>
+</g>
+<defs>
+<clipPath id="clip0_1_5">
+<rect width="96" height="96" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/core/modules/field_ui/icons/link.svg b/core/modules/field_ui/icons/link.svg
new file mode 100644
index 0000000000..1af5734270
--- /dev/null
+++ b/core/modules/field_ui/icons/link.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.364 15.536L16.95 14.12l1.414-1.414a5 5 0 1 0-7.071-7.071L9.879 7.05 8.464 5.636 9.88 4.222a7 7 0 0 1 9.9 9.9l-1.415 1.414zm-2.828 2.828l-1.415 1.414a7 7 0 0 1-9.9-9.9l1.415-1.414L7.05 9.88l-1.414 1.414a5 5 0 1 0 7.071 7.071l1.414-1.414 1.415 1.414zm-.708-10.607l1.415 1.415-7.071 7.07-1.415-1.414 7.071-7.07z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/list_integer.svg b/core/modules/field_ui/icons/list_integer.svg
new file mode 100644
index 0000000000..ddf0ad8a33
--- /dev/null
+++ b/core/modules/field_ui/icons/list_integer.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M8 4h13v2H8V4zM5 3v3h1v1H3V6h1V4H3V3h2zM3 14v-2.5h2V11H3v-1h3v2.5H4v.5h2v1H3zm2 5.5H3v-1h2V18H3v-1h3v4H3v-1h2v-.5zM8 11h13v2H8v-2zm0 7h13v2H8v-2z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/list_string.svg b/core/modules/field_ui/icons/list_string.svg
new file mode 100644
index 0000000000..5f8815d1aa
--- /dev/null
+++ b/core/modules/field_ui/icons/list_string.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M8 4h13v2H8V4zm-5-.5h3v3H3v-3zm0 7h3v3H3v-3zm0 7h3v3H3v-3zM8 11h13v2H8v-2zm0 7h13v2H8v-2z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/shape.svg b/core/modules/field_ui/icons/shape.svg
new file mode 100644
index 0000000000..8953d8a1c1
--- /dev/null
+++ b/core/modules/field_ui/icons/shape.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path fill="none" d="M0 0h24v24H0z"/><path d="M20 16h2v6h-6v-2H8v2H2v-6h2V8H2V2h6v2h8V2h6v6h-2v8zm-2 0V8h-2V6H8v2H6v8h2v2h8v-2h2zM4 4v2h2V4H4zm0 14v2h2v-2H4zM18 4v2h2V4h-2zm0 14v2h2v-2h-2z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/telephone.svg b/core/modules/field_ui/icons/telephone.svg
new file mode 100644
index 0000000000..fc33449310
--- /dev/null
+++ b/core/modules/field_ui/icons/telephone.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path fill="none" d="M0 0h24v24H0z"/><path d="M9.366 10.682a10.556 10.556 0 0 0 3.952 3.952l.884-1.238a1 1 0 0 1 1.294-.296 11.422 11.422 0 0 0 4.583 1.364 1 1 0 0 1 .921.997v4.462a1 1 0 0 1-.898.995c-.53.055-1.064.082-1.602.082C9.94 21 3 14.06 3 5.5c0-.538.027-1.072.082-1.602A1 1 0 0 1 4.077 3h4.462a1 1 0 0 1 .997.921A11.422 11.422 0 0 0 10.9 8.504a1 1 0 0 1-.296 1.294l-1.238.884zm-2.522-.657l1.9-1.357A13.41 13.41 0 0 1 7.647 5H5.01c-.006.166-.009.333-.009.5C5 12.956 11.044 19 18.5 19c.167 0 .334-.003.5-.01v-2.637a13.41 13.41 0 0 1-3.668-1.097l-1.357 1.9a12.442 12.442 0 0 1-1.588-.75l-.058-.033a12.556 12.556 0 0 1-4.702-4.702l-.033-.058a12.442 12.442 0 0 1-.75-1.588z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/text.svg b/core/modules/field_ui/icons/text.svg
new file mode 100644
index 0000000000..1de9aa0226
--- /dev/null
+++ b/core/modules/field_ui/icons/text.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M13 6v15h-2V6H5V4h14v2z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/icons/timestamp.svg b/core/modules/field_ui/icons/timestamp.svg
new file mode 100644
index 0000000000..270b958c9d
--- /dev/null
+++ b/core/modules/field_ui/icons/timestamp.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm1-8h4v2h-6V7h2v5z" fill="#55565b"/></svg>
diff --git a/core/modules/field_ui/src/AddViewModeLocalAction.php b/core/modules/field_ui/src/AddViewModeLocalAction.php
new file mode 100644
index 0000000000..e87f6c18a7
--- /dev/null
+++ b/core/modules/field_ui/src/AddViewModeLocalAction.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\field_ui;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Menu\LocalActionDefault;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Routing\RouteMatchInterface;
+
+
+/**
+ * Defines a local action plugin with a dynamic title.
+ */
+class AddViewModeLocalAction extends LocalActionDefault {
+
+ public function getOptions(RouteMatchInterface $route_match) {
+ $options = parent::getOptions($route_match);
+ $options += [
+ 'attributes' => [
+ 'class' => ['button', 'use-ajax'],
+ 'role' => 'button',
+ 'tabindex' => '0',
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => Json::encode([
+ 'width' => '85vw',
+ ]),
+ ],
+ ];
+
+
+ return $options;
+ }
+
+}
diff --git a/core/modules/field_ui/src/Controller/EntityDisplayModeController.php b/core/modules/field_ui/src/Controller/EntityDisplayModeController.php
index a433b2ea31..e2ce438d84 100644
--- a/core/modules/field_ui/src/Controller/EntityDisplayModeController.php
+++ b/core/modules/field_ui/src/Controller/EntityDisplayModeController.php
@@ -2,6 +2,7 @@
namespace Drupal\field_ui\Controller;
+use Drupal\Component\Serialization\Json;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Url;
@@ -16,20 +17,31 @@ class EntityDisplayModeController extends ControllerBase {
* @return array
* A list of entity types to add a view mode for.
*/
- public function viewModeTypeSelection() {
+ public function viewModeTypeSelection()
+ {
$entity_types = [];
foreach ($this->entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
if ($entity_type->get('field_ui_base_route') && $entity_type->hasViewBuilderClass()) {
$entity_types[$entity_type_id] = [
'title' => $entity_type->getLabel(),
- 'url' => Url::fromRoute('entity.entity_view_mode.add_form', ['entity_type_id' => $entity_type_id]),
- 'localized_options' => [],
+ 'url' => Url::fromRoute('entity.entity_view_mode.add_form', ['entity_type_id' => $entity_type_id])->setOption('attributes', [
+ 'class' => ['use-ajax'],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => Json::encode([
+ 'width' => '85vw',
+ ]),
+ ])
];
}
}
return [
'#theme' => 'admin_block_content',
'#content' => $entity_types,
+ '#attached' => [
+ 'library' => [
+ 'core/drupal.dialog.ajax'
+ ]
+ ]
];
}
diff --git a/core/modules/field_ui/src/Controller/FieldConfigListController.php b/core/modules/field_ui/src/Controller/FieldConfigListController.php
index ecfb44964b..715c8aa6f1 100644
--- a/core/modules/field_ui/src/Controller/FieldConfigListController.php
+++ b/core/modules/field_ui/src/Controller/FieldConfigListController.php
@@ -2,8 +2,11 @@
namespace Drupal\field_ui\Controller;
+use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\Controller\EntityListController;
use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Url;
+use Drupal\field_ui\FieldUI;
/**
* Defines a controller to list field instances.
@@ -24,8 +27,232 @@ class FieldConfigListController extends EntityListController {
* A render array as expected by
* \Drupal\Core\Render\RendererInterface::render().
*/
- public function listing($entity_type_id = NULL, $bundle = NULL, RouteMatchInterface $route_match = NULL) {
- return $this->entityTypeManager()->getListBuilder('field_config')->render($entity_type_id, $bundle);
+ public function listing($entity_type_id = NULL, $bundle = NULL, RouteMatchInterface $route_match = NULL)
+ {
+ $field_type_plugin_manager = \Drupal::service('plugin.manager.field.field_type');
+ $entity_type_manager = \Drupal::entityTypeManager();
+
+
+ $field_type_options = [];
+ foreach ($field_type_plugin_manager->getGroupDefinitions($field_type_plugin_manager->getUiDefinitions()) as $group => $field_types) {
+ foreach ($field_types as $name => $field_type) {
+ $target_entity_type = $entity_type_manager->getDefinition($entity_type_id);
+ if ($field_type['group_display']) {
+ $key = $group;
+ $label = $group;
+ $param = $group;
+ } else {
+ $key = $name;
+ $label = $field_type['label'];
+ $param = $name;
+ }
+ $route_parameters = [
+ // Category or actual field type.
+ 'field_type' => $param,
+ 'group' => $group,
+ ] + FieldUI::getRouteBundleParameter($target_entity_type, $bundle);
+
+ $icon = $name === 'field_ui:entity_reference:media' ? $this->getIcon('media') : $this->getIcon($field_type['id']);
+ $field_type_options[$group][$key] = [
+ '#type' => 'html_tag',
+ '#tag' => 'a',
+ '#attributes' => [
+ 'class' => ['field-option', 'use-ajax'],
+ 'role' => 'button',
+ 'tabindex' => '0',
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => Json::encode([
+ 'width' => '85vw',
+ 'title' => $this->t('Add @type Field', ['@type' => $label]),
+ ]),
+ 'href' => URL::fromRoute("field_ui.field_add_$entity_type_id", $route_parameters)->toString(),
+ ],
+ '#total_length' => strlen($field_type['description']) + strlen($field_type['label']),
+ 'thumb' => [
+ '#type' => 'container',
+ '#attributes' => [
+ 'class' => ['field-option__thumb'],
+ ],
+ 'icon' => [
+ '#theme' => 'image',
+ '#uri' => $icon['uri'],
+ '#alt' => $icon['alt'],
+ '#width' => 40,
+ ],
+ ],
+ 'words' => [
+ '#type' => 'container',
+ '#attributes' => [
+ 'class' => ['field-option__words']
+ ],
+ 'label' => [
+ '#attributes' => [
+ 'class' => ['field-option__label']
+ ],
+ '#type' => 'html_tag',
+ '#tag' => 'span',
+ '#value' => $label,
+ ],
+ 'description' => [
+ '#type' => 'container',
+ '#attributes' => [
+ 'class' => ['field-option__description'],
+ ],
+ '#markup' => $field_type['group_display'] ? $this->getGroupDescription($group) : $field_type['description']
+ ],
+ ],
+ ];
+ }
+ }
+
+ $sorted_options = $this->optionsForSidebar($field_type_options);
+
+ $build = [
+ '#type' => 'container',
+ '#attributes' => [
+ 'id' => 'manage-fields-container'
+ ],
+ 'table' => [
+ '#type' => 'container',
+ '#attributes' => [
+ 'id' => 'manage-fields-table'
+ ],
+ 'table_contents' => $this->entityTypeManager()->getListBuilder('field_config')->render($entity_type_id, $bundle),
+ ],
+ 'sidebar' => [
+ '#type' => 'container',
+ '#attributes' => [
+ 'id' => 'manage-fields-add-field'
+ ],
+ 'title' => [
+ '#type' => 'html_tag',
+ '#tag' => 'h3',
+ '#value' => $this->t('Add more fields'),
+ ],
+ 'existing' => [
+ '#type' => 'html_tag',
+ '#tag' => 'a',
+ '#value' => $this->t('Re-use existing field'),
+ '#attributes' => [
+ 'class' => ['button', 'use-ajax'],
+ 'role' => 'button',
+ 'tabindex' => '0',
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => Json::encode([
+ 'width' => '85vw',
+ 'title' => $this->t('Re-use existing field'),
+ ]),
+ 'href' => URL::fromRoute("field_ui.field_storage_config_add_$entity_type_id.reuse", ['node_type' => $bundle])->toString(),
+ ],
+ ],
+ 'add' => [
+ '#type' => 'html_tag',
+ '#tag' => 'h4',
+ '#value' => $this->t('Create a new field'),
+ ],
+ 'options' => $sorted_options,
+ ],
+ '#attached' => ['library' =>
+ [
+ 'field_ui/drupal.field_ui.manage_fields',
+ 'core/drupal.dialog.ajax',
+ ],
+ ],
+ ];
+ return $build;
+ }
+
+ private function optionsForSidebar($field_type_options) {
+ $order = ['Plain text', 'Formatted text', 'Number', 'Reference', 'Date and time', 'File upload', 'Selection list', 'Other'];
+ $sorted_options = [];
+ foreach ($order as $key) {
+ if (!isset($field_type_options[$key])) {
+ continue;
+ }
+// $sorted_options += ["header_$key" => [
+// '#type' => 'html_tag',
+// '#tag' => 'h4',
+// '#value' => $key,
+// ]];
+ // Sort by shortest description + title to longest. Not exactly what we
+ // want but surprisingly close.
+ usort($field_type_options[$key], fn($a, $b) => $a['#total_length'] <=> $b['#total_length']);
+ $sorted_options += [ $key => $field_type_options[$key] ];
+ }
+ return $sorted_options;
+ }
+
+ private function getIcon($field_name) {
+ // Switch is used for fields that share the same icon.
+ switch($field_name) {
+ case 'decimal':
+ case 'float':
+ $icon_name = 'integer';
+ break;
+ case 'entity_reference_subclass':
+ $icon_name = 'entity_reference';
+ break;
+ case 'list_float':
+ $icon_name = 'list_integer';
+ break;
+ case 'shape_required':
+ $icon_name = 'shape';
+ break;
+ case 'string':
+ case 'string_long':
+ $icon_name = 'text';
+ break;
+ case 'text_long':
+ case 'text_with_summary':
+ $icon_name = 'formatted_text';
+ break;
+ case 'file':
+ case 'image':
+ $icon_name = 'file';
+ break;
+ case 'media':
+ $icon_name = 'image';
+ break;
+ case 'boolean':
+ case 'comment':
+ case 'daterange':
+ case 'datetime':
+ case 'email':
+ case 'entity_reference':
+ case 'integer':
+ case 'link':
+ case 'list_integer':
+ case 'list_string':
+ case 'shape':
+ case 'telephone':
+ case 'text':
+ case 'timestamp':
+ $icon_name = $field_name;
+ break;
+ default:
+ // Fallback icon for fields without one.
+ $icon_name = 'fallback';
+ break;
+ }
+
+ $icon['uri'] = "core/modules/field_ui/icons/$icon_name.svg";
+ $icon['alt'] = "Icon for $field_name in add new field options.";
+ return $icon;
+ }
+
+ private function getGroupDescription($group_label): string
+ {
+ // media description is being added in media_library_field_ui_preconfigured_options_alter hook
+ $descriptions = [
+ 'Formatted text' => 'Text field with markup support and optional editor.',
+ 'Plain text' => 'Text field that does not support markup.',
+ 'Reference' => 'Field to reference other content.',
+ 'File upload' => 'Field to upload any type of files.',
+ 'Selection list' => 'Field to select from predefined options.',
+ 'Number' => 'Field to store number. I.e. id, price, or quantity.',
+ 'Date and time' => 'Field to store date and time values.'
+ ];
+ return $descriptions[$group_label] ?? 'fallback';
}
}
diff --git a/core/modules/field_ui/src/EntityDisplayModeListBuilder.php b/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
index 2591dff452..290b0b70f2 100644
--- a/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
+++ b/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
@@ -2,6 +2,7 @@
namespace Drupal\field_ui;
+use Drupal\Component\Serialization\Json;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
@@ -116,8 +117,18 @@ public function render() {
$table['#rows']['_add_new'][] = [
'data' => [
'#type' => 'link',
+ '#title' => $this->t('Add %label for @entity-type', ['@entity-type' => $this->entityTypes[$entity_type]->getLabel(), '%label' => $this->entityType->getSingularLabel()]),
'#url' => Url::fromRoute($short_type == 'view' ? 'entity.entity_view_mode.add_form' : 'entity.entity_form_mode.add_form', ['entity_type_id' => $entity_type]),
- '#title' => $this->t('Add new @entity-type %label', ['@entity-type' => $this->entityTypes[$entity_type]->getLabel(), '%label' => $this->entityType->getSingularLabel()]),
+ '#button_type' => 'small',
+ '#attributes' => [
+ 'class' => ['button', 'use-ajax'],
+ 'role' => 'button',
+ 'tabindex' => '0',
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => Json::encode([
+ 'width' => '85vw',
+ ]),
+ ],
],
'colspan' => count($table['#header']),
];
diff --git a/core/modules/field_ui/src/FieldConfigListBuilder.php b/core/modules/field_ui/src/FieldConfigListBuilder.php
index d0335c76bd..0862cfdc23 100644
--- a/core/modules/field_ui/src/FieldConfigListBuilder.php
+++ b/core/modules/field_ui/src/FieldConfigListBuilder.php
@@ -2,14 +2,15 @@
namespace Drupal\field_ui;
+use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\SortArray;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
-use Drupal\Core\Url;
use Drupal\field\FieldConfigInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -95,6 +96,7 @@ public function render($target_entity_type_id = NULL, $target_bundle = NULL) {
$build = parent::render();
$build['table']['#attributes']['id'] = 'field-overview';
$build['table']['#empty'] = $this->t('No fields are present yet.');
+ $build['#attached']['library'][] = 'field_ui/drupal.field_ui';
return $build;
}
@@ -118,12 +120,8 @@ public function load() {
*/
public function buildHeader() {
$header = [
- 'label' => $this->t('Label'),
- 'field_name' => [
- 'data' => $this->t('Machine name'),
- 'class' => [RESPONSIVE_PRIORITY_MEDIUM],
- ],
- 'field_type' => $this->t('Field type'),
+ 'label' => $this->t('Name'),
+ 'settings_summary' => $this->t('Field type'),
];
return $header + parent::buildHeader();
}
@@ -134,23 +132,25 @@ public function buildHeader() {
public function buildRow(EntityInterface $field_config) {
/** @var \Drupal\field\FieldConfigInterface $field_config */
$field_storage = $field_config->getFieldStorageDefinition();
- $route_parameters = [
- 'field_config' => $field_config->id(),
- ] + FieldUI::getRouteBundleParameter($this->entityTypeManager->getDefinition($this->targetEntityTypeId), $this->targetBundle);
+
+ $storage_summary = $this->fieldTypeManager->getStorageSettingsSummary($field_storage);
+ $instance_summary = $this->fieldTypeManager->getFieldSettingsSummary($field_config);
+ $summary_list = [...$storage_summary, ...$instance_summary];
+ uasort($summary_list, [SortArray::class, 'sortByWeightProperty']);
+
+ $settings_summary = empty($summary_list) ? '' : [
+ 'data' => [
+ '#theme' => 'item_list',
+ '#items' => $summary_list,
+ ],
+ 'class' => ['field-settings-summary-cell'],
+ ];
$row = [
'id' => Html::getClass($field_config->getName()),
'data' => [
- 'label' => $field_config->getLabel(),
- 'field_name' => $field_config->getName(),
- 'field_type' => [
- 'data' => [
- '#type' => 'link',
- '#title' => $this->fieldTypeManager->getDefinitions()[$field_storage->getType()]['label'],
- '#url' => Url::fromRoute("entity.field_config.{$this->targetEntityTypeId}_storage_edit_form", $route_parameters),
- '#options' => ['attributes' => ['title' => $this->t('Edit field settings.')]],
- ],
- ],
+ 'label' => $field_config->getLabel() . " ({$field_config->getName()})",
+ 'settings_summary' => $settings_summary,
],
];
@@ -179,6 +179,12 @@ public function getDefaultOperations(EntityInterface $entity) {
'url' => $entity->toUrl("{$entity->getTargetEntityTypeId()}-field-edit-form"),
'attributes' => [
'title' => $this->t('Edit field settings.'),
+ 'class' => ['use-ajax'],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => Json::encode([
+ 'width' => '85vw',
+ ]),
+ 'role' => 'button',
],
];
}
@@ -189,16 +195,30 @@ public function getDefaultOperations(EntityInterface $entity) {
'url' => $entity->toUrl("{$entity->getTargetEntityTypeId()}-field-delete-form"),
'attributes' => [
'title' => $this->t('Delete field.'),
+ 'class' => ['use-ajax'],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => Json::encode([
+ 'width' => '85vw',
+ ]),
+ 'role' => 'button',
],
];
}
- $operations['storage-settings'] = [
- 'title' => $this->t('Storage settings'),
- 'weight' => 20,
- 'attributes' => ['title' => $this->t('Edit storage settings.')],
- 'url' => $entity->toUrl("{$entity->getTargetEntityTypeId()}-storage-edit-form"),
- ];
+// $operations['storage-settings'] = [
+// 'title' => $this->t('Storage settings'),
+// 'weight' => 20,
+// 'attributes' => [
+// 'title' => $this->t('Edit storage settings.'),
+// 'class' => ['use-ajax'],
+// 'data-dialog-type' => 'modal',
+// 'data-dialog-options' => Json::encode([
+// 'width' => '85vw',
+// ]),
+// 'role' => 'button',
+// ],
+// 'url' => $entity->toUrl("{$entity->getTargetEntityTypeId()}-storage-edit-form"),
+// ];
return $operations;
}
diff --git a/core/modules/field_ui/src/FieldStorageConfigListBuilder.php b/core/modules/field_ui/src/FieldStorageConfigListBuilder.php
index dbf34d9b0c..2c0ff41529 100644
--- a/core/modules/field_ui/src/FieldStorageConfigListBuilder.php
+++ b/core/modules/field_ui/src/FieldStorageConfigListBuilder.php
@@ -81,6 +81,15 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function render() {
+ $build = parent::render();
+ $build['#attached']['library'][] = 'field_ui/drupal.field_ui';
+ return $build;
+ }
+
/**
* {@inheritdoc}
*/
@@ -92,6 +101,7 @@ public function buildHeader() {
'class' => [RESPONSIVE_PRIORITY_MEDIUM],
];
$header['usage'] = $this->t('Used in');
+ $header['settings_summary'] = $this->t('Summary');
return $header;
}
@@ -128,6 +138,14 @@ public function buildRow(EntityInterface $field_storage) {
'#items' => $usage,
'#context' => ['list_style' => 'comma-list'],
];
+ $summary = $this->fieldTypeManager->getStorageSettingsSummary($field_storage);
+ $row['data']['settings_summary'] = empty($summary) ? '' : [
+ 'data' => [
+ '#theme' => 'item_list',
+ '#items' => $summary,
+ ],
+ 'class' => ['storage-settings-summary-cell'],
+ ];
return $row;
}
diff --git a/core/modules/field_ui/src/Form/EntityDisplayModeAddForm.php b/core/modules/field_ui/src/Form/EntityDisplayModeAddForm.php
index 7099a36291..25eb641675 100644
--- a/core/modules/field_ui/src/Form/EntityDisplayModeAddForm.php
+++ b/core/modules/field_ui/src/Form/EntityDisplayModeAddForm.php
@@ -3,6 +3,8 @@
namespace Drupal\field_ui\Form;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\field_ui\FieldUI;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
@@ -10,7 +12,8 @@
*
* @internal
*/
-class EntityDisplayModeAddForm extends EntityDisplayModeFormBase {
+class EntityDisplayModeAddForm extends EntityDisplayModeFormBase
+{
/**
* The entity type for which the display mode is being created.
@@ -22,20 +25,37 @@ class EntityDisplayModeAddForm extends EntityDisplayModeFormBase {
/**
* {@inheritdoc}
*/
- public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL) {
+ public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL)
+ {
$this->targetEntityTypeId = $entity_type_id;
$form = parent::buildForm($form, $form_state);
+
+ $bundle_info_service = \Drupal::service('entity_type.bundle.info');
+ $bundles = $bundle_info_service->getAllBundleInfo();
+
// Change replace_pattern to avoid undesired dots.
$form['id']['#machine_name']['replace_pattern'] = '[^a-z0-9_]+';
$definition = $this->entityTypeManager->getDefinition($this->targetEntityTypeId);
- $form['#title'] = $this->t('Add new @entity-type %label', ['@entity-type' => $definition->getLabel(), '%label' => $this->entityType->getSingularLabel()]);
+ $form['#title'] = $this->t('Add new %label for @entity-type', ['@entity-type' => $definition->getLabel(), '%label' => $this->entityType->getSingularLabel()]);
+
+ $bundles_by_entity = [];
+ foreach (array_keys($bundles[$definition->id()]) as $bundle) {
+ $bundles_by_entity[$bundle] = $bundles[$definition->id()][$bundle]['label'];
+ }
+ $form['data']['bundles_by_entity'] = [
+ '#type' => 'checkboxes',
+ '#title' => 'Enable view mode for the following bundles:',
+ '#options' => $bundles_by_entity,
+ ];
+
return $form;
}
/**
* {@inheritdoc}
*/
- public function validateForm(array &$form, FormStateInterface $form_state) {
+ public function validateForm(array &$form, FormStateInterface $form_state)
+ {
parent::validateForm($form, $form_state);
$form_state->setValueForElement($form['id'], $this->targetEntityTypeId . '.' . $form_state->getValue('id'));
@@ -44,7 +64,8 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
/**
* {@inheritdoc}
*/
- protected function prepareEntity() {
+ protected function prepareEntity()
+ {
$definition = $this->entityTypeManager->getDefinition($this->targetEntityTypeId);
if (!$definition->get('field_ui_base_route') || !$definition->hasViewBuilderClass()) {
throw new NotFoundHttpException();
@@ -53,4 +74,53 @@ protected function prepareEntity() {
$this->entity->setTargetType($this->targetEntityTypeId);
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEntityDisplay($entity_type_id, $bundle, $mode)
+ {
+ $entity_display_repository = \Drupal::service('entity_display.repository');
+ return $entity_display_repository->getViewDisplay($entity_type_id, $bundle, $mode);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getOverviewUrl($mode, $bundle)
+ {
+ $entity_type = $this->entityTypeManager->getDefinition($this->targetEntityTypeId);
+ return Url::fromRoute('entity.entity_view_display.' . $this->targetEntityTypeId . '.view_mode', [
+ 'view_mode_name' => $mode,
+ ] + FieldUI::getRouteBundleParameter($entity_type, $bundle));
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(array $form, FormStateInterface $form_state)
+ {
+ parent::save($form, $form_state);
+ [, $view_mode_name] = explode('.', $form_state->getValue('id'));
+ $target_entity_id = $this->targetEntityTypeId;
+
+ foreach ($form_state->getValue('bundles_by_entity') as $bundle => $value) {
+ if (!empty($value)) {
+ if (!$this->entityTypeManager->getStorage($this->entity->getEntityTypeId())->load($target_entity_id . '.' . $value . '.' . $view_mode_name)) {
+ $display = $this->getEntityDisplay($target_entity_id, $bundle, 'default')->createCopy($view_mode_name);
+ $display->save();
+ }
+ $url = $this->getOverviewUrl($view_mode_name, $value);
+
+ $bundle_info_service = \Drupal::service('entity_type.bundle.info');
+ $bundles = $bundle_info_service->getAllBundleInfo();
+ $bundle_label = $bundles[$target_entity_id][$bundle]['label'];
+ $view_mode_label = $form_state->getValue('label');
+
+ $this->messenger()->addStatus($this->t('<a href=":url">Configure the %display_mode view mode for %bundle_label</a>.', ['%display_mode' => $view_mode_label, '%bundle_label' => $bundle_label, ':url' => $url->toString()]));
+
+ }
+
+ }
+ }
}
diff --git a/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php b/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php
index 527e5f858a..7071a23e56 100644
--- a/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php
+++ b/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php
@@ -147,4 +147,26 @@ protected function alterSettingsSummary(array &$summary, PluginSettingsInterface
$this->moduleHandler->alter('field_widget_settings_summary', $summary, $context);
}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function form(array $form, FormStateInterface $form_state) {
+ $bundle = $this->entity->getTargetBundle();
+ $bundles = \Drupal::service('entity_type.bundle.info')->getAllBundleInfo();
+ $entity = $this->entity->getTargetEntityTypeId();
+ $bundle_label = $bundles[$entity][$bundle]['label'];
+// $form['data']['add_content'] = [
+// '#type' => 'link',
+// '#title' => $this->t("Create new $bundle_label"),
+// '#url' => Url::fromRoute("node.add", ['node_type' => $bundle]),
+// '#attributes' => [
+// 'class' => ['button', 'button--action', 'button-primary'],
+// 'role' => 'button',
+// 'tabindex' => '1',
+// ],
+// ];
+ return parent::form($form, $form_state);
+ }
+
}
diff --git a/core/modules/field_ui/src/Form/FieldAddForm.php b/core/modules/field_ui/src/Form/FieldAddForm.php
new file mode 100644
index 0000000000..6c5eb719b1
--- /dev/null
+++ b/core/modules/field_ui/src/Form/FieldAddForm.php
@@ -0,0 +1,693 @@
+<?php
+
+namespace Drupal\field_ui\Form;
+
+use Drupal\Core\Ajax\AjaxFormHelperTrait;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\OpenModalDialogCommand;
+use Drupal\Core\Ajax\ReplaceCommand;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\field_ui\FieldUI;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a form for the "field storage" add page.
+ *
+ * @internal
+ */
+class FieldAddForm extends FormBase {
+
+ /**
+ * The name of the entity type.
+ *
+ * @var string
+ */
+ protected $entityTypeId;
+
+ /**
+ * The entity bundle.
+ *
+ * @var string
+ */
+ protected $bundle;
+
+ /**
+ * The type of field to add.
+ *
+ * @var string
+ */
+ protected $fieldType;
+
+ /**
+ * The type of field to add.
+ *
+ * @var string
+ */
+ protected $category;
+
+ /**
+ * @var \Drupal\field\FieldConfigInterface
+ */
+ protected $field;
+
+ /**
+ * The entity type manager.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ */
+ protected $entityTypeManager;
+
+ /**
+ * The entity field manager.
+ *
+ * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+ */
+ protected $entityFieldManager;
+
+ /**
+ * The entity display repository.
+ *
+ * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
+ */
+ protected $entityDisplayRepository;
+
+ /**
+ * The field type plugin manager.
+ *
+ * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
+ */
+ protected $fieldTypePluginManager;
+
+ /**
+ * The configuration factory.
+ *
+ * @var \Drupal\Core\Config\ConfigFactoryInterface
+ */
+ protected $configFactory;
+ /**
+ * @var string
+ */
+ protected $group;
+
+ /**
+ * Constructs a new FieldStorageAddForm object.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager
+ * The field type plugin manager.
+ * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+ * The configuration factory.
+ * @param \Drupal\Core\Entity\EntityFieldManagerInterface|null $entity_field_manager
+ * (optional) The entity field manager.
+ * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
+ * (optional) The entity display repository.
+ */
+ public function __construct(EntityTypeManagerInterface $entity_type_manager, FieldTypePluginManagerInterface $field_type_plugin_manager, ConfigFactoryInterface $config_factory, EntityFieldManagerInterface $entity_field_manager = NULL, EntityDisplayRepositoryInterface $entity_display_repository = NULL) {
+ $this->entityTypeManager = $entity_type_manager;
+ $this->fieldTypePluginManager = $field_type_plugin_manager;
+ $this->configFactory = $config_factory;
+ $this->entityFieldManager = $entity_field_manager;
+ $this->entityDisplayRepository = $entity_display_repository;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'field_ui_field_add_form';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('entity_type.manager'),
+ $container->get('plugin.manager.field.field_type'),
+ $container->get('config.factory'),
+ $container->get('entity_field.manager'),
+ $container->get('entity_display.repository')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL, $field_type = '', $group = NULL) {
+ $field_type_plugin_manager = \Drupal::service('plugin.manager.field.field_type');
+ $options = [];
+ // Field type is either the actual field type or category
+ foreach ($field_type_plugin_manager->getGroupDefinitions($field_type_plugin_manager->getUiDefinitions())[$group] as $option) {
+ if ($option['group_display']) {
+ if ($option['id'] === 'entity_reference') {
+ $options[$option['label']->render()] = $option['label'];
+ } else {
+ $options[$option['id']] = $option['label'];
+ }
+ }
+ }
+ if (!$form_state->get('entity_type_id')) {
+ $form_state->set('entity_type_id', $entity_type_id);
+ }
+ if (!$form_state->get('bundle')) {
+ $form_state->set('bundle', $bundle);
+ }
+
+ $form['#prefix'] = '<div id="field-ui-add-form">';
+ $form['#suffix'] = '</div>';
+
+ $form['status_messages'] = [
+ '#type' => 'status_messages',
+ '#weight' => -100,
+ ];
+
+ $this->entityTypeId = $form_state->get('entity_type_id');
+ $this->bundle = $form_state->get('bundle');
+ $this->fieldType = $field_type;
+ $this->group = $group;
+
+ $form['label'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Label'),
+ '#size' => 25,
+ ];
+ if ($options) {
+ $form['options'] = [
+ '#type' => 'container',
+ '#title' => $this->t('Select field type'),
+ '#attributes' => [
+ 'id' => ['select-field-option']
+ ],
+ ];
+ foreach ($options as $option_key => $option) {
+ $form['options'][$option_key] = [
+ '#type' => 'radio',
+ '#title' => $option,
+ '#description' => [
+ '#theme' => 'item_list',
+ // @todo: Replace hardcode
+ '#items' => $this->getFieldDescription($option_key),
+ ],
+ '#id' => "edit-$option_key",
+ '#parents' => ['options'],
+ '#return_value' => $option_key,
+ '#attributes' => [
+ 'class' => ['field-option-radio']
+ ],
+ '#wrapper_attributes' => [
+ 'class' => ['field-option-card'],
+ ]
+ ];
+ if ((string) $option === 'Entity reference') {
+ $form['options'][$option_key]['#title'] = 'Other';
+ $form['options'][$option_key]['#weight'] = 10;
+ }
+ }
+ }
+ $field_prefix = $this->config('field_ui.settings')->get('field_prefix');
+ $form['field_name'] = [
+ '#type' => 'machine_name',
+ '#field_prefix' => $field_prefix,
+ '#size' => 15,
+ '#description' => $this->t('A unique machine-readable name containing letters, numbers, and underscores.'),
+ // Calculate characters depending on the length of the field prefix
+ // setting. Maximum length is 32.
+ '#maxlength' => FieldStorageConfig::NAME_MAX_LENGTH - strlen($field_prefix),
+ '#machine_name' => [
+ 'source' => ['label'],
+ 'exists' => [$this, 'fieldNameExists'],
+ ],
+ '#required' => FALSE,
+ ];
+
+// // Gather valid field types.
+// $field_type_options = [];
+// foreach ($this->fieldTypePluginManager->getGroupedDefinitions($this->fieldTypePluginManager->getUiDefinitions()) as $category => $field_types) {
+// foreach ($field_types as $name => $field_type) {
+// $field_type_options[$category][$name] = $field_type['label'];
+// }
+// }
+
+//
+// $form['tabs'] = [
+// '#theme' => 'field_ui_tabs',
+// '#items' => [
+// [
+// 'value' => [
+// '#type' => 'link',
+// '#title' => $this->t('Basic settings'),
+// '#url' => Url::fromUri('internal://<none>#basic'),
+// '#attributes' => [
+// 'class' => [
+// 'tabs__link',
+// 'js-tabs-link',
+// 'is-active',
+// ],
+// ],
+// ],
+// ],
+// [
+// 'value' => [
+// '#type' => 'link',
+// '#title' => $this->t('Advanced settings'),
+// '#url' => Url::fromUri('internal://<none>#advanced'),
+// '#attributes' => [
+// 'class' => [
+// 'tabs__link',
+// 'js-tabs-link',
+// ],
+// ],
+// ],
+// ],
+// ],
+// ];
+// $form['basic'] = [
+// '#type' => 'container',
+// '#attributes' => [
+// 'id' => ['basic']
+// ],
+// '#title' => $this->t('Basic settings'),
+// ];
+// $form['advanced'] = [
+// '#type' => 'container',
+// '#attributes' => [
+// 'id' => ['advanced']
+// ],
+// '#title' => $this->t('Advanced settings'),
+// ];
+//
+// $form['basic']['label'] = [
+// '#type' => 'textfield',
+// '#title' => $this->t('Label'),
+// '#size' => 25,
+// ];
+// $field_prefix = $this->config('field_ui.settings')->get('field_prefix');
+// $form['basic']['field_name'] = [
+// '#type' => 'machine_name',
+// '#field_prefix' => $field_prefix,
+// '#size' => 15,
+// '#description' => $this->t('A unique machine-readable name containing letters, numbers, and underscores.'),
+// // Calculate characters depending on the length of the field prefix
+// // setting. Maximum length is 32.
+// '#maxlength' => FieldStorageConfig::NAME_MAX_LENGTH - strlen($field_prefix),
+// '#machine_name' => [
+// 'source' => ['basic', 'label'],
+// 'exists' => [$this, 'fieldNameExists'],
+// ],
+// '#required' => FALSE,
+// ];
+//
+// if (str_contains($this->fieldType, 'field_ui:')) {
+// [, $field_type, $option_key] = explode(':', $this->fieldType, 3);
+// // @todo Add support for pre-configured options.
+// }
+//
+// $form['basic']['cardinality'] = [
+// '#type' => 'checkbox',
+// '#title' => $this->t('Allow multiple values'),
+// ];
+// $form['basic']['cardinality_unlimited'] = [
+// '#type' => 'checkbox',
+// '#title' => $this->t('Allow unlimited values'),
+// '#states' => [
+// 'visible' => [
+// ':input[name="cardinality"]' => ['checked' => TRUE],
+// ],
+// ],
+// ];
+// $form['basic']['cardinality_number'] = [
+// '#type' => 'number',
+// '#min' => 2,
+// '#title' => $this->t('Limit'),
+// '#default_value' => '2',
+// '#size' => 2,
+// '#states' => [
+// 'visible' => [
+// ':input[name="cardinality"]' => ['checked' => TRUE],
+// ],
+// 'disabled' => [
+// ':input[name="cardinality_unlimited"]' => ['checked' => TRUE],
+// ]
+// ],
+// ];
+//
+// // Create a run-time plugin instance for the field item to render the
+// // storage settings form.
+// $data_definition = FieldItemDataDefinition::createFromDataType('field_item:' . $field_type);
+// $ids = (object) [
+// 'entity_type' => $this->entityTypeId,
+// 'bundle' => $this->bundle,
+// 'entity_id' => NULL,
+// ];
+// $entity_adapter = EntityAdapter::createFromEntity(_field_create_entity_from_ids($ids));
+// $plugin_class = $this->fieldTypePluginManager->getPluginClass($field_type);
+// /** @var \Drupal\Core\Field\FieldItemInterface $plugin_instance */
+// $plugin_instance = new $plugin_class($data_definition);
+// $plugin_instance->setContext(NULL, $entity_adapter);
+//
+// $form['basic']['field_storage_settings'] = [
+// '#tree' => TRUE,
+// ];
+// $form['basic']['field_storage_settings'] += $plugin_instance->storageSettingsForm($form, $form_state, FALSE);
+// foreach (Element::children($form['basic']['field_storage_settings']) as $child) {
+// if (isset($form['basic']['field_storage_settings'][$child]['#group'])) {
+// $form['basic']['field_storage_settings'][$child]['#parents'] = ['field_storage_settings', $child];
+// $form['advanced'][$child] = $form['basic']['field_storage_settings'][$child];
+// unset($form['basic']['field_storage_settings'][$child]);
+// }
+// }
+//
+// $form['basic']['field_settings'] = [
+// '#tree' => TRUE,
+// ];
+// $form['basic']['field_settings'] += $plugin_instance->fieldSettingsForm($form, $form_state);
+// foreach (Element::children($form['basic']['field_settings']) as $child) {
+// if (isset($form['basic']['field_settings'][$child]['#group'])) {
+// $form['basic']['field_settings'][$child]['#parents'] = ['field_settings', $child];
+// $form['advanced'][$child] = $form['basic']['field_settings'][$child];
+// unset($form['basic']['field_settings'][$child]);
+// }
+// }
+//
+//
+// $form['basic']['required'] = [
+// '#type' => 'checkbox',
+// '#title' => $this->t('Required field'),
+// ];
+//
+ $form['translatable'] = [
+ '#type' => 'value',
+ '#value' => TRUE,
+ ];
+
+// $form['advanced']['description'] = [
+// '#type' => 'textarea',
+// '#title' => $this->t('Help text'),
+// '#rows' => 3,
+// '#attributes' => [
+// 'style' => ['max-width: 50%']
+// ],
+// '#description' => $this->t('Instructions to present to the user below this field on the editing form.<br />Allowed HTML tags: @tags', ['@tags' => FieldFilteredMarkup::displayAllowedTags()]) . '<br />' . $this->t('This field supports tokens.'),
+// ];
+
+ // @todo configuring the default value depends on the field having
+ // initialized.
+// $item_list_class = $this->fieldTypePluginManager->getDefinition($field_type)['list_class'];
+// $item_list_instance = new $item_list_class($plugin_instance->getFieldDefinition());
+// $item_list_instance->setContext(NULL, $entity_adapter);
+// if ($element = $item_list_instance->defaultValuesForm($form, $form_state)) {
+// $element = array_merge($element, [
+// '#type' => 'details',
+// '#title' => $this->t('Default value'),
+// '#open' => TRUE,
+// '#tree' => TRUE,
+// '#description' => $this->t('The default value for this field, used when creating new content.'),
+// '#weight' => 12,
+// ]);
+//
+// $form['default_value'] = $element;
+// }
+
+
+ $form['actions'] = ['#type' => 'actions'];
+ $form['actions']['submit'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Save and configure'),
+ '#button_type' => 'primary',
+ '#ajax' => [
+ 'callback' => [$this, 'ajaxSubmitForm'],
+ ]
+ ];
+
+ $form['#attached']['library'][] = 'field_ui/drupal.field_ui';
+
+ return $form;
+ }
+
+ public function ajaxSubmitForm(array &$form, FormStateInterface $form_state) {
+ $response = new AjaxResponse();
+ if ($form_state::hasAnyErrors() || !$this->field) {
+ $response->addCommand(new ReplaceCommand('#field-ui-add-form', $form));
+ return $response;
+ }
+
+ /** @var \Drupal\Core\Entity\EntityFormBuilderInterface $entity_form_builder */
+ $entity_form_builder = \Drupal::service('entity.form_builder');
+ $next_form = $entity_form_builder->getForm($this->field, 'edit');
+
+ $dialog_options = [
+ 'width' => '85vw',
+ ];
+ $response->addCommand(new OpenModalDialogCommand($this->t('Configure field'), $next_form, $dialog_options));
+
+ return $response;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state) {
+ // Missing label.
+ if (!$form_state->getValue('label')) {
+ $form_state->setErrorByName('label', $this->t('Add new field: you need to provide a label.'));
+ }
+
+ // Missing field name.
+ if (!$form_state->getValue('field_name')) {
+ $form_state->setErrorByName('field_name', $this->t('Add new field: you need to provide a machine name for the field.'));
+ }
+ // Field name validation.
+ else {
+ $field_name = $form_state->getValue('field_name');
+
+ // Add the field prefix.
+ $field_name = $this->configFactory->get('field_ui.settings')->get('field_prefix') . $field_name;
+ $form_state->setValueForElement($form['field_name'], $field_name);
+ }
+ }
+
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $error = FALSE;
+ $values = $form_state->getValues();
+ $destinations = [];
+ $entity_type = $this->entityTypeManager->getDefinition($this->entityTypeId);
+ // @todo: Remove hardcode
+ $reference_fields = ['Content', 'Entity reference', 'Taxonomy term', 'User'];
+ if (in_array($values['options'], $reference_fields)) {
+ $selected_radio_option = 'entity_reference';
+ } else {
+ $selected_radio_option = $values['options'];
+ }
+ $field_type_hacky = $this->fieldType === 'field_ui:entity_reference:media' ? 'entity_reference' : $this->fieldType;
+ $field_type = $this->group === 'Other' ? $field_type_hacky : $selected_radio_option;
+ // Create new field.
+ $field_storage_values = [
+ 'field_name' => $values['field_name'],
+ 'entity_type' => $this->entityTypeId,
+ 'type' => $field_type,
+ 'translatable' => $values['translatable'],
+// 'settings' => $values['field_storage_settings'],
+// 'cardinality' => !$values['cardinality'] ? 1 : ($values['cardinality_unlimited'] ? FieldStorageConfigInterface::CARDINALITY_UNLIMITED : $values['cardinality_number']),
+ ];
+ $field_values = [
+ 'field_name' => $values['field_name'],
+ 'entity_type' => $this->entityTypeId,
+ 'bundle' => $this->bundle,
+// 'required' => $values['required'],
+ 'label' => $values['label'],
+ // Field translatability should be explicitly enabled by the users.
+ 'translatable' => FALSE,
+// 'settings' => $values['field_settings']
+ ];
+ $widget_id = $formatter_id = NULL;
+ $widget_settings = $formatter_settings = [];
+
+// // Check if we're dealing with a preconfigured field.
+// if (strpos($field_storage_values['type'], 'field_ui:') !== FALSE) {
+// [, $field_type, $option_key] = explode(':', $field_storage_values['type'], 3);
+// $field_storage_values['type'] = $field_type;
+//
+// $field_definition = $this->fieldTypePluginManager->getDefinition($field_type);
+// $options = $this->fieldTypePluginManager->getPreconfiguredOptions($field_definition['id']);
+// $field_options = $options[$option_key];
+//
+// // Merge in preconfigured field storage options.
+// if (isset($field_options['field_storage_config'])) {
+// foreach (['cardinality', 'settings'] as $key) {
+// if (isset($field_options['field_storage_config'][$key])) {
+// $field_storage_values[$key] = $field_options['field_storage_config'][$key];
+// }
+// }
+// }
+//
+// // Merge in preconfigured field options.
+// if (isset($field_options['field_config'])) {
+// foreach (['required', 'settings'] as $key) {
+// if (isset($field_options['field_config'][$key])) {
+// $field_values[$key] = $field_options['field_config'][$key];
+// }
+// }
+// }
+//
+// $widget_id = $field_options['entity_form_display']['type'] ?? NULL;
+// $widget_settings = $field_options['entity_form_display']['settings'] ?? [];
+// $formatter_id = $field_options['entity_view_display']['type'] ?? NULL;
+// $formatter_settings = $field_options['entity_view_display']['settings'] ?? [];
+// }
+
+ // Create the field storage and field.
+ try {
+ $this->entityTypeManager->getStorage('field_storage_config')->create($field_storage_values)->save();
+ $field = $this->entityTypeManager->getStorage('field_config')->create($field_values);
+ $field->save();
+ $this->field = $field;
+
+ $this->configureEntityFormDisplay($values['field_name'], $widget_id, $widget_settings);
+ $this->configureEntityViewDisplay($values['field_name'], $formatter_id, $formatter_settings);
+
+ // Always show the field settings step, as the cardinality needs to be
+ // configured for new fields.
+ $route_parameters = [
+ 'field_config' => $field->id(),
+ ] + FieldUI::getRouteBundleParameter($entity_type, $this->bundle);
+ $destinations[] = ['route_name' => "entity.field_config.{$this->entityTypeId}_storage_edit_form", 'route_parameters' => $route_parameters];
+ $destinations[] = ['route_name' => "entity.field_config.{$this->entityTypeId}_field_edit_form", 'route_parameters' => $route_parameters];
+ $destinations[] = ['route_name' => "entity.{$this->entityTypeId}.field_ui_fields", 'route_parameters' => $route_parameters];
+
+ // Store new field information for any additional submit handlers.
+ $form_state->set(['fields_added', '_add_new_field'], $values['field_name']);
+ }
+ catch (\Exception $e) {
+ $error = TRUE;
+ $this->messenger()->addError($this->t('There was a problem creating field %label: @message', ['%label' => $values['label'], '@message' => $e->getMessage()]));
+ }
+
+ if ($destinations) {
+ $destination = $this->getDestinationArray();
+ $destinations[] = $destination['destination'];
+ $form_state->setRedirectUrl(FieldUI::getNextDestination($destinations));
+ }
+ elseif (!$error) {
+ $this->messenger()->addStatus($this->t('Your settings have been saved.'));
+ }
+ }
+
+ /**
+ * Configures the field for the default form mode.
+ *
+ * @param string $field_name
+ * The field name.
+ * @param string|null $widget_id
+ * (optional) The plugin ID of the widget. Defaults to NULL.
+ * @param array $widget_settings
+ * (optional) An array of widget settings. Defaults to an empty array.
+ */
+ protected function configureEntityFormDisplay($field_name, $widget_id = NULL, array $widget_settings = []) {
+ $options = [];
+ if ($widget_id) {
+ $options['type'] = $widget_id;
+ if (!empty($widget_settings)) {
+ $options['settings'] = $widget_settings;
+ }
+ }
+ // Make sure the field is displayed in the 'default' form mode (using
+ // default widget and settings). It stays hidden for other form modes
+ // until it is explicitly configured.
+ $this->entityDisplayRepository->getFormDisplay($this->entityTypeId, $this->bundle, 'default')
+ ->setComponent($field_name, $options)
+ ->save();
+ }
+
+ /**
+ * Configures the field for the default view mode.
+ *
+ * @param string $field_name
+ * The field name.
+ * @param string|null $formatter_id
+ * (optional) The plugin ID of the formatter. Defaults to NULL.
+ * @param array $formatter_settings
+ * (optional) An array of formatter settings. Defaults to an empty array.
+ */
+ protected function configureEntityViewDisplay($field_name, $formatter_id = NULL, array $formatter_settings = []) {
+ $options = [];
+ if ($formatter_id) {
+ $options['type'] = $formatter_id;
+ if (!empty($formatter_settings)) {
+ $options['settings'] = $formatter_settings;
+ }
+ }
+ // Make sure the field is displayed in the 'default' view mode (using
+ // default formatter and settings). It stays hidden for other view
+ // modes until it is explicitly configured.
+ $this->entityDisplayRepository->getViewDisplay($this->entityTypeId, $this->bundle)
+ ->setComponent($field_name, $options)
+ ->save();
+ }
+
+ /**
+ * Checks if a field machine name is taken.
+ *
+ * @param string $value
+ * The machine name, not prefixed.
+ * @param array $element
+ * An array containing the structure of the 'field_name' element.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @return bool
+ * Whether or not the field machine name is taken.
+ */
+ public function fieldNameExists($value, $element, FormStateInterface $form_state) {
+ // Don't validate the case when an existing field has been selected.
+ if ($form_state->getValue('existing_storage_name')) {
+ return FALSE;
+ }
+
+ // Add the field prefix.
+ $field_name = $this->configFactory->get('field_ui.settings')->get('field_prefix') . $value;
+
+ $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($this->entityTypeId);
+ return isset($field_storage_definitions[$field_name]);
+ }
+
+ public function getFieldDescription($field_name_key) {
+ $descriptions = [
+ 'string' => ['Ideal for titles and names', 'Efficient storage for short text', 'Requires specifying a maximum length', 'Good for fields with known or predictable length'],
+ 'string_long' => ['Ideal for longer texts, like body or description', 'Supports long text without specifying a maximum length', 'May use more storage and be slower for searching and sorting'],
+
+ 'text' => ['Ideal for titles and names that need to support markup such as bold, italics or links', 'Efficient storage for short text', 'Requires specifying a maximum length', 'Good for fields with known or predictable lengths'],
+ 'text_long' => ['Ideal for longer texts, like body or description without a summary', 'Supports long text without specifying a maximum length', 'May use more storage and be slower for searching and sorting'],
+ 'text_with_summary' => ['Ideal for longer texts, like body or description with a summary', 'Allows specifying a summary for the text', 'Supports long text without specifying a maximum length', 'May use more storage and be slower for searching and sorting'],
+
+ 'decimal' => ['Ideal for exact counts and measures (prices, temperatures, distances, volumes, etc.)','Precise storage of decimal numbers', 'Consistent decimal place', 'For example, 12.34 km or € when used for further detailed calculations (such as summing many of these)'],
+ 'float' => ['In most instances, it is best to use Number (decimal) instead, as decimal numbers stored as floats may contain errors in precision', 'This type of field offers faster processing and more compact storage, but the differences are typically negligible on modern sites', 'For example, 123.4 km when used in imprecise contexts such as a walking trail distance'],
+ 'integer' => ['Numbers without decimals', 'For example, 123'],
+
+ 'datetime' => ['Ideal when date and time needs to be input by users, like event dates and times', 'Date or date and time stored in a readable string format', 'Easy to read and understand for humans'],
+ 'timestamp' => ['Ideal for using date and time calculations or comparisons', 'Date and time stored in the form of seconds since January 1, 1970 (UTC)', 'Compact and efficient for storage, sorting and calculations'],
+
+ 'file' => ['For uploading files', 'Can be configured with options such as allowed file extensions and maximum upload size.'],
+ 'image' => ['For uploading images', 'Allows a user to upload an image with configurable extensions, image resolutions, upload size', 'Can be configured with options such as allowed file extensions, maximum upload size and image resolution minimums/maximums.'],
+
+ 'list_float' => ['Values stored are floating-point numbers', 'For example, 123.4'],
+ 'list_integer' => ['Values stored are numbers without decimals', 'For example, 123'],
+ 'list_string' => ['Values stored are text'],
+ ];
+ return $descriptions[$field_name_key] ?? 'fallback';
+ }
+
+}
diff --git a/core/modules/field_ui/src/Form/FieldConfigEditForm.php b/core/modules/field_ui/src/Form/FieldConfigEditForm.php
index f2346e7069..4e52f521c0 100644
--- a/core/modules/field_ui/src/Form/FieldConfigEditForm.php
+++ b/core/modules/field_ui/src/Form/FieldConfigEditForm.php
@@ -2,12 +2,26 @@
namespace Drupal\field_ui\Form;
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Ajax\AjaxFormHelperTrait;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\CloseDialogCommand;
+use Drupal\Core\Ajax\CloseModalDialogCommand;
+use Drupal\Core\Ajax\OpenModalDialogCommand;
+use Drupal\Core\Ajax\RedirectCommand;
+use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Field\FieldFilteredMarkup;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Form\FormBuilderInterface;
+use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
use Drupal\Core\Url;
+use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\FieldConfigInterface;
+use Drupal\field\FieldStorageConfigInterface;
use Drupal\field_ui\FieldUI;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -25,6 +39,13 @@ class FieldConfigEditForm extends EntityForm {
*/
protected $entity;
+ /**
+ * The field storage being used by this form.
+ *
+ * @var \Drupal\field\FieldStorageConfigInterface
+ */
+ protected $fieldStorage;
+
/**
* The entity type bundle info service.
*
@@ -57,33 +78,128 @@ public static function create(ContainerInterface $container) {
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
- $field_storage = $this->entity->getFieldStorageDefinition();
- $bundles = $this->entityTypeBundleInfo->getBundleInfo($this->entity->getTargetEntityTypeId());
-
- $form_title = $this->t('%field settings for %bundle', [
- '%field' => $this->entity->getLabel(),
- '%bundle' => $bundles[$this->entity->getTargetBundle()]['label'],
- ]);
- $form['#title'] = $form_title;
+ $this->fieldStorage = $this->entity->getFieldStorageDefinition();
- if ($field_storage->isLocked()) {
+ if ($this->fieldStorage->isLocked()) {
$form['locked'] = [
'#markup' => $this->t('The field %field is locked and cannot be edited.', ['%field' => $this->entity->getLabel()]),
];
return $form;
}
+ $form['#prefix'] = '<div id="field-ui-edit-form">';
+ $form['#suffix'] = '</div>';
+
+ $form['tabs'] = [
+ '#theme' => 'field_ui_tabs',
+ '#items' => [
+ [
+ 'value' => [
+ '#type' => 'link',
+ '#title' => $this->t('Basic settings'),
+ '#url' => Url::fromUri('internal://<none>#basic'),
+ '#attributes' => [
+ 'class' => [
+ 'tabs__link',
+ 'js-tabs-link',
+ 'is-active',
+ ],
+ ],
+ ],
+ ],
+ [
+ 'value' => [
+ '#type' => 'link',
+ '#title' => $this->t('Advanced settings'),
+ '#url' => Url::fromUri('internal://<none>#advanced'),
+ '#attributes' => [
+ 'class' => [
+ 'tabs__link',
+ 'js-tabs-link',
+ ],
+ ],
+ ],
+ ],
+ ],
+ ];
+ $form['basic'] = [
+ '#type' => 'container',
+ '#attributes' => [
+ 'id' => ['basic']
+ ],
+ '#title' => $this->t('Basic settings'),
+ ];
+ $form['advanced'] = [
+ '#type' => 'container',
+ '#attributes' => [
+ 'id' => ['advanced']
+ ],
+ '#title' => $this->t('Advanced settings'),
+ ];
+
// Build the configurable field values.
- $form['label'] = [
+ $form['basic']['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
- '#default_value' => $this->entity->getLabel() ?: $field_storage->getName(),
+ '#default_value' => $this->entity->getLabel() ?: $this->fieldStorage->getName(),
'#required' => TRUE,
'#maxlength' => 255,
'#weight' => -20,
];
- $form['description'] = [
+ $form['basic']['storage'] = [];
+ $form['advanced']['storage'] = [];
+ if (array_diff($this->fieldStorage->getBundles(), [$this->entity->getTargetBundle()])) {
+ $bundle_info = $this->entityTypeBundleInfo->getAllBundleInfo();
+ $bundle_labels = array_map(function($bundle) use ($bundle_info) {
+ return $bundle_info[$this->fieldStorage->getTargetEntityTypeId()][$bundle]['label'];
+ }, $this->fieldStorage->getBundles());
+ $form['basic']['storage'] = [
+ '#type' => 'fieldset',
+ '#title' => $this->t('Field Storage'),
+ '#description_display' => 'before',
+ '#weight' => -15,
+ '#description' => $this->t('These settings apply to the %field field everywhere it is used (%other_types). Some also impact the way that data is stored and cannot be changed once data has been created.', ['%field' => $this->entity->getLabel(), '%other_types' => implode(', ', $bundle_labels)]),
+ ];
+ $form['advanced']['storage'] = [
+ '#type' => 'fieldset',
+ '#title' => $this->t('Field Storage'),
+ '#description_display' => 'before',
+ '#weight' => -15,
+ '#description' => $this->t('These settings apply to the %field field everywhere it is used (%other_types). Some also impact the way that data is stored and cannot be changed once data has been created.', ['%field' => $this->entity->getLabel(), '%other_types' => implode(', ', $bundle_labels)]),
+ ];
+ }
+
+ $form['basic']['storage']['cardinality'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Allow multiple values (cardinality)'),
+ '#default_value' => $this->fieldStorage->getCardinality() > 1 || $this->fieldStorage->getCardinality() === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
+ ];
+ $form['basic']['storage']['cardinality_unlimited'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Allow unlimited values (cardinality)'),
+ '#default_value' => $this->fieldStorage->getCardinality() === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
+ '#states' => [
+ 'invisible' => [
+ ':input[name="cardinality"]' => ['checked' => FALSE],
+ ],
+ ],
+ ];
+ $form['basic']['storage']['cardinality_number'] = [
+ '#type' => 'number',
+ '#min' => 2,
+ '#title' => $this->t('Limit'),
+ '#default_value' => $this->fieldStorage->getCardinality() > 1 ? $this->fieldStorage->getCardinality() : '2',
+ '#size' => 2,
+ '#states' => [
+ 'visible' => [
+ ':input[name="cardinality"]' => ['checked' => TRUE],
+ ':input[name="cardinality_unlimited"]' => ['checked' => FALSE],
+ ],
+ ],
+ ];
+
+ $form['advanced']['description'] = [
'#type' => 'textarea',
'#title' => $this->t('Help text'),
'#default_value' => $this->entity->getDescription(),
@@ -92,7 +208,7 @@ public function form(array $form, FormStateInterface $form_state) {
'#weight' => -10,
];
- $form['required'] = [
+ $form['basic']['required'] = [
'#type' => 'checkbox',
'#title' => $this->t('Required field'),
'#default_value' => $this->entity->isRequired(),
@@ -109,32 +225,84 @@ public function form(array $form, FormStateInterface $form_state) {
$items = $form['#entity']->get($this->entity->getName());
$item = $items->first() ?: $items->appendItem();
- // Add field settings for the field type and a container for third party
- // settings that modules can add to via hook_form_FORM_ID_alter().
- $form['settings'] = [
+ $form['basic']['storage']['field_storage_settings'] = [
+ '#tree' => TRUE,
+ ];
+ $form['basic']['storage']['field_storage_settings'] += $item->storageSettingsForm($form, $form_state, $this->fieldStorage->hasData());
+ foreach (Element::children($form['basic']['storage']['field_storage_settings']) as $child) {
+ if (isset($form['basic']['storage']['field_storage_settings'][$child]['#group'])) {
+ $form['basic']['storage']['field_storage_settings'][$child]['#parents'] = ['field_storage_settings', $child];
+ $form['advanced']['storage']['field_storage_settings'][$child] = $form['basic']['storage']['field_storage_settings'][$child];
+ unset($form['basic']['storage']['field_storage_settings'][$child]);
+ }
+ }
+
+ // Avoid empty fieldsets.
+ if (!Element::children($form['basic']['storage'])) {
+ unset($form['basic']['storage']);
+ }
+ if (!Element::children($form['advanced']['storage'])) {
+ unset($form['advanced']['storage']);
+ }
+
+ $form['basic']['settings'] = [
'#tree' => TRUE,
- '#weight' => 10,
];
- $form['settings'] += $item->fieldSettingsForm($form, $form_state);
+ $form['basic']['settings'] += $item->fieldSettingsForm($form, $form_state);
+ foreach (Element::children($form['basic']['settings']) as $child) {
+ if (isset($form['basic']['settings'][$child]['#group'])) {
+ $form['basic']['settings'][$child]['#parents'] = ['settings', $child];
+ $form['advanced']['settings'][$child] = $form['basic']['settings'][$child];
+ unset($form['basic']['settings'][$child]);
+ }
+ }
+
+
$form['third_party_settings'] = [
'#tree' => TRUE,
'#weight' => 11,
+ '#group' => 'advanced',
];
// Add handling for default value.
if ($element = $items->defaultValuesForm($form, $form_state)) {
+ $has_default_value = FALSE;
+ if ($element['widget']) {
+ foreach (Element::children($element['widget']) as $child) {
+ if (isset($element['widget'][$child]['#default_value'])) {
+ $has_default_value = TRUE;
+ break;
+ }
+ }
+ }
+ $form['advanced']['default_value_checkbox'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Set initial value (default value)'),
+ '#default_value' => $has_default_value,
+ '#description' => $this->t('Provide a pre-filled value for the editing form.'),
+ ];
+
$element = array_merge($element, [
'#type' => 'details',
- '#title' => $this->t('Default value'),
+ '#title' => $this->t('Initial value (default value)'),
'#open' => TRUE,
'#tree' => TRUE,
- '#description' => $this->t('The default value for this field, used when creating new content.'),
+ '#description' => $this->t('The default value for this field to pre-fill the form when creating new content.'),
'#weight' => 12,
+ '#states' => [
+ 'invisible' => [
+ ':input[name="default_value_checkbox"]' => ['checked' => FALSE],
+ ],
+ ],
]);
- $form['default_value'] = $element;
+ $form['advanced']['default_value'] = $element;
}
+ $form['#attached']['library'][] = 'field_ui/drupal.field_ui';
+ $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
+// $form['#action'] = Url::fromRoute("entity.field_config.{$this->entity->getTargetEntityTypeId()}_field_edit_form", ['field_config' => $this->entity->id(), 'node_type' => $this->entity->getTargetBundle()])->toString();
+
return $form;
}
@@ -144,6 +312,15 @@ public function form(array $form, FormStateInterface $form_state) {
protected function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
$actions['submit']['#value'] = $this->t('Save settings');
+ $actions['submit']['#ajax'] = [
+ 'callback' => [$this, 'ajaxSubmitForm'],
+ 'url' => Url::fromRoute("entity.field_config.{$this->entity->getTargetEntityTypeId()}_field_edit_form", ['field_config' => $this->entity->id(), 'node_type' => $this->entity->getTargetBundle()]),
+ 'options' => [
+ 'query' => [
+ FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
+ ],
+ ],
+ ];
if (!$this->entity->isNew()) {
$target_entity_type = $this->entityTypeManager->getDefinition($this->entity->getTargetEntityTypeId());
@@ -163,7 +340,11 @@ protected function actions(array $form, FormStateInterface $form_state) {
'#url' => $url,
'#access' => $this->entity->access('delete'),
'#attributes' => [
- 'class' => ['button', 'button--danger'],
+ 'class' => ['button', 'button--danger', 'use-ajax'],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => Json::encode([
+ 'width' => '85vw',
+ ]),
],
];
}
@@ -171,15 +352,27 @@ protected function actions(array $form, FormStateInterface $form_state) {
return $actions;
}
+ public function ajaxSubmitForm(array &$form, FormStateInterface $form_state) {
+ $response = new AjaxResponse();
+ if ($form_state::hasAnyErrors()) {
+ $response->addCommand(new ReplaceCommand('#field-ui-edit-form', $form));
+ return $response;
+ }
+
+ $response->addCommand(new CloseModalDialogCommand());
+ $response->addCommand(new RedirectCommand(FieldUI::getOverviewRouteInfo($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle())->toString()));
+ return $response;
+ }
+
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
- if (isset($form['default_value'])) {
+ if (isset($form['advanced']['default_value'])) {
$item = $form['#entity']->get($this->entity->getName());
- $item->defaultValuesFormValidate($form['default_value'], $form, $form_state);
+ $item->defaultValuesFormValidate($form['advanced']['default_value'], $form, $form_state);
}
}
@@ -191,11 +384,16 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
// Handle the default value.
$default_value = [];
- if (isset($form['default_value'])) {
+ if (isset($form['advanced']['default_value'])) {
$items = $form['#entity']->get($this->entity->getName());
- $default_value = $items->defaultValuesFormSubmit($form['default_value'], $form, $form_state);
+ $default_value = $items->defaultValuesFormSubmit($form['advanced']['default_value'], $form, $form_state);
}
$this->entity->setDefaultValue($default_value);
+
+ $this->fieldStorage->setCardinality(!$form_state->getValue('cardinality') ? 1 : ($form_state->getValue('cardinality_unlimited') ? FieldStorageConfigInterface::CARDINALITY_UNLIMITED : $form_state->getValue('cardinality_number')));
+ if ($form_state->getValue('field_storage_settings')) {
+ $this->fieldStorage->setSettings($form_state->getValue('field_storage_settings'));
+ }
}
/**
@@ -203,6 +401,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
*/
public function save(array $form, FormStateInterface $form_state) {
$this->entity->save();
+ $this->fieldStorage->save();
$this->messenger()->addStatus($this->t('Saved %label configuration.', ['%label' => $this->entity->getLabel()]));
diff --git a/core/modules/field_ui/src/Form/FieldStorageAddForm.php b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
index 9e4f3ddd4f..7406e912b9 100644
--- a/core/modules/field_ui/src/Form/FieldStorageAddForm.php
+++ b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
@@ -9,10 +9,12 @@
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\field_ui\FieldUI;
use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Component\Serialization\Json;
/**
* Provides a form for the "field storage" add page.
@@ -152,12 +154,18 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t
'#type' => 'item',
'#markup' => $this->t('or'),
];
+
+ $entity_type = $this->entityTypeManager->getDefinition($this->entityTypeId);
+
$form['add']['existing_storage_name'] = [
- '#type' => 'select',
- '#title' => $this->t('Re-use an existing field'),
- '#options' => $existing_field_storage_options,
- '#empty_option' => $this->t('- Select an existing field -'),
- ];
+ '#type' => 'link',
+ '#title' => 'Re-use an existing field',
+ '#value' => $this->t('Re-use an existing field'),
+ '#url' => Url::fromRoute("field_ui.field_storage_config_add_{$this->entityTypeId}.reuse", FieldUI::getRouteBundleParameter($entity_type, $this->bundle)),
+ '#attributes' => [
+ 'class' => ['button', 'button--action', 'button-primary'],
+ ]
+ ];
$form['#attached']['drupalSettings']['existingFieldLabels'] = $this->getExistingFieldLabels(array_keys($existing_field_storage_options));
}
diff --git a/core/modules/field_ui/src/Form/FieldStorageReuseForm.php b/core/modules/field_ui/src/Form/FieldStorageReuseForm.php
new file mode 100644
index 0000000000..dff1406b32
--- /dev/null
+++ b/core/modules/field_ui/src/Form/FieldStorageReuseForm.php
@@ -0,0 +1,373 @@
+<?php
+
+
+namespace Drupal\field_ui\Form;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\OpenModalDialogCommand;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\field\FieldStorageConfigInterface;
+use Drupal\field_ui\FieldUI;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a form for the "field storage" add page.
+ *
+ * @internal
+ */
+class FieldStorageReuseForm extends FormBase {
+
+ /**
+ * The name of the entity type.
+ *
+ * @var string
+ */
+ protected $entityTypeId;
+
+ /**
+ * The entity bundle.
+ *
+ * @var string
+ */
+ protected $bundle;
+
+ /**
+ * The entity type manager.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ */
+ protected $entityTypeManager;
+
+ /**
+ * The entity field manager.
+ *
+ * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+ */
+ protected $entityFieldManager;
+
+ /**
+ * The entity display repository.
+ *
+ * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
+ */
+ protected $entityDisplayRepository;
+
+ /**
+ * The field type plugin manager.
+ *
+ * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
+ */
+ protected $fieldTypePluginManager;
+
+ /**
+ * The configuration factory.
+ *
+ * @var \Drupal\Core\Config\ConfigFactoryInterface
+ */
+ protected $configFactory;
+
+ /**
+ * Constructs a new FieldStorageAddForm object.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager
+ * The field type plugin manager.
+ * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+ * The configuration factory.
+ * @param \Drupal\Core\Entity\EntityFieldManagerInterface|null $entity_field_manager
+ * (optional) The entity field manager.
+ * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
+ * (optional) The entity display repository.
+ */
+ public function __construct(EntityTypeManagerInterface $entity_type_manager, FieldTypePluginManagerInterface $field_type_plugin_manager, ConfigFactoryInterface $config_factory, EntityFieldManagerInterface $entity_field_manager = NULL, EntityDisplayRepositoryInterface $entity_display_repository = NULL)
+ {
+ $this->entityTypeManager = $entity_type_manager;
+ $this->fieldTypePluginManager = $field_type_plugin_manager;
+ $this->configFactory = $config_factory;
+ $this->entityFieldManager = $entity_field_manager;
+ $this->entityDisplayRepository = $entity_display_repository;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId()
+ {
+ return 'field_ui_field_storage_reuse_form';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container)
+ {
+ return new static(
+ $container->get('entity_type.manager'),
+ $container->get('plugin.manager.field.field_type'),
+ $container->get('config.factory'),
+ $container->get('entity_field.manager'),
+ $container->get('entity_display.repository'),
+
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL)
+ {
+ if (!$form_state->get('entity_type_id')) {
+ $form_state->set('entity_type_id', $entity_type_id);
+ }
+ if (!$form_state->get('bundle')) {
+ $form_state->set('bundle', $bundle);
+ }
+
+ $this->entityTypeId = $form_state->get('entity_type_id');
+ $this->bundle = $form_state->get('bundle');
+ $entity_type = $this->entityTypeManager->getDefinition($this->entityTypeId);
+
+ // Gather valid field types.
+ $field_type_options = [];
+ foreach ($this->fieldTypePluginManager->getGroupedDefinitions($this->fieldTypePluginManager->getUiDefinitions()) as $category => $field_types) {
+ foreach ($field_types as $name => $field_type) {
+ $field_type_options[$category][$name] = $field_type['label'];
+ }
+ }
+
+ $form['text'] = [
+ '#type' => 'inline_template',
+ '#template' => "You can re-use a field from other sub-types of the same entity type. Re-using a field creates another usage of the same field storage.",
+ ];
+
+ $form['search'] = [
+ '#type' => 'search',
+ '#placeholder' => $this->t('Search'),
+ ];
+
+ $form['add'] = [
+ '#type' => 'container',
+ '#attributes' => ['class' => ['form--inline', 'clearfix']],
+ ];
+ $bundle_info_service = \Drupal::service('entity_type.bundle.info');
+ $bundles = $bundle_info_service->getAllBundleInfo();
+ $existing_field_storage_options = $this->getExistingFieldStorageOptions();
+
+ $rows = [];
+ foreach ($existing_field_storage_options as $field) {
+ $field_bundles = $field['field_storage']->getBundles();
+ $summary = \Drupal::service('plugin.manager.field.field_type')->getStorageSettingsSummary($field['field_storage']);
+ $cardinality = $field['field_storage']->getCardinality();
+ $readable_cardinality = $cardinality === -1 ? 'Unlimited' : ($cardinality === 1 ? 'Single value' : "Multiple values: $cardinality");
+ $max_length = is_integer($field['field_storage']->getSetting('max_length')) ? "Max length: {$field['field_storage']->getSetting('max_length')}" : '';
+
+ // Remove empty values.
+ $list = array_filter([...$summary, $readable_cardinality, $max_length]);
+ $settings_summary = empty($list) ? [] : [
+ '#theme' => 'item_list',
+ '#items' => $list,
+ '#attributes' => [
+ 'class' => ['field-settings-summary-cell'],
+ ],
+ ];
+ $bundle_label_arr = [];
+ foreach ($field_bundles as $bundle) {
+ $bundle_label_arr[] = $bundles[$this->entityTypeId][$bundle]['label'];
+ }
+
+ // Combine bundles to be a single string separated by a comma.
+ $bundle_labels = implode(", ", $bundle_label_arr);
+ $row = [
+ 'field' => ['#markup' => $field['field_name']],
+ 'field_type' => ['#markup' => $field['field_type']],
+ 'settings' => $settings_summary,
+ 'content_type' => ['#markup' => $bundle_labels],
+ 'operations' => [
+ '#type' => 'button',
+ '#name' => $field['field_name'],
+ '#button_type' => 'small',
+ '#value' => $this->t('Re-use'),
+ '#wrapper_attributes' => [
+ 'colspan' => 5,
+ ],
+ '#attributes' => [
+ 'class' => ['button', 'button--action', 'button-primary', 'use-ajax'],
+ 'data-dialog-type' => 'modal',
+ ],
+ '#ajax' => [
+ 'callback' => [$this, 'reuseField'],
+ ],
+ '#submit' => [],
+ ],
+ ];
+ $rows[] = $row;
+ }
+
+ // Sort rows by field name.
+ ksort($rows);
+ $form['add']['table'] = [
+ '#type' => 'table',
+ '#header' => array($this->t('Field'), $this->t('Field Type'), $this->t('Settings'), $this->t('Content Type'), $this->t('Operations')),
+ ];
+ $form['add']['table'] += $rows;
+
+ // Place the 'translatable' property as an explicit value so that contrib
+ // modules can form_alter() the value for newly created fields. By default
+ // we create field storage as translatable so it will be possible to enable
+ // translation at field level.
+ $form['translatable'] = [
+ '#type' => 'value',
+ '#value' => TRUE,
+ ];
+
+ $form['#attached']['library'][] = 'field_ui/drupal.field_ui';
+
+ return $form;
+ }
+
+ /**
+ * Returns an array of existing field storages that can be added to a bundle.
+ *
+ * @return array
+ * An array of existing field storages keyed by name.
+ */
+ protected function getExistingFieldStorageOptions()
+ {
+ $options = [];
+ // Load the field_storages and build the list of options.
+ $field_types = $this->fieldTypePluginManager->getDefinitions();
+ foreach ($this->entityFieldManager->getFieldStorageDefinitions($this->entityTypeId) as $field_name => $field_storage) {
+ // Do not show:
+ // - non-configurable field storages,
+ // - locked field storages,
+ // - field storages that should not be added via user interface,
+ // - field storages that already have a field in the bundle.
+ $field_type = $field_storage->getType();
+ if ($field_storage instanceof FieldStorageConfigInterface
+ && !$field_storage->isLocked()
+ && empty($field_types[$field_type]['no_ui'])
+ && !in_array($this->bundle, $field_storage->getBundles(), TRUE)) {
+
+ $options[$field_name] = [
+ 'field_type' => $field_types[$field_type]['label'],
+ 'field_name' => $field_name,
+ 'field_storage' => $field_storage,
+ ];
+ }
+ }
+
+ asort($options);
+
+ return $options;
+ }
+
+ public function reuseField(array &$form, FormStateInterface $form_state)
+ {
+ $entity_type = $this->entityTypeManager->getDefinition($this->entityTypeId);
+ $reuse_button = $form_state->getTriggeringElement();
+ $field_name = $reuse_button['#name'];
+ $fields = $this->entityTypeManager->getStorage('field_config')->getQuery()
+ ->condition('entity_type', $this->entityTypeId)
+ ->condition('field_name', $field_name)
+ ->execute();
+ $field = $this->entityTypeManager->getStorage('field_config')->load(reset($fields));
+ $existing_storage_label = $field->label();
+ try {
+ $field = $this->entityTypeManager->getStorage('field_config')->create([
+ 'field_name' => $field_name,
+ 'entity_type' => $this->entityTypeId,
+ 'bundle' => $this->bundle,
+ 'label' => $existing_storage_label,
+ ]);
+ $field->save();
+
+ $this->configureEntityFormDisplay($field_name);
+ $this->configureEntityViewDisplay($field_name);
+
+ $route_parameters = [
+ 'field_config' => $field->id(),
+ ] + FieldUI::getRouteBundleParameter($entity_type, $this->bundle);
+ $destinations[] = ['route_name' => "entity.field_config.{$this->entityTypeId}_field_edit_form", 'route_parameters' => $route_parameters];
+ $destinations[] = ['route_name' => "entity.{$this->entityTypeId}.field_ui_fields", 'route_parameters' => $route_parameters];
+
+ // Store new field information for any additional submit handlers.
+ $form_state->set(['fields_added', '_add_existing_field'], $field_name);
+ /** @var \Drupal\Core\Entity\EntityFormBuilderInterface $entity_form_builder */
+ $entity_form_builder = \Drupal::service('entity.form_builder');
+ $next_form = $entity_form_builder->getForm($field, 'edit');
+ $dialog_options = [
+ 'width' => '85vw',
+ ];
+ $response = new AjaxResponse();
+ $response->addCommand(new OpenModalDialogCommand($this->t('Configure field'), $next_form, $dialog_options));
+ return $response;
+ }
+ catch (\Exception $e) {
+ $this->messenger()->addError($this->t('There was a problem reusing field %label: @message', ['%label' => $existing_storage_label, '@message' => $e->getMessage()]));
+ }
+ }
+
+ /**
+ * Configures the field for the default form mode.
+ *
+ * @param string $field_name
+ * The field name.
+ * @param string|null $widget_id
+ * (optional) The plugin ID of the widget. Defaults to NULL.
+ * @param array $widget_settings
+ * (optional) An array of widget settings. Defaults to an empty array.
+ */
+ protected function configureEntityFormDisplay($field_name, $widget_id = NULL, array $widget_settings = []) {
+ $options = [];
+ if ($widget_id) {
+ $options['type'] = $widget_id;
+ if (!empty($widget_settings)) {
+ $options['settings'] = $widget_settings;
+ }
+ }
+ // Make sure the field is displayed in the 'default' form mode (using
+ // default widget and settings). It stays hidden for other form modes
+ // until it is explicitly configured.
+ $this->entityDisplayRepository->getFormDisplay($this->entityTypeId, $this->bundle, 'default')
+ ->setComponent($field_name, $options)
+ ->save();
+ }
+
+ /**
+ * Configures the field for the default view mode.
+ *
+ * @param string $field_name
+ * The field name.
+ * @param string|null $formatter_id
+ * (optional) The plugin ID of the formatter. Defaults to NULL.
+ * @param array $formatter_settings
+ * (optional) An array of formatter settings. Defaults to an empty array.
+ */
+ protected function configureEntityViewDisplay($field_name, $formatter_id = NULL, array $formatter_settings = []) {
+ $options = [];
+ if ($formatter_id) {
+ $options['type'] = $formatter_id;
+ if (!empty($formatter_settings)) {
+ $options['settings'] = $formatter_settings;
+ }
+ }
+ // Make sure the field is displayed in the 'default' view mode (using
+ // default formatter and settings). It stays hidden for other view
+ // modes until it is explicitly configured.
+ $this->entityDisplayRepository->getViewDisplay($this->entityTypeId, $this->bundle)
+ ->setComponent($field_name, $options)
+ ->save();
+ }
+
+ public function submitForm(array &$form, FormStateInterface $form_state)
+ {
+ // TODO: Implement submitForm() method.
+ }
+}
diff --git a/core/modules/field_ui/src/Plugin/Block/FieldUiLink.php b/core/modules/field_ui/src/Plugin/Block/FieldUiLink.php
new file mode 100644
index 0000000000..15963fcf8a
--- /dev/null
+++ b/core/modules/field_ui/src/Plugin/Block/FieldUiLink.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\field_ui\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Url;
+
+/**
+ * Provides a link to add content page.
+ *
+ * @Block(
+ * id = "field_ui_link",
+ * admin_label = @Translation("Add Content")
+ * )
+ */
+class FieldUiLink extends BlockBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ return ['label_display' => FALSE];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function build() {
+ $node_type = '';
+ $path = \Drupal::service('path.current')->getPath();
+ $path_args = explode('/', $path);
+ if ($path_args[4] == 'manage') {
+ $node_type = $path_args[5];
+ }
+ $url = Url::fromRoute('node.add', ['node_type' => $node_type]);
+
+ return [
+ '#type' => 'container',
+ 'link' => [
+ '#type' => 'link',
+ '#title' => $this->t('View form'),
+ '#url' => $url,
+ '#attributes' => [
+ 'class' => ['button', 'button--small'],
+ 'target' => '_blank',
+ ],
+ '#attached' => ['library' => ['field_ui/drupal.field_ui_css']],
+ ],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheMaxAge() {
+ return 0;
+ }
+
+}
diff --git a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php
index c572216c85..5a20f5ba76 100644
--- a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php
+++ b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php
@@ -62,7 +62,9 @@ public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives["field_storage_config_add_$entity_type_id"] = [
'route_name' => "field_ui.field_storage_config_add_$entity_type_id",
'title' => $this->t('Add field'),
- 'appears_on' => ["entity.$entity_type_id.field_ui_fields"],
+ // Hacky way to get rid of this appearing. This button is replaced by
+ // field type specific ones.
+ 'appears_on' => ["entity.$entity_type_id.noop.field_ui_fields"],
];
}
}
diff --git a/core/modules/field_ui/src/Routing/RouteSubscriber.php b/core/modules/field_ui/src/Routing/RouteSubscriber.php
index 75e6b4b691..5eab180b81 100644
--- a/core/modules/field_ui/src/Routing/RouteSubscriber.php
+++ b/core/modules/field_ui/src/Routing/RouteSubscriber.php
@@ -109,6 +109,28 @@ protected function alterRoutes(RouteCollection $collection) {
);
$collection->add("field_ui.field_storage_config_add_$entity_type_id", $route);
+ $route = new Route(
+ "$path/fields/add-field/{field_type}/{group}",
+ [
+ '_form' => '\Drupal\field_ui\Form\FieldAddForm',
+ '_title' => 'Add field',
+ ] + $defaults,
+ ['_permission' => 'administer ' . $entity_type_id . ' fields'],
+ $options
+ );
+ $collection->add("field_ui.field_add_$entity_type_id", $route);
+
+ $route = new Route(
+ "$path/fields/add-field/reuse",
+ [
+ '_form' => '\Drupal\field_ui\Form\FieldStorageReuseForm',
+ '_title' => 'Re-use an existing field',
+ ] + $defaults,
+ ['_permission' => 'administer ' . $entity_type_id . ' fields'],
+ $options
+ );
+ $collection->add("field_ui.field_storage_config_add_$entity_type_id.reuse", $route);
+
$route = new Route(
"$path/form-display",
[
diff --git a/core/modules/field_ui/templates/field-ui-tabs.html.twig b/core/modules/field_ui/templates/field-ui-tabs.html.twig
new file mode 100644
index 0000000000..3b470ad700
--- /dev/null
+++ b/core/modules/field_ui/templates/field-ui-tabs.html.twig
@@ -0,0 +1,35 @@
+{#
+/**
+ * @file
+ * Theme override for an item list.
+ *
+ * Available variables:
+ * - items: A list of items. Each item contains:
+ * - attributes: HTML attributes to be applied to each list item.
+ * - value: The content of the list element.
+ * - title: The title of the list.
+ * - list_type: The tag for list element ("ul" or "ol").
+ * - wrapper_attributes: HTML attributes to be applied to the list wrapper.
+ * - attributes: HTML attributes to be applied to the list.
+ * - empty: A message to display when there are no items. Allowed value is a
+ * string or render array.
+ * - context: A list of contextual data associated with the list. May contain:
+ * - list_style: The custom list style.
+ *
+ * @see template_preprocess_item_list()
+ */
+#}
+<nav{{ wrapper_attributes.addClass('tabs-wrapper', 'tabs-wrapper--secondary', 'is-horizontal').setAttribute('id', 'field-ui-tabs') }}>
+ {%- if title is not empty -%}
+ <h3>{{ title }}</h3>
+ {%- endif -%}
+ {%- if items -%}
+ <ul{{ attributes.addClass('tabs', 'tabs--secondary') }}>
+ {%- for item in items -%}
+ <li{{ item.attributes.addClass('tabs__tab') }}>{{ item.value }}</li>
+ {%- endfor -%}
+ </{{ list_type }}>
+ {%- else -%}
+ {{- empty -}}
+ {%- endif -%}
+</nav>
diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php
index 3f9891e630..000c85e5bf 100644
--- a/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php
+++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php
@@ -4,6 +4,8 @@
use Drupal\Tests\BrowserTestBase;
+// cSpell:ignore downlander
+
/**
* Tests the Manage Display page of a fieldable entity type.
*
@@ -38,8 +40,14 @@ protected function setUp(): void {
->save();
}
+ /**
+ * Tests drop button operations on the manage fields page.
+ */
public function testFieldDropButtonOperations() {
+ $assert_session = $this->assertSession();
+
$node_type = $this->drupalCreateContentType();
+ $bundle = $node_type->id();
/** @var \Drupal\field\FieldStorageConfigInterface $storage */
$storage = $this->container->get('entity_type.manager')
@@ -55,12 +63,55 @@ public function testFieldDropButtonOperations() {
->getStorage('field_config')
->create([
'field_storage' => $storage,
- 'bundle' => $node_type->id(),
+ 'bundle' => $bundle,
])
->save();
- $this->drupalGet('/admin/structure/types/manage/' . $node_type->id() . '/fields');
+ $this->drupalGet("/admin/structure/types/manage/{$bundle}/fields");
+
+ // Check that the summary element for the string field type exists and has
+ // the correct text (which comes from the FieldItemBase class).
+ $element = $assert_session->elementExists('css', '#highlander');
+ $summary = $assert_session->elementExists('css', '.field-settings-summary-cell > ul > li', $element);
+ $field_label = $this->container->get('plugin.manager.field.field_type')->getDefinitions()['string']['label'];
+ $this->assertEquals($field_label, $summary->getText());
+
+ // Add an entity reference field, and check that its summary is custom.
+ /** @var \Drupal\field\FieldStorageConfigInterface $storage */
+ $storage = $this->container->get('entity_type.manager')
+ ->getStorage('field_storage_config')
+ ->create([
+ 'type' => 'entity_reference',
+ 'field_name' => 'downlander',
+ 'entity_type' => 'node',
+ 'settings' => [
+ 'target_type' => 'node',
+ ],
+ ]);
+ $storage->save();
+
+ $this->container->get('entity_type.manager')
+ ->getStorage('field_config')
+ ->create([
+ 'field_storage' => $storage,
+ 'bundle' => $bundle,
+ 'entity_type' => 'node',
+ 'settings' => [
+ 'handler_settings' => [
+ 'target_bundles' => [$bundle => $bundle],
+ ],
+ ],
+ ])
+ ->save();
+ $this->drupalGet("/admin/structure/types/manage/{$bundle}/fields");
+ $element = $assert_session->elementExists('css', '#downlander');
+ $summary = $assert_session->elementExists('css', '.field-settings-summary-cell > ul > li:nth-child(2)', $element);
+ $allowed_bundles = $assert_session->elementExists('css', '.field-settings-summary-cell > ul > li:nth-child(3)', $element);
+ $custom_summary_text = 'Reference type: Content';
+ $allowed_bundles_text = "Content type: $bundle";
+ $this->assertEquals($custom_summary_text, $summary->getText());
+ $this->assertEquals($allowed_bundles_text, $allowed_bundles->getText());
}
}
diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
index c8c96c2542..e933927984 100644
--- a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
+++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
@@ -26,7 +26,9 @@
* default_widget = "file_generic",
* default_formatter = "file_default",
* list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList",
- * constraints = {"ReferenceAccess" = {}, "FileValidation" = {}}
+ * constraints = {"ReferenceAccess" = {}, "FileValidation" = {}},
+ * group_display = TRUE,
+ * group = @Translation("File upload")
* )
*/
class FileItem extends EntityReferenceItem {
diff --git a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
index ca449ad890..c4fcb309fd 100644
--- a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
+++ b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
@@ -43,7 +43,9 @@
* },
* },
* list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList",
- * constraints = {"ReferenceAccess" = {}, "FileValidation" = {}}
+ * constraints = {"ReferenceAccess" = {}, "FileValidation" = {}},
+ * group_display = TRUE,
+ * group = @Translation("File upload"),
* )
*/
class ImageItem extends FileItem {
diff --git a/core/modules/media/src/Entity/Media.php b/core/modules/media/src/Entity/Media.php
index 967e933063..a012b01cfa 100644
--- a/core/modules/media/src/Entity/Media.php
+++ b/core/modules/media/src/Entity/Media.php
@@ -27,6 +27,7 @@
* singular = "@count media item",
* plural = "@count media items"
* ),
+ * category = @Translation("Reference"),
* bundle_label = @Translation("Media type"),
* handlers = {
* "storage" = "Drupal\media\MediaStorage",
@@ -80,7 +81,8 @@
* "delete-multiple-form" = "/media/delete",
* "edit-form" = "/media/{media}/edit",
* "revision" = "/media/{media}/revisions/{media_revision}/view",
- * }
+ * },
+ * group_display = TRUE,
* )
*/
class Media extends EditorialContentEntityBase implements MediaInterface {
diff --git a/core/modules/media_library/media_library.module b/core/modules/media_library/media_library.module
index 6e8a8e58a3..56b9a5ea41 100644
--- a/core/modules/media_library/media_library.module
+++ b/core/modules/media_library/media_library.module
@@ -348,6 +348,9 @@ function media_library_field_ui_preconfigured_options_alter(array &$options, $fi
// Set the default field widget for media to be the Media library.
if (!empty($options['media'])) {
$options['media']['entity_form_display']['type'] = 'media_library_widget';
+ $options['media']['description'] = 'Field to reference media. Allows uploading and selecting from uploaded media.';
+ $options['media']['group_display'] = FALSE;
+ $options['media']['group'] = 'Other';
}
}
diff --git a/core/modules/media_library/tests/modules/media_library_test/src/Plugin/Field/FieldType/EntityReferenceItemSubclass.php b/core/modules/media_library/tests/modules/media_library_test/src/Plugin/Field/FieldType/EntityReferenceItemSubclass.php
index e065d3aed4..f9774d5e8f 100644
--- a/core/modules/media_library/tests/modules/media_library_test/src/Plugin/Field/FieldType/EntityReferenceItemSubclass.php
+++ b/core/modules/media_library/tests/modules/media_library_test/src/Plugin/Field/FieldType/EntityReferenceItemSubclass.php
@@ -14,7 +14,8 @@
* category = @Translation("Reference"),
* default_widget = "entity_reference_autocomplete",
* default_formatter = "entity_reference_label",
- * list_class = "\Drupal\Core\Field\EntityReferenceFieldItemList"
+ * list_class = "\Drupal\Core\Field\EntityReferenceFieldItemList",
+ * group_display = TRUE,
* )
*/
class EntityReferenceItemSubclass extends EntityReferenceItem {
diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php
index 709b6af93e..c2cea4fcc1 100644
--- a/core/modules/node/src/NodeForm.php
+++ b/core/modules/node/src/NodeForm.php
@@ -10,6 +10,7 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
+use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -81,6 +82,23 @@ public static function create(ContainerInterface $container) {
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
+ /** @var \Drupal\node\NodeInterface $node */
+ $node = $this->entity;
+ $type_label = node_get_type_label($node);
+ $form = [
+ 'manage_form_display' => [
+ '#type' => 'container',
+ '#weight' => -100,
+ 'link' => [
+ '#type' => 'link',
+ '#title' => $this->t("Manage form display for $type_label"),
+ '#url' => Url::fromRoute("entity.entity_form_display.node.default", [
+ 'node_type' => $node->bundle(),
+ ]),
+ ],
+ ],
+ ];
+
// Try to restore from temp store, this must be done before calling
// parent::form().
$store = $this->tempStoreFactory->get('node_preview');
@@ -108,9 +126,6 @@ public function form(array $form, FormStateInterface $form_state) {
$form_state->set('has_been_previewed', TRUE);
}
- /** @var \Drupal\node\NodeInterface $node */
- $node = $this->entity;
-
if ($this->operation == 'edit') {
$form['#title'] = $this->t('<em>Edit @type</em> @title', [
'@type' => node_get_type_label($node),
diff --git a/core/modules/options/src/Plugin/Field/FieldType/ListFloatItem.php b/core/modules/options/src/Plugin/Field/FieldType/ListFloatItem.php
index a09152d509..f2edacef24 100644
--- a/core/modules/options/src/Plugin/Field/FieldType/ListFloatItem.php
+++ b/core/modules/options/src/Plugin/Field/FieldType/ListFloatItem.php
@@ -15,8 +15,10 @@
* label = @Translation("List (float)"),
* description = @Translation("This field stores float values from a list of allowed 'value => label' pairs, i.e. 'Fraction': 0 => 0, .25 => 1/4, .75 => 3/4, 1 => 1."),
* category = @Translation("Number"),
+ * group = @Translation("Selection list"),
* default_widget = "options_select",
* default_formatter = "list_default",
+ * group_display = TRUE,
* )
*/
class ListFloatItem extends ListItemBase {
diff --git a/core/modules/options/src/Plugin/Field/FieldType/ListIntegerItem.php b/core/modules/options/src/Plugin/Field/FieldType/ListIntegerItem.php
index eabec6f022..95925114cc 100644
--- a/core/modules/options/src/Plugin/Field/FieldType/ListIntegerItem.php
+++ b/core/modules/options/src/Plugin/Field/FieldType/ListIntegerItem.php
@@ -15,8 +15,10 @@
* label = @Translation("List (integer)"),
* description = @Translation("This field stores integer values from a list of allowed 'value => label' pairs, i.e. 'Lifetime in days': 1 => 1 day, 7 => 1 week, 31 => 1 month."),
* category = @Translation("Number"),
+ * group = @Translation("Selection list"),
* default_widget = "options_select",
* default_formatter = "list_default",
+ * group_display = TRUE,
* )
*/
class ListIntegerItem extends ListItemBase {
diff --git a/core/modules/options/src/Plugin/Field/FieldType/ListStringItem.php b/core/modules/options/src/Plugin/Field/FieldType/ListStringItem.php
index acd86c0fa2..fd82800ae9 100644
--- a/core/modules/options/src/Plugin/Field/FieldType/ListStringItem.php
+++ b/core/modules/options/src/Plugin/Field/FieldType/ListStringItem.php
@@ -15,8 +15,10 @@
* label = @Translation("List (text)"),
* description = @Translation("This field stores text values from a list of allowed 'value => label' pairs, i.e. 'US States': IL => Illinois, IA => Iowa, IN => Indiana."),
* category = @Translation("Text"),
+ * group = @Translation("Selection list"),
* default_widget = "options_select",
* default_formatter = "list_default",
+ * group_display = TRUE,
* )
*/
class ListStringItem extends ListItemBase {
diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathTestBaseFilledTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathTestBaseFilledTest.php
index ff0dbbccb2..1b88fe8c4f 100644
--- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathTestBaseFilledTest.php
+++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathTestBaseFilledTest.php
@@ -32,6 +32,8 @@ protected function setDatabaseDumpFiles() {
* Tests that the content and configuration were properly updated.
*/
public function testUpdatedSite() {
+ $assert_session = $this->assertSession();
+
$this->runUpdates();
$spanish = \Drupal::languageManager()->getLanguage('es');
@@ -227,24 +229,24 @@ public function testUpdatedSite() {
$this->drupalGet('admin/structure/types/manage/test_content_type/fields');
// Make sure fields are the right type.
- $this->assertSession()->linkExists('Text (formatted, long, with summary)');
- $this->assertSession()->linkExists('Boolean');
- $this->assertSession()->linkExists('Comments');
- $this->assertSession()->linkExists('Date');
- $this->assertSession()->linkExists('Email');
- $this->assertSession()->linkExists('Link');
- $this->assertSession()->linkExists('List (float)');
- $this->assertSession()->linkExists('Telephone number');
- $this->assertSession()->linkExists('Entity reference');
- $this->assertSession()->linkExists('File');
- $this->assertSession()->linkExists('Image');
- $this->assertSession()->linkExists('Text (plain, long)');
- $this->assertSession()->linkExists('List (text)');
- $this->assertSession()->linkExists('Text (formatted, long)');
- $this->assertSession()->linkExists('Text (plain)');
- $this->assertSession()->linkExists('List (integer)');
- $this->assertSession()->linkExists('Number (integer)');
- $this->assertSession()->linkExists('Number (float)');
+ $assert_session->elementContains('css', '#body', 'Text (formatted, long, with summary)');
+ $assert_session->elementContains('css', '#field-test-1', 'Boolean');
+ $assert_session->elementContains('css', '#field-test-2', 'Comments');
+ $assert_session->elementContains('css', '#field-test-3', 'Date');
+ $assert_session->elementContains('css', '#field-test-4', 'Email');
+ $assert_session->elementContains('css', '#field-test-5', 'Link');
+ $assert_session->elementContains('css', '#field-test-6', 'List (float)');
+ $assert_session->elementContains('css', '#field-test-7', 'Telephone number');
+ $assert_session->elementContains('css', '#field-test-8', 'Entity reference');
+ $assert_session->elementContains('css', '#field-test-9', 'File');
+ $assert_session->elementContains('css', '#field-test-10', 'Image');
+ $assert_session->elementContains('css', '#field-test-15', 'Text (plain, long)');
+ $assert_session->elementContains('css', '#field-test-16', 'List (text)');
+ $assert_session->elementContains('css', '#field-test-17', 'Text (formatted)');
+ $assert_session->elementContains('css', '#field-test-18', 'Text (formatted, long)');
+ $assert_session->elementContains('css', '#field-test-20', 'List (integer)');
+ $assert_session->elementContains('css', '#field-test-22', 'Number (float)');
+ $assert_session->elementContains('css', '#field-test-23', 'Number (integer)');
// Make sure our form mode exists.
$this->drupalGet('admin/structure/display-modes/form');
diff --git a/core/modules/telephone/src/Plugin/Field/FieldType/TelephoneItem.php b/core/modules/telephone/src/Plugin/Field/FieldType/TelephoneItem.php
index 3cec358056..dcbd246b42 100644
--- a/core/modules/telephone/src/Plugin/Field/FieldType/TelephoneItem.php
+++ b/core/modules/telephone/src/Plugin/Field/FieldType/TelephoneItem.php
@@ -15,7 +15,6 @@
* id = "telephone",
* label = @Translation("Telephone number"),
* description = @Translation("This field stores a telephone number in the database."),
- * category = @Translation("Number"),
* default_widget = "telephone_default",
* default_formatter = "basic_string"
* )
diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextItem.php b/core/modules/text/src/Plugin/Field/FieldType/TextItem.php
index 8926a4acb6..ce32964cea 100644
--- a/core/modules/text/src/Plugin/Field/FieldType/TextItem.php
+++ b/core/modules/text/src/Plugin/Field/FieldType/TextItem.php
@@ -15,7 +15,9 @@
* category = @Translation("Text"),
* default_widget = "text_textfield",
* default_formatter = "text_default",
- * list_class = "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList"
+ * list_class = "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList",
+ * group_display = TRUE,
+ * group = @Translation("Formatted text"),
* )
*/
class TextItem extends TextItemBase {
@@ -85,6 +87,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
'#description' => $this->t('The maximum length of the field in characters.'),
'#min' => 1,
'#disabled' => $has_data,
+ '#group' => 'advanced',
];
$element += parent::storageSettingsForm($form, $form_state, $has_data);
diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php b/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php
index e40849cbcd..cb3a51612a 100644
--- a/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php
+++ b/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php
@@ -35,6 +35,7 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
'#default_value' => !empty($settings['allowed_formats']) ? $settings['allowed_formats'] : [],
'#description' => $this->t('Select the allowed text formats. If no formats are selected, all available text formats will be displayed to the user.'),
'#element_validate' => [[static::class, 'validateAllowedFormats']],
+ '#group' => 'advanced',
];
return $element;
diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextLongItem.php b/core/modules/text/src/Plugin/Field/FieldType/TextLongItem.php
index ce35873326..6164f3167d 100644
--- a/core/modules/text/src/Plugin/Field/FieldType/TextLongItem.php
+++ b/core/modules/text/src/Plugin/Field/FieldType/TextLongItem.php
@@ -14,7 +14,9 @@
* category = @Translation("Text"),
* default_widget = "text_textarea",
* default_formatter = "text_default",
- * list_class = "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList"
+ * list_class = "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList",
+ * group_display = TRUE,
+ * group = @Translation("Formatted text"),
* )
*/
class TextLongItem extends TextItemBase {
diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php b/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php
index 088986d576..438f71997d 100644
--- a/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php
+++ b/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php
@@ -17,7 +17,9 @@
* category = @Translation("Text"),
* default_widget = "text_textarea_with_summary",
* default_formatter = "text_default",
- * list_class = "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList"
+ * list_class = "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList",
+ * group_display = TRUE,
+ * group = @Translation("Formatted text"),
* )
*/
class TextWithSummaryItem extends TextItemBase {
diff --git a/core/themes/claro/css/theme/field-ui.admin.css b/core/themes/claro/css/theme/field-ui.admin.css
index 6b37bc9593..ffcac16c22 100644
--- a/core/themes/claro/css/theme/field-ui.admin.css
+++ b/core/themes/claro/css/theme/field-ui.admin.css
@@ -80,3 +80,25 @@
.field-plugin-settings-edit-form .plugin-name {
font-weight: bold;
}
+
+.field-settings-summary-cell > .item-list > ul > li,
+.storage-settings-summary-cell > .item-list > ul > li {
+ margin: 0;
+ list-style-type: none;
+}
+
+.field-settings-summary-cell > .item-list > ul > li {
+ font-size: 0.9em;
+}
+
+.field-settings-summary-cell > .item-list > ul > li:first-child {
+ font-size: 1em;
+}
+
+#field-ui-tabs {
+ display: none;
+}
+
+.js #field-ui-tabs {
+ display: block;
+}
diff --git a/core/themes/claro/css/theme/field-ui.admin.pcss.css b/core/themes/claro/css/theme/field-ui.admin.pcss.css
index ee989fde56..e0f169d421 100644
--- a/core/themes/claro/css/theme/field-ui.admin.pcss.css
+++ b/core/themes/claro/css/theme/field-ui.admin.pcss.css
@@ -63,3 +63,24 @@
.field-plugin-settings-edit-form .plugin-name {
font-weight: bold;
}
+
+.field-settings-summary-cell > .item-list > ul > li,
+.storage-settings-summary-cell > .item-list > ul > li {
+ margin: 0;
+ list-style-type: none;
+}
+.field-settings-summary-cell > .item-list > ul > li {
+ font-size: 0.9em;
+}
+.field-settings-summary-cell > .item-list > ul > li:first-child {
+ font-size: 1em;
+}
+
+
+#field-ui-tabs {
+ display: none;
+}
+
+.js #field-ui-tabs {
+ display: block;
+}
diff --git a/core/themes/stable9/css/field_ui/field_ui.admin.css b/core/themes/stable9/css/field_ui/field_ui.admin.css
index f1716491c4..0cf8528229 100644
--- a/core/themes/stable9/css/field_ui/field_ui.admin.css
+++ b/core/themes/stable9/css/field_ui/field_ui.admin.css
@@ -10,6 +10,17 @@
.field-ui-overview .region-message td {
font-style: italic;
}
+.field-settings-summary-cell > .item-list > ul > li,
+.storage-settings-summary-cell > .item-list > ul > li {
+ margin: 0;
+ list-style-type: none;
+}
+.field-settings-summary-cell > .item-list > ul > li {
+ font-size: 0.9em;
+}
+.field-settings-summary-cell > .item-list > ul > li:first-child {
+ font-size: 1em;
+}
/* 'Manage form display' and 'Manage display' overview */
.field-ui-overview .field-plugin-summary-cell {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment