.../EntityReferenceEntityFormatter.php | 132 ++++++++++++++++++++- core/misc/ajax.es6.js | 6 + core/misc/ajax.js | 4 + .../src/Controller/EntityDisplayModeController.php | 21 ++++ .../field_ui/src/Form/EntityDisplayFormBase.php | 48 ++++++-- .../field_ui/src/Routing/RouteSubscriber.php | 5 +- .../tests/src/Functional/FieldUIRouteTest.php | 4 +- ...core.entity_view_display.media.file.default.yml | 36 +----- ...ore.entity_view_display.media.image.default.yml | 34 +----- .../system/src/Tests/Menu/BreadcrumbTest.php | 6 +- 10 files changed, 220 insertions(+), 76 deletions(-) diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php index af99436..393858c 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php @@ -2,6 +2,7 @@ namespace Drupal\Core\Field\Plugin\Field\FieldFormatter; +use Drupal\Core\Entity\Entity\EntityViewMode; use Drupal\Core\Entity\EntityDisplayRepositoryInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\FieldDefinitionInterface; @@ -9,6 +10,8 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Render\Markup; +use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -124,16 +127,141 @@ public static function defaultSettings() { ] + parent::defaultSettings(); } + protected function getSelectedViewModeName(FormStateInterface $form_state) { + return $this->getSetting('view_mode', $form_state); + } + + /** + * Wrapper around ::getSetting() to reflect current values from Form State. + * + * @param string $key + * The setting name. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * (optional) The form state object. If absent, this method is equivalent to + * parent::getSetting(). + * + * @return mixed|null + * The value of the setting, or NULL if absent. + */ + public function getSetting($key, FormStateInterface $form_state = NULL) { + if ($form_state) { + $field_name = $this->fieldDefinition->getName(); + $form_state_key = [ + 'fields', + $field_name, + 'settings_edit_form', + 'settings', + $key, + ]; + if ($form_state->hasValue($form_state_key)) { + return $form_state->getValue($form_state_key); + } + } + + return parent::getSetting($key); + } + + /** + * Ajax callback for view mode selection change. + */ + public static function onViewModeChange(array &$form, FormStateInterface $form_state) { + return $form['fields'][$form_state->get('plugin_settings_edit')]['plugin']['settings_edit_form']['settings']; + } + + /** + * Rebuilds the form on select submit. + */ + public static function rebuildOnSubmit(array &$form, FormStateInterface $form_state) { + $form_state->setRebuild(TRUE); + } + /** * {@inheritdoc} */ public function settingsForm(array $form, FormStateInterface $form_state) { + $entity_type_id = $this->getFieldSetting('target_type'); + $bundle_entity_type_id = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType(); + $bundles = array_keys($this->getFieldSetting('handler_settings')['target_bundles']); + + $currently_selected_view_mode_name = $this->getSelectedViewModeName($form_state); + $currently_selected_view_mode = EntityViewMode::load("$entity_type_id.$currently_selected_view_mode_name"); + // @todo Simplify this in https://www.drupal.org/node/2844203. + $currently_selected_view_mode_label = $currently_selected_view_mode ? $currently_selected_view_mode->label() : $this->t('Default'); + + $elements['#prefix'] = '
'; + $elements['#suffix'] = '
'; + $description = [ + '#theme' => 'item_list', + '#context' => ['list_style' => 'comma-list'], + '#items' => [], + ]; + if (count($bundles) === 1) { + $description['#items'][] = [ + '#type' => 'link', + '#title' => $this->t('Configure %view-mode-label view mode', ['%view-mode-label' => $currently_selected_view_mode_label]), + '#url' => $currently_selected_view_mode_name === 'default' + ? Url::fromRoute(sprintf('entity.entity_view_display.%s.default', $entity_type_id), [$bundle_entity_type_id => $bundles[0]]) + : Url::fromRoute(sprintf('entity.entity_view_display.%s.view_mode', $entity_type_id), [$bundle_entity_type_id => $bundles[0], 'view_mode_name' => $currently_selected_view_mode_name]), + '#attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => json_encode([ + 'width' => 800, + ]), + ], + ]; + } + else { + $bundle_info = entity_get_bundles($entity_type_id); + for ($i = 0; $i < count($bundles); $i++) { + $description['#items'][] = [ + '#type' => 'link', + '#prefix' => $i == 0 + ? $this->t('Configure %view-mode-label view mode for', ['%view-mode-label' => $currently_selected_view_mode_label]) . ' ' + : '', + '#title' => $this->t('%bundle-label %entity-type-label', ['%bundle-label' => $bundle_info[$bundles[$i]]['label'], '%entity-type-label' => 'Media items']), + '#url' => $currently_selected_view_mode_name === 'default' + ? Url::fromRoute(sprintf('entity.entity_view_display.%s.default', $entity_type_id), [$bundle_entity_type_id => $bundles[$i]]) + : Url::fromRoute(sprintf('entity.entity_view_display.%s.view_mode', $entity_type_id), [$bundle_entity_type_id => $bundles[$i], 'view_mode_name' => $currently_selected_view_mode_name]), + '#attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => json_encode([ + 'width' => 800, + ]), + ], + ]; + } + } + $description['#items'][] = [ + '#type' => 'link', + '#title' => $this->t('add new view mode'), + '#prefix' => Markup::create(t('or') . ' '), + '#url' => Url::fromRoute('entity.entity_view_mode.add_form', ['entity_type_id' => $entity_type_id]), + '#attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => json_encode([ + 'width' => 800, + ]), + ], + ]; $elements['view_mode'] = [ '#type' => 'select', - '#options' => $this->entityDisplayRepository->getViewModeOptions($this->getFieldSetting('target_type')), + '#options' => $this->entityDisplayRepository->getViewModeOptions($entity_type_id), '#title' => t('View mode'), - '#default_value' => $this->getSetting('view_mode'), + '#default_value' => $currently_selected_view_mode_name, + '#ajax' => [ + 'callback' => [static::class, 'onViewModeChange'], + 'wrapper' => 'entity-reference-entity-formatter-settings-ajax', + 'method' => 'replace', + ], + '#submit' => [[static::class, 'rebuildOnSubmit']], + '#executes_submit_callback' => TRUE, '#required' => TRUE, + '#description' => $description + [ + '#access' => \Drupal::currentUser()->hasPermission('administer ' . $entity_type_id . ' display') + ], ]; return $elements; diff --git a/core/misc/ajax.es6.js b/core/misc/ajax.es6.js index daa6254..66f5ea2 100644 --- a/core/misc/ajax.es6.js +++ b/core/misc/ajax.es6.js @@ -367,6 +367,12 @@ * @type {string} */ this.wrapper = `#${this.wrapper}`; + // To avoid problems when the same form exists multiple times on the same page, allow its + // wrapper to be scoped to the form it lives in. This can f.e. happen when the same base form + // is both in the parent page and in a modal. + if (this.scope && this.scope === 'form') { + this.wrapper = `#${element.form.id} ${this.wrapper}`; + } } /** diff --git a/core/misc/ajax.js b/core/misc/ajax.js index 1e15175..e827c88 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -163,6 +163,10 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr if (this.wrapper) { this.wrapper = '#' + this.wrapper; + + if (this.scope && this.scope === 'form') { + this.wrapper = '#' + element.form.id + ' ' + this.wrapper; + } } this.element = element; diff --git a/core/modules/field_ui/src/Controller/EntityDisplayModeController.php b/core/modules/field_ui/src/Controller/EntityDisplayModeController.php index b4a608a..763a025 100644 --- a/core/modules/field_ui/src/Controller/EntityDisplayModeController.php +++ b/core/modules/field_ui/src/Controller/EntityDisplayModeController.php @@ -3,6 +3,7 @@ namespace Drupal\field_ui\Controller; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Entity\Entity\EntityViewMode; use Drupal\Core\Url; /** @@ -10,6 +11,26 @@ */ class EntityDisplayModeController extends ControllerBase { + public function editViewDisplayTitle($entity_type_id, $bundle, $view_mode_name) { + $entity_type = $this->entityTypeManager()->getDefinition($entity_type_id); + if ($entity_type->hasKey('bundle')) { + return $this->t('Edit %view-mode-label display of %bundle-label %entity-type-label', [ + '%view-mode-label' => $view_mode_name === 'default' + ? t('Default') + : EntityViewMode::load("$entity_type_id.$view_mode_name")->label(), + '%bundle-label' => $this->entityManager()->getBundleInfo($entity_type_id)[$bundle]['label'], + '%entity-type-label' => $this->entityTypeManager()->getDefinition($entity_type_id)->getPluralLabel(), + ]); + } + else { + return $this->t('Edit %view-mode-label display of %entity-type-label', [ + '%view-mode-label' => $view_mode_name === 'default' + ? t('Default') + : EntityViewMode::load("$entity_type_id.$view_mode_name")->label(), + '%entity-type-label' => $this->entityTypeManager()->getDefinition($entity_type_id)->getPluralLabel(), + ]); + } + } /** * Provides a list of eligible entity types for adding view modes. * diff --git a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php index 0a91862..8dfbd78 100644 --- a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php +++ b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php @@ -4,6 +4,9 @@ use Drupal\Component\Plugin\Factory\DefaultFactory; use Drupal\Component\Plugin\PluginManagerBase; +use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\CloseModalDialogCommand; +use Drupal\Core\Ajax\HtmlCommand; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityWithPluginCollectionInterface; @@ -235,6 +238,7 @@ public function form(array $form, FormStateInterface $form_state) { '#submit' => ['::multistepSubmit'], '#ajax' => [ 'callback' => '::multistepAjax', + 'scope' => 'form', 'wrapper' => 'field-display-overview-wrapper', 'effect' => 'fade', // The button stays hidden, so we hide the Ajax spinner too. Ad-hoc @@ -244,19 +248,47 @@ public function form(array $form, FormStateInterface $form_state) { '#attributes' => ['class' => ['visually-hidden']] ]; - $form['actions'] = ['#type' => 'actions']; - $form['actions']['submit'] = [ - '#type' => 'submit', - '#button_type' => 'primary', - '#value' => $this->t('Save'), - ]; - $form['#attached']['library'][] = 'field_ui/drupal.field_ui'; return $form; } /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + $actions = parent::actions($form, $form_state); + $actions['submit']['#ajax'] = [ + 'callback' => '::ajaxCallback', + 'event' => 'click', + ]; + return $actions; + } + + /** + * Ajax callback to close the modal and update the selected text. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * An ajax response object. + */ + public function ajaxCallback(array $form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + if ($form_state->getErrors()) { + unset($form['#prefix'], $form['#suffix']); + $form['status_messages'] = [ + '#type' => 'status_messages', + '#weight' => -10, + ]; + $response->addCommand(new HtmlCommand('#drupal-modal', $form)); + } + else { + // @todo show message for successful saving of settings once https://www.drupal.org/node/77245 lands. This message is set by \Drupal\field_ui\Form\EntityDisplayFormBase::submitForm(). + $response->addCommand(new CloseModalDialogCommand()); + } + return $response; + } + + /** * Builds the table row structure for a single field. * * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition @@ -347,6 +379,7 @@ protected function buildFieldRow(FieldDefinitionInterface $field_definition, arr '#submit' => ['::multistepSubmit'], '#ajax' => [ 'callback' => '::multistepAjax', + 'scope' => 'form', 'wrapper' => 'field-display-overview-wrapper', 'effect' => 'fade', ], @@ -375,7 +408,6 @@ protected function buildFieldRow(FieldDefinitionInterface $field_definition, arr 'settings' => $settings_form, 'third_party_settings' => $third_party_settings_form, 'actions' => [ - '#type' => 'actions', 'save_settings' => $base_button + [ '#type' => 'submit', '#button_type' => 'primary', diff --git a/core/modules/field_ui/src/Routing/RouteSubscriber.php b/core/modules/field_ui/src/Routing/RouteSubscriber.php index b08e7f8..495f14a 100644 --- a/core/modules/field_ui/src/Routing/RouteSubscriber.php +++ b/core/modules/field_ui/src/Routing/RouteSubscriber.php @@ -5,6 +5,7 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Routing\RouteSubscriberBase; use Drupal\Core\Routing\RoutingEvents; +use Drupal\field_ui\Controller\EntityDisplayModeController; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -136,7 +137,7 @@ protected function alterRoutes(RouteCollection $collection) { "$path/display", [ '_entity_form' => 'entity_view_display.edit', - '_title' => 'Manage display', + '_title_callback' => EntityDisplayModeController::class . '::editViewDisplayTitle', 'view_mode_name' => 'default', ] + $defaults, ['_field_ui_view_mode_access' => 'administer ' . $entity_type_id . ' display'], @@ -148,7 +149,7 @@ protected function alterRoutes(RouteCollection $collection) { "$path/display/{view_mode_name}", [ '_entity_form' => 'entity_view_display.edit', - '_title' => 'Manage display', + '_title_callback' => EntityDisplayModeController::class . '::editViewDisplayTitle', ] + $defaults, ['_field_ui_view_mode_access' => 'administer ' . $entity_type_id . ' display'], $options diff --git a/core/modules/field_ui/tests/src/Functional/FieldUIRouteTest.php b/core/modules/field_ui/tests/src/Functional/FieldUIRouteTest.php index fe7a270..1b27d27 100644 --- a/core/modules/field_ui/tests/src/Functional/FieldUIRouteTest.php +++ b/core/modules/field_ui/tests/src/Functional/FieldUIRouteTest.php @@ -46,13 +46,13 @@ public function testFieldUIRoutes() { $this->assertResponse(403); $this->drupalGet('admin/config/people/accounts/display'); - $this->assertTitle('Manage display | Drupal'); + $this->assertTitle('Edit Default display of user entities | Drupal'); $this->assertLocalTasks(); $edit = ['display_modes_custom[compact]' => TRUE]; $this->drupalPostForm(NULL, $edit, t('Save')); $this->drupalGet('admin/config/people/accounts/display/compact'); - $this->assertTitle('Manage display | Drupal'); + $this->assertTitle('Edit Compact display of user entities | Drupal'); $this->assertLocalTasks(); // Test manage form display tabs and titles. diff --git a/core/modules/media/config/install/core.entity_view_display.media.file.default.yml b/core/modules/media/config/install/core.entity_view_display.media.file.default.yml index 697f117..dd704e1 100644 --- a/core/modules/media/config/install/core.entity_view_display.media.file.default.yml +++ b/core/modules/media/config/install/core.entity_view_display.media.file.default.yml @@ -3,48 +3,22 @@ status: true dependencies: config: - field.field.media.file.field_media_file - - image.style.thumbnail - media.type.file module: - file - - image - - user id: media.file.default targetEntityType: media bundle: file mode: default content: - created: - label: hidden - type: timestamp - weight: 0 - region: content - settings: - date_format: medium - custom_date_format: '' - timezone: '' - third_party_settings: { } field_media_file: - label: above + label: hidden settings: { } third_party_settings: { } type: file_default - weight: 6 - region: content - thumbnail: - type: image - weight: 5 - label: hidden - settings: - image_style: thumbnail - image_link: '' - region: content - third_party_settings: { } - uid: - label: hidden - type: author weight: 0 region: content - settings: { } - third_party_settings: { } -hidden: { } +hidden: + created: true + thumbnail: true + uid: true diff --git a/core/modules/media/config/install/core.entity_view_display.media.image.default.yml b/core/modules/media/config/install/core.entity_view_display.media.image.default.yml index e541d7f..f46a79c 100644 --- a/core/modules/media/config/install/core.entity_view_display.media.image.default.yml +++ b/core/modules/media/config/install/core.entity_view_display.media.image.default.yml @@ -7,45 +7,21 @@ dependencies: - media.type.image module: - image - - user id: media.image.default targetEntityType: media bundle: image mode: default content: - created: - label: hidden - type: timestamp - weight: 0 - region: content - settings: - date_format: medium - custom_date_format: '' - timezone: '' - third_party_settings: { } field_media_image: - label: above - settings: - image_style: '' - image_link: '' - third_party_settings: { } - type: image - weight: 6 - region: content - thumbnail: - type: image - weight: 5 label: hidden settings: image_style: thumbnail image_link: '' - region: content third_party_settings: { } - uid: - label: hidden - type: author + type: image weight: 0 region: content - settings: { } - third_party_settings: { } -hidden: { } +hidden: + created: true + thumbnail: true + uid: true diff --git a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php index 39303ff..cc9f101 100644 --- a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php +++ b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php @@ -110,10 +110,12 @@ public function testBreadCrumbs() { ]; $this->assertBreadcrumb("admin/structure/types/manage/$type/fields", $trail); $this->assertBreadcrumb("admin/structure/types/manage/$type/display", $trail); + $title_html = t('Edit %view-mode-label display of %bundle-label %entity-type-label', ['%view-mode-label' => 'Teaser', '%bundle-label' => NodeType::load($type)->label(), '%entity-type-label' => 'content items']); $trail_teaser = $trail + [ - "admin/structure/types/manage/$type/display" => t('Manage display'), + // @todo Improve ::assertBreadcrumbParts() + "admin/structure/types/manage/$type/display" => 'Edit display of ', ]; - $this->assertBreadcrumb("admin/structure/types/manage/$type/display/teaser", $trail_teaser); + $this->assertBreadcrumb("admin/structure/types/manage/$type/display/teaser", $trail_teaser, strip_tags($title_html)); $this->assertBreadcrumb("admin/structure/types/manage/$type/delete", $trail); $trail += [ "admin/structure/types/manage/$type/fields" => t('Manage fields'),