Problem/Motivation

When uploading items to the media library widget, it is possible users have selected the wrong file. Possibly even containing sensitive information. It is currently not possible to directly correct this from the media library widget.

Proposed resolution

Allow users to delete items from the media library through the media library widget.

Remaining tasks

Write patch
UX review
Accessibility review
Code review
Commit

User interface changes

Video of patch #43: https://www.drupal.org/files/issues/2019-03-05/media-library-remove-medi...

Added remove buttons:
Default state of the remove buttons.

Hover state:
Hover state of the remove buttons.

Focus state:
Focus state of the remove buttons.

Message after removing an item:
Message after removing an item.

Message after removing the last item:
Message after removing an item.

RTL display.
RTL state of the remove buttons.

API changes

Data model changes

Release notes snippet

CommentFileSizeAuthor
#81 3023801-81.patch32.53 KBseanb
#81 interdiff-78-81.txt2.59 KBseanb
#78 3023801-78.patch32.51 KBseanb
#78 interdiff-75-78.txt7.08 KBseanb
#77 3023801-77.patch31.92 KBseanb
#77 interdiff-75-77.txt1.91 KBseanb
#75 3023801-75.patch32 KBseanb
#75 interdiff-73-75.txt8.1 KBseanb
#73 3023801-73.patch27.25 KBseanb
#73 interdiff-70-73.txt8.48 KBseanb
#70 3023801-70.patch22.41 KBseanb
#70 interdiff-63-70.txt1.18 KBseanb
#63 3023801-63.patch22.15 KBseanb
#63 interdiff-60-63.txt8.12 KBseanb
#61 3023801-60-reroll.patch21.92 KBseanb
#60 3023801-60.patch21.92 KBseanb
#60 interdiff-59-60.txt3.46 KBseanb
#59 3023801-59.patch21.78 KBseanb
#59 interdiff-57-59.txt3.46 KBseanb
#57 3023801-57.patch21.82 KBseanb
#57 interdiff-56-57.txt2.4 KBseanb
#56 3023801-56.patch21.69 KBseanb
#56 interdiff-53-56.txt7.07 KBseanb
#53 3023801-53.patch20.29 KBseanb
#53 interdiff-51-53.txt2.02 KBseanb
#51 remove-message-last-updated.png653.03 KBseanb
#51 3023801-51.patch18.55 KBseanb
#51 interdiff-45-51.txt7.4 KBseanb
#48 Screen Shot 2019-03-06 at 15.45.17.png25.36 KBlauriii
#45 3023801-45.patch15.17 KBseanb
#45 interdiff-43-45.txt1.27 KBseanb
#43 media-library-remove-media.mp4648.07 KBseanb
#43 remove-message-last.png654.77 KBseanb
#43 remove-message-single.png287.12 KBseanb
#43 remove-buttons-focus.png487.4 KBseanb
#43 remove-buttons-hover.png485.1 KBseanb
#43 remove-buttons.png488.97 KBseanb
#43 3023801-43.patch14.58 KBseanb
#43 interdiff-40-43.txt7.55 KBseanb
#41 3023801-40-rerolled.patch14.85 KBseanb
#40 3023801-40.patch14.27 KBseanb
#40 interdiff-37-40.txt2.65 KBseanb
#37 3023801-37.patch14.31 KBseanb
#37 interdiff-33-37.txt8.41 KBseanb
#35 3023801-33-reroll.patch11.61 KBseanb
#33 3023801-33.patch11.58 KBseanb
#33 interdiff-30-33.txt3.73 KBseanb
#30 3023801-30.patch11.54 KBseanb
#30 interdiff-27-30.txt6.83 KBseanb
#27 drupal-n3023801-27.patch9.13 KBdamienmckenna
#18 3023801-18.patch9.24 KBseanb
#18 interdiff-16-18.txt713 bytesseanb
#16 3023801-16.patch9.21 KBseanb
#16 interdiff-13-16.txt1.96 KBseanb
#13 media-library-remove.mp41.82 MBseanb
#13 3023801-13-3023802-55.patch89.29 KBseanb
#13 3023801-13-do-not-test.patch8.94 KBseanb
#13 interdiff-8-13.txt3.91 KBseanb
#8 remove-button-rtl.png590.37 KBseanb
#8 remove-button-hover.png573.13 KBseanb
#8 remove-button.png569.74 KBseanb
#8 3023801-8-3023802-41.patch84.99 KBseanb
#8 3023801-8-do-not-test.patch8.52 KBseanb
#8 interdiff-5-8.txt4.4 KBseanb
#5 3023801-5-3023802-32.patch77.33 KBseanb
#5 3023801-5-do-not-test.patch7.1 KBseanb
#3 3023801-3-3023802-12-3020716-64.patch205.94 KBseanb
#3 3023801-3.patch16.61 KBseanb

Comments

seanB created an issue. See original summary.

ajay547’s picture

Assigned: Unassigned » ajay547
seanb’s picture

seanb’s picture

Copying this over from #3023802-21: Show media add form directly on media type tab in media library

1. 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.
2. 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.

We should make sure users can correct "mistakes" when adding new media. I think allowing users to remove added media will solve this. It would be nice to get confirmation on this.

seanb’s picture

phenaproxima’s picture

Tagging for screenshots. We will definitely need usability and accessibility review on this one.

