Problem/Motivation

In #3020716: Add vertical tabs style menu to media library the media library has been split up in different tabs per type. We want to show the source field on the tab of each media type so users can add media of that type directly.

Different media types can have different types of source fields. We currently only have an upload form implementation to add media items from the media library widget, but there is also an issue to implement oEmbed support (#2996029: Add oEmbed support to the media library). Since we don't have a base class at the moment we need to duplicate a lot of code there.

Besides that, we also want to simplify the add/upload forms to only support 1 media type at a time based on the assumptions defined in #3019150-3: Update/improve mockups and designs for the media library.

Proposed resolution

We should have a base class containing all the generic code needed to add media items.
Add an upload source field implementation on top of this class to support media types with file uploads as input.
Show the upload form directly on the media type tab implemented in #3020716: Add vertical tabs style menu to media library.

Remaining tasks

Review and commit the patch.

User interface changes

Before

Given a media reference field on a node form, you would click the "Add media" button on the node form (not the one depicted below) and get something like this:

The media library, before this change

If you click the blue "Add media" button, the modal is refreshed and contains only a standard file upload field. When you upload files, the modal asks you to fill in the required metadata. When done, you hit a "Save" button. The modal closes, and the uploaded items appear in the node form, referenced by the field.

After

When you click the "Add media" button, you see this:

The media library, after this change

The blue "Add media" button is gone, replaced by a file upload field. If you upload a file (or files) into it, the modal refreshes with the form asking for required metadata. When you click "Save", the modal does not close, but instead returns you to the initial screen, with your new upload selected (and at the top of the list).

API changes

An internal form class is split into a base class and a concrete implementation, but both are still marked internal. Media source plugins now officially accept a 'forms' array in their plugin annotation, but so far the only consumer of that information is Media Library.

Data model changes

None.

Release notes snippet

TBD

CommentFileSizeAuthor
#57 3023802-55.patch84.52 KBseanb
#57 interdiff-53-55.txt3.8 KBseanb
#55 3023802-55.patch84.52 KBseanb
#55 interdiff-53-55.txt4.23 KBseanb
#53 3023802-53.patch84.62 KBseanb
#53 interdiff-51-53.txt8.18 KBseanb
#51 3023802-51.patch83.25 KBseanb
#51 interdiff-49-51.txt1.37 KBseanb
#49 3023802-49.patch82.17 KBseanb
#49 interdiff-41-49.txt7.54 KBseanb
#45 3023802-45-demo.mov22.76 MBphenaproxima
#43 3023802-43-after.png301.41 KBphenaproxima
#43 3023802-43-before.png110.21 KBphenaproxima
#41 3023802-41.patch80.34 KBseanb
#41 interdiff-38-41.txt13.42 KBseanb
#38 3023802-38.patch76.85 KBseanb
#38 interdiff-36-38.txt3.73 KBseanb
#36 3023802-36.patch75.94 KBseanb
#36 interdiff-34-36.txt9.41 KBseanb
#34 3023802-34.patch73.26 KBseanb
#34 interdiff-32-34.txt22.7 KBseanb
#32 3023802-32.patch72.79 KBseanb
#32 interdiff-30-32.txt4.78 KBseanb
#30 3023802-30.patch72.1 KBseanb
#30 interdiff-22-30.txt16.11 KBseanb
#22 3023802-22.patch62.32 KBseanb
#22 interdiff-18-22.txt5.16 KBseanb
#19 3023802-18.patch61.74 KBseanb
#19 interdiff-12-18.txt8.13 KBseanb
#13 interdiff-10-12.txt9.44 KBseanb
#12 3023802-12-3020716-64.patch199.2 KBseanb
#12 3023802-12-do-not-test.patch61.36 KBseanb
#12 interdiff-10-12.txt691 bytesseanb
#10 3023802-10-3020716-64.patch198.96 KBseanb
#10 3023802-10-do-not-test.patch60.96 KBseanb
#10 interdiff-9-10.txt10.82 KBseanb
#9 3023802-9-3020716-64.patch198.02 KBseanb
#9 3023802-9-do-not-test.patch60.01 KBseanb
#9 interdiff-6-9.txt32.44 KBseanb
#6 3023802-6-3020716-51.patch192.87 KBseanb
#6 3023802-6-do-not-test.patch56.33 KBseanb
#5 3023802-5-do-not-test.patch55.6 KBseanb
#5 3023802-5-3020716-23.patch175.65 KBseanb
#4 3023802-4-do-not-test.patch53.97 KBseanb
#4 3023802-4-3020716-14-3020707-4-combined.patch328.56 KBseanb
#2 3023802-2-do-not-test.patch54.38 KBseanb
#2 3023802-2-3020716-14-3020707-4-combined.patch328.97 KBseanb

Comments

seanB created an issue. See original summary.

seanb’s picture

Status: Active » Needs review
StatusFileSize
new328.97 KB
new54.38 KB

Attached is a full patch built on top of #3020707: Streamline buttons in the media library widget and #3020716: Add vertical tabs style menu to media library. Also a patch with just the changes for this issue.

The last submitted patch, 2: 3023802-2-3020716-14-3020707-4-combined.patch, failed testing. View results

seanb’s picture

There was a stupid typo made last minute while creating the patches. Here is another try.

Obviously, this issue is blocked on #3020707: Streamline buttons in the media library widget and #3020716: Add vertical tabs style menu to media library for now.

seanb’s picture

StatusFileSize
new175.65 KB
new55.6 KB

#3020707: Streamline buttons in the media library widget has landed.
#3020716: Add vertical tabs style menu to media library was changed quite a bit since #4.

Attached is a reroll with some minor improvements:
- A better class name for the upload form to be more in line with the change proposed in #2996029: Add oEmbed support to the media library.
- Added access check for media type add form to the UI builder. The form should obviously not be shown when a user does not have create permissions.
- Added a test for add form access.

seanb’s picture

phenaproxima’s picture

Status: Needs review » Needs work
Issue tags: +Needs framework manager review

Partial review...

  1. +++ b/core/modules/media/src/Annotation/MediaSource.php
    @@ -57,6 +57,16 @@ class MediaSource extends Plugin {
    +  /**
    +   * The classes used to define media source specific forms.
    +   *
    +   * An example of this would be the 'media_library_add' form used by the media
    +   * library module.
    +   *
    +   * @var string[]
    +   */
    +  public $forms = [];
    

    We should clear it with the framework managers, but I think this is a clear signal to implement PluginWithFormsInterface on MediaSourceInterface and MediaSourceBase. Since there is a base class, I suspect we'd be covered under the BC policy.

  2. +++ b/core/modules/media_library/css/media_library.theme.css
    @@ -264,31 +277,30 @@
    +.media-library-add__media,
    +.media-library-add__file {
    

    In case anyone asks: the reason we're changing this here is because, in the not too distant future, the oEmbed media source will also include an "add" form which is exposed to Media Library. So it no longer makes sense to refer to it as an "upload" form, since uploading files is only one possible strategy that will be used to add media to the library.

    No need to change anything here, just explicitly calling it out.

  3. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,340 @@
    +  /**
    +   * Indicates whether the 'medium' image style exists.
    +   *
    +   * @var bool
    +   */
    +  protected $mediumStyleExists = FALSE;
    

    Let's change all references to the medium image style to use the new media_library image style that we now ship with. Since that is our fallback style, we can assume it always exists, and totally remove this member from this class and its subclasses.

  4. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,340 @@
    +    $form['status_messages'] = [
    +      '#type' => 'status_messages',
    +    ];
    

    Can we have a comment explaining why we're adding this to the form?

  5. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,340 @@
    +      $form['#attributes']['class'][] = 'media-library-add-form-has-no-input';
    

    Should we maybe use BEM conventions here? I think that media-library-add-form__without-input and media-library-add-form__with-input might be a bit clearer.

    Or, better yet, maybe we should use multiple classes. How about ['media-library-add-form', 'with-input'], to increase styling flexibility?

  6. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,340 @@
    +      $form['actions'] = [
    +        '#type' => 'actions',
    +      ];
    +      $form['actions']['submit'] = [
    +        '#type' => 'submit',
    +        '#value' => $this->t('Save'),
    +        '#ajax' => [
    +          'callback' => '::updateWidget',
    +          'wrapper' => 'media-library-add-form-wrapper',
    +        ],
    +      ];
    

    I wonder if this should be moved into its own protected method. ($this->buildActions())

  7. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,340 @@
    +  /**
    +   * Builds the element for submitting source field value(s).
    +   *
    +   * @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.
    +   */
    +  protected function buildInputElement(array $form, FormStateInterface $form_state) {
    +    return $form;
    +  }
    

    Perhaps this method should be abstract. I'm not sure why any subclass would *not* want to implement it.

  8. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,340 @@
    +        // Parents is set here as it is used in the form display.
    

    Can we rephrase? "#parents is set here because the entity form display needs it to build the entity form fields."

  9. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,340 @@
    +    $plugin_definition = $source->getPluginDefinition();
    +    if ($this->mediumStyleExists && $thumbnail_uri = $source->getMetadata($media, $plugin_definition['thumbnail_uri_metadata_attribute'])) {
    +      $element['preview']['thumbnail'] = [
    +        '#theme' => 'image_style',
    +        '#style_name' => 'medium',
    +        '#uri' => $thumbnail_uri,
    +      ];
    

    We can safely change this to use the media_library fallback style. It's not being stored in configuration at all, so changing it here won't affect any upgrade path we need to write.

  10. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,340 @@
    +    // When the name is not added to the form as a editable field, output
    

    "...as an editable field..."

  11. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,340 @@
    +    if (isset($element['fields']['revision_log_message'])) {
    +      $element['fields']['revision_log_message']['#access'] = FALSE;
    +    }
    

    Let's make sure that the tests assert that this field is not present in the media library form display.

  12. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,340 @@
    +      $form_display = EntityFormDisplay::collectRenderDisplay($media, 'media_library');
    +      $form_display->extractFormValues($media, $form['media'][$i]['fields'], $form_state);
    +      $form_display->validateFormValues($media, $form['media'][$i]['fields'], $form_state);
    

    Can we abstract this into a protected method, validateMediaEntity(), so that subclasses have more flexibility to override the per-entity validation logic? This is a similar pattern to what's going in submitForm().

  13. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,340 @@
    +    $response = new AjaxResponse();
    +    if (!empty($form_state->getErrors())) {
    +      $response->addCommand(new ReplaceCommand('#media-library-add-form-wrapper', $form));
    +    }
    +    else {
    +      $response->addCommand(new OpenModalDialogCommand($this->t('Media library'), $form, [
    +        'dialogClass' => 'media-library-widget-modal',
    +        'height' => '75%',
    +        'width' => '75%',
    +        'title' => $this->t('Media library'),
    +        'modal' => TRUE,
    +      ]));
    +    }
    

    This could use a comment or two.

  14. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,340 @@
    +  protected function createMediaEntity($value) {
    

    Let's rename $value to $source_field_value, for clarity.

  15. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -0,0 +1,207 @@
    +    $field_definition = $this->mediaType->getSource()
    +      ->getSourceFieldDefinition($this->mediaType);
    +
    +    if (!is_a($field_definition->getClass(), FileFieldItemList::class, TRUE)) {
    +      throw new \InvalidArgumentException('Can only add media types which use a file field as a source field.');
    +    }
    

    I find this awkwardly placed. I wonder if this stuff should be done in a new protected method ($this->setMediaType($form, $form_state)) so we can do this validation in there.

phenaproxima’s picture

  1. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -0,0 +1,207 @@
    +        $form['upload']['upload_help'] = [
    +          '#type' => 'html_tag',
    +          '#tag' => 'p',
    +          '#attributes' => [
    +            'class' => ['file-upload-help'],
    +          ],
    +          '#value' => \Drupal::translation()
    +            ->formatPlural($slots, 'Maximum 1 file.', 'Maximum @count files remaining.'),
    +        ];
    

    Let's be sure the tests assert that this text appears.

  2. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -0,0 +1,207 @@
    +    if ($form_state->getErrors()) {
    

    We should use $form_state::hasAnyErrors() here.

  3. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -56,16 +68,19 @@ public static function dialogOptions() {
    +   * @param bool $form_rebuild
    +   *   Whether to rebuild the form or not.
    

    Can we expand the doc comment here to explain why and how this parameter would be set?

  4. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -192,6 +211,41 @@ protected function buildMediaTypeMenu(MediaLibraryState $state) {
    +   * @param \Drupal\media_library\MediaLibraryState $state
    +   *   The media library configuration.
    

    Let's change the doc comment here: "The current state of the media library, derived from the current request."

  5. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -192,6 +211,41 @@ protected function buildMediaTypeMenu(MediaLibraryState $state) {
    +    $selected_type = $this->entityTypeManager->getStorage('media_type')->load($selected_type_id);
    +    $form_class = $selected_type->getSource()->getPluginDefinition()['forms']['media_library_add'] ?? NULL;
    

    If we use PluginWithFormsInterface, this can all go down to one long line:

    $form_class = $this->entityTypeManager->getStorage('media_type')->load($selected_type_id)->getSource()->getFormClass('media_library_add');

  6. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -192,6 +211,41 @@ protected function buildMediaTypeMenu(MediaLibraryState $state) {
    +    // media library with an new version of the media add form. The form API
    

    "...with a new version..."

  7. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -192,6 +211,41 @@ protected function buildMediaTypeMenu(MediaLibraryState $state) {
    +      $form_state->setUserInput([]);
    

    Wouldn't setRebuild() have the same effect? If not, why not?

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new32.44 KB
new60.01 KB
new198.02 KB

#7
1. Ok, let's wait for feedback from the framework managers.
2. Thanks!
3. Fixed.
4. Fixed.
5. Fixed.
6. Fixed.
7. Fixed.
8. Fixed.
9. Fixed.
10. Fixed.
11. Fixed.
12. Fixed.
13. Fixed.
14. Fixed.
15. Refactored the methods a bit since I agree some things were being done in awkward places. Most important changes:

  • Added getMediaType() the get the media type in a uniform way instead of it magically being set as a property somewhere and depending on it being there.
  • Added createFileItem() so we don't have to store the validators and upload location as properties (they are only used once).
  • Added updateFormState() to store the created media items in the form state instead of a property on the class. This allows us to abstract some code every subclass needs to implement and provide better documentation.

#8
1. Fixed.
2. Fixed.
3. We need this for #3023797: Let users choose what to do after selecting and/or adding items in the media library when users could potentially return to the media library after adding new media, so let's add it there instead of this issue.
4. Fixed.
5. :)
6. Fixed. See 3.
7. This is no longer relevant for this issue since the code has been removed until #3023797: Let users choose what to do after selecting and/or adding items in the media library, but FYI.
That didn't appear to work. Form input is always processed in the following cases (see formBuilder::doBuildForm()), so the only way to stop the form processing is setting empty input in the form state.

if ($form_state->isProgrammed() || (!empty($input) && (isset($input['form_id']) && ($input['form_id'] == $form_id))))

seanb’s picture

StatusFileSize
new10.82 KB
new60.96 KB
new198.96 KB

After testing the patch with #2113931: File Field design update: Upload field. I noticed some small changes we should make to allow everything to work together, and some small UX improvements.

Besides that I removed a unnecessary call to OpenModalDialogCommand and updated to way we fetch the field ID (since that was changed in #3020716: Add vertical tabs style menu to media library).

phenaproxima’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/media_library/css/media_library.theme.css
    @@ -70,6 +70,23 @@
    +.media-library-add-form.with-input {
    +  border-bottom: 0;
    +}
    +
    +.media-library-add-form.without-input .form-item {
    +  margin: 0 0 1em;
    +}
    

    I'm not sure we need both with-input and without-input classes. .media-library-add-form, by itself, can target the form without any input.

  2. +++ b/core/modules/media_library/css/media_library.theme.css
    @@ -264,31 +281,26 @@
    +.media-library-add__media:last-child {
    +  border-bottom: 0;
    

    Can this rule get a comment explaining why :last-child is added?

  3. +++ b/core/modules/media_library/media_library.module
    @@ -41,6 +39,21 @@ function media_library_help($route_name, RouteMatchInterface $route_match) {
    +  $addition = [
    +    'forms' => [
    +      'media_library_add' => '\Drupal\media_library\Form\FileUploadForm',
    +    ],
    +  ];
    +  $sources['audio_file'] = array_merge($sources['audio_file'], $addition);
    +  $sources['video_file'] = array_merge($sources['video_file'], $addition);
    +  $sources['file'] = array_merge($sources['file'], $addition);
    +  $sources['image'] = array_merge($sources['image'], $addition);
    

    I know I wrote this originally, but now this array_merge seems weird to me. IMHO, we should just explicitly set the class four times, like so:

    $class = '\Drupal\media_library\Form\FileUploadForm';
    $sources['audio_file']['forms']['media_library_add'] = $class;
    $sources['video_file']['forms']['media_library_add'] = $class;
    ...
    

    I think this will be less intrusive, not to mention more readily understandable, than array_merge().

  4. +++ b/core/modules/media_library/media_library.routing.yml
    @@ -1,9 +1,3 @@
    -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'
    

    I hope this doesn't constitute a BC break. Even if it does, I don't think we can keep it around -- the very form it refers to is disappearing, and that code path is dead as a doornail. So if this is a BC break, then as far as I as a subsystem maintainer am concerned, it's a necessary one in an experimental module. No need to change anything here, just pointing it out.

  5. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,405 @@
    +   * Static cache of the type of media items being created by this form.
    

    This isn't exactly a static cache (or at least, that terminology has a different connotation), so can this simply say "The media type being created by this form"?

  6. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,405 @@
    +  protected $mediaType;
    

    I'm tempted to say that we should make this private, specifically to force subclasses into using $this->getMediaType() where possible, which means that any validation or processing they do in getMediaType() will always run (which, IMHO, reduces the number of possible vectors for bugs). For now, let's hold off, but this is yet another question I want to defer to a framework manager.

  7. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,405 @@
    +    $this->mediaType = $this->entityTypeManager->getStorage('media_type')
    +      ->load($form_state->get('media_library_state')->getSelectedTypeId());
    +    if (!$this->mediaType) {
    +      throw new \LogicException('Exactly one media type is needed in order to create media items.');
    +    }
    

    This exception message no longer makes sense. How about "The '$selected_type' media type does not exist"?

  8. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,405 @@
    +      $form['#attributes']['class'][] = 'without-input';
    

    As noted above, I think we can remove this class.

  9. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,405 @@
    +      $form['actions'] = $this->buildActions($form, $form_state);
    +      $form['actions']['#type'] = 'actions';
    

    I think that buildActions() should be responsible for building the _entire_ actions element, including the #type => 'actions' piece of it.

  10. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,405 @@
    +    $form_state->set('media', $media);
    +    $form_state->setRebuild();
    

    Nit: These calls are chainable.

  11. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,405 @@
    +  /**
    +   * Prepares a created media item to be permanently saved.
    +   *
    +   * @param \Drupal\media\MediaInterface $media
    +   *   The unsaved media item.
    +   */
    +  protected function preSaveMediaEntity(MediaInterface $media) {
    +    // Intentionally empty by default.
    +  }
    

    I think we should rename this method to prepareMediaEntityForSave(). Longer, yes, but clearer! preSaveMediaEntity() sounds like a pre-save hook, which is completely different :)

  12. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,405 @@
    +    if (!empty($form_state->hasAnyErrors())) {
    

    hasAnyErrors() is static, so this should be $form_state::hasAnyErrors(). Also, we don't need to use !empty() -- hasAnyErrors() returns a boolean, so calling empty() is redundant.

  13. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -0,0 +1,232 @@
    +    if ($this->mediaType) {
    +      return $this->mediaType;
    +    }
    +
    +    $this->mediaType = parent::getMediaType($form_state);
    

    This isn't needed. We can just do $media_type = parent::getMediaType($form_state).

  14. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -0,0 +1,232 @@
    +      '#title' => $this->formatPlural($slots, 'Add file', 'Add files'),
    

    Nice!

  15. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -0,0 +1,232 @@
    +    $file_upload_help = [
    +      '#theme' => 'file_upload_help',
    +      '#upload_validators' => $form['upload']['#upload_validators'],
    +      '#cardinality' => $slots,
    +    ];
    +    $form['upload']['#description'] = $this->renderer->renderPlain($file_upload_help);
    

    Is there some reason we need to render the upload help now, rather than just set it on $form['upload_help'] and let it be rendered later? If so, we need to document that with a comment.

  16. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -0,0 +1,232 @@
    +    if ($form_state->getErrors()) {
    

    Should be $form_state::hasAnyErrors().

  17. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -0,0 +1,232 @@
    +    if (!file_prepare_directory($upload_location, FILE_CREATE_DIRECTORY)) {
    +      throw new \Exception("The destination directory '$upload_location' is not writable");
    +    }
    +    $file = file_move($file, $upload_location);
    +    if (!$file) {
    +      throw new \Exception("Unable to move file to '$upload_location'");
    +    }
    

    Let's throw \RuntimeException instead of just \Exception.

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new691 bytes
new61.36 KB
new199.2 KB

#11

1. The add form has 2 different states that look quite different. The first step is showing the source field. The second step is showing the fields for the created media items. The only way to differentiate between the 2 states from the frontend are these classes. So even though we can technically remove 1 of the classes, detecting the other state through :not('with-input') does not feel like the best solution to me.
2. Fixed.
3. Fixed.
4. Thanks.
5. Fixed.
6. Leaving this for now.
7. Fixed. We can remove the entire exception since the state validation enforces a valid selected type :)
8. See reply in 1.
9. Fixed.
10. Fixed.
11. Fixed.
12. Fixed.
13. Fixed.
14. Yay!
15. Fixed.
16. Fixed.
17. Fixed.

seanb’s picture

StatusFileSize
new9.44 KB

Not sure what happened to the interdiff. Here is the right one.

larowlan’s picture

There is a lot of work gone into this, its really appreciated.

There is a lot of css/js here so adding Needs frontend framework manager review tag.

  1. +++ b/core/modules/media_library/css/media_library.theme.css
    @@ -5,6 +5,84 @@
    +  border-right: 1px solid #fcfcfa;
    ...
    +  border-left: 1px solid #b3b2ad;
    

    do we need RTL equivalents here?

  2. +++ b/core/modules/media_library/js/media_library.ui.es6.js
    @@ -0,0 +1,312 @@
    +            $('#media-library-content').focus();
    

    should we use getElementById here instead of jQuery?

  3. +++ b/core/modules/media_library/js/media_library.ui.es6.js
    @@ -0,0 +1,312 @@
    +          .find('#media-library-modal-selection')
    +          .val(currentSelection.join())
    

    note to self, do we check access on this

  4. +++ b/core/modules/media_library/media_library.module
    @@ -90,11 +68,7 @@ function media_library_views_post_render(ViewExecutable $view, &$output, CachePl
    +      $query = MediaLibraryState::fromRequest()->all();
    

    we should pass along the request here

  5. +++ b/core/modules/media_library/media_library.services.yml
    @@ -0,0 +1,4 @@
    +  media_library.ui_builder:
    

    the only place I see this used is in the routing and in tests.

    we deliberately decided not to make controllers services in the D8 design process because we didn't want to bloat the service container.

    I don't see a reason to do it here either.

    Am I missing something?

    We have ContainerInjectionInterface if you need to inject stuff into the controller and we have the ControllerResolver if you need to instantiate the class in a test.

  6. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,401 @@
    +    $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__source-field';
    +    }
    

    Why do we hide them with CSS and not use form modes?

  7. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,401 @@
    +    return $this->entityTypeManager
    +      ->getStorage('media')
    +      ->create([
    +        'bundle' => $media_type->id(),
    +        $this->getSourceFieldName($media_type) => $source_field_value,
    

    we're calling this in the array_map which means we're doing the getStorage and the getSourceFieldame each time, but really we could do it once and pass it along

  8. +++ b/core/modules/media_library/src/MediaLibraryState.php
    @@ -0,0 +1,204 @@
    +      $request->query->get('media_library_opener_id'),
    +      $request->query->get('media_library_allowed_types'),
    +      $request->query->get('media_library_selected_type'),
    +      $request->query->get('media_library_remaining')
    

    I think we need an anti-tamper token here too

  9. +++ b/core/modules/media_library/src/MediaLibraryState.php
    @@ -0,0 +1,204 @@
    +    $existing_ids = \Drupal::entityTypeManager()->getStorage('media_type')
    

    should we inject this?

  10. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -0,0 +1,260 @@
    +    $state = MediaLibraryState::fromRequest();
    ...
    +    $state = MediaLibraryState::fromRequest();
    

    i feel we should be passing the request object here, this is a non-static method, we have container injection, we should fetch it and pass it along

    Once we do that, we make the parameter required and it no longer needs the singleton

  11. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -0,0 +1,260 @@
    +    $view = Views::getView('media_library');
    

    should we inject here instead of using the singleton?

  12. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -0,0 +1,260 @@
    +    $allowed_types = $this->entityTypeManager->getStorage('media_type')->loadMultiple($allowed_type_ids);
    

    yeah, defs should be adding an anti-tamper token to prevent people meddling with the allowed types

  13. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -0,0 +1,260 @@
    +      $query['media_library_selected_type'] = $allowed_type_id;
    

    the last one is always selected?

  14. +++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
    @@ -41,6 +43,13 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
    +  protected static $openerIdPrefix = 'field:';
    

    are we sure a global static is the right choice? - from the uses I see, could be a constant

  15. +++ b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
    @@ -64,6 +64,17 @@ public function viewsForm(array &$form, FormStateInterface $form_state) {
    +    $selection_field_id = $this->options['id'] . '_selection';
    +    $form[$selection_field_id] = [
    +      '#type' => 'hidden',
    +      '#attributes' => [
    +        // This is used to identify the hidden field in the form via JavaScript.
    +        'id' => 'media-library-modal-selection',
    +      ],
    +    ];
    

    what controls are in place to prevent someone from editing the dom on this and adding in references to media entities they cannot access?

  16. +++ b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
    @@ -95,17 +109,22 @@ public function viewsForm(array &$form, FormStateInterface $form_state) {
    +    $opener_id = MediaLibraryState::fromRequest()->getOpenerId();
    

    we should pass on the request here, obtained from DI on the plugin

seanb’s picture

@larowlan Thanks for the review. I think most of the feedback is applicable to #3020716: Add vertical tabs style menu to media library. It's kind of confusing that I am building patches on top of patches, but I guess we can't avoid that.

Could you copy the review over to the other issue as well?

larowlan’s picture

Sure, @xjm asked me to review this one - sorry for the crossed wires

phenaproxima’s picture

OK, so in addition to general framework manager sign-off, these are the two questions we specifically need framework managers to answer for us:

1. This patch introduces the idea of attaching a "media_library_add" form class to source plugin definitions. Should we, can we, implement PluginWithFormsInterface? See #7.1.

2. In AddFormBase, can we/should we make the $mediaType member private? This would force all subclasses to use the protected getMediaType() method, which is where validation or other logic surrounding the media type would go. See #11.6.

phenaproxima’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

Now that #3020716: Add vertical tabs style menu to media library is in, we probably need a reroll here.

seanb’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new8.13 KB
new61.74 KB

Reroll done!

Regarding #14. Most is fixed/answered in #3020716-69: Add vertical tabs style menu to media library, below is what is left for this patch:
6. Since the alt text, title and upload field are provided by a single widget, we need to hide the relevant part via CSS. We also have #2987921: Media Library add form should suppress extraneous components of image fields using a form alter, not CSS to improve this.
7. Fixed in this patch.

lauriii’s picture

Status: Needs review » Needs work
+++ b/core/modules/media_library/src/Form/AddFormBase.php
@@ -0,0 +1,404 @@
+      $form['#attributes']['class'][] = 'without-input';
...
+      $form['#attributes']['class'][] = 'with-input';

This should be media-library-add-form--without-input and media-library-add-form--with-input.

This was a very partial review since I don't seem to be able to get the uploading working. I get the following error when trying to upload either file or image:

An AJAX HTTP error occurred.
HTTP Result Code: 200
Debugging information follows.
Path: /media-library?media_library_opener_id=field%3Afield_media&media_library_allowed_types%5Bfile%5D=file&media_library_allowed_types%5Bimage%5D=image&media_library_selected_type=image&media_library_remaining=8&media_library_content=1&_wrapper_format=drupal_ajax&ajax_form=1
StatusText: OK
ResponseText: Error: Call to a member function transformDimensions() on null in template_preprocess_image_style() (line 301 of /Users/lauri.eskola/Projects/drupal/core/modules/image/image.module).
lauriii’s picture

I got the patch working after @phenaproxima suggested me to run database updates.

  1. Media entities selected prior to uploading new media file are lost. I would have expected those to remain attached.
  2. I found the second step of the upload confusing; I wasn't sure if the file was already saved or not. This was relevant because I accidentally first uploaded a wrong file. I'm wondering also if the text on the submit button should be changed from "Save" to something like "Save and attach" to be more clear of the action.
  3. There's no way to cancel upload without losing selected files in the media listing. This is again relevant because I uploaded a wrong file, and the only way to restart the process was to close the dialog.
  4. +++ b/core/modules/media_library/css/media_library.theme.css
    @@ -313,7 +323,7 @@
    +.media-library-add__media-preview img {
    

    Let's add a class directly to the img element and target that.

  5. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,404 @@
    +    $form['#prefix'] = '<div id="media-library-add-form-wrapper">';
    +    $form['#suffix'] = '</div>';
    

    Is there another way this markup could be generated? Generating markup like this in #prefix and #suffix isn't necessarily the cleanest approach. Also, the id given for the element is not ensured to be unique.

  6. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,404 @@
    +          'media-library-add__media',
    

    Not sure why this includes a double underscore. Feel free to come up with a better class name but this should be something like media-libary-add-media-form

  7. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,404 @@
    +          'media-library-add__media',
    ...
    +            'media-library-add__media-preview',
    

    This should be media-libary-add-media-form__preview

  8. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,404 @@
    +            'media-library-add__media-fields',
    

    This should be media-libary-add-media-form__fields

  9. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,404 @@
    +      $element['fields'][$source_field_name]['#attributes']['class'][] = 'media-library-add__source-field';
    

    This should be media-libary-add-media-form__source-field

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new5.16 KB
new62.32 KB

#20
Fixed.

#21
1. We clear the selection when the dialog closes. In #3023797: Let users choose what to do after selecting and/or adding items in the media library we want to improve this by allowing users to stay in the media library after adding new items. In this case the dialog will not be closed and the selection will be kept. The current patch only supports selecting OR uploading. Not both at the same time.
2. The items are not saved in that step. We have #3023801: Allow newly uploaded files to be deleted from the media library without saving them to improve this by allowing users to delete uploaded items directly (before saving). I think this would solve the issue.
3. Same as 2. This will probably be fixed by #3023801: Allow newly uploaded files to be deleted from the media library without saving them.
4. Fixed. I don't think we need that display block so removed it.
5. We can add a wrapper inside the form via a container element, but this has some limitations when rebuilding the form via AJAX. Not sure if there is a better way? Core uses this approach in several places. Might be a limitation of the form API.
6. Fixed. Used media-library-add-form__media*<code> since media is an element inside the <code>media-library-add-form block. According to the BEM convention elements and blocks should be separated by a double underscore.
7. Fixed.
8. Fixed.
9. Fixed.

shaal’s picture

The bug reported on #3031662 still exist in this last patch. There is no warning for the user when uploading a file bigger than what's allowed.

From a UX perspective, could we load all the media options interfaces (for image, video, etc.) and show only the active one using css? Currently, switching between media types displays a spinner and takes between 0.5 - 1 seconds to load.

lauriii’s picture

I don't see mentions of #21.1-3 on those issues. We should make sure those problems get documented somewhere specifically so they get addressed one way or another.

phenaproxima’s picture

seanb’s picture

phenaproxima’s picture

I had a long discussion with @xjm about framework manager review, and what it means. The conclusion I reached is that this issue does not need framework manager review, so I'm removing the tag.

Why?

  1. 99% of this patch is refactoring classes in Media Library which are explicitly marked @internal. This impacts no other subsystems, or even other modules (including Media). A single class is split into two (one abstract and one concrete), and both are still marked internal. All this is being done specifically in order to pave the way for #2996029: Add oEmbed support to the media library.
  2. Only one change is made to the stable Media module: a public property is added to its MediaSource plugin annotation. But this presents no problem from a backwards compatibility standpoint.
  3. A route is removed, but there was no way to keep it. Not only is it completely dead, it's not even usable with the new design and architecture of the media library, which is still experimental.

All this does not, I think, add up to a "significant" change to Drupal core. From an architecture, API, and BC standpoint, I think we're safe.

phenaproxima’s picture

@seanB and I demonstrated this patch to the UX team in the usability meeting. The findings were very productive!

  • Everyone agreed with @lauriii that the current flow, where you upload an image, enter some metadata, and click "Save", only to be kicked back to the node form with your previous selections forgotten, is unacceptable. It's too much of a regression, even to address in a follow-up issue (which would, necessarily, be a critical). So, in this patch, we are going to change the action of the Save button so that it sends you back to the media library, with your previous selections, in addition to your new upload, still selected.
  • But we can't stop there, because that workflow is going to be too cumbersome for some. To mitigate that, we're going to repurpose #3023797: Let users choose what to do after selecting and/or adding items in the media library as a high-priority follow-up which will a) add a second button the metadata screen, which will give the user two options (actual wording may vary): "Save and return to library", and "Save and insert selected items". This is still not amazing, but it allows the user to use the media library in whichever way is most efficient for them. As part of that work, we will add an area to the metadata screen which will display small thumbnails of all the items you have already selected from the library. This is important from both an accessibility and usability perspective. @ckrina has promised to create a design for this area.
phenaproxima’s picture

See #3023797-12: Let users choose what to do after selecting and/or adding items in the media library for the new direction that issue is taking based on the UX feedback.

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new16.11 KB
new72.1 KB

Here is a new patch that takes users back to the media library after adding a new file.

Status: Needs review » Needs work

The last submitted patch, 30: 3023802-30.patch, failed testing. View results

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new4.78 KB
new72.79 KB

Let's inject the UI builder. Sorry about that.

phenaproxima’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/media/src/Annotation/MediaSource.php
    @@ -57,6 +57,16 @@ class MediaSource extends Plugin {
    +   * The classes used to define media source specific forms.
    

    Nit: This should be "source-specific". Also, we should mention that these are to be keyed by operation, and the values are the fully qualified class names of the forms to use.

  2. +++ b/core/modules/media_library/js/media_library.ui.es6.js
    @@ -13,6 +13,13 @@
    +    /**
    +     * When a user is returned to the media library after adding new media items
    +     * the item IDs are passed via the Drupal settings. The IDs of processed
    +     * media items are stored to make sure every created media item is only
    +     * added to the current selection once.
    +     */
    +    processedCreatedItems: [],
    

    I'm not sure I understand the rationale for this. Why wouldn't we just add them to currentSelection? How will that cause duplication?

  3. +++ b/core/modules/media_library/js/media_library.ui.es6.js
    @@ -240,6 +248,18 @@
    +      // selection, and add the IDs to a list of processed items so the are
    

    "so they are..."

  4. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,420 @@
    +/**
    + * Provides a base class for creating media items from within the media library.
    + */
    +abstract class AddFormBase extends FormBase {
    

    I think we should mark this class @internal too, with a warning that it should not be extended or used by any code outside of Media Library.

  5. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,420 @@
    +  /**
    +   * The media library UI builder.
    +   *
    +   * @var \Drupal\media_library\MediaLibraryUiBuilder
    +   */
    +  protected $libraryUiBuilder;
    

    This almost seems like a circular dependency -- the UI builder already has the form builder, which in turn builds this form, which also has the form builder...scary stuff. Not sure if there's any reason to be concerned, but it's a bit unnerving.

  6. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,420 @@
    +    // @todo Make this configurable in https://www.drupal.org/node/2988223
    

    Make what configurable? Can we be a bit more specific?

  7. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,420 @@
    +   * @return array
    +   *   An array of supported Form API action elements keyed by name.
    +   */
    +  protected function buildActions(array $form, FormStateInterface $form_state) {
    

    This isn't really returning an array of action elements; it's returning an element containing the actions.

  8. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,420 @@
    +   * Update the form state with media items created from source field values.
    

    Let's rephrase this: "Creates media items from source field input values."

  9. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,420 @@
    +   *   The values for source field of the the media item.
    

    "field" and "item" should be plural.

  10. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,420 @@
    +  protected function updateFormState(array $source_field_values, array $form, FormStateInterface $form_state) {
    

    I think this method is strangely named. How about something like processInputValues() or createMediaItemsFromInputValues()? Not sure what would be best here, but I don't think updateFormState() is descriptive enough.

  11. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,420 @@
    +  protected function createMediaEntity(MediaTypeInterface $media_type, EntityStorageInterface $media_storage, $source_field_name, $source_field_value) {
    

    Let's rename this as well -- createMediaFromValue() is probably descriptive enough.

  12. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,420 @@
    +    if ($form_state::hasAnyErrors()) {
    +      $response = new AjaxResponse();
    +      $response->addCommand(new ReplaceCommand('#media-library-add-form-wrapper', $form));
    +      return $response;
    +    }
    

    So, question -- isn't this logic pretty much the same thing as return $form?

  13. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,420 @@
    +    $state->remove('media_library_content');
    

    What is this? Can we get a comment here?

  14. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,420 @@
    +    $response = new AjaxResponse();
    +    $response->addCommand(new ReplaceCommand('#media-library-add-form-wrapper', $library_ui));
    +    return $response;
    

    Can't we just return $library_ui here?

  15. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -0,0 +1,244 @@
    + * @internal
    + */
    +class FileUploadForm extends AddFormBase {
    

    Let's copy that @internal warning from AddFormBase here too.

  16. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -0,0 +1,244 @@
    +    // The file upload help needs to be rendered since the description does not
    +    // accept render arrays. The FileWidget::formElement() method adds the file
    +    // upload help in the same way, so any theming improvements made to file
    +    // fields would also be applied to this upload field.
    +    // @see \Drupal\file\Plugin\Field\FieldWidget\FileWidget::formElement()
    +    $form['upload']['#description'] = $this->renderer->renderPlain($file_upload_help);
    

    Nice comment here. Thanks!

  17. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -0,0 +1,244 @@
    +    if ($form_state::hasAnyErrors()) {
    +      $element['#value'] = [];
    +    }
    

    I think this could use a comment. What will setting $element['#value'] to an empty array accomplish? Is there any relevant method in ManagedFile that we can refer to?

  18. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -0,0 +1,244 @@
    +    $element['upload_button']['#ajax'] = [
    +      'callback' => '::updateFormCallback',
    +      'wrapper' => 'media-library-wrapper',
    +    ];
    

    The fact that this knows the ID of the wrapper is a bit troublesome, because it shows a lack of encapsulation. That said, given how the AJAX system works, I'm not sure what else we could do here. Also, since this code is also in Media Library, and it's marked internal, it's not a big deal for now.

  19. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -77,16 +89,25 @@ public static function dialogOptions() {
    +   * @param bool $form_rebuild
    +   *   After the form to add new media is submitted, we need to rebuild the
    +   *   media library with an new version of the media add form. The form API
    +   *   allows us to do that by forcing empty user input.
    

    We need to start this description with (optional) and mention that it defaults to FALSE.

  20. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -110,11 +131,15 @@ public function buildUi() {
    +   * @param bool $form_rebuild
    +   *   After the form to add new media is submitted, we need to rebuild the
    +   *   media library with an new version of the media add form. The form API
    +   *   allows us to do that by forcing empty user input.
    

    Same here.

  21. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -216,6 +242,44 @@ protected function buildMediaTypeMenu(MediaLibraryState $state) {
    +    $form_state = new FormState();
    +    if ($form_rebuild) {
    +      $form_state->setUserInput([]);
    +    }
    

    I'm not a huge fan of passing random boolean flags around to do stuff like this, but it makes even less sense to have the calling code create and pass in a FormState object. So I guess I'm okay with this for now...hopefully we can refactor it later.

  22. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -533,6 +533,46 @@ public function testWidgetUpload() {
    +    $assert_session->assertWaitOnAjaxRequest();
    +    $assert_session->pageTextContains('Media library');
    

    Can we wait for a specific element instead?

  23. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -533,6 +533,46 @@ public function testWidgetUpload() {
    +    $assert_session->fieldNotExists('Add file');
    +    $assert_session->fieldNotExists('Add files');
    

    I thought this was a duplicate at first :) It might be better to assert this by the name of the field, rather than the label.

  24. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -544,10 +584,15 @@ public function testWidgetUpload() {
    +    // Assert the default tab for media type one does not have an upload form.
    +    $assert_session->fieldNotExists('Add file');
    +    $assert_session->fieldNotExists('Add files');
    

    Same here.

  25. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -544,10 +584,15 @@ public function testWidgetUpload() {
    +    $page->find('css', '.media-library-menu .media-library-menu-type-three > a')->click();
    

    Can't we click on the link by its text? That'd be so much clearer. I believe we could even do

    $page->find('css', '.media-library-menu')->clickLink('Link text')</a>, which is quite a bit cleaner.
    </li>
    
    <li>
    <code>
    +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -577,52 +643,79 @@ public function testWidgetUpload() {
    +    $checkbox_selector = '.media-library-view .js-click-to-select-checkbox input';
    +    $checkboxes = $this->getSession()->getPage()->findAll('css', $checkbox_selector);
    +    $checkboxes[0]->click();
    

    This could just be one (long) line: $page->find('css', '.media-library-view .js-click-to-select-checkbox input')->click(). find() will automatically target the first one.

  26. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -577,52 +643,79 @@ public function testWidgetUpload() {
    +    // Ensure the created item is added the widget.
    

    "added the widget"? I think we're missing an "in" :)

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new22.7 KB
new73.26 KB

#33

1. Fixed.
2. Fixed. Passing the media IDs to the current selection via DrupalSettings needed this workaround. After discussing this with phenaproxima and samuel.mortenson we concluded that a custom AJAX command was the cleanest way to update the selection.
3. Removed this code.
4. Fixed.
5. In a previous version we reopened the dialog with a URL. This had 2 drawbacks. First, it needs an extra request to the server. Second, since the whole dialog is replaced this is an a11y issue. I can see how reopening the library from the form, with a new/fresh instance of the same form looks a bit weird, which is why we need to force the form API to get us a fresh copy of the form by setting the empty input in the form state. I don't see a good way around that though. The form needs to render a new copy of the library somehow.
6. Fixed.
7. Fixed.
8. Fixed.
9. Fixed.
10. Fixed.
11. Fixed.
12. Not necessarily. Depends on whether the wrapper is set on the element triggering this. The wrapper is normally set to replace the whole media library, but when there is an error you just want to replace the form.
13. Fixed.
14. Fixed. I think we can in this case.
15. Fixed.
16. Yay!
17. Fixed.
18. I see your point, but the library UI and the form are changing/replacing each other in the design. If someone has a better way of doing this I am very much open to suggestions!
19. Fixed.
20. Fixed. No longer optional here.
21. Again, if anyone has suggestion on how to improve this, I am very much open to suggestions!
22. The media library tests are currently consistently using assertWaitOnAjaxRequest(). We could change this if needed, but I suggest we do that consistently in a followup.
23. Fixed.
24. Fixed.
25. Fixed.
26. Fixed.

phenaproxima’s picture

Status: Needs review » Needs work

Awww yeah. Sam saves the day -- that's a much nicer solution.

  1. +++ b/core/modules/media_library/js/media_library.ui.es6.js
    @@ -13,13 +13,28 @@
    +  Drupal.AjaxCommands.prototype.updateCurrentSelection = function(
    

    We should probably namespace this a bit -- updateMediaLibrarySelection might be a better name.

  2. +++ b/core/modules/media_library/js/media_library.ui.es6.js
    @@ -13,13 +13,28 @@
    +    if (response.mids) {
    +      Object.values(response.mids).forEach(value => {
    +        Drupal.MediaLibrary.currentSelection.push(value);
    +      });
    +    }
    

    If we always supply an array in response.mids, even if it's empty, we don't need the if check.

  3. +++ b/core/modules/media_library/src/Ajax/UpdateCurrentSelectionCommand.php
    @@ -0,0 +1,51 @@
    +class UpdateCurrentSelectionCommand implements CommandInterface {
    

    I think the word "current" in this name is redundant. Can we just call this UpdateSelectionCommand?

  4. +++ b/core/modules/media_library/src/Ajax/UpdateCurrentSelectionCommand.php
    @@ -0,0 +1,51 @@
    +  /**
    +   * Constructs an UpdateCurrentSelectionCommand object.
    +   *
    +   * @param int[] $mids
    +   *   An array of media IDs to add to the current selection.
    +   */
    +  public function __construct(array $mids) {
    +    $this->mids = $mids;
    +  }
    

    Idea -- let's add a flag in here which allows the command to either _add_ the IDs to the current selection, or completely _replace_ the current selection with the new IDs. I think this will serve us well in the future.

  5. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -131,7 +134,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +   * the user input and store them in the form state using ::processInputValues().
    

    Supernit: This line is 82 characters long.

  6. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -586,11 +584,10 @@ public function testWidgetUpload() {
    +    $assert_session->elementExists('named', ['link', 'Type Three'])->click();
    

    I think we can use $page->clickLink() for this.

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new9.41 KB
new75.94 KB

#35
1. Fixed.
2. Fixed.
3. Fixed.
4. Let's add that when we actually need it. We could implement it later in a backwards compatible way for sure.
5. Fixed.
6. Fixed.

Also forgot to answer #23

The bug reported on #3031662 still exist in this last patch. There is no warning for the user when uploading a file bigger than what's allowed.

This is a nginx error. Afaict Drupal can't do anything about this since nginx doesn't accept the file and stops the request before it gets to Drupal. If you increase the nginx limit, the PHP limit will be used and that could solve the issue. Let's use the other issue to see what we can do.

From a UX perspective, could we load all the media options interfaces (for image, video, etc.) and show only the active one using css? Currently, switching between media types displays a spinner and takes between 0.5 - 1 seconds to load.

Since the media library loads a view with 25 items for each tab this could be a performance issue. If you configure too much media types for a field with a library that contains lots of items, you could get easily get in trouble. We should probably protect site builders from that.

phenaproxima’s picture

Status: Needs review » Needs work

Another partial review...

  1. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,430 @@
    +    $this->mediaType = $this->entityTypeManager->getStorage('media_type')
    +      ->load($form_state->get('media_library_state')->getSelectedTypeId());
    +    return $this->mediaType;
    

    For paranoia's sake, let's throw exceptions if $form_state doesn't have media_library_state, and if $this->mediaType is null after the call to load(). That will be preferable to the possibility of calling methods on null values.

  2. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,430 @@
    +
    +    $form['#attributes']['class'][] = 'media-library-add-form';
    +    $media_items = $form_state->get('media');
    +    if (empty($media_items)) {
    +      $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';
    

    Do the tests assert that these CSS classes are applied and removed as expected? If not, they probably should. :)

  3. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,430 @@
    +      foreach ($media_items as $delta => $media) {
    +        $form['media'][$delta] = $this->buildEntityForm($media, $form, $form_state, $delta);
    +      }
    

    Mink doesn't support multi-file uploading, so we'll probably have to be sure to manually test this before commit.

  4. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,430 @@
    +    // We hide certain elements in the image widget with CSS.
    

    Which elements are hidden? This should probably be a little more specific and say what is hidden, and why.

  5. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,430 @@
    +    if (isset($element['fields']['revision_log_message'])) {
    +      $element['fields']['revision_log_message']['#access'] = FALSE;
    +    }
    

    This could use a comment explaining that we hide the field here because it is not configurable at the form display level. If there's an issue to make it configurable, we should reference it here, with a @todo to remove this bit.

  6. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,430 @@
    +  protected function createMediaFromValue(MediaTypeInterface $media_type, EntityStorageInterface $media_storage, $source_field_name, $source_field_value) {
    

    I'm not entirely sure we need to pass the entity storage handler to this method; we already have the entity type manager as a protected property on this class, and it's pretty harmless to have this method call $this->entityTypeManager->getStorage(). This is a method that subclasses are likely to override, so I feel like streamlining the method signature would be preferable.

  7. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,430 @@
    +    // Pass the created IDs via the Drupal settings so we can automatically
    +    // select them in the library.
    +    $library_ui['#attached']['drupalSettings']['media_library']['created'] = $mids;
    

    Is this still needed?

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new3.73 KB
new76.85 KB

#37
1. Fixed.
2. Fixed.
3. Yeah, that sucks.
4. Fixed.
5. Fixed.
6. See #14.7
7. Fixed. Whoops!

phenaproxima’s picture

Status: Needs review » Needs work

After these changes, I have read the patch enough. From there, we'll need screenshots, plus a demo video, and manual testing (i.e., for multiple uploads and general stuff). Then RTBC.

  1. +++ b/core/modules/media_library/js/media_library.ui.es6.js
    @@ -15,6 +15,26 @@
    +    Object.values(response.mids).forEach(value => {
    

    Do we really need to use Object.values()? I ask because response.mids is just a simple list of integers, which _should_ JSON-ize as an array which can be directly forEach'ed over.

  2. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,443 @@
    +      throw new \UnexpectedValueException('The media library state is not present in the form state.');
    

    I think this should probably be InvalidArgumentException.

  3. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,443 @@
    +    if (!$this->mediaType) {
    +      throw new \UnexpectedValueException('The selected media type in the media library state does not exist.');
    +    }
    

    This exception could be a little more helpful with a message like "The '$selected_type_id' media type does not exist." Also, this should probably be InvalidArgumentException.

    Also, can we add some unit test coverage for these exceptions? I ask because, if the media type is invalid in some way, the entire form is unusable. So we should probably ensure this validation works.

  4. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -77,16 +89,25 @@ public static function dialogOptions() {
    +   *   (optional) After the form to add new media is submitted, we need to rebuild the
    

    Nit: Longer than 80 characters.

phenaproxima’s picture

+++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
@@ -77,16 +89,25 @@ public static function dialogOptions() {
+  public function buildUi(MediaLibraryState $state = NULL, $form_rebuild = FALSE) {
+    if (!$state) {
+      $state = MediaLibraryState::fromRequest($this->request);
+    }

I had an idea for a way to not continually pass $form_rebuild down through three method calls -- let's just set a flag on $state!

if (!$state) {
  $state = MediaLibraryState::fromRequest($this->request);
}
$state->set('_media_library_form_rebuild', $form_rebuild);

Then, later on, in buildMediaTypeAddForm():

$form_state = new FormState();
if ($state->get('_media_library_form_rebuild')) {
  $form_state->setUserInput([]);
  $state->remove('_media_library_form_rebuild');
}

We could also add a new getter/setter on MediaLibraryState specifically for this (which might be cleaner and doesn't require us to pollute the parameter bag); something like $state->isRebuildingMediaAddForm() and $state->setRebuildMediaAddForm().

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new13.42 KB
new80.34 KB

#39
1. Apparently it becomes an object somewhere, so yeah, I'm affraid we do.
2. Fixed.
3. Fixed.
4. Fixed.

#40
I think having this as an official part of the state might be too much. It's just an internal workaround we are using. And since we can pass a state object to buildUi(), we don't need the $form_rebuild parameter at all.

phenaproxima’s picture

+++ b/core/modules/media_library/tests/src/Kernel/MediaLibraryAddFormTest.php
@@ -0,0 +1,111 @@
+    // Assert the media library UI does not contains the add form when the user

Nit: "contains" should be "contain".

Otherwise, this is ready to go with a bit of manual testing, screenshots, and demo videos. Nice!

phenaproxima’s picture

Issue summary: View changes
StatusFileSize
new110.21 KB
new301.41 KB

Adding screenshots to the IS.

phenaproxima’s picture

Issue summary: View changes
phenaproxima’s picture

StatusFileSize
new22.76 MB

Created a short demo video. I was surprised by the error message showing up floated to the left of the vertical tabs, though, so kicking back for that. :)

phenaproxima’s picture

Status: Needs review » Needs work

Meant to send it back to "Needs work"...

dww’s picture

This mostly looks great. You two have been doing amazing work on all these media issues!

Partial drive-by review, mostly nit-tastic quibbling. I'm out of time right now so I didn't go over every line, but a few things stuck out. Please ignore as appropriate. ;)

  1. +++ b/core/modules/media/src/Annotation/MediaSource.php
    @@ -57,6 +57,16 @@ class MediaSource extends Plugin {
    +  /**
    +   * The classes used to define media source-specific forms.
    +   *
    +   * An example of this would be the 'media_library_add' form used by the media
    +   * library module.
    +   *
    +   * @var string[]
    +   */
    +  public $forms = [];
    +
    

    I don't fully grok this comment, nor the name of the member. ;) It's an array of strings. It's called "forms". The doc says "The classes used..." the example looks like a form_id, not a class name. Can we get a live example in this comment? Can the help text match? Can the member be named something more self-documenting? $formClasses or $formIds or whatever?

  2. +++ b/core/modules/media_library/media_library.module
    @@ -41,6 +40,16 @@ function media_library_help($route_name, RouteMatchInterface $route_match) {
    +  $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;
    

    Ahah! ;) That helps. So $forms is supposed to be keyed by form_id with a classname as the value, huh? Let's say so above.

  3. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,444 @@
    + * @internal
    

    FYI: I just saw @xjm complain in the JSON:API code review that she wanted an explanation why something is @internal, and what might go wrong if you try to use it. I can see her point. On the surface, it's not clear why I'm not supposed to extend this myself...

  4. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,444 @@
    +   * @return array
    +   *   The element containing the required fields sub-form.
    +   */
    +  protected function buildEntityForm(MediaInterface $media, array $form, FormStateInterface $form_state, $delta) {
    

    the return value says it's an "element", but the function name says "buildEntityForm". Should this be called buildEntityFormElement() or something?

  5. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,444 @@
    +    // display, so it hidden by changing the access.
    

    "...so hide it by..."

  6. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,444 @@
    +    // @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;
    +    }
    

    Yes, please! ;)

  7. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,444 @@
    +   *   The value for source field of the the media item.
    

    Nit: "The value for the source field of the media item."

  8. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -216,6 +234,42 @@ protected function buildMediaTypeMenu(MediaLibraryState $state) {
    +    // media library with an new instance of the media add form. The form API
    

    s/an new/a new/

  9. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -169,6 +169,18 @@ public function testWidgetAccess() {
    +    // Allow users with 'view media' permission to access the media library view
    +    // and controller.
    +    $this->grantPermissions($role, [
    +      'create type_three media',
    +    ]);
    

    Comment doesn't seem to match the permissions we're granting here.

Thanks again!
-Derek

phenaproxima’s picture

FYI: I just saw @xjm complain in the JSON:API code review that she wanted an explanation why something is @internal, and what might go wrong if you try to use it. I can see her point. On the surface, it's not clear why I'm not supposed to extend this myself...

+1 for this. I suggest we say something "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." And let's copy that warning to everything we've marked @internal. (Which should be just about everything, until we've at least fixed our must-have stable blockers.)

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new7.54 KB
new82.17 KB

Thanks for the screenshots, video and reviews @phenaproxima and @dww!

#47
1. Fixed, expanded the comment.
2. Fixed.
3. Fixed.
4. Fixed.
5. Fixed.
6. :)
7. Fixed.
8. Fixed.
9. Fixed.

#48
Fixed.

Status: Needs review » Needs work

The last submitted patch, 49: 3023802-49.patch, failed testing. View results

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new1.37 KB
new83.25 KB

Ah, figured out what caused #45. We are changing the state to rebuild the form, but also changing the state to build the links.
We shouldn't change the state for the links, just change the query parameters after they are fetched from the state.

seanb’s picture

Copy of #3023801-10: Allow newly uploaded files to be deleted from the media library without saving them, since the feedback applies to this patch.

----------------------------------------------------------

Comment #10 Pancho commented about 5 hours ago

RTL display points to a problem with the counter being two strings rather than one. However, that doesn't seem to be introduced here.

Otherwise the patch looks awesome. Just a few nits:

1.)

+   * 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().

First line has 81 characters, maybe even them out.

+    // When the source field input contains errors, replace the existing form to
+    // let the user change the source field input. If the user input is valid,
+    // the entire modal is replaced with the second step of the form to show the
+    // form fields for each media item.

Same twice.

2.)

+    $media_items = $form_state->get('media');
+    $added_media = $form_state->get('media');
+    $added_media = $form_state->get('media');
+    $media_items = $form_state->get('media') ?: [];
+    $media_items = $form_state->get('media') ?: [];

Do we want to settle with one or the other variable name?

3.)

+    foreach ($media_items as $delta => $media) {
+    foreach ($media_items as $delta => $media) {
+    foreach ($media_items as $i => $media) {

$delta is so much more descriptive than the generic $i. You purged three or four $i keys, so how about bringing the last one in line, too?

4.)

+    $mids = array_map(function (MediaInterface $media) {
+      return $media->id();
+    }, $media_items);

Not your fault, but $mids is so confusing, because unlike $nids, $tids or $vids, it resembles an actual word. I was surprised to see our coding standard now allows camelCase variables, though not mixed. In a slight stretch of the coding standards, I'd propose $mIds for improved clarity, and clarity should IMHO always be the first goal.
If considered a new convention, this would have to be separately discussed in general (think this would improve readability for $nIds, $tIds or $vIds as well). Otherwise it might just be a solution for $mids.

As I'm not yet confident enough to catch everything, we should have another review. But I think this is very close to RTBC.

----------------------------------------------------------

seanb’s picture

StatusFileSize
new8.18 KB
new84.62 KB

Phenaproxima requested a small change to the @internal description. So that was done.

About #52:

1. It's exactly 80 characters as far as I can tell?
2. Fixed. It's now $added_media
3. Fixed.
4. Fixed. Changed it to $media_ids

Regarding the RTL display of "1 of 5 items selected".

Setting the page to RTL changes the direction of the text. Apparently numbers automatically get reversed. I think when the string is translated this should be automatically fixed, but I'm not an expert on RTL implementation. We could force the div containing the text to the ltr direction, but I'm pretty sure we should not do that.

phenaproxima’s picture

Status: Needs review » Needs work

A few comment nitpicks.

  1. +++ b/core/modules/media/src/Annotation/MediaSource.php
    @@ -57,6 +57,18 @@ class MediaSource extends Plugin {
    +   * An array of form classes keyed by ID. The ID does not have to be an actual
    +   * form ID and is used to get a specific form class from the media source. An
    +   * example of this would be the 'media_library_add' form used by the media
    +   * library module.
    

    I think we should rephrase this a bit. I propose something like this:

    "An array of form class names, keyed by ID. The ID represents the operation the form is used for."

    Maybe we should avoid mentioning Media Library here for now, since it's still experimental.

  2. +++ b/core/modules/media_library/js/media_library.ui.es6.js
    @@ -15,6 +15,26 @@
    +    Object.values(response.mids).forEach(value => {
    

    For consistently, we should rename 'mids' to 'mediaIds'. (And make a similar change in the corresponding AJAX command class.)

  3. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -0,0 +1,447 @@
    + * @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.
    

    Nice.

  4. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -77,11 +90,16 @@ public static function dialogOptions() {
    +   * @param \Drupal\media_library\MediaLibraryState|null $state
    +   *   The current state of the media library, derived from the current request.
    

    We should not have |null thing in the type; instead, the description should be prefixed with (optional).

  5. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -178,13 +197,15 @@ protected function buildMediaTypeMenu(MediaLibraryState $state) {
    +    // Get the state parameters but remove the wrapper format, AJAX form and
    +    // form rebuild parameters. These are internal parameters that should never
    +    // be part of the vertical tab links.
    

    Referring to a vertical tab link here is confusing. How about "These are internal parameters that can influence the response in undesirable ways" instead?

  6. +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
    @@ -178,13 +197,15 @@ protected function buildMediaTypeMenu(MediaLibraryState $state) {
    +    // Also add the 'media_library_content' parameter so the link can replace
    +    // only the updated content for the tab.
    

    s/the link can replace/the response will contain

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new4.23 KB
new84.52 KB

#54
1. Fixed.
2. Fixed.
3. Yay!
4. Fixed.
5. The query parameters we are changing here are exactly for the vertical tabs links, so I think this is more clear.
6. Fixed.

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

I think this has undergone enough review, testing, and poking and prodding. We have other things we gotta do, and we gotta get to 'em; this unblocks two or three major must-have issues in our roadmap. Let's do it! RTBC on green.

seanb’s picture

StatusFileSize
new3.8 KB
new84.52 KB

Not sure where comment #55 went? Here is the patch again.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 57: 3023802-55.patch, failed testing. View results

seanb’s picture

Status: Needs work » Reviewed & tested by the community

Random fail outside this patch. Everything is now green, back to RTBC.

pancho’s picture

Status: Reviewed & tested by the community » Needs work

There's a nice screencast of this feature with #3023801: Allow newly uploaded files to be deleted from the media library without saving them added on top.

However:

A.) The fact that, once a new media file is being added, all preexisting media seems to disappear, is confusing. Keeping the preexisting media at the bottom of the upload form seems no option, so we need to reassure the user, where he/she is:

  1. The dialog title should switch to "Add files to media library."
  2. The "m of n items selected." counter needs to disappear from the bottom. Finally, we're in a completely different context.
  3. The "X" at the top IMO should lead back to the media library. Make sure it does.
  4. Do we need a "cancel" button as well or is the "X" at the top enough?
  5. Possibly the submit button should say something like "Save and return to media library," but not sure about that.

B.) Additional nitpick:
There is an afterglow after hovering a delete "X" button that is larger than the "X" even after the "delete" hover text disappeared, see 00:18 or 00:50
[edit: this belongs to the followup #3023801: Allow newly uploaded files to be deleted from the media library without saving them]

Sorry I have to reset this to Needs work... :/

seanb’s picture

Thanks for the feedback @Pancho.

The fact that, once a new media file is being added, all preexisting media seems to disappear, is confusing. Keeping the preexisting media at the bottom of the upload form seems no option, so we need to reassure the user, where he/she is.

We are going to fix that in #3023797: Let users choose what to do after selecting and/or adding items in the media library. Also see comment #21 and #28.

A.1. Opening a dialog from a dialog has proven to be an a11y issue (see #3020716-18: Add vertical tabs style menu to media library). Changing the dialog title would go unnoticed of we are not reopening the dialog. So I think we should look for a solution in the form (if this is really an issue at all).
A.2. Actually we are not in a different context, but this will get better after we fix #3023797: Let users choose what to do after selecting and/or adding items in the media library and remember + show the previously selected items.
A.3. Since we are not opening a second dialog, making the close button behave like a back button is not an existing pattern. I would strongly advise against this.
A.4. Not really sure about that one, we are trying not to add too many buttons. The close button would effectively mean 'Cancel', and I think this is an existing pattern for cancelling a modal action. So I would slightly prefer that.
A.5. Will be fixed in #3023797: Let users choose what to do after selecting and/or adding items in the media library.

B. This feedback is not related to this issue. Also, the "glow" is showing where the focus is, and this is standard behavior. I think removing the focus styling from a button is an a11y issue. But if needed, can you add this to the actual issue of the delete buttons #3023801: Allow newly uploaded files to be deleted from the media library without saving them?

pancho’s picture

Status: Needs work » Needs review

I can see some of your points, and certainly this will get improved in followups. Apart from that, Media (as a relatively new Core module) might have a different standard for RTBC features than the (more settled) rest of Core does.

Fact is that as much as I'm welcoming the new shiny feature (and that's why I'm reviewing it), usability-wise it's half-baked IMHO, and I think the points I raised shouldn't be brushed away that easily. But in the end, if they are relevant, at some point someone else will probably come up with the same criticisms, otherwise not.

Setting back to "Needs review." RTBC is more than I could justify here, but I'm sure it's gonna be fine in the end.

seanb’s picture

I most certainly was not trying to "brush away" any of your concerns. Sorry if I gave this impression. Your reviews are very much appreciated.

We are splitting up the development of the media library in many small, separate issues. This makes them easier to review and commit. Issues could have great improvements but not be perfect. In those cases it is acceptable to create follow ups. I don't think this is a new RTBC standard. However, deciding if it's ok if things can be fixed in a follow up is ultimately not up to me. Most important thing we is we are not adding regressions.

Leaving to 'Needs review' is ok for now. Let's ask the UX team if the points you have raised need to be addressed here, or if they can be addressed in follow up issues (most already have a follow up issue). Even though this issue blocks a lot of the other work, we need to do a fair evaluation and can not accept regressions.

pancho’s picture

Status: Needs review » Reviewed & tested by the community

this issue blocks a lot of the other work

Fair point, that's certainly the case, and indeed I didn't hit any regressions.

Probably you're right and - no matter what's missing - it's better we're getting this committed ASAP, so we're no longer piling up combined patches everywhere, given the impressive speed of development here! :)

Setting back to RTBC, as it was ahead of my UX review in #60.

lauriii’s picture

I haven't reviewed the backend parts in detail but +1 RTBC for the frontend parts.

  • Gábor Hojtsy committed 792a14f on 8.7.x
    Issue #3023802 by seanB, phenaproxima, lauriii, Pancho, larowlan, shaal...
gábor hojtsy’s picture

Status: Reviewed & tested by the community » Fixed

Reviewed as well. Discussed the form extension point with @seanB. I initially misunderstood that the media info altering would be on the type level (this requiring code changes when you manually create new media types on the UI), but it is indeed on the source level. So modules providing new sources can/should provide the file upload info as well.

That was the only concern I had, and also based on the rest of the reviews, this looks great. Thanks all! Let's work on the followups.

seanb’s picture

Yay! Thanks all, on the next ones...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.