diff -u b/core/modules/media_library/css/media_library.css b/core/modules/media_library/css/media_library.css --- b/core/modules/media_library/css/media_library.css +++ b/core/modules/media_library/css/media_library.css @@ -19,6 +19,10 @@ transition: border-color .2s, outline-color .2s; } +.media-library-item.media-library-item-in-widget { + cursor: move; +} + .media-library-item.checked { outline-color: #cfddfd; } @@ -31,7 +35,7 @@ position: absolute; top: 0; right: 0; - background: url('../icons/checkbox.svg'); + background: url('../icons/checkbox.svg') no-repeat; background-size: cover; margin: 5px 5px 0 0; opacity: 0; @@ -74,10 +78,36 @@ - opacity: 0; - margin: 5px; - right: 0; + z-index: 1; top: 0; + right: 0; + width: 20px; + height: 20px; + margin: 5px; + padding: 0; + background: url('../../../misc/icons/e32700/error.svg') no-repeat; + background-size: cover; + color: transparent; + text-shadow: none; + border: none; + opacity: 0; transition: opacity .2s; } +.media-library-item .media-library-item-remove:hover, +.media-library-item .media-library-item-remove:focus{ + background: url('../../../misc/icons/e32700/error.svg') no-repeat; + background-size: cover; + color: transparent; + text-shadow: none; +} + .media-library-open-button { margin-top: 10px; } + +.media-library-mask { + position: absolute; + background: transparent; + top: 0; + left: 0; + width: 100%; + height: 100%; +} diff -u b/core/modules/media_library/js/media_library.widget.js b/core/modules/media_library/js/media_library.widget.js --- b/core/modules/media_library/js/media_library.widget.js +++ b/core/modules/media_library/js/media_library.widget.js @@ -11,7 +11,15 @@ */ Drupal.behaviors.MediaLibraryWidget = { attach: function (context) { - // @todo Enable jQuery sort on the media items to support weight changes. + $('.media-library-selection', context).sortable({ + tolerance: 'pointer', + stop: function(event, ui) { + // Update all the hidden "weight" fields. + $(event.target).children().each(function (index) { + $(this).find('.media-library-item-weight').val(index); + }); + } + }); } }; diff -u b/core/modules/media_library/media_library.libraries.yml b/core/modules/media_library/media_library.libraries.yml --- b/core/modules/media_library/media_library.libraries.yml +++ b/core/modules/media_library/media_library.libraries.yml @@ -26,2 +26,3 @@ - core/jquery + - core/jquery.ui.sortable - media_library/style diff -u b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php --- b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php +++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php @@ -3,6 +3,7 @@ namespace Drupal\media_library\Plugin\Field\FieldWidget; use Drupal\Component\Utility\NestedArray; +use Drupal\Component\Utility\SortArray; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; @@ -93,6 +94,7 @@ // in $form_state['values'] because of validation limitations. $field_state = static::getWidgetState($parents, $field_name, $form_state); if ($field_state && isset($field_state['items'])) { + usort($field_state['items'], [SortArray::class, 'sortByWeightElement']); $items->setValue($field_state['items']); } @@ -117,12 +119,18 @@ '#cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(), 'selection' => [ '#type' => 'container', - '#attributes' => ['id' => $wrapper_id], + '#attributes' => [ + 'class' => 'media-library-selection', + ], + ], + '#attributes' => [ + 'id' => $wrapper_id, + 'class' => ['media-library-wrapper'], ], ]; if (empty($referenced_entities)) { - $element['selection']['empty_selection'] = [ + $element['empty_selection'] = [ '#markup' => $this->t('

No media selected.

' )]; } @@ -159,6 +167,14 @@ '#type' => 'hidden', '#value' => $media_item->id(), ], + // This hidden value is set by JS when items are re-sorted. + 'weight' => [ + '#type' => 'hidden', + '#default_value' => $delta, + '#attributes' => [ + 'class' => ['media-library-item-weight'], + ], + ], ]; } } @@ -171,7 +187,7 @@ '#type' => 'container', 'media_library_open_button' => [ '#type' => 'link', - '#title' => $this->t('Select from library'), + '#title' => $this->t('Add Media'), '#name' => $field_name . '-media-library-open-button', '#url' => Url::fromRoute('view.media_library.widget', [], [ 'query' => ['media_library_input' => $field_name], @@ -194,7 +210,6 @@ // to how the EntityReferenceAutocompleteWidget stores values. $element['media_library_selection'] = [ '#type' => 'textfield', - '#name' => $field_name . '-media-library-selection', '#attributes' => [ 'data-media-library-input-id' => $field_name, 'class' => ['visually-hidden'], @@ -211,9 +226,9 @@ ], '#attributes' => [ 'data-media-library-submit-id' => $field_name, - 'class' => ['visually-hidden'], + 'class' => ['visually-hidden', 'media-library-update-button'], ], - '#submit' => [[static::class, 'addItems']], + '#submit' => [[static::class, 'updateItems']], '#limit_validation_errors' => [array_merge($parents, [$field_name])], ]; @@ -231,7 +246,13 @@ * {@inheritdoc} */ public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { - return isset($values['selection']) ? $values['selection'] : []; + if (isset($values['selection'])) { + usort($values['selection'], [SortArray::class, 'sortByWeightElement']); + return $values['selection']; + } + else { + return []; + } } /** @@ -251,7 +272,9 @@ $parents = $triggering_element['#array_parents']; $parents = array_slice($parents, 0, $length); $element = NestedArray::getValue($form, $parents); - return $element['selection']; + // Always clear the textfield selection to prevent duplicate additions. + $element['media_library_selection']['#value'] = ''; + return $element; } /** @@ -273,7 +296,8 @@ $delta = $triggering_element['#media_library_remove_delta']; // Find and remove correct entity. - $values = $form_state->getValue($field_name); + $path = array_merge($form['#parents'], [$field_name]); + $values = NestedArray::getValue($form_state->getValues(), $path); $field_state = static::getWidgetState($parents, $field_name, $form_state); if (isset($values['selection'])) { if (isset($values['selection'][$delta])) { @@ -290,49 +314,52 @@ } /** - * Submission handler for the hidden "Select entities" button. + * Updates the field state based on the form state and sets the form rebuild. * * @param array $form * The form array. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state. */ - public static function addItems($form, FormStateInterface $form_state) { + public static function updateItems($form, FormStateInterface $form_state) { $button = $form_state->getTriggeringElement(); // Go one level up in the form, to the widgets container. $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1)); $field_name = $element['#field_name']; $parents = $element['#field_parents']; + $field_state = static::getWidgetState($parents, $field_name, $form_state); // Get the new media ids passed to our hidden button. - $user_input = $form_state->getUserInput(); - $input_key = $field_name . '-media-library-selection'; - if (isset($user_input[$input_key])) { - $field_state = static::getWidgetState($parents, $field_name, $form_state); + $values = $form_state->getValues(); + $path = array_merge($form['#parents'], [$field_name]); + $value = NestedArray::getValue($values, $path); + $selection = isset($value['selection']) ? $value['selection'] : []; + + $field_state['items'] = $selection; - $ids = explode(',', $user_input[$input_key]); + if (!empty($value['media_library_selection'])) { + $ids = explode(',', $value['media_library_selection']); /** @var \Drupal\media\MediaInterface[] $media */ $media = Media::loadMultiple($ids); - $delta = 0; - $field_state['items_count'] = 0; - $cardinality_unlimited = ($element['#cardinality'] == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); + + // Append the provided Media to the existing selection. + $cardinality_unlimited = ($element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); foreach ($media as $media_item) { - if ($cardinality_unlimited || ($delta < $element['#cardinality'])) { + if ($cardinality_unlimited || (count($field_state['items']) < $element['#cardinality'])) { if ($media && $media_item->access('view')) { - $field_state['items'][$delta] = [ + $field_state['items'][] = [ 'target_id' => $media_item->id(), ]; - $delta++; } } } + } - $field_state['items_count'] = count($field_state['items']); - static::setWidgetState($parents, $field_name, $form_state, $field_state); + $field_state['items_count'] = count($field_state['items']); + static::setWidgetState($parents, $field_name, $form_state, $field_state); - $form_state->setRebuild(); - } + $form_state->setRebuild(); } } diff -u b/core/modules/media_library/templates/media--media-library.html.twig b/core/modules/media_library/templates/media--media-library.html.twig --- b/core/modules/media_library/templates/media--media-library.html.twig +++ b/core/modules/media_library/templates/media--media-library.html.twig @@ -47,4 +47,6 @@
{{ author_info }}
+ {# This is used to prevent dragging nested elements in the widget. #} +
{% endif %}