phenaproxima’s picture

Issue tags: -accessibility +Accessibility
+++ b/core/modules/media_library/src/Form/AddFormBase.php
@@ -299,6 +314,23 @@ protected function prepareMediaEntityForSave(MediaInterface $media) {
+    $triggering_element = $form_state->getTriggeringElement();
+    $mid_slice = array_slice($triggering_element['#array_parents'], -2, 1);
+    $mid = reset($mid_slice);
+    $added_media = $form_state->get('media');
+    unset($added_media[$mid]);
+    $form_state->set('media', $added_media)->setRebuild();

Yikes. This needs a big comment.

seanb’s picture

Issue summary: View changes
StatusFileSize
new4.4 KB
new8.52 KB
new84.99 KB
new569.74 KB
new573.13 KB
new590.37 KB

Fixed #7 and added some screenshots to the IS.

phenaproxima’s picture

Title: Delete items from media library » Allow newly uploaded files to be deleted from the media library without saving them

Ret-titling for clarity.

pancho’s picture

Issue tags: -Needs screenshots

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

The mentioned feedback is partially applicable to #3023802: Show media add form directly on media type tab in media library. I think you reviewed the combined patch 3023801-8-3023802-41.patch instead of 3023801-8-do-not-test.patch?

Sorry for the confusion, I'm building patches on top of patches to make progress, but I can understand how this is not always clear.

I will copy over your feedback and fix this in the other issue.

pancho’s picture

Indeed, I did. No problem, though. I was just a bit surprised where you took the whole amount of new code in a quite good condition... :)

seanb’s picture

Issue summary: View changes
StatusFileSize
new3.91 KB
new8.94 KB
new89.29 KB
new1.82 MB

Reroll on top of #55 of #3023802: Show media add form directly on media type tab in media library. Everything in #10 should be be fixed.

pancho’s picture

Moved nitpick from #3023802-60: Show media add form directly on media type tab in media library:

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

@seanB's in #61:

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.

I'm not saying we should remove the focus styling. Rather we should make sure the button is reset to its original "X" (without the "close" hover) before the "afterglow" kicks in.

phenaproxima’s picture

  1. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -179,6 +179,9 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +    $parents = $form['#parents'];
    +    $id_suffix = $parents ? '-' . implode('-', $parents) : '';
    

    Is this really needed? I think the calling code sets #parents on the entity form element, so won't remove_button get a workable name generated automatically?

    If we _do_ need it, this should get a comment too.

  2. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -324,6 +343,26 @@ protected function prepareMediaEntityForSave(MediaInterface $media) {
    +    $delta_slice = array_slice($triggering_element['#array_parents'], -2, 1);
    +    $delta = reset($delta_slice);
    

    This can be one line:

    $delta = array_slice($triggering_element['#array_parents'], -2, 1)[0].

seanb’s picture

StatusFileSize
new1.96 KB
new9.21 KB

Reroll now #3023802: Show media add form directly on media type tab in media library is in!

#14
Added a fix that shows the text on focus. That looks a lot better and is better for a11y as well probably.

#15
1. Added a comment why we need it :)
2. Fixed.

phenaproxima’s picture

+++ b/core/modules/media_library/src/Form/AddFormBase.php
@@ -179,6 +179,9 @@ public function buildForm(array $form, FormStateInterface $form_state) {
+    // We need to make sure each button has a unique name attribute. The default
+    // name for button elements is 'op'. If the name is not unique, the
+    // triggering element is not set correctly.

Can we add a @see referencing removeButtonSubmit()?

Apart from this, I think the code looks good to me.

seanb’s picture

StatusFileSize
new713 bytes
new9.24 KB

Done!

phenaproxima’s picture

This issue is a security and privacy improvement, because without it: say you accidentally upload a file into the media library that you shouldn't. Right now, you would have to add the file to your media library, and actually save it into the media library -- which means it would be visible to anyone who has permission to see the media library -- and you can't remove it without going to another screen and deleting the media item (which you might not have permission to do).

With this patch, you can remove the bad upload right away, without needing to save it into the media library (for all to see) first.

berdir’s picture

> This issue is a security and privacy improvement, because without it: say you accidentally upload a file into the media library that you shouldn't

Well. It would be more useful if we'd actually delete the file that you uploaded, but we don't. #2821423: Dealing with unexpected file deletion due to incorrect file usage. Maybe that whole topic should be moved under the media umbrella, so it receives some attention again? Working only through media entities vastly simplifies the possible file usage problems as it is really just tracking the media entity usage and it is left to the user if and when he wants to delete that.

phenaproxima’s picture

Well. It would be more useful if we'd actually delete the file that you uploaded, but we don't.

True, but a cruft file that is marked temporary and not saved to the media library is, at least, not immediately visible to anyone who has permission to use the media library, and will be deleted later by cron (as far as I know).

berdir’s picture

Yes, it is not listed, that is true. *But* it is not marked as temporary with the default settings and will therefore also not be deleted. That was disabled by default in 8.4 or 8.3 and nobody touched the file usage issues anymore since that happened :)

marcoscano’s picture

I agree it would be ideal to avoid leaving the file languishing around, if we can. This could be a follow-up improvement though, if you feel it doesn't belong here.

