Commit 792a14f5 authored by Gábor Hojtsy's avatar Gábor Hojtsy
Browse files

Issue #3023802 by seanB, phenaproxima, lauriii, Pancho, larowlan, shaal, dww:...

Issue #3023802 by seanB, phenaproxima, lauriii, Pancho, larowlan, shaal, dww: Show media add form directly on media type tab in media library
parent ab15b22f
......@@ -57,6 +57,16 @@ class MediaSource extends Plugin {
*/
public $allowed_field_types = [];
/**
* The classes used to define media source-specific forms.
*
* An array of form class names, keyed by ID. The ID represents the operation
* the form is used for.
*
* @var string[]
*/
public $forms = [];
/**
* A filename for the default thumbnail.
*
......
......@@ -83,6 +83,19 @@
border-left: 0;
}
.media-library-add-form--without-input {
margin-bottom: 1em;
border-bottom: 1px solid #c0c0c0;
}
.media-library-add-form--without-input .form-item {
margin: 0 0 1em;
}
.media-library-add-form .file-upload-help {
margin: 8px 0 0;
}
.media-library-views-form__header .form-item {
margin-right: 8px;
}
......@@ -276,31 +289,34 @@
border-color: #40b6ff;
}
/* Style the wrappers around new media and files */
.media-library-upload__media,
.media-library-upload__file {
/* Style the wrappers around new media and files. */
.media-library-add-form__media {
display: flex;
padding: 20px 0 20px 0;
border-bottom: 1px solid #c0c0c0;
}
.media-library-upload__file {
align-items: center;
/* Do not show the top padding for the first item. */
.media-library-add-form__media:first-child {
padding-top: 0;
}
.media-library-upload__file-label {
margin-right: 10px;
/* Do not show the bottom border and padding for the last item. */
.media-library-add-form__media:last-child {
border-bottom: 0;
padding-bottom: 0;
}
/* @todo Remove in https://www.drupal.org/project/drupal/issues/2987921 */
.media-library-upload__source-field .file,
.media-library-upload__source-field .button,
.media-library-upload__source-field .image-preview,
.media-library-upload__source-field .form-type-managed-file > label,
.media-library-upload__source-field .file-size {
.media-library-add-form__source-field .file,
.media-library-add-form__source-field .button,
.media-library-add-form__source-field .image-preview,
.media-library-add-form__source-field .form-type-managed-file > label,
.media-library-add-form__source-field .file-size {
display: none;
}
.media-library-upload__media-preview {
.media-library-add-form__preview {
display: flex;
justify-content: center;
align-items: center;
......@@ -308,15 +324,11 @@
margin-right: 20px;
background: #ebebeb;
}
[dir="rtl"] .media-library-upload__media-preview {
[dir="rtl"] .media-library-add-form__preview {
margin-right: 0;
margin-left: 20px;
}
.media-library-upload__media-preview img {
display: block;
}
/* @todo Remove or re-work in https://www.drupal.org/node/2985168 */
.media-library-widget .media-library-item__name a,
.media-library-view.view-display-id-widget .media-library-item__name a {
......
......@@ -15,6 +15,26 @@
currentSelection: [],
};
/**
* Command to update the current media library selection.
*
* @param {Drupal.Ajax} [ajax]
* The Drupal Ajax object.
* @param {object} response
* Object holding the server response.
* @param {number} [status]
* The HTTP status code.
*/
Drupal.AjaxCommands.prototype.updateMediaLibrarySelection = function(
ajax,
response,
status,
) {
Object.values(response.mediaIds).forEach(value => {
Drupal.MediaLibrary.currentSelection.push(value);
});
};
/**
* Warn users when clicking outgoing links from the library or widget.
*
......
......@@ -10,6 +10,12 @@
currentSelection: []
};
Drupal.AjaxCommands.prototype.updateMediaLibrarySelection = function (ajax, response, status) {
Object.values(response.mediaIds).forEach(function (value) {
Drupal.MediaLibrary.currentSelection.push(value);
});
};
Drupal.behaviors.MediaLibraryWidgetWarn = {
attach: function attach(context) {
$('.js-media-library-item a[href]', context).once('media-library-warn-link').on('click', function (e) {
......
......@@ -5,7 +5,6 @@
* Contains hook implementations for the media_library module.
*/
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
......@@ -17,11 +16,11 @@
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\image\Entity\ImageStyle;
use Drupal\image\Plugin\Field\FieldType\ImageItem;
use Drupal\media\MediaTypeForm;
use Drupal\media\MediaTypeInterface;
use Drupal\media_library\Form\FileUploadForm;
use Drupal\media_library\MediaLibraryState;
use Drupal\views\Form\ViewsForm;
use Drupal\views\Plugin\views\cache\CachePluginBase;
......@@ -41,6 +40,16 @@ function media_library_help($route_name, RouteMatchInterface $route_match) {
}
}
/**
* Implements hook_media_source_info_alter().
*/
function media_library_media_source_info_alter(array &$sources) {
$sources['audio_file']['forms']['media_library_add'] = FileUploadForm::class;
$sources['file']['forms']['media_library_add'] = FileUploadForm::class;
$sources['image']['forms']['media_library_add'] = FileUploadForm::class;
$sources['video_file']['forms']['media_library_add'] = FileUploadForm::class;
}
/**
* Implements hook_theme().
*/
......@@ -52,36 +61,6 @@ function media_library_theme() {
];
}
/**
* Implements hook_preprocess_view().
*
* Adds a link to add media above the view.
*/
function media_library_preprocess_views_view(&$variables) {
$view = $variables['view'];
if ($view->id() === 'media_library' && $view->current_display === 'widget') {
$url = Url::fromRoute('media_library.upload');
if ($url->access()) {
$url->setOption('query', \Drupal::request()->query->all());
$variables['header']['add_media'] = [
'#type' => 'link',
'#title' => t('Add media'),
'#url' => $url,
'#attributes' => [
'class' => ['button', 'button-action', 'button--primary', 'use-ajax'],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'dialogClass' => 'media-library-widget-modal',
'height' => '75%',
'width' => '75%',
'title' => t('Add media'),
]),
],
];
}
}
}
/**
* Implements hook_views_post_render().
*/
......
media_library.upload:
path: '/admin/content/media-widget-upload'
defaults:
_form: '\Drupal\media_library\Form\MediaLibraryUploadForm'
requirements:
_custom_access: '\Drupal\media_library\Form\MediaLibraryUploadForm::access'
media_library.ui:
path: '/media-library'
defaults:
......
services:
media_library.ui_builder:
class: Drupal\media_library\MediaLibraryUiBuilder
arguments: ['@entity_type.manager', '@request_stack', '@views.executable']
arguments: ['@entity_type.manager', '@request_stack', '@views.executable', '@form_builder']
<?php
namespace Drupal\media_library\Ajax;
use Drupal\Core\Ajax\CommandInterface;
/**
* AJAX command for adding media items to the media library selection.
*
* This command instructs the client to add the given media item IDs to the
* current selection of the media library stored in
* Drupal.MediaLibrary.currentSelection.
*
* This command is implemented by
* Drupal.AjaxCommands.prototype.updateMediaLibrarySelection() defined in
* media_library.ui.js.
*
* @ingroup ajax
*
* @internal
* Media Library is an experimental module and its internal code may be
* subject to change in minor releases. External code should not instantiate
* or extend this class.
*/
class UpdateSelectionCommand implements CommandInterface {
/**
* An array of media IDs to add to the current selection.
*
* @var int[]
*/
protected $mediaIds;
/**
* Constructs an UpdateSelectionCommand object.
*
* @param int[] $media_ids
* An array of media IDs to add to the current selection.
*/
public function __construct(array $media_ids) {
$this->mediaIds = $media_ids;
}
/**
* {@inheritdoc}
*/
public function render() {
return [
'command' => 'updateMediaLibrarySelection',
'mediaIds' => $this->mediaIds,
];
}
}
<?php
namespace Drupal\media_library\Form;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;
use Drupal\media_library\Ajax\UpdateSelectionCommand;
use Drupal\media_library\MediaLibraryState;
use Drupal\media_library\MediaLibraryUiBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a base class for creating media items from within the media library.
*
* @internal
* Media Library is an experimental module and its internal code may be
* subject to change in minor releases. External code should not instantiate
* or extend this class.
*/
abstract class AddFormBase extends FormBase {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The media library UI builder.
*
* @var \Drupal\media_library\MediaLibraryUiBuilder
*/
protected $libraryUiBuilder;
/**
* The type of media items being created by this form.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $mediaType;
/**
* Constructs a AddFormBase object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\media_library\MediaLibraryUiBuilder $library_ui_builder
* The media library UI builder.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, MediaLibraryUiBuilder $library_ui_builder) {
$this->entityTypeManager = $entity_type_manager;
$this->libraryUiBuilder = $library_ui_builder;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
$container->get('media_library.ui_builder')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'media_library_add_form';
}
/**
* Get the media type from the form state.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
*
* @return \Drupal\media\MediaTypeInterface
* The media type.
*/
protected function getMediaType(FormStateInterface $form_state) {
if ($this->mediaType) {
return $this->mediaType;
}
$state = $form_state->get('media_library_state');
if (!$state) {
throw new \InvalidArgumentException('The media library state is not present in the form state.');
}
$selected_type_id = $form_state->get('media_library_state')->getSelectedTypeId();
$this->mediaType = $this->entityTypeManager->getStorage('media_type')->load($selected_type_id);
if (!$this->mediaType) {
throw new \InvalidArgumentException("The '$selected_type_id' media type does not exist.");
}
return $this->mediaType;
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['#prefix'] = '<div id="media-library-add-form-wrapper">';
$form['#suffix'] = '</div>';
$form['#attached']['library'][] = 'media_library/style';
// The form is posted via AJAX. When there are messages set during the
// validation or submission of the form, the messages need to be shown to
// the user.
$form['status_messages'] = [
'#type' => 'status_messages',
];
$form['#attributes']['class'][] = 'media-library-add-form';
$added_media = $form_state->get('media');
if (empty($added_media)) {
$form['#attributes']['class'][] = 'media-library-add-form--without-input';
$form = $this->buildInputElement($form, $form_state);
}
else {
$form['#attributes']['class'][] = 'media-library-add-form--with-input';
$form['media'] = [
'#type' => 'container',
];
foreach ($added_media as $delta => $media) {
$form['media'][$delta] = $this->buildEntityFormElement($media, $form, $form_state, $delta);
}
$form['actions'] = $this->buildActions($form, $form_state);
}
return $form;
}
/**
* Builds the element for submitting source field value(s).
*
* The input element needs to have a submit handler to create media items from
* the user input and store them in the form state using
* ::processInputValues().
*
* @param array $form
* The complete form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
*
* @return array
* The complete form, with the element added.
*
* @see ::processInputValues()
*/
abstract protected function buildInputElement(array $form, FormStateInterface $form_state);
/**
* Builds the sub-form for setting required fields on a new media item.
*
* @param \Drupal\media\MediaInterface $media
* A new, unsaved media item.
* @param array $form
* The complete form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
* @param int $delta
* The delta of the media item.
*
* @return array
* The element containing the required fields sub-form.
*/
protected function buildEntityFormElement(MediaInterface $media, array $form, FormStateInterface $form_state, $delta) {
$element = [
'#type' => 'container',
'#attributes' => [
'class' => [
'media-library-add-form__media',
],
],
'preview' => [
'#type' => 'container',
'#attributes' => [
'class' => [
'media-library-add-form__preview',
],
],
],
'fields' => [
'#type' => 'container',
'#attributes' => [
'class' => [
'media-library-add-form__fields',
],
],
// The '#parents' are set here because the entity form display needs it
// to build the entity form fields.
'#parents' => ['media', $delta, 'fields'],
],
];
// @todo Make the image style configurable in
// https://www.drupal.org/node/2988223
$source = $media->getSource();
$plugin_definition = $source->getPluginDefinition();
if ($thumbnail_uri = $source->getMetadata($media, $plugin_definition['thumbnail_uri_metadata_attribute'])) {
$element['preview']['thumbnail'] = [
'#theme' => 'image_style',
'#style_name' => 'media_library',
'#uri' => $thumbnail_uri,
];
}
$form_display = EntityFormDisplay::collectRenderDisplay($media, 'media_library');
// When the name is not added to the form as an editable field, output
// the name as a fixed element to confirm the right file was uploaded.
if (!$form_display->getComponent('name')) {
$element['fields']['name'] = [
'#type' => 'item',
'#title' => $this->t('Name'),
'#markup' => $media->getName(),
];
}
$form_display->buildForm($media, $element['fields'], $form_state);
// We hide the preview of the uploaded file in the image widget with CSS.
// @todo Improve hiding file widget elements in
// https://www.drupal.org/project/drupal/issues/2987921
$source_field_name = $this->getSourceFieldName($media->bundle->entity);
if (isset($element['fields'][$source_field_name])) {
$element['fields'][$source_field_name]['#attributes']['class'][] = 'media-library-add-form__source-field';
}
// The revision log field is currently not configurable from the form
// display, so hide it by changing the access.
// @todo Make the revision_log_message field configurable in
// https://www.drupal.org/project/drupal/issues/2696555
if (isset($element['fields']['revision_log_message'])) {
$element['fields']['revision_log_message']['#access'] = FALSE;
}
return $element;
}
/**
* Returns an array of supported actions for the form.
*
* @param array $form
* The complete form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
*
* @return array
* An actions element containing the actions of the form.
*/
protected function buildActions(array $form, FormStateInterface $form_state) {
return [
'#type' => 'actions',
'submit' => [
'#type' => 'submit',
'#value' => $this->t('Save'),
'#ajax' => [
'callback' => '::updateWidget',
'wrapper' => 'media-library-add-form-wrapper',
],
],
];
}
/**
* Creates media items from source field input values.
*
* @param mixed[] $source_field_values
* The values for source fields of the media items.
* @param array $form
* The complete form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
*/
protected function processInputValues(array $source_field_values, array $form, FormStateInterface $form_state) {
$media_type = $this->getMediaType($form_state);
$media_storage = $this->entityTypeManager->getStorage('media');
$source_field_name = $this->getSourceFieldName($media_type);
$media = array_map(function ($source_field_value) use ($media_type, $media_storage, $source_field_name) {
return $this->createMediaFromValue($media_type, $media_storage, $source_field_name, $source_field_value);
}, $source_field_values);
$form_state->set('media', $media)->setRebuild();
}
/**
* Creates a new, unsaved media item from a source field value.
*
* @param \Drupal\media\MediaTypeInterface $media_type
* The media type of the media item.
* @param \Drupal\Core\Entity\EntityStorageInterface $media_storage
* The media storage.
* @param string $source_field_name
* The name of the media type's source field.
* @param mixed $source_field_value
* The value for the source field of the media item.
*
* @return \Drupal\media\MediaInterface
* An unsaved media entity.
*/
protected function createMediaFromValue(MediaTypeInterface $media_type, EntityStorageInterface $media_storage, $source_field_name, $source_field_value) {
return $media_storage->create([
'bundle' => $media_type->id(),
$source_field_name => $source_field_value,
]);
}
/**
* Prepares a created media item to be permanently saved.