diff --git a/core/modules/media_library/config/install/core.entity_view_mode.media.media_library.yml b/core/modules/media_library/config/install/core.entity_view_mode.media.media_library.yml
new file mode 100644
index 0000000..8c9ddd3
--- /dev/null
+++ b/core/modules/media_library/config/install/core.entity_view_mode.media.media_library.yml
@@ -0,0 +1,9 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+id: media.media_library
+label: 'Media Library'
+targetEntityType: media
+cache: true
diff --git a/core/modules/media_library/config/install/views.view.media_library.yml b/core/modules/media_library/config/install/views.view.media_library.yml
new file mode 100644
index 0000000..04a91a1
--- /dev/null
+++ b/core/modules/media_library/config/install/views.view.media_library.yml
@@ -0,0 +1,647 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_view_mode.media.media_library
+  module:
+    - media
+    - media_library
+    - system
+    - user
+id: media_library
+label: Media
+module: views
+description: ''
+tag: ''
+base_table: media_field_data
+base_field: mid
+core: 8.x
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: 0
+    display_options:
+      access:
+        type: perm
+        options:
+          perm: 'view media'
+      cache:
+        type: tag
+        options: {  }
+      query:
+        type: views_query
+        options:
+          disable_sql_rewrite: false
+          distinct: false
+          replica: false
+          query_comment: ''
+          query_tags: {  }
+      exposed_form:
+        type: basic
+        options:
+          submit_button: Apply
+          reset_button: false
+          reset_button_label: Reset
+          exposed_sorts_label: 'Sort by'
+          expose_sort_order: false
+          sort_asc_label: Asc
+          sort_desc_label: Desc
+      pager:
+        type: mini
+        options:
+          items_per_page: 25
+          offset: 0
+          id: 0
+          total_pages: null
+          expose:
+            items_per_page: false
+            items_per_page_label: 'Items per page'
+            items_per_page_options: '5, 10, 25, 50'
+            items_per_page_options_all: false
+            items_per_page_options_all_label: '- All -'
+            offset: false
+            offset_label: Offset
+          tags:
+            previous: ‹‹
+            next: ››
+      style:
+        type: default
+        options:
+          grouping: {  }
+          row_class: media-library-item
+          default_row_class: true
+      row:
+        type: fields
+        options:
+          default_field_elements: true
+          inline: {  }
+          separator: ''
+          hide_empty: false
+      fields:
+        media_bulk_form:
+          id: media_bulk_form
+          table: media
+          field: media_bulk_form
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: media-library-item-checkbox
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          action_title: Action
+          include_exclude: exclude
+          selected_actions: {  }
+          entity_type: media
+          plugin_id: bulk_form
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: media-library-item-content
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          view_mode: media_library
+          entity_type: media
+          plugin_id: rendered_entity
+      filters:
+        status:
+          id: status
+          table: media_field_data
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: '1'
+          group: 1
+          exposed: true
+          expose:
+            operator_id: ''
+            label: 'Publishing status'
+            description: null
+            use_operator: false
+            operator: status_op
+            identifier: status
+            required: true
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+          is_grouped: true
+          group_info:
+            label: Published
+            description: ''
+            identifier: status
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items:
+              1:
+                title: Published
+                operator: '='
+                value: '1'
+              2:
+                title: Unpublished
+                operator: '='
+                value: '0'
+          plugin_id: boolean
+          entity_type: media
+          entity_field: status
+        name:
+          id: name
+          table: media_field_data
+          field: name
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: contains
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: name_op
+            label: Name
+            description: ''
+            use_operator: false
+            operator: name_op
+            identifier: name
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: name
+          plugin_id: string
+      sorts:
+        created:
+          id: created
+          table: media_field_data
+          field: created
+          relationship: none
+          group_type: group
+          admin_label: ''
+          order: DESC
+          exposed: true
+          expose:
+            label: 'Newest first'
+          granularity: second
+          entity_type: media
+          entity_field: created
+          plugin_id: date
+      title: Media
+      header: {  }
+      footer: {  }
+      empty: {  }
+      relationships: {  }
+      arguments:
+        bundle:
+          id: bundle
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          default_action: ignore
+          exception:
+            value: all
+            title_enable: false
+            title: All
+          title_enable: false
+          title: ''
+          default_argument_type: fixed
+          default_argument_options:
+            argument: ''
+          default_argument_skip_url: false
+          summary_options:
+            base_path: ''
+            count: true
+            items_per_page: 25
+            override: false
+          summary:
+            sort_order: asc
+            number_of_records: 0
+            format: default_summary
+          specify_validation: true
+          validate:
+            type: 'entity:media_type'
+            fail: 'access denied'
+          validate_options:
+            access: true
+            operation: view
+            multiple: 0
+            bundles: {  }
+          glossary: false
+          limit: 0
+          case: none
+          path_case: none
+          transform_dash: false
+          break_phrase: false
+          entity_type: media
+          entity_field: bundle
+          plugin_id: string
+      display_extenders: {  }
+      use_ajax: true
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.type_one.default'
+        - 'config:core.entity_view_display.media.type_two.default'
+  bundle_page:
+    display_plugin: page
+    id: bundle_page
+    display_title: 'Bundle Page'
+    position: 2
+    display_options:
+      display_extenders: {  }
+      path: admin/content/media/%bundle
+      display_description: ''
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.type_one.default'
+        - 'config:core.entity_view_display.media.type_two.default'
+  embed:
+    display_plugin: embed
+    id: embed
+    display_title: Embed
+    position: 4
+    display_options:
+      display_extenders: {  }
+      fields:
+        media_select_media:
+          id: media_select_media
+          table: media
+          field: media_select_media
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: media-library-item-checkbox
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          entity_type: media
+          plugin_id: select_media
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: media-library-item-content
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          view_mode: media_library
+          entity_type: media
+          plugin_id: rendered_entity
+      defaults:
+        fields: false
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.type_one.default'
+        - 'config:core.entity_view_display.media.type_two.default'
+  page:
+    display_plugin: page
+    id: page
+    display_title: Page
+    position: 1
+    display_options:
+      display_extenders: {  }
+      path: admin/content/media
+      menu:
+        type: tab
+        title: Media
+        description: 'Allows users to administer Media'
+        expanded: false
+        parent: system.admin_content
+        weight: 5
+        context: '0'
+        menu_name: admin
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.type_one.default'
+        - 'config:core.entity_view_display.media.type_two.default'
+  widget:
+    display_plugin: page
+    id: widget
+    display_title: Widget
+    position: 4
+    display_options:
+      display_extenders: {  }
+      fields:
+        media_select_media:
+          id: media_select_media
+          table: media
+          field: media_select_media
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: media-library-item-checkbox
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          entity_type: media
+          plugin_id: select_media
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: media-library-item-content
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          view_mode: media_library
+          entity_type: media
+          plugin_id: rendered_entity
+      defaults:
+        fields: false
+      display_description: ''
+      path: admin/media_field_widget
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.type_one.default'
+        - 'config:core.entity_view_display.media.type_two.default'
diff --git a/core/modules/media_library/css/media_library.css b/core/modules/media_library/css/media_library.css
new file mode 100644
index 0000000..ee061bc
--- /dev/null
+++ b/core/modules/media_library/css/media_library.css
@@ -0,0 +1,113 @@
+/**
+* @file media_library.view.css
+*/
+
+.media-library-item {
+  display: inline-flex;
+  justify-content: center;
+  flex-wrap: wrap;
+  vertical-align: top;
+  position: relative;
+  border: 5px solid #ebebeb;
+  outline: 10px solid transparent;
+  margin: 20px 20px 0 0;
+  width: 160px;
+  height: 160px;
+  overflow: hidden;
+  cursor: pointer;
+  background: #ebebeb;
+  transition: border-color .2s, outline-color .2s;
+}
+
+.media-library-item.media-library-item-in-widget {
+  cursor: move;
+}
+
+.media-library-item.checked {
+  outline-color: #cfddfd;
+}
+
+.media-library-item::after {
+  display: block;
+  width: 25px;
+  height: 25px;
+  content: "";
+  position: absolute;
+  top: 0;
+  right: 0;
+  background: url('../icons/checkbox.svg') no-repeat;
+  background-size: cover;
+  margin: 5px 5px 0 0;
+  opacity: 0;
+  transition: opacity .2s;
+}
+
+.media-library-item:hover {
+  border-color: #5792fb;
+}
+
+.media-library-item-checkbox {
+   display: none;
+}
+
+.media-library-item-attributes {
+  position: absolute;
+  left: 0;
+  width: 100%;
+  bottom: 0;
+  padding: 5px;
+  user-select: none;
+  background: rgba(87, 146, 251, 0.5);
+  color: black;
+  opacity: 0;
+  transition: opacity .2s;
+}
+
+.media-library-item:hover .media-library-item-attributes,
+.media-library-item.checked::after,
+.media-library-item:hover .media-library-item-remove {
+  opacity: 1;
+}
+
+.media-library-item-name {
+  font-size: 16px;
+}
+
+.media-library-item .media-library-item-remove {
+  position: absolute;
+  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 --git a/core/modules/media_library/icons/checkbox.svg b/core/modules/media_library/icons/checkbox.svg
new file mode 100644
index 0000000..5c17e04
--- /dev/null
+++ b/core/modules/media_library/icons/checkbox.svg
@@ -0,0 +1,4 @@
+<svg fill="#91b6f9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+    <path d="M0 0h24v24H0z" fill="none"/>
+    <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
+</svg>
\ No newline at end of file
diff --git a/core/modules/media_library/js/media_library.view.js b/core/modules/media_library/js/media_library.view.js
new file mode 100644
index 0000000..bab6ac4
--- /dev/null
+++ b/core/modules/media_library/js/media_library.view.js
@@ -0,0 +1,118 @@
+/**
+ * @file media_library.view.js
+ */
+
+(function ($, Drupal) {
+
+  "use strict";
+
+  // Locally-scoped variable that stores the current View selection. We track
+  // this in a variable as users may click a media item and then re-load the
+  // View with AJAX (filters, pagers, sorts, etc.), which will lose what is
+  // currently selected in the bulk form. This could likely be back-ported to
+  // core.
+  var MediaLibrarySelectedEntities = {};
+
+  /**
+   * Allows users to click on hidden Views select checkboxes by clicking on the
+   * media item (which is also a Views row).
+   */
+  Drupal.behaviors.MediaLibraryCheckboxes = {
+    attach: function (context) {
+      $('.media-library-item', context).once('media-library-checkbox').on('click', function (event) {
+        // Links inside the rendered media item should not be click-able.
+        event.preventDefault();
+        // Click the hidden checkbox when the media item is clicked.
+        var $input = $(this).find('.media-library-item-checkbox input');
+        $input.prop('checked', !$input.prop('checked'));
+        if ($input.prop('checked')) {
+          $(this).addClass('checked');
+        }
+        else {
+          $(this).removeClass('checked');
+        }
+        $input.trigger('change');
+      });
+    }
+  };
+
+  /**
+   * Persists bulk and select form selections across AJAX View re-renders.
+   */
+  Drupal.behaviors.MediaLibraryPersistentSelection = {
+    attach: function (context) {
+      // For each View on the page, store the dom-id as a data attribute so
+      // that we can easily access it later. This is normally only stored in a
+      // class.
+      $('[class*="js-view-dom-id-"]', context).once('set-dom-id-data-attribute').each(function () {
+        var element = $(this);
+        var classes = $(this).attr('class').split(/\s+/);
+        $(classes).each(function (i, class_name) {
+          var dom_id = class_name.match(new RegExp('^js-view-dom-id-(.*)$'));
+          if (dom_id) {
+            element.attr('data-views-dom-id', dom_id[1]);
+            if (!MediaLibrarySelectedEntities[dom_id[1]]) {
+              MediaLibrarySelectedEntities[dom_id[1]] = [];
+            }
+          }
+        });
+      });
+
+      // Check/uncheck checkboxes in the view based on the selected media.
+      $('.media-library-item-checkbox input', context).once('data-media-select').each(function (i, element) {
+        $(this).on('change', function () {
+          var checked = $(this).prop('checked');
+          var views_dom_id = $(this).closest('[data-views-dom-id]').data('views-dom-id');
+          var entity = $(this).val();
+          if (checked) {
+            MediaLibrarySelectedEntities[views_dom_id].push(entity);
+          }
+          else {
+            MediaLibrarySelectedEntities[views_dom_id] = $(MediaLibrarySelectedEntities[views_dom_id]).filter(function (i, value) {
+              return !(entity == value);
+            });
+          }
+        });
+      });
+    }
+  };
+
+  /**
+   * Attaches extra POST data when the Views bulk form is submitted, to ensure
+   * that selections that may be off-screen persist during the submit process.
+   */
+  Drupal.behaviors.MediaLibraryViewsSubmit = {
+    attach: function (context, settings) {
+      $('[data-views-dom-id]', context).once('media-library-checkbox-submit').each(function () {
+        var $view = $(this);
+        var views_dom_id = $(this).data('views-dom-id');
+        $.each(MediaLibrarySelectedEntities[views_dom_id], function(index, items) {
+          $view.find('input[value="' + items + '"]').prop('checked', true);
+          $view.find('input[value="' + items + '"]').closest('.media-library-item').addClass('checked');
+        });
+
+        // Propagate all selected entities to the form submission.
+        $view.find('[data-media-select-submit]').each(function () {
+          var button = $(this);
+          $.each(Drupal.ajax.instances, function () {
+            if (this && $(this.element).is(button)) {
+              if (settings && settings.views && settings.views.ajaxViews) {
+                var ajaxViews = settings.views.ajaxViews;
+                for (var i in ajaxViews) {
+                  var ajaxViewsInstance = Drupal.views.instances[i];
+                  if (ajaxViewsInstance.settings.view_dom_id == views_dom_id) {
+                    this.submit = $.extend(this.submit, ajaxViewsInstance.settings, {
+                      selected_entities: MediaLibrarySelectedEntities[views_dom_id],
+                      media_library_input: settings.media_library.media_library_inputs[views_dom_id]
+                    });
+                  }
+                }
+              }
+            }
+          });
+        });
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/media_library/js/media_library.widget.js b/core/modules/media_library/js/media_library.widget.js
new file mode 100644
index 0000000..a8ab262
--- /dev/null
+++ b/core/modules/media_library/js/media_library.widget.js
@@ -0,0 +1,26 @@
+/**
+ * @file media_library.widget.js
+ */
+
+(function ($, Drupal) {
+
+  "use strict";
+
+  /**
+   * Provides custom JS behaviors related to the media library field widget.
+   */
+  Drupal.behaviors.MediaLibraryWidget = {
+    attach: function (context) {
+      $('.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);
+          });
+        }
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/media_library/media_library.info.yml b/core/modules/media_library/media_library.info.yml
new file mode 100644
index 0000000..4bb4583
--- /dev/null
+++ b/core/modules/media_library/media_library.info.yml
@@ -0,0 +1,10 @@
+name: 'Media library'
+type: module
+description: 'Provides a library for adding and re-using Media Items.'
+package: Core (Experimental)
+version: VERSION
+core: 8.x
+dependencies:
+  - drupal:media
+  - drupal:views
+  - drupal:user
diff --git a/core/modules/media_library/media_library.libraries.yml b/core/modules/media_library/media_library.libraries.yml
new file mode 100644
index 0000000..44e62ce
--- /dev/null
+++ b/core/modules/media_library/media_library.libraries.yml
@@ -0,0 +1,28 @@
+style:
+  version: VERSION
+  css:
+    theme:
+      css/media_library.css: {}
+
+view:
+  version: VERSION
+  js:
+    js/media_library.view.js: {}
+  css:
+    theme:
+      css/media_library.css: {}
+  dependencies:
+    - core/drupal
+    - core/drupal.dialog.ajax
+    - core/jquery
+    - core/jquery.once
+
+widget:
+  version: VERSION
+  js:
+    js/media_library.widget.js: {}
+  dependencies:
+    - core/drupal
+    - core/jquery
+    - core/jquery.ui.sortable
+    - media_library/style
diff --git a/core/modules/media_library/media_library.links.action.yml b/core/modules/media_library/media_library.links.action.yml
new file mode 100644
index 0000000..be1484b
--- /dev/null
+++ b/core/modules/media_library/media_library.links.action.yml
@@ -0,0 +1,6 @@
+media_library.add:
+  route_name: entity.media.add_page
+  title: 'Add media'
+  appears_on:
+    - view.media_library.page
+    - view.media_library.bundle_page
diff --git a/core/modules/media_library/media_library.links.task.yml b/core/modules/media_library/media_library.links.task.yml
new file mode 100644
index 0000000..4d3000c
--- /dev/null
+++ b/core/modules/media_library/media_library.links.task.yml
@@ -0,0 +1,8 @@
+media_library.local_tasks:
+  deriver: 'Drupal\media_library\Plugin\Derivative\MediaLibraryLocalTasks'
+  weight: 1
+
+media_library.local_tasks.all:
+  title: 'All categories'
+  route_name: view.media_library.page
+  parent_id: views_view:view.media_library.page
diff --git a/core/modules/media_library/media_library.module b/core/modules/media_library/media_library.module
new file mode 100644
index 0000000..73e96c4
--- /dev/null
+++ b/core/modules/media_library/media_library.module
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Contains hook implementation for the media_library module.
+ */
+
+use Drupal\views\Plugin\views\cache\CachePluginBase;
+use Drupal\views\ViewExecutable;
+
+/**
+ * Implements hook_theme().
+ */
+function media_library_theme() {
+  return [
+    'media__media_library' => [
+      'base hook' => 'media',
+    ],
+  ];
+}
+
+/**
+ * Implements hook_views_post_render().
+ */
+function media_library_views_post_render(ViewExecutable $view, &$output, CachePluginBase $cache) {
+  if ($view->id() === 'media_library') {
+    $output['#attached']['library'][] = 'media_library/view';
+  }
+}
+
+/**
+ * Implements hook_preprocess_media().
+ */
+function media_library_preprocess_media(&$variables) {
+  if ($variables['view_mode'] === 'media_library') {
+    /** @var \Drupal\media\MediaInterface $media */
+    $media = $variables['media'];
+    /** @var \Drupal\user\UserInterface $user */
+    $user = $media->getRevisionUser();
+    $variables['author_info'] = [
+      '#markup' => t('Created by %author', ['%author' => $user->getDisplayName()]),
+    ];
+    $variables['created_ago'] = [
+      '#markup' => _media_library_pretty_time($media->getCreatedTime()),
+    ];
+  }
+}
+
+/**
+ * Displays a timestamp in a human-readable fashion.
+ *
+ * @todo This might be suitable for core as a Twig function.
+ *
+ * @param int $time
+ *   A timestamp.
+ *
+ * @return \Drupal\Core\StringTranslation\TranslatableMarkup
+ *   Markup representing a formatted time.
+ */
+function _media_library_pretty_time($time) {
+  $time = (int) $time;
+  $too_old = strtotime('-1 month');
+  // Show formatted time differences for edits younger than a month.
+  if ($time > $too_old) {
+    $diff = \Drupal::service('date.formatter')->formatTimeDiffSince($time, ['granularity' => 1]);
+    $time_pretty = t('@diff ago', ['@diff' => $diff]);
+  }
+  else {
+    $date = date('m/d/Y - h:i A', $time);
+    $time_pretty = t('on @date', ['@date' => $date]);
+  }
+  return $time_pretty;
+}
diff --git a/core/modules/media_library/media_library.views.inc b/core/modules/media_library/media_library.views.inc
new file mode 100644
index 0000000..6a8b182
--- /dev/null
+++ b/core/modules/media_library/media_library.views.inc
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Implements hook_views_data().
+ */
+function media_library_views_data() {
+  $data = [];
+  // Registers an library select field per entity.
+  foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type => $entity_info) {
+    $data[$entity_info->getBaseTable()][$entity_type . '_select_media'] = array(
+      'title' => t('Select media'),
+      'help' => t('Provides a field for selecting media entities in our media library view'),
+      'real field' => $entity_info->getKey('id'),
+      'field' => array(
+        'id' => 'select_media',
+      ),
+    );
+  }
+  return $data;
+}
diff --git a/core/modules/media_library/src/Plugin/Derivative/MediaLibraryLocalTasks.php b/core/modules/media_library/src/Plugin/Derivative/MediaLibraryLocalTasks.php
new file mode 100644
index 0000000..97e399d
--- /dev/null
+++ b/core/modules/media_library/src/Plugin/Derivative/MediaLibraryLocalTasks.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\media_library\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\media\Entity\MediaType;
+
+/**
+ * Defines dynamic local tasks for the Media Library module.
+ */
+class MediaLibraryLocalTasks extends DeriverBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    $weight = 0;
+    foreach (MediaType::loadMultiple() as $id => $bundle) {
+      $this->derivatives[$id] = $base_plugin_definition;
+      $this->derivatives[$id]['title'] = $bundle->label();
+      $this->derivatives[$id]['route_name'] = 'view.media_library.bundle_page';
+      $this->derivatives[$id]['parent_id'] = 'views_view:view.media_library.page';
+      $this->derivatives[$id]['weight'] = ++$weight;
+      $this->derivatives[$id]['route_parameters'] = ['bundle' => $id];
+    }
+    return $this->derivatives;
+  }
+
+}
diff --git a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
new file mode 100644
index 0000000..a2e290c
--- /dev/null
+++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
@@ -0,0 +1,365 @@
+<?php
+
+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;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Field\WidgetBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Url;
+use Drupal\media\Entity\Media;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\ConstraintViolationInterface;
+
+/**
+ * Plugin implementation of the 'media_library_widget' widget.
+ *
+ * @todo We don't support the "auto_create", "auto_create_bundle", or
+ * "handler" field settings. Determine how to document or address this.
+ *
+ * @FieldWidget(
+ *   id = "media_library_widget",
+ *   label = @Translation("Media Library"),
+ *   description = @Translation("Allows you to re-use Media Entities."),
+ *   field_types = {
+ *     "entity_reference"
+ *   },
+ *   multiple_values = TRUE
+ * )
+ */
+class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * Entity type manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a MediaLibraryWidget widget.
+   *
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The definition of the field to which the widget is associated.
+   * @param array $settings
+   *   The widget settings.
+   * @param array $third_party_settings
+   *   Any third party settings.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   Entity type manager service.
+   */
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $plugin_id,
+      $plugin_definition,
+      $configuration['field_definition'],
+      $configuration['settings'],
+      $configuration['third_party_settings'],
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function isApplicable(FieldDefinitionInterface $field_definition) {
+    return $field_definition->getSetting('target_type') === 'media';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) {
+    $field_name = $this->fieldDefinition->getName();
+    $parents = $form['#parents'];
+
+    // Load the items for form rebuilds from the field state as they might not be
+    // 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']);
+    }
+
+    $build = parent::form($items, $form, $form_state, $get_delta);
+
+    return $build;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+    /** @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items */
+    $referenced_entities = $items->referencedEntities();
+    $view_builder = $this->entityTypeManager->getViewBuilder($this->getFieldSetting('target_type'));
+    $field_name = $this->fieldDefinition->getName();
+    $wrapper_id = $field_name . '-media-library-wrapper';
+    $parents = $form['#parents'];
+
+    $element += [
+      '#type' => 'fieldset',
+      '#cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(),
+      'selection' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'class' => 'media-library-selection',
+        ],
+      ],
+      '#attributes' => [
+        'id' => $wrapper_id,
+        'class' => ['media-library-wrapper'],
+      ],
+    ];
+
+    if (empty($referenced_entities)) {
+      $element['empty_selection'] = [
+        '#markup' => $this->t('<p>No media selected.</p>'
+      )];
+    }
+    else {
+      foreach ($referenced_entities as $delta => $media_item) {
+        $element['selection'][$delta] = [
+          '#type' => 'container',
+          '#attributes' => [
+            'class' => ['media-library-item', 'media-library-item-in-widget'],
+          ],
+          'preview' => [
+            '#type' => 'container',
+            'view' => $view_builder->view($media_item, 'media_library'),
+            '#attached' => [
+              'library' => ['media_library/widget'],
+            ],
+            'remove_button' => [
+              '#type' => 'submit',
+              '#media_library_remove_delta' =>$delta,
+              '#name' => $field_name . '-' .  $delta . '-media-library-remove-button',
+              '#value' => $this->t('Remove'),
+              '#attributes' => [
+                'class' => ['media-library-item-remove'],
+              ],
+              '#ajax' => [
+                'callback' => [static::class, 'updateWidget'],
+                'wrapper' => $wrapper_id,
+              ],
+              '#submit' => [[static::class, 'removeItem']],
+              '#limit_validation_errors' => [array_merge($form['#parents'], [$field_name])],
+            ],
+          ],
+          'target_id' => [
+            '#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'],
+            ],
+          ],
+        ];
+      }
+    }
+
+    // @todo We use a use-ajax link here as it is the only way we've seen
+    // contextual filters work when using a view in a modal. Unfortunately
+    // this only works the first time you open the modal - opening it twice
+    // in one page load breaks Views AJAX.
+    $element['media_library_open_container'] = [
+      '#type' => 'container',
+      'media_library_open_button' => [
+        '#type' => 'link',
+        '#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],
+        ]),
+        '#attributes' => [
+          'class' => ['button', 'use-ajax', 'media-library-open-button'],
+          'data-dialog-type' => 'modal',
+          'data-dialog-options' => json_encode([
+            'height' => '500',
+            'width' => '70%',
+            'title' => $this->t('Media library'),
+          ]),
+        ],
+        '#limit_validation_errors' => [array_merge($parents, [$field_name])],
+      ],
+    ];
+
+    // Add a hidden textfield to the form, which is used by the select_media
+    // Views field to pass values from the modal to the widget. This is similar
+    // to how the EntityReferenceAutocompleteWidget stores values.
+    $element['media_library_selection'] = [
+      '#type' => 'textfield',
+      '#attributes' => [
+        'data-media-library-input-id' => $field_name,
+        'class' => ['visually-hidden'],
+      ],
+    ];
+
+    $element['media_library_update_widget'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Update widget'),
+      '#name' => $field_name . '-media-library-update-button',
+      '#ajax' => [
+        'callback' => [static::class, 'updateWidget'],
+        'wrapper' => $wrapper_id,
+      ],
+      '#attributes' => [
+        'data-media-library-submit-id' => $field_name,
+        'class' => ['visually-hidden', 'media-library-update-button'],
+      ],
+      '#submit' => [[static::class, 'updateItems']],
+      '#limit_validation_errors' => [array_merge($parents, [$field_name])],
+    ];
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function errorElement(array $element, ConstraintViolationInterface $error, array $form, FormStateInterface $form_state) {
+    return isset($element['target_id']) ? $element['target_id'] : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
+    if (isset($values['selection'])) {
+      usort($values['selection'], [SortArray::class, 'sortByWeightElement']);
+      return $values['selection'];
+    }
+    else {
+      return [];
+    }
+  }
+
+  /**
+   * AJAX callback to update the widget when the selection changes.
+   *
+   * @param array $form
+   *   The form array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   An array representing the updated widget.
+   */
+  public static function updateWidget($form, FormStateInterface $form_state) {
+    $triggering_element = $form_state->getTriggeringElement();
+    $length = isset($triggering_element['#media_library_remove_delta']) ? -4 : -1;
+    $parents = $triggering_element['#array_parents'];
+    $parents = array_slice($parents, 0, $length);
+    $element = NestedArray::getValue($form, $parents);
+    // Always clear the textfield selection to prevent duplicate additions.
+    $element['media_library_selection']['#value'] = '';
+    return $element;
+  }
+
+  /**
+   * Submit callback for remove buttons.
+   *
+   * @param array $form
+   *   The form array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   */
+  public static function removeItem($form, FormStateInterface $form_state) {
+    $triggering_element = $form_state->getTriggeringElement();
+
+    $parents = $triggering_element['#array_parents'];
+    $parents = array_slice($parents, 0, -4);
+    $element = NestedArray::getValue($form, $parents);
+    $field_name = $element['#field_name'];
+    $parents = $element['#field_parents'];
+    $delta = $triggering_element['#media_library_remove_delta'];
+
+    // Find and remove correct entity.
+    $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])) {
+        array_splice($values['selection'], $delta, 1);
+        $field_state['items'] = $values['selection'];
+        $field_state['items_count'] = count($field_state['items']);
+      }
+    }
+    $form_state->setValue($field_name, $values);
+    static::setWidgetState($parents, $field_name, $form_state, $field_state);
+
+    // Rebuild form.
+    $form_state->setRebuild();
+  }
+
+  /**
+   * 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 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.
+    $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;
+
+    if (!empty($value['media_library_selection'])) {
+      $ids = explode(',', $value['media_library_selection']);
+      /** @var \Drupal\media\MediaInterface[] $media */
+      $media = Media::loadMultiple($ids);
+
+      // Append the provided Media to the existing selection.
+      $cardinality_unlimited = ($element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
+      foreach ($media as $media_item) {
+        if ($cardinality_unlimited || (count($field_state['items']) < $element['#cardinality'])) {
+          if ($media && $media_item->access('view')) {
+            $field_state['items'][] = [
+              'target_id' => $media_item->id(),
+            ];
+          }
+        }
+      }
+    }
+
+    $field_state['items_count'] = count($field_state['items']);
+    static::setWidgetState($parents, $field_name, $form_state, $field_state);
+
+    $form_state->setRebuild();
+  }
+
+}
diff --git a/core/modules/media_library/src/Plugin/views/field/SelectMedia.php b/core/modules/media_library/src/Plugin/views/field/SelectMedia.php
new file mode 100644
index 0000000..9b01e48
--- /dev/null
+++ b/core/modules/media_library/src/Plugin/views/field/SelectMedia.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\media_library\Plugin\views\field\SelectEntity.
+ */
+
+namespace Drupal\media_library\Plugin\views\field;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\CloseModalDialogCommand;
+use Drupal\Core\Ajax\InvokeCommand;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Url;
+use Drupal\system\Plugin\views\field\SelectFormBase;
+
+/**
+ * Provides a field for selecting media entities in our media library view.
+ *
+ * @ViewsField("select_media")
+ */
+class SelectMedia extends SelectFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewsForm(&$form, FormStateInterface $form_state) {
+    parent::viewsForm($form, $form_state);
+    if (!empty($form[$this->options['id']])) {
+      foreach (Element::children($form[$this->options['id']]) as $index) {
+        $item = &$form[$this->options['id']][$index];
+        $item['#attributes']['data-media-select'] = TRUE;
+      }
+    }
+    if (isset($form['actions']['submit'])) {
+      $form['actions']['submit']['#attributes']['data-media-select-submit'] = TRUE;
+      $form['actions']['submit']['#ajax']['url'] = Url::fromRoute('<current>');
+      $form['actions']['submit']['#ajax']['callback'] = function ($form, FormStateInterface $form_state) {
+        $response = new AjaxResponse();
+        $response->addCommand(new CloseModalDialogCommand());
+        $form_state->setResponse($response);
+      };
+    }
+    if ($media_library_input = \Drupal::request()->get('media_library_input')) {
+      $form['#attached']['drupalSettings']['media_library']['media_library_inputs'][$this->view->dom_id] = $media_library_input;
+    }
+  }
+
+  /**
+   * Submit handler for the bulk form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   Thrown when the user tried to access an action without access to it.
+   */
+  public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
+    if ($form_state->get('step') == 'views_form_views_form') {
+      // Prefer the js provided selection, because it contains selected entities
+      // that aren't currently on screen and tracks the order of the selection.
+      if (\Drupal::request()->request->has('selected_entities')) {
+        $selected = \Drupal::request()->request->get('selected_entities');
+      }
+      else {
+        // Filter only selected checkboxes.
+        $selected = array_filter($form_state->getValue($this->options['id']));
+      }
+
+      $entities = [];
+
+      foreach ($selected as $bulk_form_key) {
+        $entity = $this->loadEntityFromBulkFormKey($bulk_form_key);
+        $entities[$bulk_form_key] = $entity;
+      }
+
+      $ids = [];
+      foreach ($entities as $entity) {
+        $ids[] = $entity->id();
+      }
+
+      $response = new AjaxResponse();
+      $library_input = \Drupal::request()->get('media_library_input');
+      $input_selector = '[data-media-library-input-id="' . $library_input  . '"]';
+      $submit_selector = '[data-media-library-submit-id="' . $library_input  . '"]';
+      $response->addCommand(new InvokeCommand($input_selector, 'val', [implode(',', $ids)]));
+      $response->addCommand(new InvokeCommand($submit_selector, 'trigger', ['mousedown']));
+      $response->addCommand(new CloseModalDialogCommand());
+      $form_state->setResponse($response);
+    }
+  }
+
+}
diff --git a/core/modules/media_library/templates/media--media-library.html.twig b/core/modules/media_library/templates/media--media-library.html.twig
new file mode 100644
index 0000000..073f000
--- /dev/null
+++ b/core/modules/media_library/templates/media--media-library.html.twig
@@ -0,0 +1,52 @@
+{#
+/**
+ * @file
+ * Default theme implementation to present a media entity in the media library.
+ *
+ * The major difference between this template and the default template is that
+ * "name" is omitted, as "name" is output as a field in the Media library view.
+ * Other themes are welcome to override this template, which will not break the
+ * functionality of the library.
+ *
+ * Available variables:
+ * - media: The entity with limited access to object properties and methods.
+ *   Only method names starting with "get", "has", or "is" and a few common
+ *   methods such as "id", "label", and "bundle" are available. For example:
+ *   - entity.getEntityTypeId() will return the entity type ID.
+ *   - entity.hasField('field_example') returns TRUE if the entity includes
+ *     field_example. (This does not indicate the presence of a value in this
+ *     field.)
+ *   Calling other methods, such as entity.delete(), will result in an exception.
+ *   See \Drupal\Core\Entity\EntityInterface for a full list of methods.
+ * - name: Name of the media.
+ * - content: Media content.
+ * - title_prefix: Additional output populated by modules, intended to be
+ *   displayed in front of the main title tag that appears in the template.
+ * - title_suffix: Additional output populated by modules, intended to be
+ *   displayed after the main title tag that appears in the template.
+ * - view_mode: View mode; for example, "teaser" or "full".
+ * - attributes: HTML attributes for the containing element.
+ * - title_attributes: Same as attributes, except applied to the main title
+ *   tag that appears in the template.
+ * - author_info: This is a Media Library specific variable that contains the
+ *   display name of the author for this media.
+ * - created_ago: This is a Media Library specific variable that contains a
+ *   formatted "time ago" version of the created date.
+ *
+ * @see template_preprocess_media()
+ *
+ * @ingroup themeable
+ */
+#}
+<article{{ attributes }}>
+  {% if content %}
+    {{ content|without('name', 'created', 'uid') }}
+    <div class="media-library-item-attributes">
+      <div class="media-library-item-name">{{ name }}</div>
+      <div class="media-library-item-created">{{ created_ago }}</div>
+      <div class="media-library-item-author">{{ author_info }}</div>
+    </div>
+    {# This is used to prevent dragging nested elements in the widget. #}
+    <div class="media-library-mask"></div>
+  {% endif %}
+</article>
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_one.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_one.default.yml
new file mode 100644
index 0000000..212daf8
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_one.default.yml
@@ -0,0 +1,43 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.type_one.field_media_test
+    - media.type.type_one
+id: media.type_one.default
+targetEntityType: media
+bundle: type_one
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 10
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  field_media_test:
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+    type: string_textfield
+    weight: 11
+    region: content
+  name:
+    type: string_textfield
+    weight: -5
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 5
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    region: content
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_two.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_two.default.yml
new file mode 100644
index 0000000..fabd13b
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_two.default.yml
@@ -0,0 +1,43 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.type_two.field_media_test_1
+    - media.type.type_two
+id: media.type_two.default
+targetEntityType: media
+bundle: type_two
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 10
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  field_media_test_1:
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+    type: string_textfield
+    weight: 11
+    region: content
+  name:
+    type: string_textfield
+    weight: -5
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 5
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    region: content
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.node.media_library_test.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.node.media_library_test.default.yml
new file mode 100644
index 0000000..775319d
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.node.media_library_test.default.yml
@@ -0,0 +1,71 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.node.media_library_test.field_media
+    - field.field.node.media_library_test.field_multivalue_media
+    - node.type.media_library_test
+  module:
+    - media_library
+    - path
+id: node.media_library_test.default
+targetEntityType: node
+bundle: media_library_test
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 10
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  field_media:
+    weight: 31
+    settings: {  }
+    third_party_settings: {  }
+    type: media_library_widget
+    region: content
+  field_multivalue_media:
+    weight: 32
+    settings: {  }
+    third_party_settings: {  }
+    type: media_library_widget
+    region: content
+  path:
+    type: path
+    weight: 30
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  promote:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 15
+    region: content
+    third_party_settings: {  }
+  sticky:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 16
+    region: content
+    third_party_settings: {  }
+  title:
+    type: string_textfield
+    weight: -5
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 5
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    region: content
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_one.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_one.default.yml
new file mode 100644
index 0000000..8f5f22a
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_one.default.yml
@@ -0,0 +1,50 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.type_one.field_media_test
+    - image.style.thumbnail
+    - media.type.type_one
+  module:
+    - image
+    - user
+id: media.type_one.default
+targetEntityType: media
+bundle: type_one
+mode: default
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 0
+    region: content
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_media_test:
+    label: above
+    settings:
+      link_to_entity: false
+    third_party_settings: {  }
+    type: string
+    weight: 6
+    region: content
+  thumbnail:
+    type: image
+    weight: 5
+    label: hidden
+    settings:
+      image_style: thumbnail
+      image_link: ''
+    region: content
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 0
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_two.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_two.default.yml
new file mode 100644
index 0000000..a884fee
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_two.default.yml
@@ -0,0 +1,50 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.type_two.field_media_test_1
+    - image.style.thumbnail
+    - media.type.type_two
+  module:
+    - image
+    - user
+id: media.type_two.default
+targetEntityType: media
+bundle: type_two
+mode: default
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 0
+    region: content
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_media_test_1:
+    label: above
+    settings:
+      link_to_entity: false
+    third_party_settings: {  }
+    type: string
+    weight: 6
+    region: content
+  thumbnail:
+    type: image
+    weight: 5
+    label: hidden
+    settings:
+      image_style: thumbnail
+      image_link: ''
+    region: content
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 0
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.node.media_library_test.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.node.media_library_test.default.yml
new file mode 100644
index 0000000..3650d3b
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.node.media_library_test.default.yml
@@ -0,0 +1,34 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.node.media_library_test.field_media
+    - field.field.node.media_library_test.field_multivalue_media
+    - node.type.media_library_test
+  module:
+    - user
+id: node.media_library_test.default
+targetEntityType: node
+bundle: media_library_test
+mode: default
+content:
+  field_media:
+    weight: 101
+    label: above
+    settings:
+      link: true
+    third_party_settings: {  }
+    type: entity_reference_label
+    region: content
+  field_multivalue_media:
+    weight: 102
+    label: above
+    settings:
+      link: true
+    third_party_settings: {  }
+    type: entity_reference_label
+    region: content
+  links:
+    weight: 100
+    region: content
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_one.field_media_test.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_one.field_media_test.yml
new file mode 100644
index 0000000..f19207c
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_one.field_media_test.yml
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_test
+    - media.type.type_one
+id: media.type_one.field_media_test
+field_name: field_media_test
+entity_type: media
+bundle: type_one
+label: field_media_test
+description: ''
+required: false
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: string
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_two.field_media_test_1.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_two.field_media_test_1.yml
new file mode 100644
index 0000000..5b093ca
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_two.field_media_test_1.yml
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_test_1
+    - media.type.type_two
+id: media.type_two.field_media_test_1
+field_name: field_media_test_1
+entity_type: media
+bundle: type_two
+label: field_media_test_1
+description: ''
+required: false
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: string
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_media.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_media.yml
new file mode 100644
index 0000000..e5773ab
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_media.yml
@@ -0,0 +1,29 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.node.field_media
+    - media.type.type_one
+    - media.type.type_two
+    - node.type.media_library_test
+id: node.media_library_test.field_media
+field_name: field_media
+entity_type: node
+bundle: media_library_test
+label: Media
+description: ''
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings:
+  handler: 'default:media'
+  handler_settings:
+    target_bundles:
+      type_one: type_one
+      type_two: type_two
+    sort:
+      field: _none
+    auto_create: false
+    auto_create_bundle: type_one
+field_type: entity_reference
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_multivalue_media.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_multivalue_media.yml
new file mode 100644
index 0000000..f92f30f
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_multivalue_media.yml
@@ -0,0 +1,29 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.node.field_multivalue_media
+    - media.type.type_one
+    - media.type.type_two
+    - node.type.media_library_test
+id: node.media_library_test.field_multivalue_media
+field_name: field_multivalue_media
+entity_type: node
+bundle: media_library_test
+label: 'Multivalue Media'
+description: ''
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings:
+  handler: 'default:media'
+  handler_settings:
+    target_bundles:
+      type_one: type_one
+      type_two: type_two
+    sort:
+      field: _none
+    auto_create: false
+    auto_create_bundle: type_one
+field_type: entity_reference
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test.yml
new file mode 100644
index 0000000..40a7791
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test.yml
@@ -0,0 +1,20 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+id: media.field_media_test
+field_name: field_media_test
+entity_type: media
+type: string
+settings:
+  max_length: 255
+  is_ascii: false
+  case_sensitive: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test_1.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test_1.yml
new file mode 100644
index 0000000..73b1105
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test_1.yml
@@ -0,0 +1,20 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+id: media.field_media_test_1
+field_name: field_media_test_1
+entity_type: media
+type: string
+settings:
+  max_length: 255
+  is_ascii: false
+  case_sensitive: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_media.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_media.yml
new file mode 100644
index 0000000..bbec21d
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_media.yml
@@ -0,0 +1,19 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+    - node
+id: node.field_media
+field_name: field_media
+entity_type: node
+type: entity_reference
+settings:
+  target_type: media
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_multivalue_media.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_multivalue_media.yml
new file mode 100644
index 0000000..9937919
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_multivalue_media.yml
@@ -0,0 +1,19 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+    - node
+id: node.field_multivalue_media
+field_name: field_multivalue_media
+entity_type: node
+type: entity_reference
+settings:
+  target_type: media
+module: core
+locked: false
+cardinality: 5
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_one.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_one.yml
new file mode 100644
index 0000000..c3f7cf3
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_one.yml
@@ -0,0 +1,16 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+    - media_test_handler
+id: type_one
+label: 'Type One'
+description: ''
+handler: test
+queue_thumbnail_downloads: false
+new_revision: false
+handler_configuration:
+  source_field: field_media_test
+  test_config_value: 'This is default value.'
+field_map: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_two.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_two.yml
new file mode 100644
index 0000000..1e0ad59
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_two.yml
@@ -0,0 +1,16 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+    - media_test_handler
+id: type_two
+label: 'Type Two'
+description: ''
+handler: test
+queue_thumbnail_downloads: false
+new_revision: false
+handler_configuration:
+  source_field: field_media_test_1
+  test_config_value: 'This is default value.'
+field_map: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/node.type.media_library_test.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/node.type.media_library_test.yml
new file mode 100644
index 0000000..2c3ba17
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/node.type.media_library_test.yml
@@ -0,0 +1,17 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - menu_ui
+third_party_settings:
+  menu_ui:
+    available_menus:
+      - main
+    parent: 'main:'
+name: 'Media Library Test'
+type: media_library_test
+description: ''
+help: ''
+new_revision: true
+preview_mode: 1
+display_submitted: true
diff --git a/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml
new file mode 100644
index 0000000..b12c7fa
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml
@@ -0,0 +1,11 @@
+name: 'Media library test'
+type: module
+description: 'Test module for Media Library.'
+package: Testing
+core: 8.x
+dependencies:
+  - drupal:media_library
+  - drupal:media_test_handler
+  - drupal:menu_ui
+  - drupal:node
+  - drupal:path
diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
new file mode 100644
index 0000000..0f4d63b
--- /dev/null
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Drupal\Tests\media_library\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use Drupal\media\Entity\Media;
+
+/**
+ * Contains Media Library integration tests.
+ *
+ * @group media_library
+ */
+class MediaLibraryTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['block', 'media_library_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Create a few example media items for use in selection.
+    $media = [
+      'type_one' => [
+        'media_1',
+        'media_2',
+      ],
+      'type_two' => [
+        'media_3',
+        'media_4',
+      ],
+    ];
+
+    foreach ($media as $type => $names) {
+      foreach ($names as $name) {
+        Media::create(['name' => $name, 'bundle' => $type])->save();
+      }
+    }
+
+    // Create a user who can use the Media Library.
+    $user = $this->drupalCreateUser([
+      'access content',
+      'access content overview',
+      'access administration pages',
+      'administer media',
+      'create media_library_test content',
+      'edit own media_library_test content',
+      'delete any media',
+      'delete own media_library_test content',
+      'view media',
+    ]);
+    $this->drupalLogin($user);
+    $this->drupalPlaceBlock('local_tasks_block');
+  }
+
+  /**
+   * Tests that the Media Library's administration page works as expected.
+   */
+  public function testAdministrationPage() {
+    // Visit the administration page.
+    $this->drupalGet('admin/content/media');
+
+    // Verify that media from two separate types is present.
+    $this->assertSession()->pageTextContains('media_1');
+    $this->assertSession()->pageTextContains('media_3');
+
+    // Test the tabs that allow users to filter by type.
+    $this->clickLink('Type One');
+    $this->assertSession()->pageTextContains('media_2');
+    $this->assertSession()->pageTextNotContains('media_4');
+    $this->clickLink('Type Two');
+    $this->assertSession()->pageTextNotContains('media_2');
+    $this->assertSession()->pageTextContains('media_4');
+  }
+
+}