+++ b/core/modules/media_library/src/Form/AddFormBase.php
@@ -324,6 +347,25 @@ protected function prepareMediaEntityForSave(MediaInterface $media) {
+    unset($added_media[$delta]);

Sorry if I'm missing part of the story, but at this point isn't it safe enough to assume that the file $added_media[$delta] points to is not being used anywhere else (because it was just uploaded), and then we can just delete it right here?

This would avoid the need to track any usage at all, as long as this form is not used for in-line editing (which I don't think it will, but I could be wrong here).

berdir’s picture

Sorry if I was unclear, I did definitely not expect to solve that problem here, I'm just trying to bring some light into a rather dark corner of Drupal core that everyone is ignoring and that we should really improve (anyone trying to follow GDPR is going to have a problem with that eventually).

But yes, in this specific case, we can possibly just mark the file as temporary or even actually delete it directly.

seanb’s picture

We have tests in MediaLibraryTest::testWidgetUpload() to ensure uploaded files via the media add form in the media library are temporary until the form is saved.

    // Files are temporary until the form is saved.
    $files = $file_storage->loadMultiple();
    $file = array_pop($files);
    $this->assertSame('public://type-three-dir', $file_system->dirname($file->getFileUri()));
    $this->assertTrue($file->isTemporary());

Since the media add form for the library is a custom thing, I think this is already handled.

phenaproxima’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll
damienmckenna’s picture

Issue tags: -Needs reroll
StatusFileSize
new9.13 KB

Rerolled patch #18 against the latest 8.7.x codebase.

damienmckenna’s picture

Status: Needs work » Needs review
damienmckenna’s picture

Assigned: ajay547 » Unassigned
seanb’s picture

StatusFileSize
new6.83 KB
new11.54 KB

Added small padding to the remove button to make the click area a little bigger and the focus style better. Also added minor doc improvements and remove button tests for the oEmbed add form.

wim leers’s picture

@Berdir++ for drawing attention to #2821423: Dealing with unexpected file deletion due to incorrect file usage :) I agree with #23 that it doesn't belong here. Ideally, we could do what @Berdir suggests in #24. But … the good news that this is already happening: we can possibly just mark the file as temporary — this is already true :) The "removal" functionality is really only available immediately after creating media, BEFORE it is saved, so status=0 is by definition still true. So, AFAICT @Berdir's concerns are already addressed.
(I just tested it manually while reviewing the patch and was pleasantly surprised this just happens to already work as one would hope!)

  1. 👎 Accessibility concern discovered during manual testing: I don't see any focus style for the "remove" button? For a non-visually-impaired but keyboard user, that's essential.
  2. +++ b/core/modules/media_library/css/media_library.theme.css
    @@ -372,6 +377,50 @@
    +.media-library-add-form .media-library-add-form__remove-button[type="submit"] {
    +  position: absolute;
    +  top: 25px;
    +  right: 6px; /* LTR */
    +  width: auto;
    +  margin: 0;
    +  padding: 2px 20px 2px 2px; /* LTR */
    +  text-transform: lowercase;
    +  color: transparent;
    +  border: 0;
    +  border-radius: 0;
    +  background: transparent url(../../../misc/icons/787878/ex.svg) right 2px no-repeat; /* LTR */
    +  font-weight: normal;
    +  line-height: 16px;
    +}
    

    🤔 This is setting a lot of stuff. I would like to see a review from @lauriii on this.

  3. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -179,6 +179,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +    // triggering element is not set correctly.
    

    🤔
    … and the wrong media item is removed.
    Right?

  4. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -186,6 +193,22 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
    +        // Prevent errors in other media items from preventing removal.
    

    🔍🐛 Nit: "Prevent … preventing …" is hard to read, how about "Ensure … preventing …"?

  5. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -348,6 +390,14 @@ public function updateFormCallback(array &$form, FormStateInterface $form_state)
    +    // When the list of added media is empty, return to the media library.
    +    $added_media = $form_state->get('media');
    +    if (empty($added_media)) {
    +      $response = new AjaxResponse();
    +      $response->addCommand(new ReplaceCommand('#media-library-add-form-wrapper', $this->buildMediaLibraryUi()));
    +      return $response;
    +    }
    

    👍This is surprisingly elegant!

phenaproxima’s picture

+1 on a front-end review from Lauri.

seanb’s picture

StatusFileSize
new3.73 KB
new11.58 KB

#31

1. Fixed. Guess I broke that in the last patch, because it worked before. Thanks!
2. We are overriding the default button styling (and that contains a lot).
3. Correct, no matter which button you click, the last item is removed.
4. Fixed.
5. Yay!

Status: Needs review » Needs work

The last submitted patch, 33: 3023801-33.patch, failed testing. View results

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new11.61 KB
phenaproxima’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/media_library/css/media_library.theme.css
    @@ -344,6 +345,10 @@
    +.media-library-add-form__media:first-child .media-library-add-form__remove-button[type="submit"] {
    +  top: 5px;
    +}
    

    I think this needs a comment.

  2. +++ b/core/modules/media_library/css/media_library.theme.css
    @@ -372,6 +377,54 @@
    +.media-library-add-form .media-library-add-form__remove-button[type="submit"] {
    

    Because we're using BEM in this style sheet, I think the selectors can simply be .media-library-add-form__remove-button, plus whatever modifiers are called for.

  3. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -179,6 +179,14 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +    // We need to make sure each button has a unique name attribute. The default
    +    // name for button elements is 'op'. If the name is not unique, the
    +    // triggering element is not set correctly and the wrong media item is
    +    // removed.
    +    // @see ::removeButtonSubmit()
    +    $parents = $form['#parents'];
    +    $id_suffix = $parents ? '-' . implode('-', $parents) : '';
    

    Thanks for this excellent and useful comment :)

  4. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -326,6 +350,25 @@ protected function prepareMediaEntityForSave(MediaInterface $media) {
    +    $form_state->set('media', $added_media)->setRebuild();
    

    Question -- should we re-key the media items (i.e., array_values($added_media)) here?

  5. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -768,6 +768,25 @@ public function testWidgetUpload() {
    +    // Assert removing an uploaded media item before save works as expected.
    +    $assert_session->elementExists('css', '.media-library-open-button[href*="field_unlimited_media"]')->click();
    +    $assert_session->assertWaitOnAjaxRequest();
    +    $assert_session->pageTextContains('Add or select media');
    +    $page->clickLink('Type Three');
    +    $assert_session->assertWaitOnAjaxRequest();
    +    $page->attachFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri));
    +    $assert_session->assertWaitOnAjaxRequest();
    +    // Assert the media item fields are shown and the vertical tabs are no
    +    // longer shown.
    +    $assert_session->elementExists('css', '.media-library-add-form__fields');
    +    $assert_session->elementNotExists('css', '.media-library-menu');
    +    // Press the 'Remove button' and assert the user is sent back to the media
    +    // library.
    +    $assert_session->elementExists('css', '.media-library-add-form__remove-button')->click();
    +    $assert_session->assertWaitOnAjaxRequest();
    +    $assert_session->elementNotExists('css', '.media-library-add-form__fields');
    +    $assert_session->elementExists('css', '.media-library-menu');
    

    Should we also assert that the file entity itself is marked temporary?

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new8.41 KB
new14.31 KB

#36

1. Fixed.
2. Fixed. You are right :)
3. Yay!
4. Fixed. We apparently can.
5. We already have tests for that. We could repeat it, but it wouldn't add any value imho.

Besides that, added an announcement for the add/remove actions in the form and made sure the focus is set on the first tabbable element in the form after something is added or removed.

phenaproxima’s picture

  1. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -381,25 +385,42 @@ public function removeButtonSubmit(array $form, FormStateInterface $form_state)
    +    $clicked_button = end($triggering_element['#array_parents']);
    +    if ($clicked_button === 'remove_button') {
    

    We don't need to store $clicked_button in its own variable.

  2. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -785,6 +785,10 @@ public function testWidgetUpload() {
    +    $this->assertJsCondition($this->getSession()->evaluateScript('jQuery("#media-library-add-form-wrapper :tabbable:first").is(":focus")'));
    

    This is not a correct usage of assertJsCondition(); I think you're just supposed to pass the JavaScript directly to $this->assertJsCondition().

phenaproxima’s picture

Status: Needs review » Needs work

Kicking back for those things.

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new2.65 KB
new14.27 KB

Fixed #38.

seanb’s picture

phenaproxima’s picture

Status: Needs review » Needs work
Issue tags: -Needs usability review, -Needs accessibility review

We demoed this to the UX team today, in the presence of the mighty @andrewmacpherson. Feedback was very positive and, with a few changes, we have sign-off from both the usability and accessibility angles.

  • The only commit-blocking problem we turned up is the fact that, when you upload a file, the focus is immediately moved to the remove button. That's really dangerous from a usability standpoint. The solution we came up with is to move the Remove button after the required metadata fields, then style it so that it shows up in the top right. @andrewmacpherson confirmed that this doesn't really constitute a violation of the WCAG DOM-order provision because the design basically has three columns (thumbnail, fields, remove button), and the natural way to read that is to go from top-to-bottom in each column before advancing to the next one. This holds up even at smaller screen sizes.
  • It's okay for us to keep the hover state and color change for the remove buttons, because it is still accessible to screen readers and people who cannot use pointer devices.
  • It would be very helpful, though not a blocker if it's hard to do, for us to add a "foobar.jpg was deleted" message after an item is removed. If this is tricky or has special implications (and it might, since the media item might not have a name by the time it's removed), we can deal with it in a follow-up.

I think the path forward is clear; let's get this done!

seanb’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new7.55 KB
new14.58 KB
new488.97 KB
new485.1 KB
new487.4 KB
new287.12 KB
new654.77 KB
new648.07 KB

New patch to:
- Change the DOM order so the remove button is after the fields.
- Add a remove message to the form
- Fix an issue with the focus state having a border while it shouldn't

Also updated the tests and added new screenshots and a video to the IS.

phenaproxima’s picture

+++ b/core/modules/media_library/src/Form/AddFormBase.php
@@ -231,6 +214,22 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
+      'remove_button' => [
+        '#type' => 'submit',
+        '#value' => $this->t('Remove'),
+        '#name' => 'media-' . $delta . '-remove-button' . $id_suffix,
+        '#attributes' => [
+          'class' => ['media-library-add-form__remove-button'],
+          'aria-label' => $this->t('Remove @label', ['@label' => $media->getName()]),
+        ],
+        '#ajax' => [
+          'callback' => '::updateFormCallback',
+          'wrapper' => 'media-library-add-form-wrapper',
+        ],
+        '#submit' => ['::removeButtonSubmit'],
+        // Ensure errors in other media items do not prevent removal.
+        '#limit_validation_errors' => [],
+      ],

To guarantee the correct ordering, maybe we should set explicit weights on the preview, remove_button, and fields sub-elements.

Interdiff looks good otherwise.

seanb’s picture

StatusFileSize
new1.27 KB
new15.17 KB

Added the weights.

Status: Needs review » Needs work

The last submitted patch, 45: 3023801-45.patch, failed testing. View results

seanb’s picture

Status: Needs work » Needs review

That was an unrelated fail. Back to NR.

lauriii’s picture

Issue summary: View changes
Status: Needs review » Needs work
Issue tags: -Needs frontend framework manager review
StatusFileSize
new25.36 KB
  • The focus is still immediately moved to the remove button when uploading files to media types that don't contain additional fields.
  • Wondering if this was given consideration before, but when a single file is being uploaded, the remove button is hard to distinguish from the close button since they look exactly the same.
  • +++ b/core/modules/media_library/css/media_library.theme.css
    @@ -446,6 +453,55 @@
    +.media-library-add-form__remove-button[type="submit"] {
    ...
    +[dir="rtl"] .media-library-add-form__remove-button[type="submit"] {
    

    Let's add a todo to remove [type="submit"] from the selector when this is moved to Seven.

seanb’s picture

The focus is still immediately moved to the remove button when uploading files to media types that don't contain additional fields.

Ahh, you are right! Not sure what to do about this. We could:

  1. Accept this since there really is no other tabbable element related to the added media item to receive the focus.
  2. Skip the remove button and shift focus to the submit button.
  3. Set the focus on the wrapper div.

I think this needs a11y feedback.

Wondering if this was given consideration before, but when a single file is being uploaded, the remove button is hard to distinguish from the close button since they look exactly the same.

This has come up before. This is a case where nobody really know how big this problem actually is, and since the design of the remove button came from #2113931: File Field design update: Upload field., which has been looked at multiple times and has UX signoff, I think we should just continue and find out if this really is a problem or not. Changing the icon later should be an easy change, but I think we need real world feedback to decide if this is needed or not.

Let's add a todo to remove [type="submit"] from the selector when this is moved to Seven.

Will add that when I know what to do with the first point.

phenaproxima’s picture

Skip the remove button and shift focus to the submit button.

I'm very much in favor of this option. It makes the most sense from a usability perspective, and if it doesn't compromise the accessibility of the Remove button(s), it's pretty much perfect.

seanb’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new7.4 KB
new18.55 KB
new653.03 KB

Let's add a todo to remove [type="submit"] from the selector when this is moved to Seven.

Fixed!

Implemented option 3 from #49. The reason I did this:
1. The delete button, since it is a destructive action, should probably not get the focus by default.
3. Skipping the entire form and go the the save button could give the impression that there is no form/remove button for screen reader users. Screen reader users could potentially miss information if we do this.

Also fixed an issue for the status messages when the last item is removed. It used to look like this:
Message after removing an item.

Now it looks like this:
Message after removing an item (updated).

Status: Needs review » Needs work

The last submitted patch, 51: 3023801-51.patch, failed testing. View results

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new2.02 KB
new20.29 KB

Fixed the tests.

rainbreaw’s picture

@seanB and @phenaproxima demo'd this to me via screencast today to review accessibility concerns, and the functionality seems to pass accessibility checks with the current patch:

  • Remove is now the last item in the DOM for each item wrapper, rather than the first
  • The focus moves to the wrapper itself, which will receive an aria-label so that the screen reader user knows what they are encountering
  • When focus lands on the remove item, the word remove becomes visible and it has its own focus state
  • We also talked about adding aria-live="polite" so that the user is updated when the change occurs

A couple new questions:

  1. One question I didn't think to review when we were discussing: where does the focus go after an item is removed? If it focuses on the next non-removed item in the list, or (in the event no items remain) the add media button, then this should be acceptable.
  2. And another question that I now have from reviewing this issue and all of the comments after the demo: why was the outline removed from the remove item in the focus state? For keyboard only users, leaving the outline enabled would probably be preferable because the gray with the x for focused is subtle, and the user might not realize that this is the focused item. A combination of the color change + the outline would follow expectations and make the state easier to identify.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

seanb’s picture

Issue tags: -Needs accessibility review
StatusFileSize
new7.07 KB
new21.69 KB

Incorporated the newly added wrapper div for the added items from #3023797: Let users choose what to do after selecting and/or adding items in the media library to make sure the patches are going to work together.

Regarding the questions in #54:
1. Fixed this in this patch. When there are more items to remove, the focus is automatically set to the next remove button.
2. I don't think we remove the outline on the focused state? Just double checked. Also see the screenshot in the IS.

seanb’s picture

Issue tags: +backport
StatusFileSize
new2.4 KB
new21.82 KB

Added an AJAX message for when the remove button is clicked (like in #3035446: Inform assistive tech users about the outcome of using the MediaLibraryWidget dialog) and optimized the remove button AJAX callback a bit.

I'm also tagging this for backport to 8.7.x, since Media Library will hugely benefit from this UX improvement and, as an experimental module, this patch may be eligible to land in alpha.

phenaproxima’s picture

+++ b/core/modules/media_library/css/media_library.theme.css
@@ -104,13 +112,17 @@
+.media-library-add-form-description {
+  margin: 0;
+}

This seems like it should use BEM (media-library-add-form__description)...?

seanb’s picture

StatusFileSize
new3.46 KB
new21.78 KB

That shouldn't even be in here, copied a bit too much from #3023797: Let users choose what to do after selecting and/or adding items in the media library. I did find that media-library-add-form-media could also make better use of the BEM convention. Since media-library-add-form__media was already used for the add media items, I renamed the wrapper to media-library-add-form__added-media.

seanb’s picture

seanb’s picture

StatusFileSize
new21.92 KB

New reroll now #3037767: Improve responsive styling of grid items in the media library landed as well. No changes since #60.

phenaproxima’s picture

Status: Needs review » Needs work

Nothing major found!

  1. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -109,7 +110,7 @@ protected function getMediaType(FormStateInterface $form_state) {
    +    $form['#prefix'] = '<div id="media-library-add-form-wrapper" class="media-library-add-form-wrapper">';
    

    Should we add a todo here pointing out that we can remove the ID once the AJAX system allows targeting by selectors that aren't IDs? (With a @see to the issue.)

  2. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -194,6 +210,7 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
           'preview' => [
             '#type' => 'container',
    +        '#weight' => 1,
    

    To allow extending classes to more easily insert things between these controls, I think the weights should be 10, 20, and 30 respectively.

  3. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -344,17 +408,43 @@ protected function prepareMediaEntityForSave(MediaInterface $media) {
    +      $response->addCommand(new InvokeCommand('#media-library-add-form-wrapper .media-library-add-form__added-media', 'focus'));
    

    I don't think this needs the #media-library-add-form-wrapper prefix.

  4. +++ b/core/modules/media_library/src/Form/FileUploadForm.php
    @@ -122,8 +122,16 @@ protected function buildInputElement(array $form, FormStateInterface $form_state
    +    // Add a container to group the input elements.
    +    $form['container'] = [
    +      '#type' => 'container',
    +      '#attributes' => [
    +        'class' => ['media-library-add-form-input-wrapper'],
    +      ],
    +    ];
    

    Should this class name be using BEM? (media-library-add-form__input-wrapper)?

    Also, I'm not really clear on why this container element is needed...if it's really needed, can the comment explain that better?

  5. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -822,6 +822,33 @@ public function testWidgetUpload() {
    +    $page->clickLink('Type Three');
    

    Is this still necessary?

  6. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -822,6 +822,33 @@ public function testWidgetUpload() {
    +    $this->assertJsCondition('jQuery("#media-library-add-form-wrapper .media-library-add-form__added-media").is(":focus")');
    

    I don't think this needs the #media-library-add-form-wrapper prefix.

  7. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -822,6 +822,33 @@ public function testWidgetUpload() {
    +    $filename = $png_image->filename;
    +    $assert_session->pageTextContains("The media item $filename has been removed.");
    

    Nit: There is no need for $filename to be its own variable. The string can just be "The media item $png_image->filename has been removed."

  8. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -921,6 +948,33 @@ public function testWidgetOEmbed() {
    +    // Assert removing an uploaded media item before save works as expected.
    

    Copypasta :) We're testing oEmbed here, so the item was not uploaded.

  9. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -921,6 +948,33 @@ public function testWidgetOEmbed() {
    +    $this->assertJsCondition('jQuery("#media-library-add-form-wrapper .media-library-add-form__added-media").is(":focus")');
    

    #media-library-add-form-wrapper not needed here.

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new8.12 KB
new22.15 KB

Fixed #62.

1. Fixed.
2. Fixed.
3. Fixed.
4. Fixed.
5. Yes, type_three is only first for field_twin_media.
6. Fixed.
7. Fixed.
8. Fixed.
9. Fixed.

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

Okay! Thanks for fixing my feedback.

This has now been code reviewed multiple times, demoed to the UX team more than once (and approved), and been through an accessibility review thanks to @rainbreaw. It's a major UX improvement with (as the issue tags suggest) privacy implications, and there is extensive test coverage, so I see little reason to wait longer to land this one.

RTBC once green on all backends.

webchick credited ckrina.

webchick’s picture

Thanks so much, @rainbreaw for jumping in here on the accessibility side! Adding credits for everyone here, plus the participants in the March 5 UX meeting https://www.youtube.com/watch?v=t_UVusCsurI

lauriii’s picture

Status: Reviewed & tested by the community » Needs work

Sorry to bump this back to NW, but since this patch contains some duplicate changes with #3023797: Let users choose what to do after selecting and/or adding items in the media library, the feedback I posted there is relevant here as well.

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new1.18 KB
new22.41 KB

Fixed #69. I think this was referring to comment #64 and #66 in #3023797: Let users choose what to do after selecting and/or adding items in the media library regarding the code below.

+++ b/core/modules/media_library/css/media_library.theme.css
@@ -95,6 +95,10 @@
+.media-library-add-form__added-media {
+  outline: none;
+}

+++ b/core/modules/media_library/src/Form/AddFormBase.php
@@ -139,14 +154,48 @@ public function buildForm(array $form, FormStateInterface $form_state) {
+          'tabindex' => -1,

Added some documentation to clarify why we need it.

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

Looks like Lauri's feedback is, indeed, addressed. I think we are good to go.

lauriii’s picture

Status: Reviewed & tested by the community » Needs work
  1. When uploading multiple files at once and you remove a media, the focus is shifted to removing the next item. We could avoid this, and make using this view more intuitive by making the list of files role="list" and the list items role="listitem. We should also add aria-label attribute to the list items, as well as tabindex="-1". This would allow us to focus on the previous media item after removing media from the list.
  2. +++ b/core/modules/media_library/css/media_library.theme.css
    @@ -84,17 +84,31 @@
    +  outline: none;
    

    I also couldn't figure out what is this needed for. I removed this and tested manually different possible scenarios and couldn't get the outline visible. If this is indeed needed, let's add some documentation here as well.

  3. +++ b/core/modules/media_library/css/media_library.theme.css
    @@ -84,17 +84,31 @@
    + * need the outline.
    

    Could we include an explanation on why the outline isn't needed?

  4. +++ b/core/modules/media_library/css/media_library.theme.css
    @@ -476,6 +491,12 @@
    +/* The first item doesn't have a top padding, change the location of the remove
    +   button as well. */
    
    @@ -504,6 +525,57 @@
    +/* @todo Remove [type="submit"] when styles are moved to the seven theme in
    +     https://www.drupal.org/project/drupal/issues/2980769 */
    

    Nitpick: let's format these according to our coding standards.

  5. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -139,6 +142,15 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +          // Add the tabindex '-1' to allow the focus to be shifted to the
    +          // container.
    

    I think this comment would be the most benefitial if we explained a scenario where the focus is shifted here (and why) 🙂

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new8.48 KB
new27.25 KB

Fixed #72.

1. Fixed. This is a nice improvement. Thanks!
2. Not sure which element your comment is referring to? I did find that the following code didn't do anything:

-.media-library-add-form-wrapper {
-  outline: none;
-}

3. Fixed.
4. Fixed. I changed all comments in the CSS to follow the coding standards.
5. Fixed. Added some more docs.

phenaproxima’s picture

Status: Needs review » Needs work
Issue tags: +Needs manual testing

I gave this a quick manual test and found a little bug that needs fixing. Try this:

  1. Upload multiple images at once.
  2. Enter alt text for one of them (NOT the last one), then remove it (without saving).
  3. You'll see that the one you removed is gone, but the alt text you entered is transferred into the one that came after it.

This won't be automatically testable since Mink doesn't support multi-file uploads. So I'm marking this for manual testing.

The good news is, I gave the widget a quick test using the keyboard. It seemed to work quite nicely -- the tab order made perfect sense. In addition to that, the interdiff makes sense; the added comments really do a lot to clarify some of the quirks of our implementation. So, thanks!

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new8.1 KB
new32 KB

Apparently we CAN test multiple file uploads (thanks @lendude for the tip!). Fixed #74 and added tests for it.

phenaproxima’s picture

  1. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -449,20 +449,24 @@ public function updateFormCallback(array &$form, FormStateInterface $form_state)
    +        $nth_child = 0;
    +        foreach ($added_media as $delta => $media) {
    +          $nth_child++;
    +          if ($delta > $removed_delta) {
    +            break;
    +          }
    

    Couldn't we just use max() here? (Since it's a variadic function, we'd probably need to do something like $nth_child = call_user_func_array('max', array_merge(array_keys($added_media), [$delta])) + 1;

  2. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -848,6 +844,59 @@ public function testWidgetUpload() {
    +    $assert_session->assertWaitOnAjaxRequest();
    +    $assert_session->pageTextContains('Add or select media');
    

    This seems like a good place for $this->assertNotEmpty($assert_session->waitForText('Add or select media')).

  3. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -848,6 +844,59 @@ public function testWidgetUpload() {
    +    // Set alt texts.
    +    $page->fillField('media[0][fields][field_media_test_image][0][alt]', $filenames[0]);
    +    $page->fillField('media[1][fields][field_media_test_image][0][alt]', $filenames[1]);
    +    $page->fillField('media[2][fields][field_media_test_image][0][alt]', $filenames[2]);
    +    // Remove the second file and assert the focus is shifted to the container
    +    // of the next media item and field values are still correct.
    +    $page->pressButton('media-1-remove-button');
    +    $this->assertJsCondition('jQuery(".media-library-add-form__media:nth-child(2)").is(":focus")');
    +    $assert_session->pageTextContains('The media item ' . $filenames[1] . ' has been removed.');
    +    $assert_session->fieldValueEquals('media[0][fields][name][0][value]', $filenames[0]);
    +    $assert_session->fieldValueEquals('media[0][fields][field_media_test_image][0][alt]', $filenames[0]);
    +    $assert_session->fieldNotExists('media[1][fields][name][0][value]');
    +    $assert_session->fieldNotExists('media[1][fields][field_media_test_image][0][alt]');
    +    $assert_session->fieldValueEquals('media[2][fields][name][0][value]', $filenames[2]);
    +    $assert_session->fieldValueEquals('media[2][fields][field_media_test_image][0][alt]', $filenames[2]);
    

    When I encountered this bug, I left one of the alt text fields empty. Can we do that here, just to verify that it works in that case (it will also serve as proof that the remove button does not try to validate the form).

  4. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -848,6 +844,59 @@ public function testWidgetUpload() {
    +    $assert_session->pageTextContains('The media item ' . $filenames[2] . ' has been removed.');
    +    $assert_session->fieldValueEquals('media[0][fields][name][0][value]', $filenames[0]);
    +    $assert_session->fieldValueEquals('media[0][fields][field_media_test_image][0][alt]', $filenames[0]);
    +    $assert_session->fieldNotExists('media[1][fields][name][0][value]');
    +    $assert_session->fieldNotExists('media[1][fields][field_media_test_image][0][alt]');
    +    $assert_session->fieldNotExists('media[2][fields][name][0][value]');
    +    $assert_session->fieldNotExists('media[2][fields][field_media_test_image][0][alt]');
    

    This test uses a LOT of very long selectors, and that makes it hard for human brains to parse. I think we might want to do something like $first_media = $assert_session->elementExists('css', 'selector-for-first-media-item'), and then run further asserts on that (like $assert_session->fieldExists('Name', $first_media)). This might be a lot more readable.

seanb’s picture

StatusFileSize
new1.91 KB
new31.92 KB

#76:

1. I don't think so? Max returns the highest value, while we want the next value. The problem is we can't predict what that value is going to be. For example, we have delta's [1,2,3,4,5,6]. When we delete number 3, the focus should be shifted to number 4 (that's predictable). If we then delete item 2, the next item will be 5. That second case is the problem. The max() would return 6 in both cases. What we actually want is to know what the sequance number is of the first element with a delta larger than the removed one, and as far as I could find there is no function that does that.

2. We actually do this in 13 places in the test. I think it would be best to keep it consistent.

3. Fixed.

4. We don't really have delta based selectors we can use. We could use nth-child() selectors, but that would also be a little hard to grasp since item 3 becomes number 2 if you remove something. The field name is the only thing that contains the delta consistently (so media[2] stays media[2]).

seanb’s picture

StatusFileSize
new7.08 KB
new32.51 KB

Here is an attempt to improve #76 1 and 4. I have added a data attribute data-media-library-added-delta containing the delta of each added media item. I used a data attribute instead of a class, because this allows us to target the delta very specifically, and because we don't use this for styling at all.

This data attribute allows us to replace the nth-child stuff with actual delta's. We still need to loop over everything until we find the item we want to set focus on, but at least we no longer have to worry about the difference between the position of the media item and its delta. Which hopefully makes the code easier to understand.

This also allows us to make the tests a bit more readable.

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs manual testing
+++ b/core/modules/media_library/src/Form/AddFormBase.php
@@ -223,6 +223,10 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
+        // Add a data attribute containing the delta to allow us to easily shift
+        // the focus to a specific media item.
+        // @see ::updateFormCallback()
+        'data-media-library-added-delta' => $delta,

Even though this isn't strictly necessary for things to work, I think I like it. I think it increases clarity of the code, and improves future flexibility (and potentially themeability). +1!

I think these interdiffs look right to me, and we have put this patch through its paces enough times at this point. The privacy hole is filled -- if there are other bugs after this, we can deal with them in follow-up issues. Go team!

larowlan’s picture

  1. +++ b/core/modules/media_library/src/Form/AddFormBase.php
    @@ -332,6 +389,33 @@ protected function prepareMediaEntityForSave(MediaInterface $media) {
    +    $form_state->set('media', $added_media)->setRebuild();
    

    would it be better to use array_values here instead so we've always got a sequential set of deltas and avoid doing it during submit - or does that mess with the parents logic?

  2. +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
    @@ -822,6 +818,87 @@ public function testWidgetUpload() {
    +    $this->assertJsCondition('jQuery("#media-library-add-form-wrapper :tabbable").is(":focus")');
    

    should we be checking this is the first tabbable element using .get(0)?

seanb’s picture

StatusFileSize
new2.59 KB
new32.53 KB

1. We had that, but that causes the bug in #74. So I think we need to keep the delta's the same.
2. Added :first to the selector, that should also do the trick and that way we could do the same in the InvokeCommand().

gábor hojtsy’s picture

Version: 8.8.x-dev » 8.7.x-dev
Status: Reviewed & tested by the community » Fixed

Superb, thanks all! I really like how this came together with feedback from all angles.

  • Gábor Hojtsy committed d772fbb on 8.8.x
    Issue #3023801 by seanB, DamienMcKenna, lauriii, phenaproxima, Berdir,...

  • Gábor Hojtsy committed 58ec100 on 8.7.x
    Issue #3023801 by seanB, DamienMcKenna, lauriii, phenaproxima, Berdir,...

  • Gábor Hojtsy committed 58ec100 on 8.7.x
    Issue #3023801 by seanB, DamienMcKenna, lauriii, phenaproxima, Berdir,...

Status: Fixed » Closed (fixed)

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

idebr’s picture

This issue was a big improvement for the usability of the Media widget! However, a vanilla installation will still leave the removed file for 6 hours before it is removed with a cron task. I think we can improve this situation by removing the file immediately when pressing the 'Remove' button. This option was actually already suggested by Berdir here in #24:

But yes, in this specific case, we can possibly just mark the file as temporary or even actually delete it directly.

To this end I have added a patch at #3073734: Immediately delete a media's file when pressing 'Remove' in the media creation modal to remove the file attachment immediately after clicking 'Remove' in the media creation modal.

wim leers’s picture